@bilalimamoglu/sift 0.3.2 → 0.4.0
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 +191 -184
- package/dist/cli.js +2174 -339
- package/dist/index.d.ts +19 -1
- package/dist/index.js +2085 -310
- package/package.json +4 -2
package/dist/cli.js
CHANGED
|
@@ -1262,7 +1262,8 @@ function renderInstructionBody() {
|
|
|
1262
1262
|
"- After making or planning a fix, refresh the truth with `sift rerun` so the same full suite runs again at `standard` and shows what is resolved or still remaining.",
|
|
1263
1263
|
"- The normal stop budget is `standard` first, then at most one zoom step before raw.",
|
|
1264
1264
|
"- Only if more detail is still needed after `sift rerun`, use `sift rerun --remaining --detail focused`, then `sift rerun --remaining --detail verbose`, then `sift rerun --remaining --detail verbose --show-raw`.",
|
|
1265
|
-
"- `sift rerun --remaining`
|
|
1265
|
+
"- `sift rerun --remaining` narrows automatically for `pytest` and reruns the full original command for `vitest` and `jest` while keeping the diagnosis focused on what still fails.",
|
|
1266
|
+
"- For other runners, rerun a narrowed command manually with `sift exec --preset test-status -- <narrowed test command>` if you need a smaller surface.",
|
|
1266
1267
|
"- Start with `standard` text. Use diagnose JSON only when automation or machine branching truly needs it.",
|
|
1267
1268
|
"- If `standard` already shows bucket-level root cause, anchor, and fix lines, trust it and report from it directly.",
|
|
1268
1269
|
"- In that case, do not re-verify the same bucket with raw pytest; at most do one targeted source read before you edit.",
|
|
@@ -1876,7 +1877,7 @@ function showPreset(config, name, includeInternal = false) {
|
|
|
1876
1877
|
}
|
|
1877
1878
|
|
|
1878
1879
|
// src/core/escalate.ts
|
|
1879
|
-
import
|
|
1880
|
+
import pc4 from "picocolors";
|
|
1880
1881
|
|
|
1881
1882
|
// src/core/insufficient.ts
|
|
1882
1883
|
function isInsufficientSignalOutput(output) {
|
|
@@ -1897,8 +1898,8 @@ function buildInsufficientSignalOutput(input) {
|
|
|
1897
1898
|
} else {
|
|
1898
1899
|
hint = "Hint: the captured output did not contain a clear answer for this preset.";
|
|
1899
1900
|
}
|
|
1900
|
-
|
|
1901
|
-
|
|
1901
|
+
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;
|
|
1902
|
+
return [INSUFFICIENT_SIGNAL_TEXT, hint, presetSuggestion].filter((value) => Boolean(value)).join("\n");
|
|
1902
1903
|
}
|
|
1903
1904
|
|
|
1904
1905
|
// src/core/run.ts
|
|
@@ -2124,7 +2125,125 @@ function createProvider(config) {
|
|
|
2124
2125
|
|
|
2125
2126
|
// src/core/testStatusDecision.ts
|
|
2126
2127
|
import { z as z2 } from "zod";
|
|
2127
|
-
|
|
2128
|
+
|
|
2129
|
+
// src/core/testStatusTargets.ts
|
|
2130
|
+
function unique(values) {
|
|
2131
|
+
return [...new Set(values)];
|
|
2132
|
+
}
|
|
2133
|
+
function normalizeTestId(value) {
|
|
2134
|
+
return value.replace(/\\/g, "/").replace(/\s+/g, " ").trim();
|
|
2135
|
+
}
|
|
2136
|
+
function stripMatcherProse(value) {
|
|
2137
|
+
return value.replace(/\s+-\s+.*$/, "").trim();
|
|
2138
|
+
}
|
|
2139
|
+
function extractJsFile(value) {
|
|
2140
|
+
const match = value.match(/([A-Za-z0-9_./-]+\.(?:test|spec)\.[cm]?[jt]sx?)/i);
|
|
2141
|
+
return match ? normalizeTestId(match[1]) : null;
|
|
2142
|
+
}
|
|
2143
|
+
function normalizeFailingTarget(label, runner) {
|
|
2144
|
+
const normalized = normalizeTestId(label).replace(/^['"]|['"]$/g, "");
|
|
2145
|
+
if (runner === "pytest") {
|
|
2146
|
+
return stripMatcherProse(normalized);
|
|
2147
|
+
}
|
|
2148
|
+
if (runner === "vitest" || runner === "jest") {
|
|
2149
|
+
const compact = normalized.replace(/^FAIL\s+/i, "").replace(/^[❯×]\s*/, "").replace(/\s+\[[^\]]+\]\s*$/, "").trim();
|
|
2150
|
+
const file = extractJsFile(compact);
|
|
2151
|
+
if (!file) {
|
|
2152
|
+
return stripMatcherProse(compact);
|
|
2153
|
+
}
|
|
2154
|
+
const fileIndex = compact.indexOf(file);
|
|
2155
|
+
const suffix = compact.slice(fileIndex + file.length).trim();
|
|
2156
|
+
if (!suffix) {
|
|
2157
|
+
return file;
|
|
2158
|
+
}
|
|
2159
|
+
if (suffix.startsWith(">")) {
|
|
2160
|
+
const testName = stripMatcherProse(suffix.replace(/^>\s*/, ""));
|
|
2161
|
+
return testName.length > 0 ? `${file} > ${testName}` : file;
|
|
2162
|
+
}
|
|
2163
|
+
return file;
|
|
2164
|
+
}
|
|
2165
|
+
return normalized;
|
|
2166
|
+
}
|
|
2167
|
+
function extractFamilyPrefix(value) {
|
|
2168
|
+
const normalized = normalizeTestId(value);
|
|
2169
|
+
const filePart = normalized.split("::")[0]?.split(" > ")[0]?.trim() ?? normalized;
|
|
2170
|
+
const workflowMatch = filePart.match(/^(\.github\/workflows\/)/);
|
|
2171
|
+
if (workflowMatch) {
|
|
2172
|
+
return workflowMatch[1];
|
|
2173
|
+
}
|
|
2174
|
+
const testsMatch = filePart.match(/^((?:test|tests)\/[^/]+\/)/);
|
|
2175
|
+
if (testsMatch) {
|
|
2176
|
+
return testsMatch[1];
|
|
2177
|
+
}
|
|
2178
|
+
const srcMatch = filePart.match(/^(src\/[^/]+\/)/);
|
|
2179
|
+
if (srcMatch) {
|
|
2180
|
+
return srcMatch[1];
|
|
2181
|
+
}
|
|
2182
|
+
const configMatch = filePart.match(
|
|
2183
|
+
/^((?:[^/]+\/)*(?:package\.json|pytest\.ini|pyproject\.toml|tox\.ini|conftest\.py|(?:vitest|jest)\.config\.[^/]+|tsconfig(?:\.[^/]+)?\.json|[^/]*config[^/]*\.(?:json|ya?ml)))$/i
|
|
2184
|
+
);
|
|
2185
|
+
if (configMatch) {
|
|
2186
|
+
return configMatch[1];
|
|
2187
|
+
}
|
|
2188
|
+
const segments = filePart.replace(/^\/+/, "").split("/").filter(Boolean);
|
|
2189
|
+
if (segments.length >= 2) {
|
|
2190
|
+
return `${segments[0]}/${segments[1]}/`;
|
|
2191
|
+
}
|
|
2192
|
+
if (segments.length === 1) {
|
|
2193
|
+
return segments[0];
|
|
2194
|
+
}
|
|
2195
|
+
return "other";
|
|
2196
|
+
}
|
|
2197
|
+
function buildTestTargetSummary(values) {
|
|
2198
|
+
const uniqueValues = unique(values);
|
|
2199
|
+
const counts = /* @__PURE__ */ new Map();
|
|
2200
|
+
for (const value of uniqueValues) {
|
|
2201
|
+
const prefix = extractFamilyPrefix(value);
|
|
2202
|
+
counts.set(prefix, (counts.get(prefix) ?? 0) + 1);
|
|
2203
|
+
}
|
|
2204
|
+
const families = [...counts.entries()].map(([prefix, count]) => ({
|
|
2205
|
+
prefix,
|
|
2206
|
+
count
|
|
2207
|
+
})).sort((left, right) => {
|
|
2208
|
+
if (right.count !== left.count) {
|
|
2209
|
+
return right.count - left.count;
|
|
2210
|
+
}
|
|
2211
|
+
return left.prefix.localeCompare(right.prefix);
|
|
2212
|
+
}).slice(0, 5);
|
|
2213
|
+
return {
|
|
2214
|
+
count: uniqueValues.length,
|
|
2215
|
+
families
|
|
2216
|
+
};
|
|
2217
|
+
}
|
|
2218
|
+
function formatTargetSummary(summary) {
|
|
2219
|
+
if (summary.count === 0) {
|
|
2220
|
+
return "count=0";
|
|
2221
|
+
}
|
|
2222
|
+
const families = summary.families.length > 0 ? summary.families.map((family) => `${family.prefix}${family.count}`).join(", ") : "none";
|
|
2223
|
+
return `count=${summary.count}; families=${families}`;
|
|
2224
|
+
}
|
|
2225
|
+
function joinFamilies(families) {
|
|
2226
|
+
if (families.length === 0) {
|
|
2227
|
+
return "";
|
|
2228
|
+
}
|
|
2229
|
+
if (families.length === 1) {
|
|
2230
|
+
return families[0];
|
|
2231
|
+
}
|
|
2232
|
+
if (families.length === 2) {
|
|
2233
|
+
return `${families[0]} and ${families[1]}`;
|
|
2234
|
+
}
|
|
2235
|
+
return `${families.slice(0, -1).join(", ")}, and ${families.at(-1)}`;
|
|
2236
|
+
}
|
|
2237
|
+
function describeTargetSummary(summary) {
|
|
2238
|
+
if (summary.count === 0 || summary.families.length === 0) {
|
|
2239
|
+
return null;
|
|
2240
|
+
}
|
|
2241
|
+
const families = summary.families.map((family) => `${family.prefix} (${family.count})`);
|
|
2242
|
+
return `across ${joinFamilies(families)}`;
|
|
2243
|
+
}
|
|
2244
|
+
|
|
2245
|
+
// src/core/testStatusDecision.ts
|
|
2246
|
+
var TEST_STATUS_DIAGNOSE_JSON_CONTRACT = '{"status":"ok|insufficient","diagnosis_complete":boolean,"raw_needed":boolean,"additional_source_read_likely_low_value":boolean,"read_raw_only_if":string|null,"decision":"stop|zoom|read_source|read_raw","remaining_mode":"none|subset_rerun|full_rerun_diff","primary_suspect_kind":"test|app_code|config|environment|tooling|unknown","confidence_reason":string,"dominant_blocker_bucket_index":number|null,"provider_used":boolean,"provider_confidence":number|null,"provider_failed":boolean,"raw_slice_used":boolean,"raw_slice_strategy":"none|bucket_evidence|traceback_window|head_tail","resolved_summary":{"count":number,"families":[{"prefix":string,"count":number}]},"remaining_summary":{"count":number,"families":[{"prefix":string,"count":number}]},"remaining_subset_available":boolean,"main_buckets":[{"bucket_index":number,"label":string,"count":number,"root_cause":string,"suspect_kind":"test|app_code|config|environment|tooling|unknown","fix_hint":string,"evidence":string[],"bucket_confidence":number,"root_cause_confidence":number,"dominant":boolean,"secondary_visible_despite_blocker":boolean,"mini_diff":{"added_paths"?:number,"removed_models"?:number,"changed_task_mappings"?:number}|null}],"read_targets":[{"file":string,"line":number|null,"why":string,"bucket_index":number,"context_hint":{"start_line":number|null,"end_line":number|null,"search_hint":string|null}}],"next_best_action":{"code":"fix_dominant_blocker|read_source_for_bucket|read_raw_for_exact_traceback|insufficient_signal","bucket_index":number|null,"note":string},"resolved_tests"?:string[],"remaining_tests"?:string[]}';
|
|
2128
2247
|
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
2248
|
var nextBestActionSchema = z2.object({
|
|
2130
2249
|
code: z2.enum([
|
|
@@ -2166,6 +2285,16 @@ var testStatusDiagnoseContractSchema = z2.object({
|
|
|
2166
2285
|
additional_source_read_likely_low_value: z2.boolean(),
|
|
2167
2286
|
read_raw_only_if: z2.string().nullable(),
|
|
2168
2287
|
decision: z2.enum(["stop", "zoom", "read_source", "read_raw"]),
|
|
2288
|
+
remaining_mode: z2.enum(["none", "subset_rerun", "full_rerun_diff"]),
|
|
2289
|
+
primary_suspect_kind: z2.enum([
|
|
2290
|
+
"test",
|
|
2291
|
+
"app_code",
|
|
2292
|
+
"config",
|
|
2293
|
+
"environment",
|
|
2294
|
+
"tooling",
|
|
2295
|
+
"unknown"
|
|
2296
|
+
]),
|
|
2297
|
+
confidence_reason: z2.string().min(1),
|
|
2169
2298
|
dominant_blocker_bucket_index: z2.number().int().nullable(),
|
|
2170
2299
|
provider_used: z2.boolean(),
|
|
2171
2300
|
provider_confidence: z2.number().min(0).max(1).nullable(),
|
|
@@ -2180,6 +2309,15 @@ var testStatusDiagnoseContractSchema = z2.object({
|
|
|
2180
2309
|
label: z2.string(),
|
|
2181
2310
|
count: z2.number().int(),
|
|
2182
2311
|
root_cause: z2.string(),
|
|
2312
|
+
suspect_kind: z2.enum([
|
|
2313
|
+
"test",
|
|
2314
|
+
"app_code",
|
|
2315
|
+
"config",
|
|
2316
|
+
"environment",
|
|
2317
|
+
"tooling",
|
|
2318
|
+
"unknown"
|
|
2319
|
+
]),
|
|
2320
|
+
fix_hint: z2.string().min(1),
|
|
2183
2321
|
evidence: z2.array(z2.string()).max(2),
|
|
2184
2322
|
bucket_confidence: z2.number(),
|
|
2185
2323
|
root_cause_confidence: z2.number(),
|
|
@@ -2230,6 +2368,42 @@ function parseTestStatusProviderSupplement(input) {
|
|
|
2230
2368
|
return testStatusProviderSupplementSchema.parse(JSON.parse(input));
|
|
2231
2369
|
}
|
|
2232
2370
|
var extendedBucketSpecs = [
|
|
2371
|
+
{
|
|
2372
|
+
prefix: "service unavailable:",
|
|
2373
|
+
type: "service_unavailable",
|
|
2374
|
+
label: "service unavailable",
|
|
2375
|
+
genericTitle: "Service unavailable failures",
|
|
2376
|
+
defaultCoverage: "error",
|
|
2377
|
+
rootCauseConfidence: 0.9,
|
|
2378
|
+
dominantPriority: 2,
|
|
2379
|
+
dominantBlocker: true,
|
|
2380
|
+
why: "it contains the dependency service or API path that is unavailable in the test environment",
|
|
2381
|
+
fix: "Restore the dependency service or test double before rerunning the full suite."
|
|
2382
|
+
},
|
|
2383
|
+
{
|
|
2384
|
+
prefix: "db refused:",
|
|
2385
|
+
type: "db_connection_failure",
|
|
2386
|
+
label: "database connection",
|
|
2387
|
+
genericTitle: "Database connection failures",
|
|
2388
|
+
defaultCoverage: "error",
|
|
2389
|
+
rootCauseConfidence: 0.9,
|
|
2390
|
+
dominantPriority: 2,
|
|
2391
|
+
dominantBlocker: true,
|
|
2392
|
+
why: "it contains the database host, DSN, or startup path that is refusing connections",
|
|
2393
|
+
fix: "Restore the test database connectivity before rerunning the full suite."
|
|
2394
|
+
},
|
|
2395
|
+
{
|
|
2396
|
+
prefix: "auth bypass absent:",
|
|
2397
|
+
type: "auth_bypass_absent",
|
|
2398
|
+
label: "auth bypass missing",
|
|
2399
|
+
genericTitle: "Auth bypass setup failures",
|
|
2400
|
+
defaultCoverage: "error",
|
|
2401
|
+
rootCauseConfidence: 0.86,
|
|
2402
|
+
dominantPriority: 2,
|
|
2403
|
+
dominantBlocker: true,
|
|
2404
|
+
why: "it contains the auth bypass fixture or setup path that tests expected to be active",
|
|
2405
|
+
fix: "Restore the test auth bypass fixture or mock before rerunning the full suite."
|
|
2406
|
+
},
|
|
2233
2407
|
{
|
|
2234
2408
|
prefix: "snapshot mismatch:",
|
|
2235
2409
|
type: "snapshot_mismatch",
|
|
@@ -2414,6 +2588,16 @@ var extendedBucketSpecs = [
|
|
|
2414
2588
|
why: "it contains the deprecated API or warning filter that is failing the test run",
|
|
2415
2589
|
fix: "Update the deprecated call site or relax the warning policy only if that is intentional."
|
|
2416
2590
|
},
|
|
2591
|
+
{
|
|
2592
|
+
prefix: "assertion failed:",
|
|
2593
|
+
type: "assertion_failure",
|
|
2594
|
+
label: "assertion failure",
|
|
2595
|
+
genericTitle: "Assertion failures",
|
|
2596
|
+
defaultCoverage: "failed",
|
|
2597
|
+
rootCauseConfidence: 0.76,
|
|
2598
|
+
why: "it contains the expected-versus-actual assertion that failed inside the visible test",
|
|
2599
|
+
fix: "Read the assertion diff or expectation and fix the code or expected value before rerunning."
|
|
2600
|
+
},
|
|
2417
2601
|
{
|
|
2418
2602
|
prefix: "xfail strict:",
|
|
2419
2603
|
type: "xfail_strict_unexpected_pass",
|
|
@@ -2435,54 +2619,127 @@ function extractReasonDetail(reason, prefix) {
|
|
|
2435
2619
|
function formatCount(count, singular, plural = `${singular}s`) {
|
|
2436
2620
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
2437
2621
|
}
|
|
2438
|
-
function
|
|
2622
|
+
function unique2(values) {
|
|
2439
2623
|
return [...new Set(values)];
|
|
2440
2624
|
}
|
|
2441
|
-
function
|
|
2625
|
+
function normalizeTestId2(value) {
|
|
2442
2626
|
return value.replace(/\\/g, "/").trim();
|
|
2443
2627
|
}
|
|
2444
|
-
function
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
if (testsMatch) {
|
|
2448
|
-
return testsMatch[1];
|
|
2628
|
+
function normalizePathCandidate(value) {
|
|
2629
|
+
if (!value) {
|
|
2630
|
+
return null;
|
|
2449
2631
|
}
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2632
|
+
let normalized = value.replace(/\\/g, "/").trim();
|
|
2633
|
+
normalized = normalized.replace(/^[("'`<\[]+/, "").replace(/[>"'`\]),:;]+$/, "");
|
|
2634
|
+
normalized = normalized.replace(/^<repo>\//, "").replace(/^\.\//, "");
|
|
2635
|
+
if (normalized.includes("::")) {
|
|
2636
|
+
normalized = normalized.split("::")[0]?.trim() ?? normalized;
|
|
2453
2637
|
}
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2638
|
+
if (normalized.startsWith("/") && !normalized.startsWith("/tmp/") && !normalized.startsWith("/var/tmp/")) {
|
|
2639
|
+
return null;
|
|
2640
|
+
}
|
|
2641
|
+
if (/^\.github\/workflows\/.+\.(?:yml|yaml)$/i.test(normalized)) {
|
|
2642
|
+
return normalized;
|
|
2643
|
+
}
|
|
2644
|
+
if (/^(?:src|test|tests)\/.+\.[A-Za-z0-9._-]+$/i.test(normalized)) {
|
|
2645
|
+
return normalized;
|
|
2457
2646
|
}
|
|
2458
|
-
|
|
2647
|
+
if (/^(?:package\.json|pytest\.ini|pyproject\.toml|tox\.ini|(?:[A-Za-z0-9._/-]+\/)?conftest\.py)$/i.test(
|
|
2648
|
+
normalized
|
|
2649
|
+
)) {
|
|
2650
|
+
return normalized;
|
|
2651
|
+
}
|
|
2652
|
+
if (/^(?:[A-Za-z0-9._/-]+\/)?(?:vitest|jest)\.config\.[A-Za-z0-9._-]+$/i.test(normalized)) {
|
|
2653
|
+
return normalized;
|
|
2654
|
+
}
|
|
2655
|
+
if (/^(?:[A-Za-z0-9._/-]+\/)?tsconfig(?:\.[A-Za-z0-9_-]+)?\.json$/i.test(normalized)) {
|
|
2656
|
+
return normalized;
|
|
2657
|
+
}
|
|
2658
|
+
if (/^[A-Za-z0-9._/-]*config[A-Za-z0-9._/-]*\.(?:json|yml|yaml)$/i.test(normalized)) {
|
|
2659
|
+
return normalized;
|
|
2660
|
+
}
|
|
2661
|
+
return null;
|
|
2459
2662
|
}
|
|
2460
|
-
function
|
|
2461
|
-
|
|
2462
|
-
|
|
2463
|
-
const prefix = extractTestFamilyPrefix(value);
|
|
2464
|
-
counts.set(prefix, (counts.get(prefix) ?? 0) + 1);
|
|
2663
|
+
function addPathCandidatesFromText(target, text) {
|
|
2664
|
+
if (!text) {
|
|
2665
|
+
return;
|
|
2465
2666
|
}
|
|
2466
|
-
const
|
|
2467
|
-
|
|
2468
|
-
|
|
2469
|
-
|
|
2470
|
-
|
|
2471
|
-
|
|
2667
|
+
const pattern = /(?:^|[\s("'`])((?:\.github\/workflows\/[A-Za-z0-9._/-]+\.(?:yml|yaml)|(?:src|test|tests)\/[A-Za-z0-9._/-]+\.[A-Za-z0-9._-]+|package\.json|pytest\.ini|pyproject\.toml|tox\.ini|(?:[A-Za-z0-9._/-]+\/)?conftest\.py|(?:[A-Za-z0-9._/-]+\/)?(?:vitest|jest)\.config\.[A-Za-z0-9._-]+|(?:[A-Za-z0-9._/-]+\/)?tsconfig(?:\.[A-Za-z0-9_-]+)?\.json|[A-Za-z0-9._/-]*config[A-Za-z0-9._/-]*\.(?:json|yml|yaml)))/g;
|
|
2668
|
+
for (const match of text.matchAll(pattern)) {
|
|
2669
|
+
const normalized = normalizePathCandidate(match[1] ?? null);
|
|
2670
|
+
if (normalized) {
|
|
2671
|
+
target.add(normalized);
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2674
|
+
}
|
|
2675
|
+
function extractBucketPathCandidates(args) {
|
|
2676
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
2677
|
+
const push = (value) => {
|
|
2678
|
+
const normalized = normalizePathCandidate(value);
|
|
2679
|
+
if (normalized) {
|
|
2680
|
+
candidates.add(normalized);
|
|
2472
2681
|
}
|
|
2473
|
-
return left.prefix.localeCompare(right.prefix);
|
|
2474
|
-
}).slice(0, 5);
|
|
2475
|
-
return {
|
|
2476
|
-
count: values.length,
|
|
2477
|
-
families
|
|
2478
2682
|
};
|
|
2683
|
+
push(args.readTarget?.file);
|
|
2684
|
+
for (const item of args.bucket.representativeItems) {
|
|
2685
|
+
push(item.file);
|
|
2686
|
+
addPathCandidatesFromText(candidates, item.label);
|
|
2687
|
+
addPathCandidatesFromText(candidates, item.reason);
|
|
2688
|
+
}
|
|
2689
|
+
addPathCandidatesFromText(candidates, args.bucket.reason);
|
|
2690
|
+
addPathCandidatesFromText(candidates, args.bucket.headline);
|
|
2691
|
+
for (const line of args.bucket.summaryLines) {
|
|
2692
|
+
addPathCandidatesFromText(candidates, line);
|
|
2693
|
+
}
|
|
2694
|
+
return [...candidates];
|
|
2479
2695
|
}
|
|
2480
|
-
function
|
|
2481
|
-
|
|
2482
|
-
|
|
2696
|
+
function isConfigPathCandidate(path8) {
|
|
2697
|
+
return /^\.github\/workflows\/.+\.(?:yml|yaml)$/i.test(path8) || /^(?:package\.json|pytest\.ini|pyproject\.toml|tox\.ini|(?:[A-Za-z0-9._/-]+\/)?conftest\.py)$/i.test(
|
|
2698
|
+
path8
|
|
2699
|
+
) || /^(?:[A-Za-z0-9._/-]+\/)?(?:vitest|jest)\.config\.[A-Za-z0-9._-]+$/i.test(path8) || /^(?:[A-Za-z0-9._/-]+\/)?tsconfig(?:\.[A-Za-z0-9_-]+)?\.json$/i.test(path8) || /^[A-Za-z0-9._/-]*config[A-Za-z0-9._/-]*\.(?:json|yml|yaml)$/i.test(path8);
|
|
2700
|
+
}
|
|
2701
|
+
function isAppPathCandidate(path8) {
|
|
2702
|
+
return path8.startsWith("src/");
|
|
2703
|
+
}
|
|
2704
|
+
function isTestPathCandidate(path8) {
|
|
2705
|
+
return path8.startsWith("test/") || path8.startsWith("tests/");
|
|
2706
|
+
}
|
|
2707
|
+
function looksLikeMatcherLiteralComparison(detail) {
|
|
2708
|
+
return /\bexpected\b[\s\S]*\bto (?:be|contain)\b/i.test(detail);
|
|
2709
|
+
}
|
|
2710
|
+
function looksLikeGoldenLiteralDrift(detail) {
|
|
2711
|
+
return /\\n/.test(detail) || /-\s+(?:Tests|Decision|Likely owner|Next|Stop signal)\b/.test(detail) || /\b(?:node-version|workflow_dispatch|run-name|matrix|registry-url)\b/i.test(detail);
|
|
2712
|
+
}
|
|
2713
|
+
function isGoldenOutputDriftBucket(bucket) {
|
|
2714
|
+
if (bucket.type !== "assertion_failure") {
|
|
2715
|
+
return false;
|
|
2483
2716
|
}
|
|
2484
|
-
const
|
|
2485
|
-
|
|
2717
|
+
const detail = extractReasonDetail(bucket.reason, "assertion failed:") ?? bucket.reason;
|
|
2718
|
+
if (!looksLikeMatcherLiteralComparison(detail)) {
|
|
2719
|
+
return false;
|
|
2720
|
+
}
|
|
2721
|
+
if (bucket.reason.startsWith("snapshot mismatch:")) {
|
|
2722
|
+
return false;
|
|
2723
|
+
}
|
|
2724
|
+
if (!looksLikeGoldenLiteralDrift(detail)) {
|
|
2725
|
+
return false;
|
|
2726
|
+
}
|
|
2727
|
+
const candidates = extractBucketPathCandidates({
|
|
2728
|
+
bucket
|
|
2729
|
+
});
|
|
2730
|
+
return candidates.some((candidate) => isConfigPathCandidate(candidate) || isTestPathCandidate(candidate));
|
|
2731
|
+
}
|
|
2732
|
+
function specializeBucket(bucket) {
|
|
2733
|
+
if (!isGoldenOutputDriftBucket(bucket)) {
|
|
2734
|
+
return bucket;
|
|
2735
|
+
}
|
|
2736
|
+
return {
|
|
2737
|
+
...bucket,
|
|
2738
|
+
type: "golden_output_drift",
|
|
2739
|
+
reason: "golden output drift: expected literal or golden output no longer matches current output",
|
|
2740
|
+
labelOverride: "golden output drift",
|
|
2741
|
+
hint: "Update the expected literal or golden output if the new output is intentional; otherwise fix the generated output and rerun."
|
|
2742
|
+
};
|
|
2486
2743
|
}
|
|
2487
2744
|
function classifyGenericBucketType(reason) {
|
|
2488
2745
|
const extended = findExtendedBucketSpec(reason);
|
|
@@ -2507,6 +2764,9 @@ function classifyGenericBucketType(reason) {
|
|
|
2507
2764
|
if (reason.startsWith("missing module:")) {
|
|
2508
2765
|
return "import_dependency_failure";
|
|
2509
2766
|
}
|
|
2767
|
+
if (reason.startsWith("golden output drift:")) {
|
|
2768
|
+
return "golden_output_drift";
|
|
2769
|
+
}
|
|
2510
2770
|
if (reason.startsWith("assertion failed:")) {
|
|
2511
2771
|
return "assertion_failure";
|
|
2512
2772
|
}
|
|
@@ -2659,7 +2919,7 @@ function mergeBucketDetails(existing, incoming) {
|
|
|
2659
2919
|
count,
|
|
2660
2920
|
confidence: Math.max(existing.confidence, incoming.confidence),
|
|
2661
2921
|
representativeItems,
|
|
2662
|
-
entities:
|
|
2922
|
+
entities: unique2([...existing.entities, ...incoming.entities]),
|
|
2663
2923
|
hint: existing.hint ?? incoming.hint,
|
|
2664
2924
|
overflowCount: Math.max(
|
|
2665
2925
|
existing.overflowCount,
|
|
@@ -2851,6 +3111,9 @@ function labelForBucket(bucket) {
|
|
|
2851
3111
|
if (bucket.type === "import_dependency_failure") {
|
|
2852
3112
|
return "import dependency failure";
|
|
2853
3113
|
}
|
|
3114
|
+
if (bucket.type === "golden_output_drift") {
|
|
3115
|
+
return "golden output drift";
|
|
3116
|
+
}
|
|
2854
3117
|
if (bucket.type === "assertion_failure") {
|
|
2855
3118
|
return "assertion failure";
|
|
2856
3119
|
}
|
|
@@ -2885,6 +3148,9 @@ function rootCauseConfidenceFor(bucket) {
|
|
|
2885
3148
|
if (bucket.type === "contract_snapshot_drift") {
|
|
2886
3149
|
return bucket.entities.length > 0 ? 0.92 : 0.76;
|
|
2887
3150
|
}
|
|
3151
|
+
if (bucket.type === "golden_output_drift") {
|
|
3152
|
+
return 0.78;
|
|
3153
|
+
}
|
|
2888
3154
|
if (bucket.source === "provider") {
|
|
2889
3155
|
return Math.max(0.6, Math.min(bucket.confidence, 0.82));
|
|
2890
3156
|
}
|
|
@@ -2959,6 +3225,9 @@ function buildReadTargetWhy(args) {
|
|
|
2959
3225
|
if (args.bucket.type === "import_dependency_failure") {
|
|
2960
3226
|
return "it is the first visible failing module in this missing dependency bucket";
|
|
2961
3227
|
}
|
|
3228
|
+
if (args.bucket.type === "golden_output_drift") {
|
|
3229
|
+
return "it is the first visible golden or literal drift anchor for this bucket";
|
|
3230
|
+
}
|
|
2962
3231
|
if (args.bucket.type === "assertion_failure") {
|
|
2963
3232
|
return "it is the first visible failing test in this bucket";
|
|
2964
3233
|
}
|
|
@@ -3036,6 +3305,9 @@ function buildReadTargetSearchHint(bucket, anchor) {
|
|
|
3036
3305
|
if (assertionText) {
|
|
3037
3306
|
return assertionText;
|
|
3038
3307
|
}
|
|
3308
|
+
if (bucket.type === "golden_output_drift") {
|
|
3309
|
+
return bucket.representativeItems.map((item) => item.reason.match(/^assertion failed:\s+(.+)$/)?.[1] ?? item.reason).find(Boolean) ?? anchor.label.split("::")[1]?.trim() ?? null;
|
|
3310
|
+
}
|
|
3039
3311
|
if (bucket.reason.startsWith("unknown ")) {
|
|
3040
3312
|
return anchor.reason;
|
|
3041
3313
|
}
|
|
@@ -3090,18 +3362,36 @@ function buildConcreteNextNote(args) {
|
|
|
3090
3362
|
}
|
|
3091
3363
|
const lead = primaryTarget.context_hint.start_line !== null && primaryTarget.context_hint.end_line !== null ? `Read ${primaryTarget.file} lines ${primaryTarget.context_hint.start_line}-${primaryTarget.context_hint.end_line} first; ${primaryTarget.why}.` : primaryTarget.context_hint.search_hint ? `Search for ${primaryTarget.context_hint.search_hint} in ${primaryTarget.file} first; ${primaryTarget.why}.` : `Read ${formatReadTargetLocation(primaryTarget)} first; ${primaryTarget.why}.`;
|
|
3092
3364
|
if (args.nextBestAction.code === "fix_dominant_blocker") {
|
|
3365
|
+
if (args.remainingMode === "subset_rerun") {
|
|
3366
|
+
return "Fix the remaining bucket first, then refresh the full-suite truth with sift rerun.";
|
|
3367
|
+
}
|
|
3368
|
+
if (args.remainingMode === "full_rerun_diff") {
|
|
3369
|
+
return "Fix the remaining bucket first. The cached full-suite baseline is still preserved; use sift rerun when you want to refresh it.";
|
|
3370
|
+
}
|
|
3093
3371
|
if (args.nextBestAction.bucket_index === 1 && args.hasSecondaryVisibleBucket) {
|
|
3094
3372
|
return "Fix bucket 1 first, then rerun the full suite at standard. Secondary buckets are already visible behind it.";
|
|
3095
3373
|
}
|
|
3096
3374
|
return `Fix bucket ${args.nextBestAction.bucket_index ?? 1} first, then rerun the full suite at standard.`;
|
|
3097
3375
|
}
|
|
3098
3376
|
if (args.nextBestAction.code === "read_source_for_bucket") {
|
|
3377
|
+
if (args.remainingMode === "subset_rerun") {
|
|
3378
|
+
return "Fix the remaining bucket first, then refresh the full-suite truth with sift rerun.";
|
|
3379
|
+
}
|
|
3380
|
+
if (args.remainingMode === "full_rerun_diff") {
|
|
3381
|
+
return "Fix the remaining bucket first. The cached full-suite baseline is still preserved; use sift rerun when you want to refresh it.";
|
|
3382
|
+
}
|
|
3099
3383
|
return lead;
|
|
3100
3384
|
}
|
|
3101
3385
|
if (args.nextBestAction.code === "insufficient_signal") {
|
|
3102
|
-
if (args.nextBestAction.note.startsWith("Provider follow-up
|
|
3386
|
+
if (args.nextBestAction.note.startsWith("Provider follow-up")) {
|
|
3103
3387
|
return args.nextBestAction.note;
|
|
3104
3388
|
}
|
|
3389
|
+
if (args.remainingMode === "subset_rerun") {
|
|
3390
|
+
return "Fix the remaining bucket first, then refresh the full-suite truth with sift rerun.";
|
|
3391
|
+
}
|
|
3392
|
+
if (args.remainingMode === "full_rerun_diff") {
|
|
3393
|
+
return "Fix the remaining bucket first. The cached full-suite baseline is still preserved; use sift rerun when you want to refresh it.";
|
|
3394
|
+
}
|
|
3105
3395
|
return `${lead} Then take one deeper sift pass before raw traceback.`;
|
|
3106
3396
|
}
|
|
3107
3397
|
return args.nextBestAction.note;
|
|
@@ -3110,13 +3400,13 @@ function extractMiniDiff(input, bucket) {
|
|
|
3110
3400
|
if (bucket.type !== "contract_snapshot_drift") {
|
|
3111
3401
|
return null;
|
|
3112
3402
|
}
|
|
3113
|
-
const addedPaths =
|
|
3403
|
+
const addedPaths = unique2(
|
|
3114
3404
|
[...input.matchAll(/[+-]\s+'(\/api\/[^']+)'/g)].map((match) => match[1])
|
|
3115
3405
|
).length;
|
|
3116
|
-
const removedModels =
|
|
3406
|
+
const removedModels = unique2(
|
|
3117
3407
|
[...input.matchAll(/[+-]\s+'([A-Za-z0-9._/-]+-[A-Za-z0-9._-]+)'/g)].map((match) => match[1])
|
|
3118
3408
|
).length;
|
|
3119
|
-
const changedTaskMappings =
|
|
3409
|
+
const changedTaskMappings = unique2(
|
|
3120
3410
|
[...input.matchAll(/[+-]\s+'([a-z]+(?:_[a-z0-9]+)+)'/g)].map((match) => match[1])
|
|
3121
3411
|
).length;
|
|
3122
3412
|
if (addedPaths === 0 && removedModels === 0 && changedTaskMappings === 0) {
|
|
@@ -3203,7 +3493,7 @@ function buildProviderSupplementBuckets(args) {
|
|
|
3203
3493
|
});
|
|
3204
3494
|
}
|
|
3205
3495
|
function pickUnknownAnchor(args) {
|
|
3206
|
-
const fromStatusItems = args.kind === "error" ? args.analysis.visibleErrorItems[0] :
|
|
3496
|
+
const fromStatusItems = args.kind === "error" ? args.analysis.visibleErrorItems[0] : args.analysis.visibleFailedItems[0];
|
|
3207
3497
|
if (fromStatusItems) {
|
|
3208
3498
|
return {
|
|
3209
3499
|
label: fromStatusItems.label,
|
|
@@ -3217,7 +3507,7 @@ function pickUnknownAnchor(args) {
|
|
|
3217
3507
|
}
|
|
3218
3508
|
const label = args.kind === "error" ? args.analysis.visibleErrorLabels[0] : args.analysis.visibleFailedLabels[0];
|
|
3219
3509
|
if (label) {
|
|
3220
|
-
const normalizedLabel =
|
|
3510
|
+
const normalizedLabel = normalizeTestId2(label);
|
|
3221
3511
|
const fileMatch = normalizedLabel.match(/^([A-Za-z0-9_./-]+\.[A-Za-z0-9]+)\b/);
|
|
3222
3512
|
const file = fileMatch?.[1] ?? normalizedLabel.split("::")[0] ?? null;
|
|
3223
3513
|
return {
|
|
@@ -3240,12 +3530,14 @@ function buildUnknownBucket(args) {
|
|
|
3240
3530
|
const isError = args.kind === "error";
|
|
3241
3531
|
const label = isError ? "unknown setup blocker" : "unknown failure family";
|
|
3242
3532
|
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";
|
|
3533
|
+
const firstConcreteSignal = anchor && anchor.reason !== reason && anchor.reason !== "setup failures share a repeated but unclassified pattern" && anchor.reason !== "failing tests share a repeated but unclassified pattern" ? `First concrete signal: ${anchor.reason}` : null;
|
|
3243
3534
|
return {
|
|
3244
3535
|
type: "unknown_failure",
|
|
3245
3536
|
headline: `${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`,
|
|
3246
3537
|
summaryLines: [
|
|
3247
|
-
`${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern
|
|
3248
|
-
|
|
3538
|
+
`${label}: ${formatCount(args.count, "visible failure")} share a repeated but unclassified pattern.`,
|
|
3539
|
+
firstConcreteSignal
|
|
3540
|
+
].filter((value) => Boolean(value)),
|
|
3249
3541
|
reason,
|
|
3250
3542
|
count: args.count,
|
|
3251
3543
|
confidence: 0.45,
|
|
@@ -3343,16 +3635,29 @@ function buildDecisionLine(contract) {
|
|
|
3343
3635
|
}
|
|
3344
3636
|
return "- Decision: raw only if exact traceback is required.";
|
|
3345
3637
|
}
|
|
3638
|
+
function buildRemainingPassLine(contract) {
|
|
3639
|
+
if (contract.remaining_mode === "subset_rerun") {
|
|
3640
|
+
return "- Remaining pass: showing only what is still failing from the cached baseline.";
|
|
3641
|
+
}
|
|
3642
|
+
if (contract.remaining_mode === "full_rerun_diff") {
|
|
3643
|
+
return "- Remaining pass: full rerun analyzed against the cached baseline because narrowed rerun is not available for this runner.";
|
|
3644
|
+
}
|
|
3645
|
+
return null;
|
|
3646
|
+
}
|
|
3346
3647
|
function buildComparisonLines(contract) {
|
|
3347
3648
|
const lines = [];
|
|
3649
|
+
const resolvedSummary = buildTestTargetSummary(contract.resolved_tests);
|
|
3650
|
+
const remainingSummary = buildTestTargetSummary(contract.remaining_tests);
|
|
3348
3651
|
if (contract.resolved_tests.length > 0) {
|
|
3652
|
+
const summaryText = describeTargetSummary(resolvedSummary);
|
|
3349
3653
|
lines.push(
|
|
3350
|
-
`- Resolved in this rerun: ${formatCount(contract.resolved_tests.length, "test")} dropped out of the failing set.`
|
|
3654
|
+
`- Resolved in this rerun: ${formatCount(contract.resolved_tests.length, "test")} dropped out of the failing set${summaryText ? ` ${summaryText}` : ""}.`
|
|
3351
3655
|
);
|
|
3352
3656
|
}
|
|
3353
|
-
if (contract.
|
|
3657
|
+
if (contract.remaining_tests.length > 0 && (contract.resolved_tests.length > 0 || contract.remaining_mode !== "none")) {
|
|
3658
|
+
const summaryText = describeTargetSummary(remainingSummary);
|
|
3354
3659
|
lines.push(
|
|
3355
|
-
`- Remaining failing targets: ${formatCount(contract.remaining_tests.length, "test/module", "tests/modules")}.`
|
|
3660
|
+
`- Remaining failing targets: ${formatCount(contract.remaining_tests.length, "test/module", "tests/modules")}${summaryText ? ` ${summaryText}` : ""}.`
|
|
3356
3661
|
);
|
|
3357
3662
|
}
|
|
3358
3663
|
return lines;
|
|
@@ -3372,7 +3677,7 @@ function buildStandardAnchorText(target) {
|
|
|
3372
3677
|
}
|
|
3373
3678
|
return formatReadTargetLocation(target);
|
|
3374
3679
|
}
|
|
3375
|
-
function
|
|
3680
|
+
function resolveBucketFixHint(args) {
|
|
3376
3681
|
if (args.bucket.hint) {
|
|
3377
3682
|
return args.bucket.hint;
|
|
3378
3683
|
}
|
|
@@ -3421,13 +3726,96 @@ function buildStandardFixText(args) {
|
|
|
3421
3726
|
if (args.bucket.type === "runtime_failure") {
|
|
3422
3727
|
return `Fix the visible ${args.bucketLabel} and rerun the full suite at standard.`;
|
|
3423
3728
|
}
|
|
3424
|
-
return
|
|
3729
|
+
return "Inspect the first visible anchor for this bucket, apply the smallest fix that explains it, then rerun the full suite at standard.";
|
|
3730
|
+
}
|
|
3731
|
+
function deriveBucketSuspectKind(args) {
|
|
3732
|
+
const pathCandidates = extractBucketPathCandidates({
|
|
3733
|
+
bucket: args.bucket,
|
|
3734
|
+
readTarget: args.readTarget
|
|
3735
|
+
});
|
|
3736
|
+
const hasConfigCandidate = pathCandidates.some((candidate) => isConfigPathCandidate(candidate));
|
|
3737
|
+
const hasAppCandidate = pathCandidates.some((candidate) => isAppPathCandidate(candidate));
|
|
3738
|
+
const hasTestCandidate = pathCandidates.some((candidate) => isTestPathCandidate(candidate));
|
|
3739
|
+
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") {
|
|
3740
|
+
return "environment";
|
|
3741
|
+
}
|
|
3742
|
+
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") {
|
|
3743
|
+
return "config";
|
|
3744
|
+
}
|
|
3745
|
+
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") {
|
|
3746
|
+
return "test";
|
|
3747
|
+
}
|
|
3748
|
+
if (args.bucket.type === "golden_output_drift") {
|
|
3749
|
+
if (hasConfigCandidate) {
|
|
3750
|
+
return "config";
|
|
3751
|
+
}
|
|
3752
|
+
if (hasAppCandidate) {
|
|
3753
|
+
return "app_code";
|
|
3754
|
+
}
|
|
3755
|
+
if (hasTestCandidate) {
|
|
3756
|
+
return "test";
|
|
3757
|
+
}
|
|
3758
|
+
return "unknown";
|
|
3759
|
+
}
|
|
3760
|
+
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") {
|
|
3761
|
+
return "tooling";
|
|
3762
|
+
}
|
|
3763
|
+
if (args.bucket.type === "unknown_failure") {
|
|
3764
|
+
return "unknown";
|
|
3765
|
+
}
|
|
3766
|
+
if (args.bucket.type === "assertion_failure" || args.bucket.type === "runtime_failure" || args.bucket.type === "type_error_failure" || args.bucket.type === "serialization_encoding_failure") {
|
|
3767
|
+
if (hasConfigCandidate) {
|
|
3768
|
+
return "config";
|
|
3769
|
+
}
|
|
3770
|
+
if (hasAppCandidate) {
|
|
3771
|
+
return "app_code";
|
|
3772
|
+
}
|
|
3773
|
+
if (hasTestCandidate) {
|
|
3774
|
+
return "test";
|
|
3775
|
+
}
|
|
3776
|
+
return "unknown";
|
|
3777
|
+
}
|
|
3778
|
+
return "unknown";
|
|
3779
|
+
}
|
|
3780
|
+
function derivePrimarySuspectKind(args) {
|
|
3781
|
+
const primaryBucket = (args.dominantBlockerBucketIndex !== null ? args.mainBuckets.find((bucket) => bucket.bucket_index === args.dominantBlockerBucketIndex) : null) ?? args.mainBuckets[0];
|
|
3782
|
+
return primaryBucket?.suspect_kind ?? "unknown";
|
|
3783
|
+
}
|
|
3784
|
+
function buildConfidenceReason(args) {
|
|
3785
|
+
const primaryBucket = args.mainBuckets.find((bucket) => bucket.dominant) ?? args.mainBuckets[0];
|
|
3786
|
+
if (args.decision === "stop" && primaryBucket && args.primarySuspectKind !== "unknown") {
|
|
3787
|
+
return `Dominant blocker (${primaryBucket.label}) is anchored and actionable.`;
|
|
3788
|
+
}
|
|
3789
|
+
if (args.decision === "zoom") {
|
|
3790
|
+
return "Unknown or low-confidence buckets remain; one deeper sift pass is justified.";
|
|
3791
|
+
}
|
|
3792
|
+
if (args.decision === "read_source") {
|
|
3793
|
+
return "The bucket is identified, but source context is still needed to make the next fix clear.";
|
|
3794
|
+
}
|
|
3795
|
+
return "Heuristic signal is still insufficient; exact traceback lines are needed.";
|
|
3796
|
+
}
|
|
3797
|
+
function formatSuspectKindLabel(kind) {
|
|
3798
|
+
switch (kind) {
|
|
3799
|
+
case "test":
|
|
3800
|
+
return "test code";
|
|
3801
|
+
case "app_code":
|
|
3802
|
+
return "application code";
|
|
3803
|
+
case "config":
|
|
3804
|
+
return "test or project configuration";
|
|
3805
|
+
case "environment":
|
|
3806
|
+
return "environment setup";
|
|
3807
|
+
case "tooling":
|
|
3808
|
+
return "test runner or tooling";
|
|
3809
|
+
default:
|
|
3810
|
+
return "unknown";
|
|
3811
|
+
}
|
|
3425
3812
|
}
|
|
3426
3813
|
function buildStandardBucketSupport(args) {
|
|
3427
3814
|
return {
|
|
3428
3815
|
headline: args.bucket.summaryLines[0] ? `- ${args.bucket.summaryLines[0]}` : renderBucketHeadline(args.contractBucket),
|
|
3816
|
+
firstConcreteSignalText: args.bucket.source === "unknown" ? args.bucket.summaryLines[1] ?? null : null,
|
|
3429
3817
|
anchorText: buildStandardAnchorText(args.readTarget),
|
|
3430
|
-
fixText:
|
|
3818
|
+
fixText: resolveBucketFixHint({
|
|
3431
3819
|
bucket: args.bucket,
|
|
3432
3820
|
bucketLabel: args.contractBucket.label
|
|
3433
3821
|
})
|
|
@@ -3435,6 +3823,10 @@ function buildStandardBucketSupport(args) {
|
|
|
3435
3823
|
}
|
|
3436
3824
|
function renderStandard(args) {
|
|
3437
3825
|
const lines = [...buildOutcomeLines(args.analysis), ...buildComparisonLines(args.contract)];
|
|
3826
|
+
const remainingPassLine = buildRemainingPassLine(args.contract);
|
|
3827
|
+
if (remainingPassLine) {
|
|
3828
|
+
lines.push(remainingPassLine);
|
|
3829
|
+
}
|
|
3438
3830
|
if (args.contract.main_buckets.length > 0) {
|
|
3439
3831
|
for (const bucket of args.contract.main_buckets.slice(0, 3)) {
|
|
3440
3832
|
const rawBucket = args.buckets[bucket.bucket_index - 1];
|
|
@@ -3450,6 +3842,9 @@ function renderStandard(args) {
|
|
|
3450
3842
|
)
|
|
3451
3843
|
});
|
|
3452
3844
|
lines.push(support.headline);
|
|
3845
|
+
if (support.firstConcreteSignalText) {
|
|
3846
|
+
lines.push(`- ${support.firstConcreteSignalText}`);
|
|
3847
|
+
}
|
|
3453
3848
|
if (support.anchorText) {
|
|
3454
3849
|
lines.push(`- Anchor: ${support.anchorText}`);
|
|
3455
3850
|
}
|
|
@@ -3459,12 +3854,19 @@ function renderStandard(args) {
|
|
|
3459
3854
|
}
|
|
3460
3855
|
}
|
|
3461
3856
|
lines.push(buildDecisionLine(args.contract));
|
|
3857
|
+
if (args.contract.main_buckets.length > 0 && args.contract.primary_suspect_kind !== "unknown") {
|
|
3858
|
+
lines.push(`- Likely owner: ${formatSuspectKindLabel(args.contract.primary_suspect_kind)}`);
|
|
3859
|
+
}
|
|
3462
3860
|
lines.push(`- Next: ${args.contract.next_best_action.note}`);
|
|
3463
3861
|
lines.push(buildStopSignal(args.contract));
|
|
3464
3862
|
return lines.join("\n");
|
|
3465
3863
|
}
|
|
3466
3864
|
function renderFocused(args) {
|
|
3467
3865
|
const lines = [...buildOutcomeLines(args.analysis), ...buildComparisonLines(args.contract)];
|
|
3866
|
+
const remainingPassLine = buildRemainingPassLine(args.contract);
|
|
3867
|
+
if (remainingPassLine) {
|
|
3868
|
+
lines.push(remainingPassLine);
|
|
3869
|
+
}
|
|
3468
3870
|
for (const bucket of args.contract.main_buckets) {
|
|
3469
3871
|
const rawBucket = args.buckets[bucket.bucket_index - 1];
|
|
3470
3872
|
lines.push(
|
|
@@ -3484,6 +3886,10 @@ function renderFocused(args) {
|
|
|
3484
3886
|
}
|
|
3485
3887
|
function renderVerbose(args) {
|
|
3486
3888
|
const lines = [...buildOutcomeLines(args.analysis), ...buildComparisonLines(args.contract)];
|
|
3889
|
+
const remainingPassLine = buildRemainingPassLine(args.contract);
|
|
3890
|
+
if (remainingPassLine) {
|
|
3891
|
+
lines.push(remainingPassLine);
|
|
3892
|
+
}
|
|
3487
3893
|
for (const bucket of args.contract.main_buckets) {
|
|
3488
3894
|
const rawBucket = args.buckets[bucket.bucket_index - 1];
|
|
3489
3895
|
lines.push(
|
|
@@ -3533,7 +3939,9 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
3533
3939
|
count: residuals.remainingFailed
|
|
3534
3940
|
})
|
|
3535
3941
|
].filter((bucket) => Boolean(bucket));
|
|
3536
|
-
const buckets = prioritizeBuckets(
|
|
3942
|
+
const buckets = prioritizeBuckets(
|
|
3943
|
+
[...combinedBuckets, ...unknownBuckets].map((bucket) => specializeBucket(bucket))
|
|
3944
|
+
).slice(0, 3);
|
|
3537
3945
|
const simpleCollectionFailure = args.analysis.collectionErrorCount !== void 0 && args.analysis.collectionItems.length === 0 && buckets.length === 0;
|
|
3538
3946
|
const dominantBucket = buckets.map((bucket, index) => ({
|
|
3539
3947
|
bucket,
|
|
@@ -3546,29 +3954,49 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
3546
3954
|
})[0] ?? null;
|
|
3547
3955
|
const hasUnknownBucket = buckets.some((bucket) => isUnknownBucket(bucket));
|
|
3548
3956
|
const hasConcreteCoverage = args.analysis.failed === 0 && args.analysis.errors === 0 ? true : residuals.remainingErrors === 0 && residuals.remainingFailed === 0;
|
|
3549
|
-
const diagnosisComplete = args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0 || simpleCollectionFailure || buckets.length > 0 && hasConcreteCoverage && !hasUnknownBucket && (dominantBucket?.bucket.confidence ?? 0) >= 0.6;
|
|
3550
|
-
const rawNeeded = buckets.length === 0 ? !(args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0 || simpleCollectionFailure) : !diagnosisComplete && !hasUnknownBucket && buckets.every((bucket) => bucket.confidence < 0.7);
|
|
3551
3957
|
const dominantBlockerBucketIndex = dominantBucket && isDominantBlockerType(dominantBucket.bucket.type) ? dominantBucket.index + 1 : null;
|
|
3552
3958
|
const readTargets = buildReadTargets({
|
|
3553
3959
|
buckets,
|
|
3554
3960
|
dominantBucketIndex: dominantBlockerBucketIndex
|
|
3555
3961
|
});
|
|
3556
|
-
const
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3962
|
+
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"));
|
|
3963
|
+
const smallConcreteSuite = args.analysis.failed + args.analysis.errors <= 2 && residuals.remainingErrors === 0 && residuals.remainingFailed === 0 && buckets.length === 1 && !hasUnknownBucket && dominantBucket !== null && dominantBucketHasConcreteAnchor;
|
|
3964
|
+
const dominantConfidenceThreshold = smallConcreteSuite ? 0.55 : 0.6;
|
|
3965
|
+
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;
|
|
3966
|
+
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);
|
|
3967
|
+
const mainBuckets = buckets.map((bucket, index) => {
|
|
3968
|
+
const bucketIndex = index + 1;
|
|
3969
|
+
const label = labelForBucket(bucket);
|
|
3970
|
+
const readTarget = readTargets.find((target) => target.bucket_index === bucketIndex);
|
|
3971
|
+
return {
|
|
3972
|
+
bucket_index: bucketIndex,
|
|
3973
|
+
label,
|
|
3974
|
+
count: bucket.count,
|
|
3975
|
+
root_cause: bucket.reason,
|
|
3976
|
+
suspect_kind: deriveBucketSuspectKind({
|
|
3977
|
+
bucket,
|
|
3978
|
+
readTarget
|
|
3979
|
+
}),
|
|
3980
|
+
fix_hint: resolveBucketFixHint({
|
|
3981
|
+
bucket,
|
|
3982
|
+
bucketLabel: label
|
|
3983
|
+
}),
|
|
3984
|
+
evidence: buildBucketEvidence(bucket),
|
|
3985
|
+
bucket_confidence: Number(bucket.confidence.toFixed(2)),
|
|
3986
|
+
root_cause_confidence: Number(rootCauseConfidenceFor(bucket).toFixed(2)),
|
|
3987
|
+
dominant: dominantBucket?.index === index,
|
|
3988
|
+
secondary_visible_despite_blocker: dominantBlockerBucketIndex !== null && dominantBlockerBucketIndex !== bucketIndex,
|
|
3989
|
+
mini_diff: extractMiniDiff(args.input, bucket)
|
|
3990
|
+
};
|
|
3991
|
+
});
|
|
3992
|
+
const resolvedTests = unique2(args.resolvedTests ?? []);
|
|
3993
|
+
const remainingTests = unique2(
|
|
3994
|
+
args.remainingTests ?? unique2([...args.analysis.visibleErrorLabels, ...args.analysis.visibleFailedLabels])
|
|
3571
3995
|
);
|
|
3996
|
+
const primarySuspectKind = derivePrimarySuspectKind({
|
|
3997
|
+
mainBuckets,
|
|
3998
|
+
dominantBlockerBucketIndex
|
|
3999
|
+
});
|
|
3572
4000
|
let nextBestAction;
|
|
3573
4001
|
if (args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0) {
|
|
3574
4002
|
nextBestAction = {
|
|
@@ -3613,7 +4041,10 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
3613
4041
|
raw_needed: rawNeeded,
|
|
3614
4042
|
additional_source_read_likely_low_value: diagnosisComplete && !rawNeeded,
|
|
3615
4043
|
read_raw_only_if: rawNeeded ? "you still need exact traceback lines after focused or verbose detail" : null,
|
|
4044
|
+
remaining_mode: args.remainingMode ?? "none",
|
|
3616
4045
|
dominant_blocker_bucket_index: dominantBlockerBucketIndex,
|
|
4046
|
+
primary_suspect_kind: primarySuspectKind,
|
|
4047
|
+
confidence_reason: "Unknown or low-confidence buckets remain; one deeper sift pass is justified.",
|
|
3617
4048
|
provider_used: false,
|
|
3618
4049
|
provider_confidence: null,
|
|
3619
4050
|
provider_failed: false,
|
|
@@ -3641,13 +4072,21 @@ function buildTestStatusDiagnoseContract(args) {
|
|
|
3641
4072
|
readTargets,
|
|
3642
4073
|
hasSecondaryVisibleBucket: mainBuckets.some(
|
|
3643
4074
|
(bucket) => bucket.secondary_visible_despite_blocker
|
|
3644
|
-
)
|
|
4075
|
+
),
|
|
4076
|
+
remainingMode: args.contractOverrides?.remaining_mode ?? baseContract.remaining_mode
|
|
3645
4077
|
})
|
|
3646
4078
|
}
|
|
3647
4079
|
};
|
|
4080
|
+
const resolvedDecision = effectiveDecision ?? deriveDecision(mergedContractWithoutDecision);
|
|
4081
|
+
const resolvedConfidenceReason = buildConfidenceReason({
|
|
4082
|
+
decision: resolvedDecision,
|
|
4083
|
+
mainBuckets,
|
|
4084
|
+
primarySuspectKind: mergedContractWithoutDecision.primary_suspect_kind
|
|
4085
|
+
});
|
|
3648
4086
|
const contract = testStatusDiagnoseContractSchema.parse({
|
|
3649
4087
|
...mergedContractWithoutDecision,
|
|
3650
|
-
|
|
4088
|
+
confidence_reason: resolvedConfidenceReason,
|
|
4089
|
+
decision: resolvedDecision
|
|
3651
4090
|
});
|
|
3652
4091
|
return {
|
|
3653
4092
|
contract,
|
|
@@ -3699,6 +4138,7 @@ function buildTestStatusAnalysisContext(args) {
|
|
|
3699
4138
|
`- diagnosis_complete=${args.contract.diagnosis_complete}`,
|
|
3700
4139
|
`- raw_needed=${args.contract.raw_needed}`,
|
|
3701
4140
|
`- decision=${args.contract.decision}`,
|
|
4141
|
+
`- remaining_mode=${args.contract.remaining_mode}`,
|
|
3702
4142
|
`- provider_used=${args.contract.provider_used}`,
|
|
3703
4143
|
`- provider_failed=${args.contract.provider_failed}`,
|
|
3704
4144
|
`- raw_slice_strategy=${args.contract.raw_slice_strategy}`,
|
|
@@ -4052,6 +4492,27 @@ function buildFallbackOutput(args) {
|
|
|
4052
4492
|
var RISK_LINE_PATTERN = /(destroy|delete|drop|recreate|replace|revoke|deny|downtime|data loss|iam|network exposure)/i;
|
|
4053
4493
|
var ZERO_DESTRUCTIVE_SUMMARY_PATTERN = /\b0\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/i;
|
|
4054
4494
|
var SAFE_LINE_PATTERN = /(no changes|up-to-date|up to date|no risky changes|safe to apply)/i;
|
|
4495
|
+
var RESOURCE_DESTROY_HEADER_PATTERN = /^#\s+.+\bwill be (destroyed|deleted|replaced)\b/i;
|
|
4496
|
+
var DESTROY_ERROR_PATTERN = /(instance cannot be destroyed|prevent_destroy|downtime|data loss)/i;
|
|
4497
|
+
var ACTION_DESTROY_PATTERN = /^-\s+destroy$/i;
|
|
4498
|
+
var TSC_CODE_LABELS = {
|
|
4499
|
+
TS1002: "syntax error",
|
|
4500
|
+
TS1005: "syntax error",
|
|
4501
|
+
TS2304: "cannot find name",
|
|
4502
|
+
TS2307: "cannot find module",
|
|
4503
|
+
TS2322: "type mismatch",
|
|
4504
|
+
TS2339: "missing property on type",
|
|
4505
|
+
TS2345: "argument type mismatch",
|
|
4506
|
+
TS2554: "wrong argument count",
|
|
4507
|
+
TS2741: "missing required property",
|
|
4508
|
+
TS2769: "no matching overload",
|
|
4509
|
+
TS5083: "config file error",
|
|
4510
|
+
TS6133: "declared but unused",
|
|
4511
|
+
TS7006: "implicit any",
|
|
4512
|
+
TS18003: "no inputs were found",
|
|
4513
|
+
TS18046: "unknown type",
|
|
4514
|
+
TS18048: "possibly undefined"
|
|
4515
|
+
};
|
|
4055
4516
|
function collectEvidence(input, matcher, limit = 3) {
|
|
4056
4517
|
return input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && matcher.test(line)).slice(0, limit);
|
|
4057
4518
|
}
|
|
@@ -4065,63 +4526,231 @@ function inferPackage(line) {
|
|
|
4065
4526
|
function inferRemediation(pkg2) {
|
|
4066
4527
|
return `Upgrade ${pkg2} to a patched version.`;
|
|
4067
4528
|
}
|
|
4068
|
-
function
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
return lastMatch ? Number(lastMatch[1]) : 0;
|
|
4072
|
-
}
|
|
4073
|
-
function detectTestRunner(input) {
|
|
4074
|
-
if (/^\s*Test Files?\s+(?:\d+\s+failed\s*\|\s*)?\d+\s+passed/m.test(input) || /^\s*Tests?\s+(?:\d+\s+failed\s*\|\s*)?\d+\s+passed/m.test(input) || /^\s*Snapshots?\s+(?:\d+\s+failed\s*\|\s*)?\d+\s+passed/m.test(input) || /⎯{2,}\s+Failed Tests?\s+\d+\s+⎯{2,}/.test(input)) {
|
|
4075
|
-
return "vitest";
|
|
4076
|
-
}
|
|
4077
|
-
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)) {
|
|
4078
|
-
return "jest";
|
|
4529
|
+
function parseCompactAuditVulnerability(line) {
|
|
4530
|
+
if (/^Severity:\s*/i.test(line)) {
|
|
4531
|
+
return null;
|
|
4079
4532
|
}
|
|
4080
|
-
if (
|
|
4081
|
-
return
|
|
4533
|
+
if (!/\b(critical|high)\b/i.test(line)) {
|
|
4534
|
+
return null;
|
|
4082
4535
|
}
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
function extractVitestLineCount(input, label, metric) {
|
|
4086
|
-
const matcher = new RegExp(`^\\s*${label}\\s+(.+)$`, "gmi");
|
|
4087
|
-
const lines = [...input.matchAll(matcher)];
|
|
4088
|
-
const line = lines.at(-1)?.[1];
|
|
4089
|
-
if (!line) {
|
|
4536
|
+
const pkg2 = inferPackage(line);
|
|
4537
|
+
if (!pkg2) {
|
|
4090
4538
|
return null;
|
|
4091
4539
|
}
|
|
4092
|
-
|
|
4093
|
-
|
|
4540
|
+
return {
|
|
4541
|
+
package: pkg2,
|
|
4542
|
+
severity: inferSeverity(line),
|
|
4543
|
+
remediation: inferRemediation(pkg2)
|
|
4544
|
+
};
|
|
4094
4545
|
}
|
|
4095
|
-
function
|
|
4096
|
-
const
|
|
4097
|
-
|
|
4098
|
-
const line = lines.at(-1)?.[1];
|
|
4099
|
-
if (!line) {
|
|
4546
|
+
function inferAuditPackageHeader(line) {
|
|
4547
|
+
const trimmed = line.trim();
|
|
4548
|
+
if (trimmed.length === 0 || trimmed.startsWith("#") || trimmed.includes(":") || /^node_modules\//i.test(trimmed)) {
|
|
4100
4549
|
return null;
|
|
4101
4550
|
}
|
|
4102
|
-
const
|
|
4103
|
-
return
|
|
4551
|
+
const match = trimmed.match(/^([@a-z0-9._/-]+)(?:\s{2,}|\s+(?:[<>=~^*]|\d))/i);
|
|
4552
|
+
return match?.[1] ?? null;
|
|
4104
4553
|
}
|
|
4105
|
-
function
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
}
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4554
|
+
function collectAuditCriticalVulnerabilities(input) {
|
|
4555
|
+
const lines = input.split("\n");
|
|
4556
|
+
const vulnerabilities = [];
|
|
4557
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4558
|
+
const pushVulnerability = (pkg2, severity) => {
|
|
4559
|
+
const key = `${pkg2}:${severity}`;
|
|
4560
|
+
if (seen.has(key)) {
|
|
4561
|
+
return;
|
|
4562
|
+
}
|
|
4563
|
+
seen.add(key);
|
|
4564
|
+
vulnerabilities.push({
|
|
4565
|
+
package: pkg2,
|
|
4566
|
+
severity,
|
|
4567
|
+
remediation: inferRemediation(pkg2)
|
|
4568
|
+
});
|
|
4569
|
+
};
|
|
4570
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4571
|
+
const line = lines[index].trim();
|
|
4572
|
+
if (!line) {
|
|
4573
|
+
continue;
|
|
4574
|
+
}
|
|
4575
|
+
const compact = parseCompactAuditVulnerability(line);
|
|
4576
|
+
if (compact) {
|
|
4577
|
+
pushVulnerability(compact.package, compact.severity);
|
|
4578
|
+
continue;
|
|
4579
|
+
}
|
|
4580
|
+
const pkg2 = inferAuditPackageHeader(line);
|
|
4581
|
+
if (!pkg2) {
|
|
4582
|
+
continue;
|
|
4583
|
+
}
|
|
4584
|
+
for (let cursor = index + 1; cursor < Math.min(lines.length, index + 5); cursor += 1) {
|
|
4585
|
+
const candidate = lines[cursor].trim();
|
|
4586
|
+
if (!candidate) {
|
|
4587
|
+
continue;
|
|
4588
|
+
}
|
|
4589
|
+
const severityMatch = candidate.match(/^Severity:\s*(critical|high)\b/i);
|
|
4590
|
+
if (severityMatch) {
|
|
4591
|
+
pushVulnerability(pkg2, severityMatch[1].toLowerCase());
|
|
4592
|
+
break;
|
|
4593
|
+
}
|
|
4594
|
+
if (inferAuditPackageHeader(candidate) || parseCompactAuditVulnerability(candidate)) {
|
|
4595
|
+
break;
|
|
4596
|
+
}
|
|
4597
|
+
}
|
|
4598
|
+
}
|
|
4599
|
+
return vulnerabilities;
|
|
4600
|
+
}
|
|
4601
|
+
function getCount(input, label) {
|
|
4602
|
+
const matches = [...input.matchAll(new RegExp(`(\\d+)\\s+${label}`, "gi"))];
|
|
4603
|
+
const lastMatch = matches.at(-1);
|
|
4604
|
+
return lastMatch ? Number(lastMatch[1]) : 0;
|
|
4605
|
+
}
|
|
4606
|
+
function collectInfraRiskEvidence(input) {
|
|
4607
|
+
const lines = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
|
4608
|
+
const evidence = [];
|
|
4609
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4610
|
+
const pushMatches = (matcher, options) => {
|
|
4611
|
+
let added = 0;
|
|
4612
|
+
for (const line of lines) {
|
|
4613
|
+
if (!matcher.test(line)) {
|
|
4614
|
+
continue;
|
|
4615
|
+
}
|
|
4616
|
+
if (options?.exclude?.test(line)) {
|
|
4617
|
+
continue;
|
|
4618
|
+
}
|
|
4619
|
+
if (seen.has(line)) {
|
|
4620
|
+
continue;
|
|
4621
|
+
}
|
|
4622
|
+
evidence.push(line);
|
|
4623
|
+
seen.add(line);
|
|
4624
|
+
added += 1;
|
|
4625
|
+
if (options?.limit && added >= options.limit) {
|
|
4626
|
+
return;
|
|
4627
|
+
}
|
|
4628
|
+
if (evidence.length >= (options?.maxEvidence ?? 4)) {
|
|
4629
|
+
return;
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
};
|
|
4633
|
+
pushMatches(/Plan:/i, {
|
|
4634
|
+
exclude: ZERO_DESTRUCTIVE_SUMMARY_PATTERN,
|
|
4635
|
+
limit: 1
|
|
4636
|
+
});
|
|
4637
|
+
if (evidence.length < 4) {
|
|
4638
|
+
pushMatches(RESOURCE_DESTROY_HEADER_PATTERN, { limit: 2 });
|
|
4639
|
+
}
|
|
4640
|
+
if (evidence.length < 4) {
|
|
4641
|
+
pushMatches(DESTROY_ERROR_PATTERN, { limit: 1 });
|
|
4642
|
+
}
|
|
4643
|
+
if (evidence.length < 4) {
|
|
4644
|
+
pushMatches(ACTION_DESTROY_PATTERN, { limit: 1 });
|
|
4645
|
+
}
|
|
4646
|
+
if (evidence.length < 4) {
|
|
4647
|
+
pushMatches(RISK_LINE_PATTERN, {
|
|
4648
|
+
exclude: /->\s+null$|\b0\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/i,
|
|
4649
|
+
maxEvidence: 4
|
|
4650
|
+
});
|
|
4651
|
+
}
|
|
4652
|
+
return evidence.slice(0, 4);
|
|
4653
|
+
}
|
|
4654
|
+
function collectInfraDestroyTargets(input) {
|
|
4655
|
+
const targets = [];
|
|
4656
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4657
|
+
for (const line of input.split("\n").map((entry) => entry.trim())) {
|
|
4658
|
+
const match = line.match(/^#\s+(.+?)\s+will be (destroyed|deleted|replaced)\b/i);
|
|
4659
|
+
const target = match?.[1]?.trim();
|
|
4660
|
+
if (!target || seen.has(target)) {
|
|
4661
|
+
continue;
|
|
4662
|
+
}
|
|
4663
|
+
seen.add(target);
|
|
4664
|
+
targets.push(target);
|
|
4665
|
+
}
|
|
4666
|
+
return targets;
|
|
4667
|
+
}
|
|
4668
|
+
function inferInfraDestroyCount(input, destroyTargets) {
|
|
4669
|
+
const matches = [
|
|
4670
|
+
...input.matchAll(/\b(\d+)\s+to\s+(destroy|delete|drop|recreate|replace|revoke)\b/gi)
|
|
4671
|
+
];
|
|
4672
|
+
const lastMatch = matches.at(-1);
|
|
4673
|
+
return lastMatch ? Number(lastMatch[1]) : destroyTargets.length;
|
|
4674
|
+
}
|
|
4675
|
+
function collectInfraBlockers(input) {
|
|
4676
|
+
const lines = input.split("\n");
|
|
4677
|
+
const blockers = [];
|
|
4678
|
+
const seen = /* @__PURE__ */ new Set();
|
|
4679
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
4680
|
+
const trimmed = lines[index]?.trim();
|
|
4681
|
+
const errorMatch = trimmed?.match(/^(?:[│|]\s*)?Error:\s+(.+)$/);
|
|
4682
|
+
if (!errorMatch) {
|
|
4683
|
+
continue;
|
|
4684
|
+
}
|
|
4685
|
+
const message = errorMatch[1].trim();
|
|
4686
|
+
const nearby = lines.slice(index, index + 8).join("\n");
|
|
4687
|
+
const preventDestroyTarget = nearby.match(/Resource\s+([^\s]+)\s+has lifecycle\.prevent_destroy set/i)?.[1] ?? null;
|
|
4688
|
+
const type = preventDestroyTarget ? "prevent_destroy" : "destroy_blocked";
|
|
4689
|
+
const key = `${type}:${preventDestroyTarget ?? ""}:${message}`;
|
|
4690
|
+
if (seen.has(key)) {
|
|
4691
|
+
continue;
|
|
4692
|
+
}
|
|
4693
|
+
seen.add(key);
|
|
4694
|
+
blockers.push({
|
|
4695
|
+
type,
|
|
4696
|
+
target: preventDestroyTarget,
|
|
4697
|
+
message
|
|
4698
|
+
});
|
|
4699
|
+
}
|
|
4700
|
+
return blockers;
|
|
4701
|
+
}
|
|
4702
|
+
function detectTestRunner(input) {
|
|
4703
|
+
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)) {
|
|
4704
|
+
return "vitest";
|
|
4705
|
+
}
|
|
4706
|
+
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)) {
|
|
4707
|
+
return "jest";
|
|
4708
|
+
}
|
|
4709
|
+
if (/\bpytest\b/i.test(input) || /^\s*(?:FAILED|ERROR)\s+[A-Za-z0-9_./-]+::[^\n]+$/m.test(input) || /^\s*=+.*\b\d+\s+failed\b.*=+\s*$/m.test(input) || /\bcollected\s+\d+\s+items\b/i.test(input)) {
|
|
4710
|
+
return "pytest";
|
|
4711
|
+
}
|
|
4712
|
+
return "unknown";
|
|
4713
|
+
}
|
|
4714
|
+
function extractVitestLineCount(input, label, metric) {
|
|
4715
|
+
const matcher = new RegExp(`^\\s*${label}\\s+(.+)$`, "gmi");
|
|
4716
|
+
const lines = [...input.matchAll(matcher)];
|
|
4717
|
+
const line = lines.at(-1)?.[1];
|
|
4718
|
+
if (!line) {
|
|
4719
|
+
return null;
|
|
4720
|
+
}
|
|
4721
|
+
const metricMatch = line.match(new RegExp(`(\\d+)\\s+${metric}`, "i"));
|
|
4722
|
+
return metricMatch ? Number(metricMatch[1]) : null;
|
|
4723
|
+
}
|
|
4724
|
+
function extractJestLineCount(input, label, metric) {
|
|
4725
|
+
const matcher = new RegExp(`^\\s*${label}:\\s+(.+)$`, "gmi");
|
|
4726
|
+
const lines = [...input.matchAll(matcher)];
|
|
4727
|
+
const line = lines.at(-1)?.[1];
|
|
4728
|
+
if (!line) {
|
|
4729
|
+
return null;
|
|
4730
|
+
}
|
|
4731
|
+
const metricMatch = line.match(new RegExp(`(\\d+)\\s+${metric}`, "i"));
|
|
4732
|
+
return metricMatch ? Number(metricMatch[1]) : null;
|
|
4733
|
+
}
|
|
4734
|
+
function extractTestStatusCounts(input, runner) {
|
|
4735
|
+
if (runner === "vitest") {
|
|
4736
|
+
return {
|
|
4737
|
+
passed: extractVitestLineCount(input, "Tests?", "passed") ?? getCount(input, "passed"),
|
|
4738
|
+
failed: extractVitestLineCount(input, "Tests?", "failed") ?? getCount(input, "failed"),
|
|
4739
|
+
errors: extractVitestLineCount(input, "Errors?", "error") ?? extractVitestLineCount(input, "Errors?", "errors") ?? Math.max(getCount(input, "errors"), getCount(input, "error")),
|
|
4740
|
+
skipped: extractVitestLineCount(input, "Tests?", "skipped") ?? getCount(input, "skipped"),
|
|
4741
|
+
snapshotFailures: extractVitestLineCount(input, "Snapshots?", "failed") ?? void 0
|
|
4742
|
+
};
|
|
4743
|
+
}
|
|
4744
|
+
if (runner === "jest") {
|
|
4745
|
+
return {
|
|
4746
|
+
passed: extractJestLineCount(input, "Tests", "passed") ?? getCount(input, "passed"),
|
|
4747
|
+
failed: extractJestLineCount(input, "Tests", "failed") ?? getCount(input, "failed"),
|
|
4748
|
+
errors: Math.max(getCount(input, "errors"), getCount(input, "error")),
|
|
4749
|
+
skipped: extractJestLineCount(input, "Tests", "skipped") ?? getCount(input, "skipped")
|
|
4750
|
+
};
|
|
4751
|
+
}
|
|
4752
|
+
return {
|
|
4753
|
+
passed: getCount(input, "passed"),
|
|
4125
4754
|
failed: getCount(input, "failed"),
|
|
4126
4755
|
errors: Math.max(getCount(input, "errors"), getCount(input, "error")),
|
|
4127
4756
|
skipped: getCount(input, "skipped")
|
|
@@ -4147,6 +4776,21 @@ function collectUniqueMatches(input, matcher, limit = 6) {
|
|
|
4147
4776
|
}
|
|
4148
4777
|
return values;
|
|
4149
4778
|
}
|
|
4779
|
+
function compactDisplayFile(file) {
|
|
4780
|
+
const normalized = file.replace(/\\/g, "/").trim();
|
|
4781
|
+
if (!normalized) {
|
|
4782
|
+
return file;
|
|
4783
|
+
}
|
|
4784
|
+
const looksAbsolute = normalized.startsWith("/") || /^[A-Za-z]:\//.test(normalized);
|
|
4785
|
+
if (!looksAbsolute && normalized.length <= 60) {
|
|
4786
|
+
return normalized;
|
|
4787
|
+
}
|
|
4788
|
+
const basename = normalized.split("/").at(-1);
|
|
4789
|
+
return basename && basename.length > 0 ? basename : normalized;
|
|
4790
|
+
}
|
|
4791
|
+
function formatDisplayedFiles(files, limit = 3) {
|
|
4792
|
+
return [...new Set([...files].map((file) => file.trim()).filter(Boolean))].sort((left, right) => left.localeCompare(right)).slice(0, limit).map((file) => compactDisplayFile(file));
|
|
4793
|
+
}
|
|
4150
4794
|
function emptyAnchor() {
|
|
4151
4795
|
return {
|
|
4152
4796
|
file: null,
|
|
@@ -4426,6 +5070,31 @@ function classifyFailureReason(line, options) {
|
|
|
4426
5070
|
group: "permission or locked resource failures"
|
|
4427
5071
|
};
|
|
4428
5072
|
}
|
|
5073
|
+
const osDiskFullFailure = normalized.match(
|
|
5074
|
+
/(OSError:\s*\[Errno 28\][^$]*|No space left on device)/i
|
|
5075
|
+
);
|
|
5076
|
+
if (osDiskFullFailure) {
|
|
5077
|
+
return {
|
|
5078
|
+
reason: buildClassifiedReason(
|
|
5079
|
+
"configuration",
|
|
5080
|
+
`disk full (${buildExcerptDetail(
|
|
5081
|
+
osDiskFullFailure[1] ?? normalized,
|
|
5082
|
+
"No space left on device"
|
|
5083
|
+
)})`
|
|
5084
|
+
),
|
|
5085
|
+
group: "test configuration failures"
|
|
5086
|
+
};
|
|
5087
|
+
}
|
|
5088
|
+
const osPermissionFailure = normalized.match(/OSError:\s*\[Errno 13\][^$]*/i);
|
|
5089
|
+
if (osPermissionFailure) {
|
|
5090
|
+
return {
|
|
5091
|
+
reason: buildClassifiedReason(
|
|
5092
|
+
"permission",
|
|
5093
|
+
buildExcerptDetail(osPermissionFailure[0] ?? normalized, "permission denied")
|
|
5094
|
+
),
|
|
5095
|
+
group: "permission or locked resource failures"
|
|
5096
|
+
};
|
|
5097
|
+
}
|
|
4429
5098
|
const xdistWorkerCrash = normalized.match(
|
|
4430
5099
|
/(worker ['"][^'"]+['"] crashed|node down:\s*[^,;]+|WorkerLost[^,;]*|Worker exited unexpectedly[^,;]*|worker exited unexpectedly[^,;]*)/i
|
|
4431
5100
|
);
|
|
@@ -4451,7 +5120,7 @@ function classifyFailureReason(line, options) {
|
|
|
4451
5120
|
};
|
|
4452
5121
|
}
|
|
4453
5122
|
const networkFailure = normalized.match(
|
|
4454
|
-
/(Max retries exceeded[^,;]*|gaierror[^,;]*|SSLCertVerificationError[^,;]*|Network is unreachable)/i
|
|
5123
|
+
/(Max retries exceeded[^,;]*|gaierror[^,;]*|SSLCertVerificationError[^,;]*|Network is unreachable|ConnectionResetError[^,;]*|BrokenPipeError[^,;]*|HTTPError:\s*[45]\d\d[^,;]*)/i
|
|
4455
5124
|
);
|
|
4456
5125
|
if (networkFailure) {
|
|
4457
5126
|
return {
|
|
@@ -4462,6 +5131,15 @@ function classifyFailureReason(line, options) {
|
|
|
4462
5131
|
group: "network dependency failures"
|
|
4463
5132
|
};
|
|
4464
5133
|
}
|
|
5134
|
+
const matcherAssertionFailure = normalized.match(
|
|
5135
|
+
/(expect\(received\)\.(?:toBe|toEqual|toStrictEqual|toMatchObject)\(expected\))/i
|
|
5136
|
+
);
|
|
5137
|
+
if (matcherAssertionFailure) {
|
|
5138
|
+
return {
|
|
5139
|
+
reason: `assertion failed: ${matcherAssertionFailure[1]}`.slice(0, 120),
|
|
5140
|
+
group: "assertion failures"
|
|
5141
|
+
};
|
|
5142
|
+
}
|
|
4465
5143
|
const relationMigration = normalized.match(/relation ["'`]([^"'`]+)["'`] does not exist/i);
|
|
4466
5144
|
if (relationMigration) {
|
|
4467
5145
|
return {
|
|
@@ -4500,6 +5178,34 @@ function classifyFailureReason(line, options) {
|
|
|
4500
5178
|
group: "memory exhaustion failures"
|
|
4501
5179
|
};
|
|
4502
5180
|
}
|
|
5181
|
+
const propertySetterOverrideFailure = normalized.match(
|
|
5182
|
+
/AttributeError:\s*(property ['"][^'"]+['"] of ['"][^'"]+['"] object has no setter|can't set attribute|readonly attribute|read-only attribute)/i
|
|
5183
|
+
);
|
|
5184
|
+
if (propertySetterOverrideFailure) {
|
|
5185
|
+
return {
|
|
5186
|
+
reason: buildClassifiedReason(
|
|
5187
|
+
"configuration",
|
|
5188
|
+
`invalid test setup override (${buildExcerptDetail(
|
|
5189
|
+
`AttributeError: ${propertySetterOverrideFailure[1] ?? normalized}`,
|
|
5190
|
+
"AttributeError: can't set attribute"
|
|
5191
|
+
)})`
|
|
5192
|
+
),
|
|
5193
|
+
group: "test configuration failures"
|
|
5194
|
+
};
|
|
5195
|
+
}
|
|
5196
|
+
const setupOverrideFailure = normalized.match(/\b(AttributeError|TypeError):\s*(.+)$/i);
|
|
5197
|
+
if (setupOverrideFailure && /(monkeypatch|patch|fixture|settings|conftest)/i.test(normalized)) {
|
|
5198
|
+
return {
|
|
5199
|
+
reason: buildClassifiedReason(
|
|
5200
|
+
"configuration",
|
|
5201
|
+
`invalid test setup override (${buildExcerptDetail(
|
|
5202
|
+
`${setupOverrideFailure[1]}: ${setupOverrideFailure[2] ?? ""}`,
|
|
5203
|
+
`${setupOverrideFailure[1]}`
|
|
5204
|
+
)})`
|
|
5205
|
+
),
|
|
5206
|
+
group: "test configuration failures"
|
|
5207
|
+
};
|
|
5208
|
+
}
|
|
4503
5209
|
const typeErrorFailure = normalized.match(/TypeError:\s*(.+)$/i);
|
|
4504
5210
|
if (typeErrorFailure) {
|
|
4505
5211
|
return {
|
|
@@ -5205,6 +5911,9 @@ function classifyBucketTypeFromReason(reason) {
|
|
|
5205
5911
|
if (reason.startsWith("missing module:")) {
|
|
5206
5912
|
return "import_dependency_failure";
|
|
5207
5913
|
}
|
|
5914
|
+
if (reason.startsWith("golden output drift:")) {
|
|
5915
|
+
return "golden_output_drift";
|
|
5916
|
+
}
|
|
5208
5917
|
if (reason.startsWith("assertion failed:")) {
|
|
5209
5918
|
return "assertion_failure";
|
|
5210
5919
|
}
|
|
@@ -5581,13 +6290,17 @@ function analyzeTestStatus(input) {
|
|
|
5581
6290
|
const interrupted = /\binterrupted\b/i.test(input) || /\bKeyboardInterrupt\b/i.test(input);
|
|
5582
6291
|
const collectionItems = chooseStrongestFailureItems(collectCollectionFailureItems(input));
|
|
5583
6292
|
const inlineItems = chooseStrongestFailureItems(collectInlineFailureItems(input));
|
|
6293
|
+
const statusItems = collectInlineFailureItemsWithStatus(input);
|
|
5584
6294
|
const visibleErrorItems = chooseStrongestStatusFailureItems([
|
|
5585
6295
|
...collectionItems.map((item) => ({
|
|
5586
6296
|
...item,
|
|
5587
6297
|
status: "error"
|
|
5588
6298
|
})),
|
|
5589
|
-
...
|
|
6299
|
+
...statusItems.filter((item) => item.status === "error")
|
|
5590
6300
|
]);
|
|
6301
|
+
const visibleFailedItems = chooseStrongestStatusFailureItems(
|
|
6302
|
+
statusItems.filter((item) => item.status === "failed")
|
|
6303
|
+
);
|
|
5591
6304
|
const labels = collectFailureLabels(input);
|
|
5592
6305
|
const visibleErrorLabels = labels.filter((item) => item.status === "error").map((item) => item.label);
|
|
5593
6306
|
const visibleFailedLabels = labels.filter((item) => item.status === "failed").map((item) => item.label);
|
|
@@ -5646,6 +6359,7 @@ function analyzeTestStatus(input) {
|
|
|
5646
6359
|
visibleErrorLabels,
|
|
5647
6360
|
visibleFailedLabels,
|
|
5648
6361
|
visibleErrorItems,
|
|
6362
|
+
visibleFailedItems,
|
|
5649
6363
|
buckets
|
|
5650
6364
|
};
|
|
5651
6365
|
}
|
|
@@ -5706,20 +6420,18 @@ function testStatusHeuristic(input, detail = "standard") {
|
|
|
5706
6420
|
return null;
|
|
5707
6421
|
}
|
|
5708
6422
|
function auditCriticalHeuristic(input) {
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
};
|
|
5722
|
-
}).filter((item) => item !== null);
|
|
6423
|
+
if (/\bfound\s+0\s+vulnerabilities\b/i.test(input) || /\b0\s+vulnerabilities\b/i.test(input)) {
|
|
6424
|
+
return JSON.stringify(
|
|
6425
|
+
{
|
|
6426
|
+
status: "ok",
|
|
6427
|
+
vulnerabilities: [],
|
|
6428
|
+
summary: "No high or critical vulnerabilities found in the provided input."
|
|
6429
|
+
},
|
|
6430
|
+
null,
|
|
6431
|
+
2
|
|
6432
|
+
);
|
|
6433
|
+
}
|
|
6434
|
+
const vulnerabilities = collectAuditCriticalVulnerabilities(input);
|
|
5723
6435
|
if (vulnerabilities.length === 0) {
|
|
5724
6436
|
return null;
|
|
5725
6437
|
}
|
|
@@ -5735,16 +6447,19 @@ function auditCriticalHeuristic(input) {
|
|
|
5735
6447
|
);
|
|
5736
6448
|
}
|
|
5737
6449
|
function infraRiskHeuristic(input) {
|
|
6450
|
+
const destroyTargets = collectInfraDestroyTargets(input);
|
|
6451
|
+
const blockers = collectInfraBlockers(input);
|
|
5738
6452
|
const zeroDestructiveEvidence = input.split("\n").map((line) => line.trim()).filter((line) => line.length > 0 && ZERO_DESTRUCTIVE_SUMMARY_PATTERN.test(line)).slice(0, 3);
|
|
5739
|
-
const riskEvidence = input
|
|
5740
|
-
(line) => line.length > 0 && RISK_LINE_PATTERN.test(line) && !ZERO_DESTRUCTIVE_SUMMARY_PATTERN.test(line)
|
|
5741
|
-
).slice(0, 3);
|
|
6453
|
+
const riskEvidence = collectInfraRiskEvidence(input);
|
|
5742
6454
|
if (riskEvidence.length > 0) {
|
|
5743
6455
|
return JSON.stringify(
|
|
5744
6456
|
{
|
|
5745
6457
|
verdict: "fail",
|
|
5746
6458
|
reason: "Destructive or clearly risky infrastructure change signals are present.",
|
|
5747
|
-
evidence: riskEvidence
|
|
6459
|
+
evidence: riskEvidence,
|
|
6460
|
+
destroy_count: inferInfraDestroyCount(input, destroyTargets),
|
|
6461
|
+
destroy_targets: destroyTargets,
|
|
6462
|
+
blockers
|
|
5748
6463
|
},
|
|
5749
6464
|
null,
|
|
5750
6465
|
2
|
|
@@ -5755,7 +6470,10 @@ function infraRiskHeuristic(input) {
|
|
|
5755
6470
|
{
|
|
5756
6471
|
verdict: "pass",
|
|
5757
6472
|
reason: "The provided input explicitly indicates zero destructive changes.",
|
|
5758
|
-
evidence: zeroDestructiveEvidence
|
|
6473
|
+
evidence: zeroDestructiveEvidence,
|
|
6474
|
+
destroy_count: 0,
|
|
6475
|
+
destroy_targets: [],
|
|
6476
|
+
blockers: []
|
|
5759
6477
|
},
|
|
5760
6478
|
null,
|
|
5761
6479
|
2
|
|
@@ -5767,7 +6485,10 @@ function infraRiskHeuristic(input) {
|
|
|
5767
6485
|
{
|
|
5768
6486
|
verdict: "pass",
|
|
5769
6487
|
reason: "The provided input explicitly indicates no risky infrastructure changes.",
|
|
5770
|
-
evidence: safeEvidence
|
|
6488
|
+
evidence: safeEvidence,
|
|
6489
|
+
destroy_count: 0,
|
|
6490
|
+
destroy_targets: [],
|
|
6491
|
+
blockers: []
|
|
5771
6492
|
},
|
|
5772
6493
|
null,
|
|
5773
6494
|
2
|
|
@@ -5775,6 +6496,551 @@ function infraRiskHeuristic(input) {
|
|
|
5775
6496
|
}
|
|
5776
6497
|
return null;
|
|
5777
6498
|
}
|
|
6499
|
+
function parseTscErrors(input) {
|
|
6500
|
+
const diagnostics = [];
|
|
6501
|
+
for (const rawLine of input.split("\n")) {
|
|
6502
|
+
const line = rawLine.replace(/\u001b\[[0-9;]*m/g, "").trimEnd();
|
|
6503
|
+
if (!line.trim()) {
|
|
6504
|
+
continue;
|
|
6505
|
+
}
|
|
6506
|
+
let match = line.match(/^(.+)\((\d+),(\d+)\):\s+error\s+(TS\d+):\s+(.+)$/);
|
|
6507
|
+
if (match) {
|
|
6508
|
+
diagnostics.push({
|
|
6509
|
+
file: match[1].replace(/\\/g, "/").trim(),
|
|
6510
|
+
line: Number(match[2]),
|
|
6511
|
+
column: Number(match[3]),
|
|
6512
|
+
code: match[4],
|
|
6513
|
+
message: match[5].trim()
|
|
6514
|
+
});
|
|
6515
|
+
continue;
|
|
6516
|
+
}
|
|
6517
|
+
match = line.match(/^(.+):(\d+):(\d+)\s+-\s+error\s+(TS\d+):\s+(.+)$/);
|
|
6518
|
+
if (match) {
|
|
6519
|
+
diagnostics.push({
|
|
6520
|
+
file: match[1].replace(/\\/g, "/").trim(),
|
|
6521
|
+
line: Number(match[2]),
|
|
6522
|
+
column: Number(match[3]),
|
|
6523
|
+
code: match[4],
|
|
6524
|
+
message: match[5].trim()
|
|
6525
|
+
});
|
|
6526
|
+
continue;
|
|
6527
|
+
}
|
|
6528
|
+
match = line.match(/^\s*error\s+(TS\d+):\s+(.+)$/);
|
|
6529
|
+
if (match) {
|
|
6530
|
+
diagnostics.push({
|
|
6531
|
+
file: null,
|
|
6532
|
+
line: null,
|
|
6533
|
+
column: null,
|
|
6534
|
+
code: match[1],
|
|
6535
|
+
message: match[2].trim()
|
|
6536
|
+
});
|
|
6537
|
+
}
|
|
6538
|
+
}
|
|
6539
|
+
return diagnostics;
|
|
6540
|
+
}
|
|
6541
|
+
function extractTscSummary(input) {
|
|
6542
|
+
const matches = [
|
|
6543
|
+
...input.matchAll(/\bFound\s+(\d+)\s+errors?\b(?:\s+in\s+(\d+)\s+files?)?\.?/gi)
|
|
6544
|
+
];
|
|
6545
|
+
const summary = matches.at(-1);
|
|
6546
|
+
if (!summary) {
|
|
6547
|
+
return null;
|
|
6548
|
+
}
|
|
6549
|
+
return {
|
|
6550
|
+
errorCount: Number(summary[1]),
|
|
6551
|
+
fileCount: summary[2] ? Number(summary[2]) : null
|
|
6552
|
+
};
|
|
6553
|
+
}
|
|
6554
|
+
function formatTscGroup(args) {
|
|
6555
|
+
const label = TSC_CODE_LABELS[args.code];
|
|
6556
|
+
const displayFiles = formatDisplayedFiles(args.files);
|
|
6557
|
+
let line = `- ${args.code}`;
|
|
6558
|
+
if (label) {
|
|
6559
|
+
line += ` (${label})`;
|
|
6560
|
+
}
|
|
6561
|
+
line += `: ${formatCount2(args.count, "occurrence")}`;
|
|
6562
|
+
if (displayFiles.length > 0) {
|
|
6563
|
+
line += ` across ${displayFiles.join(", ")}`;
|
|
6564
|
+
}
|
|
6565
|
+
return `${line}.`;
|
|
6566
|
+
}
|
|
6567
|
+
function typecheckSummaryHeuristic(input) {
|
|
6568
|
+
if (input.trim().length === 0) {
|
|
6569
|
+
return null;
|
|
6570
|
+
}
|
|
6571
|
+
const diagnostics = parseTscErrors(input);
|
|
6572
|
+
const summary = extractTscSummary(input);
|
|
6573
|
+
const hasTscSignal = diagnostics.length > 0 || summary !== null || /\berror\s+TS\d+:/m.test(input);
|
|
6574
|
+
if (!hasTscSignal) {
|
|
6575
|
+
return null;
|
|
6576
|
+
}
|
|
6577
|
+
if (summary?.errorCount === 0) {
|
|
6578
|
+
return "No type errors.";
|
|
6579
|
+
}
|
|
6580
|
+
if (diagnostics.length === 0 && summary === null) {
|
|
6581
|
+
return null;
|
|
6582
|
+
}
|
|
6583
|
+
const errorCount = summary?.errorCount ?? diagnostics.length;
|
|
6584
|
+
const allFiles = new Set(
|
|
6585
|
+
diagnostics.map((diagnostic) => diagnostic.file).filter((file) => Boolean(file))
|
|
6586
|
+
);
|
|
6587
|
+
const fileCount = summary?.fileCount ?? (allFiles.size > 0 ? allFiles.size : null);
|
|
6588
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6589
|
+
for (const diagnostic of diagnostics) {
|
|
6590
|
+
const group = groups.get(diagnostic.code) ?? {
|
|
6591
|
+
count: 0,
|
|
6592
|
+
files: /* @__PURE__ */ new Set()
|
|
6593
|
+
};
|
|
6594
|
+
group.count += 1;
|
|
6595
|
+
if (diagnostic.file) {
|
|
6596
|
+
group.files.add(diagnostic.file);
|
|
6597
|
+
}
|
|
6598
|
+
groups.set(diagnostic.code, group);
|
|
6599
|
+
}
|
|
6600
|
+
const bullets = [
|
|
6601
|
+
`- Typecheck failed: ${formatCount2(errorCount, "error")}${fileCount ? ` in ${formatCount2(fileCount, "file")}` : ""}.`
|
|
6602
|
+
];
|
|
6603
|
+
const sortedGroups = [...groups.entries()].map(([code, group]) => ({
|
|
6604
|
+
code,
|
|
6605
|
+
count: group.count,
|
|
6606
|
+
files: group.files
|
|
6607
|
+
})).sort((left, right) => right.count - left.count || left.code.localeCompare(right.code));
|
|
6608
|
+
for (const group of sortedGroups.slice(0, 3)) {
|
|
6609
|
+
bullets.push(formatTscGroup(group));
|
|
6610
|
+
}
|
|
6611
|
+
if (sortedGroups.length > 3) {
|
|
6612
|
+
const overflowFiles = /* @__PURE__ */ new Set();
|
|
6613
|
+
for (const group of sortedGroups.slice(3)) {
|
|
6614
|
+
for (const file of group.files) {
|
|
6615
|
+
overflowFiles.add(file);
|
|
6616
|
+
}
|
|
6617
|
+
}
|
|
6618
|
+
let overflow = `- ${formatCount2(sortedGroups.length - 3, "more error code")}`;
|
|
6619
|
+
if (overflowFiles.size > 0) {
|
|
6620
|
+
overflow += ` across ${formatCount2(overflowFiles.size, "file")}`;
|
|
6621
|
+
}
|
|
6622
|
+
bullets.push(`${overflow}.`);
|
|
6623
|
+
}
|
|
6624
|
+
return bullets.join("\n");
|
|
6625
|
+
}
|
|
6626
|
+
function looksLikeEslintFileHeader(line) {
|
|
6627
|
+
if (line.trim().length === 0 || line.trim() !== line) {
|
|
6628
|
+
return false;
|
|
6629
|
+
}
|
|
6630
|
+
if (/^\s*[✖×x]\s+\d+\s+problems?\b/i.test(line) || /potentially\s+fixable/i.test(line) || /^\d+\s+problems?\b/i.test(line)) {
|
|
6631
|
+
return false;
|
|
6632
|
+
}
|
|
6633
|
+
const normalized = line.replace(/\\/g, "/");
|
|
6634
|
+
const pathLike = normalized.startsWith("/") || normalized.startsWith("./") || normalized.startsWith("../") || /^[A-Za-z]:\//.test(normalized) || /^[A-Za-z0-9_.-]+\//.test(normalized);
|
|
6635
|
+
return pathLike && /\.[A-Za-z0-9]+$/.test(normalized);
|
|
6636
|
+
}
|
|
6637
|
+
function normalizeEslintRule(rule, message) {
|
|
6638
|
+
if (rule && rule.trim().length > 0) {
|
|
6639
|
+
return rule.trim();
|
|
6640
|
+
}
|
|
6641
|
+
if (/parsing error/i.test(message)) {
|
|
6642
|
+
return "parsing error";
|
|
6643
|
+
}
|
|
6644
|
+
if (/fatal/i.test(message)) {
|
|
6645
|
+
return "fatal error";
|
|
6646
|
+
}
|
|
6647
|
+
return "unclassified lint error";
|
|
6648
|
+
}
|
|
6649
|
+
function parseEslintStylish(input) {
|
|
6650
|
+
const violations = [];
|
|
6651
|
+
let currentFile = null;
|
|
6652
|
+
for (const rawLine of input.split("\n")) {
|
|
6653
|
+
const line = rawLine.replace(/\u001b\[[0-9;]*m/g, "").replace(/\r$/, "");
|
|
6654
|
+
if (looksLikeEslintFileHeader(line.trim())) {
|
|
6655
|
+
currentFile = line.trim().replace(/\\/g, "/");
|
|
6656
|
+
continue;
|
|
6657
|
+
}
|
|
6658
|
+
let match = line.match(/^\s*(\d+):(\d+)\s+(error|warning)\s+(.+?)\s{2,}(\S+)\s*$/);
|
|
6659
|
+
if (match) {
|
|
6660
|
+
violations.push({
|
|
6661
|
+
file: currentFile ?? "(unknown file)",
|
|
6662
|
+
line: Number(match[1]),
|
|
6663
|
+
column: Number(match[2]),
|
|
6664
|
+
severity: match[3],
|
|
6665
|
+
message: match[4].trim(),
|
|
6666
|
+
rule: normalizeEslintRule(match[5], match[4])
|
|
6667
|
+
});
|
|
6668
|
+
continue;
|
|
6669
|
+
}
|
|
6670
|
+
match = line.match(/^\s*(\d+):(\d+)\s+(error|warning)\s+(.+?)\s*$/);
|
|
6671
|
+
if (match) {
|
|
6672
|
+
violations.push({
|
|
6673
|
+
file: currentFile ?? "(unknown file)",
|
|
6674
|
+
line: Number(match[1]),
|
|
6675
|
+
column: Number(match[2]),
|
|
6676
|
+
severity: match[3],
|
|
6677
|
+
message: match[4].trim(),
|
|
6678
|
+
rule: normalizeEslintRule(null, match[4])
|
|
6679
|
+
});
|
|
6680
|
+
}
|
|
6681
|
+
}
|
|
6682
|
+
return violations;
|
|
6683
|
+
}
|
|
6684
|
+
function extractEslintSummary(input) {
|
|
6685
|
+
const summaryMatches = [
|
|
6686
|
+
...input.matchAll(
|
|
6687
|
+
/^\s*[✖×x]?\s*(\d+)\s+problems?\s+\((\d+)\s+errors?,\s+(\d+)\s+warnings?\)/gim
|
|
6688
|
+
)
|
|
6689
|
+
];
|
|
6690
|
+
const summary = summaryMatches.at(-1);
|
|
6691
|
+
if (!summary) {
|
|
6692
|
+
return null;
|
|
6693
|
+
}
|
|
6694
|
+
const fixableMatch = input.match(
|
|
6695
|
+
/(\d+)\s+errors?\s+and\s+(\d+)\s+warnings?\s+(?:are|is)\s+potentially\s+fixable/i
|
|
6696
|
+
);
|
|
6697
|
+
return {
|
|
6698
|
+
problems: Number(summary[1]),
|
|
6699
|
+
errors: Number(summary[2]),
|
|
6700
|
+
warnings: Number(summary[3]),
|
|
6701
|
+
fixableProblems: fixableMatch ? Number(fixableMatch[1]) + Number(fixableMatch[2]) : null
|
|
6702
|
+
};
|
|
6703
|
+
}
|
|
6704
|
+
function formatLintGroup(args) {
|
|
6705
|
+
const totalErrors = args.errors;
|
|
6706
|
+
const totalWarnings = args.warnings;
|
|
6707
|
+
const displayFiles = formatDisplayedFiles(args.files);
|
|
6708
|
+
let detail = "";
|
|
6709
|
+
if (totalErrors > 0 && totalWarnings > 0) {
|
|
6710
|
+
detail = `${formatCount2(totalErrors, "error")}, ${formatCount2(totalWarnings, "warning")}`;
|
|
6711
|
+
} else if (totalErrors > 0) {
|
|
6712
|
+
detail = formatCount2(totalErrors, "error");
|
|
6713
|
+
} else {
|
|
6714
|
+
detail = formatCount2(totalWarnings, "warning");
|
|
6715
|
+
}
|
|
6716
|
+
let line = `- ${args.rule}: ${detail}`;
|
|
6717
|
+
if (displayFiles.length > 0) {
|
|
6718
|
+
line += ` across ${displayFiles.join(", ")}`;
|
|
6719
|
+
}
|
|
6720
|
+
return `${line}.`;
|
|
6721
|
+
}
|
|
6722
|
+
function lintFailuresHeuristic(input) {
|
|
6723
|
+
const trimmed = input.trim();
|
|
6724
|
+
if (trimmed.length === 0 || trimmed.startsWith("[") || trimmed.startsWith("{")) {
|
|
6725
|
+
return null;
|
|
6726
|
+
}
|
|
6727
|
+
const summary = extractEslintSummary(input);
|
|
6728
|
+
const violations = parseEslintStylish(input);
|
|
6729
|
+
if (summary === null && violations.length === 0) {
|
|
6730
|
+
return null;
|
|
6731
|
+
}
|
|
6732
|
+
if (summary?.problems === 0) {
|
|
6733
|
+
return "No lint failures.";
|
|
6734
|
+
}
|
|
6735
|
+
const problems = summary?.problems ?? violations.length;
|
|
6736
|
+
const errors = summary?.errors ?? countPattern(input, /^\s*\d+:\d+\s+error\b/gm);
|
|
6737
|
+
const warnings = summary?.warnings ?? countPattern(input, /^\s*\d+:\d+\s+warning\b/gm);
|
|
6738
|
+
const bullets = [];
|
|
6739
|
+
if (errors > 0) {
|
|
6740
|
+
let headline = `- Lint failed: ${formatCount2(problems, "problem")} (${formatCount2(errors, "error")}, ${formatCount2(warnings, "warning")}).`;
|
|
6741
|
+
if ((summary?.fixableProblems ?? 0) > 0) {
|
|
6742
|
+
headline += ` ${formatCount2(summary.fixableProblems, "problem")} potentially fixable with --fix.`;
|
|
6743
|
+
}
|
|
6744
|
+
bullets.push(headline);
|
|
6745
|
+
} else {
|
|
6746
|
+
bullets.push(`- No lint errors visible: ${formatCount2(warnings, "warning")}.`);
|
|
6747
|
+
}
|
|
6748
|
+
const groups = /* @__PURE__ */ new Map();
|
|
6749
|
+
for (const violation of violations) {
|
|
6750
|
+
const group = groups.get(violation.rule) ?? {
|
|
6751
|
+
errors: 0,
|
|
6752
|
+
warnings: 0,
|
|
6753
|
+
files: /* @__PURE__ */ new Set()
|
|
6754
|
+
};
|
|
6755
|
+
if (violation.severity === "error") {
|
|
6756
|
+
group.errors += 1;
|
|
6757
|
+
} else {
|
|
6758
|
+
group.warnings += 1;
|
|
6759
|
+
}
|
|
6760
|
+
group.files.add(violation.file);
|
|
6761
|
+
groups.set(violation.rule, group);
|
|
6762
|
+
}
|
|
6763
|
+
const sortedGroups = [...groups.entries()].map(([rule, group]) => ({
|
|
6764
|
+
rule,
|
|
6765
|
+
errors: group.errors,
|
|
6766
|
+
warnings: group.warnings,
|
|
6767
|
+
total: group.errors + group.warnings,
|
|
6768
|
+
files: group.files
|
|
6769
|
+
})).sort((left, right) => {
|
|
6770
|
+
const leftHasErrors = left.errors > 0 ? 1 : 0;
|
|
6771
|
+
const rightHasErrors = right.errors > 0 ? 1 : 0;
|
|
6772
|
+
return rightHasErrors - leftHasErrors || right.total - left.total || left.rule.localeCompare(right.rule);
|
|
6773
|
+
});
|
|
6774
|
+
for (const group of sortedGroups.slice(0, 3)) {
|
|
6775
|
+
bullets.push(formatLintGroup(group));
|
|
6776
|
+
}
|
|
6777
|
+
if (sortedGroups.length > 3) {
|
|
6778
|
+
const overflowFiles = /* @__PURE__ */ new Set();
|
|
6779
|
+
for (const group of sortedGroups.slice(3)) {
|
|
6780
|
+
for (const file of group.files) {
|
|
6781
|
+
overflowFiles.add(file);
|
|
6782
|
+
}
|
|
6783
|
+
}
|
|
6784
|
+
let overflow = `- ${formatCount2(sortedGroups.length - 3, "more rule")}`;
|
|
6785
|
+
if (overflowFiles.size > 0) {
|
|
6786
|
+
overflow += ` across ${formatCount2(overflowFiles.size, "file")}`;
|
|
6787
|
+
}
|
|
6788
|
+
bullets.push(`${overflow}.`);
|
|
6789
|
+
}
|
|
6790
|
+
return bullets.join("\n");
|
|
6791
|
+
}
|
|
6792
|
+
function stripAnsiText(input) {
|
|
6793
|
+
return input.replace(/\u001b\[[0-9;]*m/g, "");
|
|
6794
|
+
}
|
|
6795
|
+
function normalizeBuildPath(file) {
|
|
6796
|
+
return file.replace(/\\/g, "/").replace(/^\.\//, "").trim();
|
|
6797
|
+
}
|
|
6798
|
+
function trimTrailingSentencePunctuation(input) {
|
|
6799
|
+
return input.replace(/[.:]+$/, "").trim();
|
|
6800
|
+
}
|
|
6801
|
+
function containsKnownBuildFailureSignal(input) {
|
|
6802
|
+
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);
|
|
6803
|
+
}
|
|
6804
|
+
function detectExplicitBuildSuccess(input) {
|
|
6805
|
+
if (containsKnownBuildFailureSignal(input)) {
|
|
6806
|
+
return false;
|
|
6807
|
+
}
|
|
6808
|
+
return /\bcompiled successfully\b/i.test(input) || /^\s*Build succeeded\.?\s*$/im.test(input) || /\bcompiled with 0 errors?\b/i.test(input);
|
|
6809
|
+
}
|
|
6810
|
+
function inferBuildFailureCategory(message) {
|
|
6811
|
+
if (/module not found|can't resolve|could not resolve|cannot find module|no required module provides package/i.test(
|
|
6812
|
+
message
|
|
6813
|
+
)) {
|
|
6814
|
+
return "module-resolution";
|
|
6815
|
+
}
|
|
6816
|
+
if (/no matching export|does not provide an export named|missing export/i.test(message)) {
|
|
6817
|
+
return "missing-export";
|
|
6818
|
+
}
|
|
6819
|
+
if (/cannot find name|cannot find value|not found in this scope|undefined:|undeclared identifier/i.test(
|
|
6820
|
+
message
|
|
6821
|
+
)) {
|
|
6822
|
+
return "undefined-identifier";
|
|
6823
|
+
}
|
|
6824
|
+
if (/syntax error|unexpected token|expected ['"`;)]|expected .* after expression/i.test(message)) {
|
|
6825
|
+
return "syntax";
|
|
6826
|
+
}
|
|
6827
|
+
if (/\bTS\d+\b/.test(message) || /type .* is not assignable|type error|no matching overload/i.test(message)) {
|
|
6828
|
+
return "type";
|
|
6829
|
+
}
|
|
6830
|
+
return "generic";
|
|
6831
|
+
}
|
|
6832
|
+
function buildFailureSuggestion(category) {
|
|
6833
|
+
switch (category) {
|
|
6834
|
+
case "module-resolution":
|
|
6835
|
+
return "Install the missing package or fix the import path.";
|
|
6836
|
+
case "missing-export":
|
|
6837
|
+
return "Check the export name in the source module.";
|
|
6838
|
+
case "undefined-identifier":
|
|
6839
|
+
return "Define or import the missing identifier.";
|
|
6840
|
+
case "syntax":
|
|
6841
|
+
return "Fix the syntax error at the indicated location.";
|
|
6842
|
+
case "type":
|
|
6843
|
+
return "Fix the type error at the indicated location.";
|
|
6844
|
+
case "wrapper":
|
|
6845
|
+
return "Check the underlying build tool output above.";
|
|
6846
|
+
default:
|
|
6847
|
+
return "Fix the first reported error and rebuild.";
|
|
6848
|
+
}
|
|
6849
|
+
}
|
|
6850
|
+
function formatBuildFailureOutput(match) {
|
|
6851
|
+
const message = trimTrailingSentencePunctuation(match.message);
|
|
6852
|
+
const suggestion = buildFailureSuggestion(match.category);
|
|
6853
|
+
const displayFile = match.file ? compactDisplayFile(match.file) : null;
|
|
6854
|
+
if (displayFile && match.line !== null) {
|
|
6855
|
+
return `Build failed: ${message} in ${displayFile}:${match.line}. Fix: ${suggestion}`;
|
|
6856
|
+
}
|
|
6857
|
+
if (displayFile) {
|
|
6858
|
+
return `Build failed: ${message} in ${displayFile}. Fix: ${suggestion}`;
|
|
6859
|
+
}
|
|
6860
|
+
return `Build failed: ${message}. Fix: ${suggestion}`;
|
|
6861
|
+
}
|
|
6862
|
+
function extractWebpackBuildFailure(input) {
|
|
6863
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
|
|
6864
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
6865
|
+
const match = lines[index]?.match(/^ERROR in (.+?)(?:\s+(\d+):(\d+))?$/);
|
|
6866
|
+
if (!match) {
|
|
6867
|
+
continue;
|
|
6868
|
+
}
|
|
6869
|
+
const candidates = [];
|
|
6870
|
+
for (let cursor = index + 1; cursor < Math.min(lines.length, index + 6); cursor += 1) {
|
|
6871
|
+
const candidate = lines[cursor]?.trim();
|
|
6872
|
+
if (!candidate) {
|
|
6873
|
+
continue;
|
|
6874
|
+
}
|
|
6875
|
+
if (/^ERROR in /.test(candidate) || /compiled with \d+ errors?/i.test(candidate)) {
|
|
6876
|
+
break;
|
|
6877
|
+
}
|
|
6878
|
+
if (/^(?:>|\|)|^\d+\s+\|/.test(candidate)) {
|
|
6879
|
+
continue;
|
|
6880
|
+
}
|
|
6881
|
+
candidates.push(candidate);
|
|
6882
|
+
}
|
|
6883
|
+
let message = "Compilation error";
|
|
6884
|
+
if (candidates.length > 0) {
|
|
6885
|
+
const preferred = candidates.find(
|
|
6886
|
+
(candidate) => !/^Module build failed\b/i.test(candidate) && !/^Error:\s+TypeScript compilation failed\b/i.test(candidate) && inferBuildFailureCategory(candidate) !== "generic"
|
|
6887
|
+
) ?? candidates.find(
|
|
6888
|
+
(candidate) => !/^Module build failed\b/i.test(candidate) && !/^Error:\s+TypeScript compilation failed\b/i.test(candidate)
|
|
6889
|
+
) ?? candidates[0];
|
|
6890
|
+
message = preferred ?? message;
|
|
6891
|
+
}
|
|
6892
|
+
return {
|
|
6893
|
+
message,
|
|
6894
|
+
file: normalizeBuildPath(match[1]),
|
|
6895
|
+
line: match[2] ? Number(match[2]) : null,
|
|
6896
|
+
column: match[3] ? Number(match[3]) : null,
|
|
6897
|
+
category: inferBuildFailureCategory(message)
|
|
6898
|
+
};
|
|
6899
|
+
}
|
|
6900
|
+
return null;
|
|
6901
|
+
}
|
|
6902
|
+
function extractViteImportAnalysisBuildFailure(input) {
|
|
6903
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trim());
|
|
6904
|
+
for (const line of lines) {
|
|
6905
|
+
const match = line.match(
|
|
6906
|
+
/^\[plugin:vite:import-analysis\]\s+Failed to resolve import\s+"([^"]+)"\s+from\s+"([^"]+)"/i
|
|
6907
|
+
);
|
|
6908
|
+
if (!match) {
|
|
6909
|
+
continue;
|
|
6910
|
+
}
|
|
6911
|
+
return {
|
|
6912
|
+
message: `Failed to resolve import "${match[1]}"`,
|
|
6913
|
+
file: normalizeBuildPath(match[2]),
|
|
6914
|
+
line: null,
|
|
6915
|
+
column: null,
|
|
6916
|
+
category: "module-resolution"
|
|
6917
|
+
};
|
|
6918
|
+
}
|
|
6919
|
+
return null;
|
|
6920
|
+
}
|
|
6921
|
+
function extractEsbuildBuildFailure(input) {
|
|
6922
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
|
|
6923
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
6924
|
+
const match = lines[index]?.match(/^(?:[✘✗]\s*)?\[ERROR\]\s*(.+)$/);
|
|
6925
|
+
if (!match) {
|
|
6926
|
+
continue;
|
|
6927
|
+
}
|
|
6928
|
+
const message = match[1].replace(/^\[vite\]\s*/i, "").trim();
|
|
6929
|
+
let file = null;
|
|
6930
|
+
let line = null;
|
|
6931
|
+
let column = null;
|
|
6932
|
+
for (let cursor = index + 1; cursor < Math.min(lines.length, index + 6); cursor += 1) {
|
|
6933
|
+
const locationMatch = lines[cursor]?.trim().match(/^(.+?):(\d+):(\d+):$/);
|
|
6934
|
+
if (!locationMatch) {
|
|
6935
|
+
continue;
|
|
6936
|
+
}
|
|
6937
|
+
file = normalizeBuildPath(locationMatch[1]);
|
|
6938
|
+
line = Number(locationMatch[2]);
|
|
6939
|
+
column = Number(locationMatch[3]);
|
|
6940
|
+
break;
|
|
6941
|
+
}
|
|
6942
|
+
return {
|
|
6943
|
+
message,
|
|
6944
|
+
file,
|
|
6945
|
+
line,
|
|
6946
|
+
column,
|
|
6947
|
+
category: inferBuildFailureCategory(message)
|
|
6948
|
+
};
|
|
6949
|
+
}
|
|
6950
|
+
return null;
|
|
6951
|
+
}
|
|
6952
|
+
function extractCargoBuildFailure(input) {
|
|
6953
|
+
if (!/^error(?:\[E\d+\])?:\s+/m.test(input) || !(/^\s*-->\s+/m.test(input) || /could not compile/i.test(input))) {
|
|
6954
|
+
return null;
|
|
6955
|
+
}
|
|
6956
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
|
|
6957
|
+
for (let index = 0; index < lines.length; index += 1) {
|
|
6958
|
+
const match = lines[index]?.match(/^error(?:\[(E\d+)\])?:\s+(.+)$/);
|
|
6959
|
+
if (!match) {
|
|
6960
|
+
continue;
|
|
6961
|
+
}
|
|
6962
|
+
const code = match[1];
|
|
6963
|
+
const locationMatch = lines.slice(index + 1, index + 7).join("\n").match(/^\s*-->\s+(.+?):(\d+):(\d+)/m);
|
|
6964
|
+
return {
|
|
6965
|
+
message: code ? `${code}: ${match[2].trim()}` : match[2].trim(),
|
|
6966
|
+
file: locationMatch ? normalizeBuildPath(locationMatch[1]) : null,
|
|
6967
|
+
line: locationMatch ? Number(locationMatch[2]) : null,
|
|
6968
|
+
column: locationMatch ? Number(locationMatch[3]) : null,
|
|
6969
|
+
category: inferBuildFailureCategory(match[2])
|
|
6970
|
+
};
|
|
6971
|
+
}
|
|
6972
|
+
return null;
|
|
6973
|
+
}
|
|
6974
|
+
function extractCompilerStyleBuildFailure(input) {
|
|
6975
|
+
const lines = stripAnsiText(input).split("\n").map((line) => line.trimEnd());
|
|
6976
|
+
for (const rawLine of lines) {
|
|
6977
|
+
let match = rawLine.match(
|
|
6978
|
+
/^(.+?\.(?:c|cc|cpp|cxx|h|hpp|m|mm)):([0-9]+):([0-9]+):\s*error:\s+(.+)$/
|
|
6979
|
+
);
|
|
6980
|
+
if (match) {
|
|
6981
|
+
return {
|
|
6982
|
+
message: match[4].trim(),
|
|
6983
|
+
file: normalizeBuildPath(match[1]),
|
|
6984
|
+
line: Number(match[2]),
|
|
6985
|
+
column: Number(match[3]),
|
|
6986
|
+
category: inferBuildFailureCategory(match[4])
|
|
6987
|
+
};
|
|
6988
|
+
}
|
|
6989
|
+
match = rawLine.match(/^(.+?\.go):([0-9]+):([0-9]+):\s+(.+)$/);
|
|
6990
|
+
if (match && !/^\s*warning:/i.test(match[4])) {
|
|
6991
|
+
return {
|
|
6992
|
+
message: match[4].trim(),
|
|
6993
|
+
file: normalizeBuildPath(match[1]),
|
|
6994
|
+
line: Number(match[2]),
|
|
6995
|
+
column: Number(match[3]),
|
|
6996
|
+
category: inferBuildFailureCategory(match[4])
|
|
6997
|
+
};
|
|
6998
|
+
}
|
|
6999
|
+
}
|
|
7000
|
+
return null;
|
|
7001
|
+
}
|
|
7002
|
+
function extractTscBuildFailure(input) {
|
|
7003
|
+
const diagnostics = parseTscErrors(input);
|
|
7004
|
+
const first = diagnostics[0];
|
|
7005
|
+
if (!first) {
|
|
7006
|
+
return null;
|
|
7007
|
+
}
|
|
7008
|
+
return {
|
|
7009
|
+
message: `${first.code}: ${first.message}`,
|
|
7010
|
+
file: first.file,
|
|
7011
|
+
line: first.line,
|
|
7012
|
+
column: first.column,
|
|
7013
|
+
category: inferBuildFailureCategory(`${first.code}: ${first.message}`)
|
|
7014
|
+
};
|
|
7015
|
+
}
|
|
7016
|
+
function extractWrapperBuildFailure(input) {
|
|
7017
|
+
if (!/^\s*npm ERR!|\bERR_PNPM_|^\s*error Command failed/m.test(input)) {
|
|
7018
|
+
return null;
|
|
7019
|
+
}
|
|
7020
|
+
const npmCommandMatch = input.match(/^\s*npm ERR!\s+.*?\bbuild:\s+`([^`]+)`/m);
|
|
7021
|
+
const genericCommandMatch = input.match(/^\s*.+?\s+build:\s+`([^`]+)`/m);
|
|
7022
|
+
const command = npmCommandMatch?.[1] ?? genericCommandMatch?.[1] ?? null;
|
|
7023
|
+
return {
|
|
7024
|
+
message: command ? `build script \`${command}\` failed` : "the build script failed",
|
|
7025
|
+
file: null,
|
|
7026
|
+
line: null,
|
|
7027
|
+
column: null,
|
|
7028
|
+
category: "wrapper"
|
|
7029
|
+
};
|
|
7030
|
+
}
|
|
7031
|
+
function buildFailureHeuristic(input) {
|
|
7032
|
+
if (input.trim().length === 0) {
|
|
7033
|
+
return null;
|
|
7034
|
+
}
|
|
7035
|
+
if (detectExplicitBuildSuccess(input)) {
|
|
7036
|
+
return "Build succeeded.";
|
|
7037
|
+
}
|
|
7038
|
+
const match = extractViteImportAnalysisBuildFailure(input) ?? extractWebpackBuildFailure(input) ?? extractEsbuildBuildFailure(input) ?? extractCargoBuildFailure(input) ?? extractCompilerStyleBuildFailure(input) ?? extractTscBuildFailure(input) ?? extractWrapperBuildFailure(input);
|
|
7039
|
+
if (!match) {
|
|
7040
|
+
return null;
|
|
7041
|
+
}
|
|
7042
|
+
return formatBuildFailureOutput(match);
|
|
7043
|
+
}
|
|
5778
7044
|
function applyHeuristicPolicy(policyName, input, detail) {
|
|
5779
7045
|
if (!policyName) {
|
|
5780
7046
|
return null;
|
|
@@ -5788,6 +7054,15 @@ function applyHeuristicPolicy(policyName, input, detail) {
|
|
|
5788
7054
|
if (policyName === "test-status") {
|
|
5789
7055
|
return testStatusHeuristic(input, detail);
|
|
5790
7056
|
}
|
|
7057
|
+
if (policyName === "typecheck-summary") {
|
|
7058
|
+
return typecheckSummaryHeuristic(input);
|
|
7059
|
+
}
|
|
7060
|
+
if (policyName === "lint-failures") {
|
|
7061
|
+
return lintFailuresHeuristic(input);
|
|
7062
|
+
}
|
|
7063
|
+
if (policyName === "build-failure") {
|
|
7064
|
+
return buildFailureHeuristic(input);
|
|
7065
|
+
}
|
|
5791
7066
|
return null;
|
|
5792
7067
|
}
|
|
5793
7068
|
|
|
@@ -5867,36 +7142,168 @@ function truncateInput(input, options) {
|
|
|
5867
7142
|
truncatedApplied: true
|
|
5868
7143
|
};
|
|
5869
7144
|
}
|
|
5870
|
-
|
|
5871
|
-
// src/core/pipeline.ts
|
|
5872
|
-
function prepareInput(raw, config) {
|
|
5873
|
-
const sanitized = sanitizeInput(raw, config.stripAnsi);
|
|
5874
|
-
const redacted = config.redact || config.redactStrict ? redactInput(sanitized, { strict: config.redactStrict }) : sanitized;
|
|
5875
|
-
const truncated = truncateInput(redacted, {
|
|
5876
|
-
maxInputChars: config.maxInputChars,
|
|
5877
|
-
headChars: config.headChars,
|
|
5878
|
-
tailChars: config.tailChars
|
|
7145
|
+
|
|
7146
|
+
// src/core/pipeline.ts
|
|
7147
|
+
function prepareInput(raw, config) {
|
|
7148
|
+
const sanitized = sanitizeInput(raw, config.stripAnsi);
|
|
7149
|
+
const redacted = config.redact || config.redactStrict ? redactInput(sanitized, { strict: config.redactStrict }) : sanitized;
|
|
7150
|
+
const truncated = truncateInput(redacted, {
|
|
7151
|
+
maxInputChars: config.maxInputChars,
|
|
7152
|
+
headChars: config.headChars,
|
|
7153
|
+
tailChars: config.tailChars
|
|
7154
|
+
});
|
|
7155
|
+
return {
|
|
7156
|
+
raw,
|
|
7157
|
+
sanitized,
|
|
7158
|
+
redacted,
|
|
7159
|
+
truncated: truncated.text,
|
|
7160
|
+
meta: {
|
|
7161
|
+
originalLength: raw.length,
|
|
7162
|
+
finalLength: truncated.text.length,
|
|
7163
|
+
redactionApplied: config.redact || config.redactStrict,
|
|
7164
|
+
truncatedApplied: truncated.truncatedApplied
|
|
7165
|
+
}
|
|
7166
|
+
};
|
|
7167
|
+
}
|
|
7168
|
+
|
|
7169
|
+
// src/core/rawSlice.ts
|
|
7170
|
+
function escapeRegExp2(value) {
|
|
7171
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7172
|
+
}
|
|
7173
|
+
function unique3(values) {
|
|
7174
|
+
return [...new Set(values)];
|
|
7175
|
+
}
|
|
7176
|
+
var genericBucketSearchTerms = /* @__PURE__ */ new Set([
|
|
7177
|
+
"runtimeerror",
|
|
7178
|
+
"typeerror",
|
|
7179
|
+
"error",
|
|
7180
|
+
"exception",
|
|
7181
|
+
"failed",
|
|
7182
|
+
"failure",
|
|
7183
|
+
"visible failure",
|
|
7184
|
+
"failing tests",
|
|
7185
|
+
"setup failures",
|
|
7186
|
+
"runtime failure",
|
|
7187
|
+
"assertion failed",
|
|
7188
|
+
"network",
|
|
7189
|
+
"permission",
|
|
7190
|
+
"configuration"
|
|
7191
|
+
]);
|
|
7192
|
+
function normalizeSearchTerm(value) {
|
|
7193
|
+
return value.replace(/^['"`]+|['"`]+$/g, "").trim();
|
|
7194
|
+
}
|
|
7195
|
+
function isHighSignalSearchTerm(term) {
|
|
7196
|
+
const normalized = normalizeSearchTerm(term);
|
|
7197
|
+
if (normalized.length < 4) {
|
|
7198
|
+
return false;
|
|
7199
|
+
}
|
|
7200
|
+
const lower = normalized.toLowerCase();
|
|
7201
|
+
if (genericBucketSearchTerms.has(lower)) {
|
|
7202
|
+
return false;
|
|
7203
|
+
}
|
|
7204
|
+
if (/^(runtime|type|assertion|network|permission|configuration)\b/i.test(normalized)) {
|
|
7205
|
+
return false;
|
|
7206
|
+
}
|
|
7207
|
+
return true;
|
|
7208
|
+
}
|
|
7209
|
+
function scoreSearchTerm(term) {
|
|
7210
|
+
const normalized = normalizeSearchTerm(term);
|
|
7211
|
+
let score = normalized.length;
|
|
7212
|
+
if (/^[A-Z][A-Z0-9_]{2,}$/.test(normalized)) {
|
|
7213
|
+
score += 80;
|
|
7214
|
+
}
|
|
7215
|
+
if (/^TS\d+$/.test(normalized)) {
|
|
7216
|
+
score += 70;
|
|
7217
|
+
}
|
|
7218
|
+
if (/^[45]\d\d\b/.test(normalized) || /\bHTTPError:\s*[45]\d\d\b/i.test(normalized)) {
|
|
7219
|
+
score += 60;
|
|
7220
|
+
}
|
|
7221
|
+
if (normalized.includes("/") || normalized.includes("\\")) {
|
|
7222
|
+
score += 50;
|
|
7223
|
+
}
|
|
7224
|
+
if (/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\b/.test(normalized)) {
|
|
7225
|
+
score += 40;
|
|
7226
|
+
}
|
|
7227
|
+
if (/['"`]/.test(term)) {
|
|
7228
|
+
score += 30;
|
|
7229
|
+
}
|
|
7230
|
+
if (normalized.includes("::")) {
|
|
7231
|
+
score += 25;
|
|
7232
|
+
}
|
|
7233
|
+
return score;
|
|
7234
|
+
}
|
|
7235
|
+
function collectCandidateSearchTerms(value) {
|
|
7236
|
+
const candidates = [];
|
|
7237
|
+
const normalized = value.trim();
|
|
7238
|
+
if (!normalized) {
|
|
7239
|
+
return candidates;
|
|
7240
|
+
}
|
|
7241
|
+
for (const match of normalized.matchAll(/['"`]([^'"`]{4,})['"`]/g)) {
|
|
7242
|
+
candidates.push(match[1]);
|
|
7243
|
+
}
|
|
7244
|
+
for (const match of normalized.matchAll(/\b[A-Z][A-Z0-9_]{2,}\b/g)) {
|
|
7245
|
+
candidates.push(match[0]);
|
|
7246
|
+
}
|
|
7247
|
+
for (const match of normalized.matchAll(/\bTS\d+\b/g)) {
|
|
7248
|
+
candidates.push(match[0]);
|
|
7249
|
+
}
|
|
7250
|
+
for (const match of normalized.matchAll(/\bHTTPError:\s*[45]\d\d\b/gi)) {
|
|
7251
|
+
candidates.push(match[0]);
|
|
7252
|
+
}
|
|
7253
|
+
for (const match of normalized.matchAll(/\/[A-Za-z0-9_./:{}-]{4,}/g)) {
|
|
7254
|
+
candidates.push(match[0]);
|
|
7255
|
+
}
|
|
7256
|
+
for (const match of normalized.matchAll(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/g)) {
|
|
7257
|
+
candidates.push(match[0]);
|
|
7258
|
+
}
|
|
7259
|
+
for (const match of normalized.matchAll(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\b/g)) {
|
|
7260
|
+
candidates.push(match[0]);
|
|
7261
|
+
}
|
|
7262
|
+
const detail = normalized.split(":").slice(1).join(":").trim();
|
|
7263
|
+
if (detail.length >= 8) {
|
|
7264
|
+
candidates.push(detail);
|
|
7265
|
+
}
|
|
7266
|
+
return candidates;
|
|
7267
|
+
}
|
|
7268
|
+
function extractBucketSearchTerms(args) {
|
|
7269
|
+
const sources = [
|
|
7270
|
+
args.bucket.root_cause,
|
|
7271
|
+
...args.bucket.evidence,
|
|
7272
|
+
...args.readTargets.filter((target) => target.bucket_index === args.bucket.bucket_index).flatMap((target) => [target.context_hint.search_hint ?? "", target.file])
|
|
7273
|
+
];
|
|
7274
|
+
const prioritized = unique3(
|
|
7275
|
+
sources.flatMap((value) => collectCandidateSearchTerms(value)).filter(isHighSignalSearchTerm)
|
|
7276
|
+
).sort((left, right) => {
|
|
7277
|
+
const delta = scoreSearchTerm(right) - scoreSearchTerm(left);
|
|
7278
|
+
if (delta !== 0) {
|
|
7279
|
+
return delta;
|
|
7280
|
+
}
|
|
7281
|
+
return left.localeCompare(right);
|
|
5879
7282
|
});
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
5885
|
-
|
|
5886
|
-
|
|
5887
|
-
finalLength: truncated.text.length,
|
|
5888
|
-
redactionApplied: config.redact || config.redactStrict,
|
|
5889
|
-
truncatedApplied: truncated.truncatedApplied
|
|
5890
|
-
}
|
|
5891
|
-
};
|
|
5892
|
-
}
|
|
5893
|
-
|
|
5894
|
-
// src/core/rawSlice.ts
|
|
5895
|
-
function escapeRegExp2(value) {
|
|
5896
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
7283
|
+
if (prioritized.length > 0) {
|
|
7284
|
+
return prioritized.slice(0, 6);
|
|
7285
|
+
}
|
|
7286
|
+
const fallbackTerms = unique3(
|
|
7287
|
+
[...args.bucket.evidence, args.bucket.root_cause].flatMap((value) => value.split(/->|:/).map((part) => normalizeSearchTerm(part))).filter(isHighSignalSearchTerm)
|
|
7288
|
+
);
|
|
7289
|
+
return fallbackTerms.slice(0, 4);
|
|
5897
7290
|
}
|
|
5898
|
-
function
|
|
5899
|
-
|
|
7291
|
+
function clusterIndexes(indexes, maxGap = 12) {
|
|
7292
|
+
if (indexes.length === 0) {
|
|
7293
|
+
return [];
|
|
7294
|
+
}
|
|
7295
|
+
const clusters = [];
|
|
7296
|
+
let currentCluster = [indexes[0]];
|
|
7297
|
+
for (const index of indexes.slice(1)) {
|
|
7298
|
+
if (index - currentCluster[currentCluster.length - 1] <= maxGap) {
|
|
7299
|
+
currentCluster.push(index);
|
|
7300
|
+
continue;
|
|
7301
|
+
}
|
|
7302
|
+
clusters.push(currentCluster);
|
|
7303
|
+
currentCluster = [index];
|
|
7304
|
+
}
|
|
7305
|
+
clusters.push(currentCluster);
|
|
7306
|
+
return clusters;
|
|
5900
7307
|
}
|
|
5901
7308
|
function buildLineWindows(args) {
|
|
5902
7309
|
const selected = /* @__PURE__ */ new Set();
|
|
@@ -5913,11 +7320,17 @@ function buildLineWindows(args) {
|
|
|
5913
7320
|
}
|
|
5914
7321
|
return [...selected].sort((left, right) => left - right).map((index) => args.lines[index]);
|
|
5915
7322
|
}
|
|
7323
|
+
function buildPriorityLineGroup(args) {
|
|
7324
|
+
return unique3([
|
|
7325
|
+
...args.indexes.map((index) => args.lines[index]).filter(Boolean),
|
|
7326
|
+
...buildLineWindows(args)
|
|
7327
|
+
]);
|
|
7328
|
+
}
|
|
5916
7329
|
function collapseSelectedLines(args) {
|
|
5917
7330
|
if (args.lines.length === 0) {
|
|
5918
7331
|
return args.fallback();
|
|
5919
7332
|
}
|
|
5920
|
-
const joined =
|
|
7333
|
+
const joined = unique3(args.lines).join("\n").trim();
|
|
5921
7334
|
if (joined.length === 0) {
|
|
5922
7335
|
return args.fallback();
|
|
5923
7336
|
}
|
|
@@ -6061,15 +7474,16 @@ function buildTestStatusRawSlice(args) {
|
|
|
6061
7474
|
) ? index : -1
|
|
6062
7475
|
).filter((index) => index >= 0);
|
|
6063
7476
|
const bucketGroups = args.contract.main_buckets.map((bucket) => {
|
|
6064
|
-
const bucketTerms =
|
|
6065
|
-
|
|
6066
|
-
|
|
7477
|
+
const bucketTerms = extractBucketSearchTerms({
|
|
7478
|
+
bucket,
|
|
7479
|
+
readTargets: args.contract.read_targets
|
|
7480
|
+
});
|
|
6067
7481
|
const indexes = lines.map(
|
|
6068
7482
|
(line, index) => bucketTerms.some((term) => new RegExp(escapeRegExp2(term), "i").test(line)) ? index : -1
|
|
6069
7483
|
).filter((index) => index >= 0);
|
|
6070
|
-
return
|
|
7484
|
+
return unique3([
|
|
6071
7485
|
...indexes.map((index) => lines[index]).filter(Boolean),
|
|
6072
|
-
...
|
|
7486
|
+
...buildPriorityLineGroup({
|
|
6073
7487
|
lines,
|
|
6074
7488
|
indexes,
|
|
6075
7489
|
radius: 2,
|
|
@@ -6077,30 +7491,59 @@ function buildTestStatusRawSlice(args) {
|
|
|
6077
7491
|
})
|
|
6078
7492
|
]);
|
|
6079
7493
|
});
|
|
6080
|
-
const targetGroups = args.contract.read_targets.
|
|
6081
|
-
|
|
7494
|
+
const targetGroups = args.contract.read_targets.flatMap((target) => {
|
|
7495
|
+
const searchHintIndexes = findSearchHintIndexes({
|
|
6082
7496
|
lines,
|
|
6083
|
-
|
|
6084
|
-
|
|
6085
|
-
|
|
6086
|
-
|
|
6087
|
-
|
|
6088
|
-
|
|
6089
|
-
|
|
6090
|
-
|
|
6091
|
-
|
|
6092
|
-
|
|
6093
|
-
|
|
6094
|
-
|
|
6095
|
-
|
|
6096
|
-
|
|
7497
|
+
searchHint: target.context_hint.search_hint
|
|
7498
|
+
});
|
|
7499
|
+
const fileIndexes = findReadTargetIndexes({
|
|
7500
|
+
lines,
|
|
7501
|
+
file: target.file,
|
|
7502
|
+
line: target.line,
|
|
7503
|
+
contextHint: target.context_hint
|
|
7504
|
+
});
|
|
7505
|
+
const radius = target.line === null ? 1 : 2;
|
|
7506
|
+
const maxLines = target.line === null ? 6 : 8;
|
|
7507
|
+
const groups = [
|
|
7508
|
+
searchHintIndexes.length > 0 ? buildPriorityLineGroup({
|
|
7509
|
+
lines,
|
|
7510
|
+
indexes: searchHintIndexes,
|
|
7511
|
+
radius,
|
|
7512
|
+
maxLines
|
|
7513
|
+
}) : null,
|
|
7514
|
+
fileIndexes.length > 0 ? buildPriorityLineGroup({
|
|
7515
|
+
lines,
|
|
7516
|
+
indexes: fileIndexes,
|
|
7517
|
+
radius,
|
|
7518
|
+
maxLines
|
|
7519
|
+
}) : null
|
|
7520
|
+
].filter((group) => group !== null && group.length > 0);
|
|
7521
|
+
if (groups.length > 0) {
|
|
7522
|
+
return groups;
|
|
7523
|
+
}
|
|
7524
|
+
return [
|
|
7525
|
+
buildPriorityLineGroup({
|
|
7526
|
+
lines,
|
|
7527
|
+
indexes: unique3([...searchHintIndexes, ...fileIndexes]),
|
|
7528
|
+
radius,
|
|
7529
|
+
maxLines
|
|
7530
|
+
})
|
|
7531
|
+
];
|
|
7532
|
+
});
|
|
7533
|
+
const failureHeaderIndexes = lines.map((line, index) => /\b(FAILED|ERROR)\b/.test(line) ? index : -1).filter((index) => index >= 0);
|
|
7534
|
+
const failureIndexes = (failureHeaderIndexes.length > 0 ? failureHeaderIndexes : lines.map((line, index) => /^E\s/.test(line) ? index : -1).filter((index) => index >= 0)).filter((index) => index >= 0);
|
|
7535
|
+
const failureHeaderGroups = clusterIndexes(failureIndexes).slice(0, 8).map(
|
|
7536
|
+
(cluster) => buildPriorityLineGroup({
|
|
7537
|
+
lines,
|
|
7538
|
+
indexes: cluster,
|
|
7539
|
+
radius: 1,
|
|
7540
|
+
maxLines: 8
|
|
6097
7541
|
})
|
|
6098
|
-
);
|
|
6099
|
-
const failureIndexes = lines.map((line, index) => /\b(FAILED|ERROR)\b/.test(line) || /^E\s/.test(line) ? index : -1).filter((index) => index >= 0);
|
|
7542
|
+
).filter((group) => group.length > 0);
|
|
6100
7543
|
const selected = collapseSelectedLineGroups({
|
|
6101
7544
|
groups: [
|
|
6102
7545
|
...targetGroups,
|
|
6103
|
-
|
|
7546
|
+
unique3([
|
|
6104
7547
|
...summaryIndexes.map((index) => lines[index]).filter(Boolean),
|
|
6105
7548
|
...buildLineWindows({
|
|
6106
7549
|
lines,
|
|
@@ -6110,12 +7553,14 @@ function buildTestStatusRawSlice(args) {
|
|
|
6110
7553
|
})
|
|
6111
7554
|
]),
|
|
6112
7555
|
...bucketGroups,
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
7556
|
+
...failureHeaderGroups.length > 0 ? failureHeaderGroups : [
|
|
7557
|
+
buildLineWindows({
|
|
7558
|
+
lines,
|
|
7559
|
+
indexes: failureIndexes,
|
|
7560
|
+
radius: 1,
|
|
7561
|
+
maxLines: 24
|
|
7562
|
+
})
|
|
7563
|
+
]
|
|
6119
7564
|
],
|
|
6120
7565
|
maxInputChars: args.config.maxInputChars,
|
|
6121
7566
|
fallback: () => truncateInput(args.input, {
|
|
@@ -6256,7 +7701,8 @@ function withInsufficientHint(args) {
|
|
|
6256
7701
|
return buildInsufficientSignalOutput({
|
|
6257
7702
|
presetName: args.request.presetName,
|
|
6258
7703
|
originalLength: args.prepared.meta.originalLength,
|
|
6259
|
-
truncatedApplied: args.prepared.meta.truncatedApplied
|
|
7704
|
+
truncatedApplied: args.prepared.meta.truncatedApplied,
|
|
7705
|
+
recognizedRunner: detectTestRunner(args.prepared.redacted)
|
|
6260
7706
|
});
|
|
6261
7707
|
}
|
|
6262
7708
|
async function generateWithRetry(args) {
|
|
@@ -6295,6 +7741,34 @@ function hasRecognizableTestStatusSignal(input) {
|
|
|
6295
7741
|
const analysis = analyzeTestStatus(input);
|
|
6296
7742
|
return analysis.collectionErrorCount !== void 0 || analysis.noTestsCollected || analysis.interrupted || analysis.failed > 0 || analysis.errors > 0 || analysis.passed > 0 || analysis.inlineItems.length > 0 || analysis.buckets.length > 0;
|
|
6297
7743
|
}
|
|
7744
|
+
function shouldUseCompactTestStatusBypass(args) {
|
|
7745
|
+
if (args.request.policyName !== "test-status") {
|
|
7746
|
+
return false;
|
|
7747
|
+
}
|
|
7748
|
+
if (args.request.detail && args.request.detail !== "standard") {
|
|
7749
|
+
return false;
|
|
7750
|
+
}
|
|
7751
|
+
if (args.request.goal === "diagnose" && args.request.format === "json") {
|
|
7752
|
+
return false;
|
|
7753
|
+
}
|
|
7754
|
+
if (args.request.testStatusContext?.resolvedTests?.length || args.request.testStatusContext?.remainingTests?.length || args.request.testStatusContext?.remainingSubsetAvailable || args.request.testStatusContext?.remainingMode && args.request.testStatusContext.remainingMode !== "none") {
|
|
7755
|
+
return false;
|
|
7756
|
+
}
|
|
7757
|
+
return args.analysis.failed === 0 && args.analysis.errors === 0 && args.analysis.passed > 0 || args.analysis.collectionErrorCount !== void 0 && args.analysis.collectionItems.length === 0 && args.analysis.inlineItems.length === 0 && args.analysis.buckets.length === 0 || args.analysis.noTestsCollected || args.analysis.interrupted && args.analysis.failed === 0 && args.analysis.errors === 0;
|
|
7758
|
+
}
|
|
7759
|
+
function sanitizeProviderFailureReason(reason) {
|
|
7760
|
+
const normalized = reason.trim();
|
|
7761
|
+
const httpStatus = normalized.match(/\bHTTP\s+(\d{3})\b/i)?.[1];
|
|
7762
|
+
if (httpStatus) {
|
|
7763
|
+
return `provider follow-up unavailable (HTTP ${httpStatus})`;
|
|
7764
|
+
}
|
|
7765
|
+
if (/unterminated string|invalid json|unexpected token|json at position|schema|zod|parse/i.test(
|
|
7766
|
+
normalized
|
|
7767
|
+
)) {
|
|
7768
|
+
return "provider follow-up returned unusable structured output";
|
|
7769
|
+
}
|
|
7770
|
+
return "provider follow-up failed";
|
|
7771
|
+
}
|
|
6298
7772
|
function renderTestStatusDecisionOutput(args) {
|
|
6299
7773
|
if (args.request.goal === "diagnose" && args.request.format === "json") {
|
|
6300
7774
|
return JSON.stringify(
|
|
@@ -6316,12 +7790,49 @@ function renderTestStatusDecisionOutput(args) {
|
|
|
6316
7790
|
return args.decision.standardText;
|
|
6317
7791
|
}
|
|
6318
7792
|
function buildTestStatusProviderFailureDecision(args) {
|
|
7793
|
+
const sanitizedReason = sanitizeProviderFailureReason(args.reason);
|
|
7794
|
+
const concreteReadTarget = args.baseDecision.contract.read_targets.find(
|
|
7795
|
+
(target) => Boolean(target.file)
|
|
7796
|
+
);
|
|
7797
|
+
const hasUnknownBucket = args.baseDecision.contract.main_buckets.some(
|
|
7798
|
+
(bucket) => bucket.root_cause.startsWith("unknown ")
|
|
7799
|
+
);
|
|
7800
|
+
if (concreteReadTarget && !hasUnknownBucket) {
|
|
7801
|
+
return buildTestStatusDiagnoseContract({
|
|
7802
|
+
input: args.input,
|
|
7803
|
+
analysis: args.analysis,
|
|
7804
|
+
resolvedTests: args.baseDecision.contract.resolved_tests,
|
|
7805
|
+
remainingTests: args.baseDecision.contract.remaining_tests,
|
|
7806
|
+
remainingMode: args.request.testStatusContext?.remainingMode,
|
|
7807
|
+
contractOverrides: {
|
|
7808
|
+
...args.baseDecision.contract,
|
|
7809
|
+
diagnosis_complete: false,
|
|
7810
|
+
raw_needed: false,
|
|
7811
|
+
additional_source_read_likely_low_value: false,
|
|
7812
|
+
read_raw_only_if: null,
|
|
7813
|
+
decision: "read_source",
|
|
7814
|
+
provider_used: true,
|
|
7815
|
+
provider_confidence: null,
|
|
7816
|
+
provider_failed: true,
|
|
7817
|
+
raw_slice_used: args.rawSliceUsed,
|
|
7818
|
+
raw_slice_strategy: args.rawSliceStrategy,
|
|
7819
|
+
next_best_action: {
|
|
7820
|
+
code: "read_source_for_bucket",
|
|
7821
|
+
bucket_index: args.baseDecision.contract.dominant_blocker_bucket_index ?? concreteReadTarget.bucket_index,
|
|
7822
|
+
note: `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
7823
|
+
1
|
|
7824
|
+
)}. The heuristic anchor is concrete enough to inspect source for the current bucket before reading raw traceback.`
|
|
7825
|
+
}
|
|
7826
|
+
}
|
|
7827
|
+
});
|
|
7828
|
+
}
|
|
6319
7829
|
const shouldZoomFirst = args.request.detail !== "verbose";
|
|
6320
7830
|
return buildTestStatusDiagnoseContract({
|
|
6321
7831
|
input: args.input,
|
|
6322
7832
|
analysis: args.analysis,
|
|
6323
7833
|
resolvedTests: args.baseDecision.contract.resolved_tests,
|
|
6324
7834
|
remainingTests: args.baseDecision.contract.remaining_tests,
|
|
7835
|
+
remainingMode: args.request.testStatusContext?.remainingMode,
|
|
6325
7836
|
contractOverrides: {
|
|
6326
7837
|
...args.baseDecision.contract,
|
|
6327
7838
|
diagnosis_complete: false,
|
|
@@ -6337,12 +7848,16 @@ function buildTestStatusProviderFailureDecision(args) {
|
|
|
6337
7848
|
next_best_action: {
|
|
6338
7849
|
code: shouldZoomFirst ? "insufficient_signal" : "read_raw_for_exact_traceback",
|
|
6339
7850
|
bucket_index: args.baseDecision.contract.dominant_blocker_bucket_index ?? args.baseDecision.contract.main_buckets[0]?.bucket_index ?? null,
|
|
6340
|
-
note: shouldZoomFirst ?
|
|
7851
|
+
note: shouldZoomFirst ? `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
7852
|
+
1
|
|
7853
|
+
)}. Use one deeper sift pass on the same cached output before reading raw traceback lines.` : `${sanitizedReason[0].toUpperCase()}${sanitizedReason.slice(
|
|
7854
|
+
1
|
|
7855
|
+
)}. Read raw traceback only if exact stack lines are still needed.`
|
|
6341
7856
|
}
|
|
6342
7857
|
}
|
|
6343
7858
|
});
|
|
6344
7859
|
}
|
|
6345
|
-
async function
|
|
7860
|
+
async function runSiftCore(request, recorder) {
|
|
6346
7861
|
const prepared = prepareInput(request.stdin, request.config.input);
|
|
6347
7862
|
const heuristicInput = prepared.redacted;
|
|
6348
7863
|
const heuristicInputTruncated = false;
|
|
@@ -6358,23 +7873,28 @@ async function runSift(request) {
|
|
|
6358
7873
|
const provider = createProvider(request.config);
|
|
6359
7874
|
const hasTestStatusSignal = request.policyName === "test-status" && hasRecognizableTestStatusSignal(heuristicInput);
|
|
6360
7875
|
const testStatusAnalysis = hasTestStatusSignal ? analyzeTestStatus(heuristicInput) : null;
|
|
6361
|
-
const
|
|
7876
|
+
const useCompactTestStatusOutput = hasTestStatusSignal && testStatusAnalysis ? shouldUseCompactTestStatusBypass({
|
|
7877
|
+
request,
|
|
7878
|
+
analysis: testStatusAnalysis
|
|
7879
|
+
}) : false;
|
|
7880
|
+
const testStatusDecision = hasTestStatusSignal && testStatusAnalysis && !useCompactTestStatusOutput ? buildTestStatusDiagnoseContract({
|
|
6362
7881
|
input: heuristicInput,
|
|
6363
7882
|
analysis: testStatusAnalysis,
|
|
6364
7883
|
resolvedTests: request.testStatusContext?.resolvedTests,
|
|
6365
|
-
remainingTests: request.testStatusContext?.remainingTests
|
|
7884
|
+
remainingTests: request.testStatusContext?.remainingTests,
|
|
7885
|
+
remainingMode: request.testStatusContext?.remainingMode
|
|
6366
7886
|
}) : null;
|
|
6367
7887
|
const testStatusHeuristicOutput = testStatusDecision ? renderTestStatusDecisionOutput({
|
|
6368
7888
|
request,
|
|
6369
7889
|
decision: testStatusDecision
|
|
6370
|
-
}) : null;
|
|
7890
|
+
}) : useCompactTestStatusOutput ? applyHeuristicPolicy("test-status", heuristicInput, "standard") : null;
|
|
6371
7891
|
if (request.config.runtime.verbose) {
|
|
6372
7892
|
process.stderr.write(
|
|
6373
7893
|
`${pc2.dim("sift")} provider=${provider.name} model=${request.config.provider.model} base_url=${request.config.provider.baseUrl} input_chars=${prepared.meta.finalLength}
|
|
6374
7894
|
`
|
|
6375
7895
|
);
|
|
6376
7896
|
}
|
|
6377
|
-
const heuristicOutput = request.policyName === "test-status" ? testStatusDecision?.contract.diagnosis_complete ? testStatusHeuristicOutput : null : applyHeuristicPolicy(request.policyName, heuristicInput, request.detail);
|
|
7897
|
+
const heuristicOutput = request.policyName === "test-status" ? useCompactTestStatusOutput ? testStatusHeuristicOutput : testStatusDecision?.contract.diagnosis_complete ? testStatusHeuristicOutput : null : applyHeuristicPolicy(request.policyName, heuristicInput, request.detail);
|
|
6378
7898
|
if (heuristicOutput) {
|
|
6379
7899
|
if (request.config.runtime.verbose) {
|
|
6380
7900
|
process.stderr.write(`${pc2.dim("sift")} heuristic=${request.policyName}
|
|
@@ -6428,6 +7948,7 @@ async function runSift(request) {
|
|
|
6428
7948
|
finalOutput
|
|
6429
7949
|
});
|
|
6430
7950
|
}
|
|
7951
|
+
recorder?.heuristic();
|
|
6431
7952
|
return finalOutput;
|
|
6432
7953
|
}
|
|
6433
7954
|
if (testStatusDecision && testStatusAnalysis) {
|
|
@@ -6497,6 +8018,7 @@ async function runSift(request) {
|
|
|
6497
8018
|
analysis: testStatusAnalysis,
|
|
6498
8019
|
resolvedTests: request.testStatusContext?.resolvedTests,
|
|
6499
8020
|
remainingTests: request.testStatusContext?.remainingTests,
|
|
8021
|
+
remainingMode: request.testStatusContext?.remainingMode,
|
|
6500
8022
|
providerBucketSupplements: supplement.bucket_supplements,
|
|
6501
8023
|
contractOverrides: {
|
|
6502
8024
|
diagnosis_complete: supplement.diagnosis_complete,
|
|
@@ -6527,6 +8049,7 @@ async function runSift(request) {
|
|
|
6527
8049
|
providerInputChars: providerPrepared2.truncated.length,
|
|
6528
8050
|
providerOutputChars: result.text.length
|
|
6529
8051
|
});
|
|
8052
|
+
recorder?.provider(result.usage);
|
|
6530
8053
|
return finalOutput;
|
|
6531
8054
|
} catch (error) {
|
|
6532
8055
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
@@ -6561,6 +8084,7 @@ async function runSift(request) {
|
|
|
6561
8084
|
rawSliceChars: rawSlice.text.length,
|
|
6562
8085
|
providerInputChars: providerPrepared2.truncated.length
|
|
6563
8086
|
});
|
|
8087
|
+
recorder?.fallback();
|
|
6564
8088
|
return finalOutput;
|
|
6565
8089
|
}
|
|
6566
8090
|
}
|
|
@@ -6617,6 +8141,7 @@ async function runSift(request) {
|
|
|
6617
8141
|
})) {
|
|
6618
8142
|
throw new Error("Model output rejected by quality gate");
|
|
6619
8143
|
}
|
|
8144
|
+
recorder?.provider(result.usage);
|
|
6620
8145
|
return withInsufficientHint({
|
|
6621
8146
|
output: normalizeOutput(result.text, providerPrompt.responseMode),
|
|
6622
8147
|
request,
|
|
@@ -6624,6 +8149,7 @@ async function runSift(request) {
|
|
|
6624
8149
|
});
|
|
6625
8150
|
} catch (error) {
|
|
6626
8151
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
8152
|
+
recorder?.fallback();
|
|
6627
8153
|
return withInsufficientHint({
|
|
6628
8154
|
output: buildFallbackOutput({
|
|
6629
8155
|
format: request.format,
|
|
@@ -6637,6 +8163,72 @@ async function runSift(request) {
|
|
|
6637
8163
|
});
|
|
6638
8164
|
}
|
|
6639
8165
|
}
|
|
8166
|
+
async function runSift(request) {
|
|
8167
|
+
return runSiftCore(request);
|
|
8168
|
+
}
|
|
8169
|
+
async function runSiftWithStats(request) {
|
|
8170
|
+
if (request.dryRun) {
|
|
8171
|
+
return {
|
|
8172
|
+
output: await runSiftCore(request),
|
|
8173
|
+
stats: null
|
|
8174
|
+
};
|
|
8175
|
+
}
|
|
8176
|
+
const startedAt = Date.now();
|
|
8177
|
+
let layer = "fallback";
|
|
8178
|
+
let providerCalled = false;
|
|
8179
|
+
let totalTokens = null;
|
|
8180
|
+
const output = await runSiftCore(request, {
|
|
8181
|
+
heuristic() {
|
|
8182
|
+
layer = "heuristic";
|
|
8183
|
+
providerCalled = false;
|
|
8184
|
+
totalTokens = null;
|
|
8185
|
+
},
|
|
8186
|
+
provider(usage) {
|
|
8187
|
+
layer = "provider";
|
|
8188
|
+
providerCalled = true;
|
|
8189
|
+
totalTokens = usage?.totalTokens ?? null;
|
|
8190
|
+
},
|
|
8191
|
+
fallback() {
|
|
8192
|
+
layer = "fallback";
|
|
8193
|
+
providerCalled = true;
|
|
8194
|
+
totalTokens = null;
|
|
8195
|
+
}
|
|
8196
|
+
});
|
|
8197
|
+
return {
|
|
8198
|
+
output,
|
|
8199
|
+
stats: {
|
|
8200
|
+
layer,
|
|
8201
|
+
providerCalled,
|
|
8202
|
+
totalTokens,
|
|
8203
|
+
durationMs: Date.now() - startedAt,
|
|
8204
|
+
presetName: request.presetName
|
|
8205
|
+
}
|
|
8206
|
+
};
|
|
8207
|
+
}
|
|
8208
|
+
|
|
8209
|
+
// src/core/stats.ts
|
|
8210
|
+
import pc3 from "picocolors";
|
|
8211
|
+
function formatDuration(durationMs) {
|
|
8212
|
+
return durationMs >= 1e3 ? `${(durationMs / 1e3).toFixed(1)}s` : `${durationMs}ms`;
|
|
8213
|
+
}
|
|
8214
|
+
function formatStatsFooter(stats) {
|
|
8215
|
+
const duration = formatDuration(stats.durationMs);
|
|
8216
|
+
if (stats.layer === "heuristic") {
|
|
8217
|
+
return `[sift: heuristic \u2022 LLM skipped \u2022 summary ${duration}]`;
|
|
8218
|
+
}
|
|
8219
|
+
if (stats.layer === "provider") {
|
|
8220
|
+
const tokenSegment = stats.totalTokens !== null ? ` \u2022 ${stats.totalTokens} tokens` : "";
|
|
8221
|
+
return `[sift: provider \u2022 LLM used${tokenSegment} \u2022 summary ${duration}]`;
|
|
8222
|
+
}
|
|
8223
|
+
return `[sift: fallback \u2022 provider failed \u2022 summary ${duration}]`;
|
|
8224
|
+
}
|
|
8225
|
+
function emitStatsFooter(args) {
|
|
8226
|
+
if (args.quiet || !args.stats || !process.stderr.isTTY) {
|
|
8227
|
+
return;
|
|
8228
|
+
}
|
|
8229
|
+
process.stderr.write(`${pc3.dim(formatStatsFooter(args.stats))}
|
|
8230
|
+
`);
|
|
8231
|
+
}
|
|
6640
8232
|
|
|
6641
8233
|
// src/core/testStatusState.ts
|
|
6642
8234
|
import fs5 from "fs";
|
|
@@ -6672,6 +8264,7 @@ var failureBucketTypeSchema = z3.enum([
|
|
|
6672
8264
|
"import_dependency_failure",
|
|
6673
8265
|
"collection_failure",
|
|
6674
8266
|
"assertion_failure",
|
|
8267
|
+
"golden_output_drift",
|
|
6675
8268
|
"runtime_failure",
|
|
6676
8269
|
"interrupted_run",
|
|
6677
8270
|
"no_tests_collected",
|
|
@@ -6712,7 +8305,19 @@ var cachedPytestStateSchema = z3.object({
|
|
|
6712
8305
|
failingNodeIds: z3.array(z3.string()),
|
|
6713
8306
|
remainingNodeIds: z3.array(z3.string()).optional()
|
|
6714
8307
|
}).optional();
|
|
6715
|
-
var
|
|
8308
|
+
var testRunnerSchema = z3.enum(["pytest", "vitest", "jest", "unknown"]);
|
|
8309
|
+
var cachedRunnerSubsetSchema = z3.object({
|
|
8310
|
+
available: z3.boolean(),
|
|
8311
|
+
strategy: z3.enum(["pytest-node-ids", "none"]),
|
|
8312
|
+
baseArgv: z3.array(z3.string()).min(1).optional()
|
|
8313
|
+
});
|
|
8314
|
+
var cachedRunnerStateSchema = z3.object({
|
|
8315
|
+
name: testRunnerSchema,
|
|
8316
|
+
failingTargets: z3.array(z3.string()),
|
|
8317
|
+
baselineCommand: cachedCommandSchema,
|
|
8318
|
+
subset: cachedRunnerSubsetSchema
|
|
8319
|
+
});
|
|
8320
|
+
var cachedRunV1Schema = z3.object({
|
|
6716
8321
|
version: z3.literal(1),
|
|
6717
8322
|
timestamp: z3.string(),
|
|
6718
8323
|
presetName: z3.literal("test-status"),
|
|
@@ -6730,6 +8335,25 @@ var cachedRunSchema = z3.object({
|
|
|
6730
8335
|
analysis: cachedAnalysisSchema,
|
|
6731
8336
|
pytest: cachedPytestStateSchema
|
|
6732
8337
|
});
|
|
8338
|
+
var cachedRunV2Schema = z3.object({
|
|
8339
|
+
version: z3.literal(2),
|
|
8340
|
+
timestamp: z3.string(),
|
|
8341
|
+
presetName: z3.literal("test-status"),
|
|
8342
|
+
cwd: z3.string(),
|
|
8343
|
+
commandKey: z3.string(),
|
|
8344
|
+
commandPreview: z3.string(),
|
|
8345
|
+
command: cachedCommandSchema,
|
|
8346
|
+
detail: detailSchema,
|
|
8347
|
+
exitCode: z3.number().int(),
|
|
8348
|
+
rawOutput: z3.string(),
|
|
8349
|
+
capture: z3.object({
|
|
8350
|
+
originalChars: countSchema,
|
|
8351
|
+
truncatedApplied: z3.boolean()
|
|
8352
|
+
}),
|
|
8353
|
+
analysis: cachedAnalysisSchema,
|
|
8354
|
+
runner: cachedRunnerStateSchema
|
|
8355
|
+
});
|
|
8356
|
+
var cachedRunSchema = z3.discriminatedUnion("version", [cachedRunV1Schema, cachedRunV2Schema]);
|
|
6733
8357
|
var MissingCachedTestStatusRunError = class extends Error {
|
|
6734
8358
|
constructor() {
|
|
6735
8359
|
super(
|
|
@@ -6778,6 +8402,37 @@ function isPytestExecutable(value) {
|
|
|
6778
8402
|
function isPythonExecutable(value) {
|
|
6779
8403
|
return basenameMatches(value, /^python(?:\d+(?:\.\d+)*)?(?:\.exe)?$/i);
|
|
6780
8404
|
}
|
|
8405
|
+
function detectRunnerFromCommand(command) {
|
|
8406
|
+
if (!command) {
|
|
8407
|
+
return "unknown";
|
|
8408
|
+
}
|
|
8409
|
+
if (command.mode === "argv") {
|
|
8410
|
+
const [first, second, third] = command.argv;
|
|
8411
|
+
if (first && isPytestExecutable(first)) {
|
|
8412
|
+
return "pytest";
|
|
8413
|
+
}
|
|
8414
|
+
if (first && isPythonExecutable(first) && second === "-m" && third === "pytest") {
|
|
8415
|
+
return "pytest";
|
|
8416
|
+
}
|
|
8417
|
+
if (first && basenameMatches(first, /^vitest(?:\.exe)?$/i)) {
|
|
8418
|
+
return "vitest";
|
|
8419
|
+
}
|
|
8420
|
+
if (first && basenameMatches(first, /^jest(?:\.exe)?$/i)) {
|
|
8421
|
+
return "jest";
|
|
8422
|
+
}
|
|
8423
|
+
return "unknown";
|
|
8424
|
+
}
|
|
8425
|
+
if (/\bpython(?:\d+(?:\.\d+)*)?\s+-m\s+pytest\b|\bpytest\b/i.test(command.shellCommand)) {
|
|
8426
|
+
return "pytest";
|
|
8427
|
+
}
|
|
8428
|
+
if (/\bvitest\b/i.test(command.shellCommand)) {
|
|
8429
|
+
return "vitest";
|
|
8430
|
+
}
|
|
8431
|
+
if (/\bjest\b/i.test(command.shellCommand)) {
|
|
8432
|
+
return "jest";
|
|
8433
|
+
}
|
|
8434
|
+
return "unknown";
|
|
8435
|
+
}
|
|
6781
8436
|
var shortPytestOptionsWithValue = /* @__PURE__ */ new Set([
|
|
6782
8437
|
"-c",
|
|
6783
8438
|
"-k",
|
|
@@ -6872,26 +8527,52 @@ function buildCachedCommand(args) {
|
|
|
6872
8527
|
}
|
|
6873
8528
|
return void 0;
|
|
6874
8529
|
}
|
|
6875
|
-
function
|
|
8530
|
+
function buildFailingTargets(analysis) {
|
|
8531
|
+
const runner = analysis.runner;
|
|
6876
8532
|
const values = [];
|
|
6877
8533
|
for (const value of [...analysis.visibleErrorLabels, ...analysis.visibleFailedLabels]) {
|
|
6878
|
-
|
|
6879
|
-
|
|
8534
|
+
const normalized = normalizeFailingTarget(value, runner);
|
|
8535
|
+
if (normalized.length > 0 && !values.includes(normalized)) {
|
|
8536
|
+
values.push(normalized);
|
|
6880
8537
|
}
|
|
6881
8538
|
}
|
|
6882
8539
|
return values;
|
|
6883
8540
|
}
|
|
6884
|
-
function
|
|
8541
|
+
function buildCachedRunnerState(args) {
|
|
6885
8542
|
const baseArgv = args.command?.mode === "argv" && isSubsetCapablePytestArgv(args.command.argv) ? [...args.command.argv] : void 0;
|
|
8543
|
+
const runnerName = args.analysis.runner !== "unknown" ? args.analysis.runner : detectRunnerFromCommand(args.command);
|
|
6886
8544
|
return {
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
8545
|
+
name: runnerName,
|
|
8546
|
+
failingTargets: buildFailingTargets(args.analysis),
|
|
8547
|
+
baselineCommand: args.command,
|
|
8548
|
+
subset: {
|
|
8549
|
+
available: runnerName === "pytest" && Boolean(baseArgv),
|
|
8550
|
+
strategy: runnerName === "pytest" && baseArgv ? "pytest-node-ids" : "none",
|
|
8551
|
+
...runnerName === "pytest" && baseArgv ? { baseArgv } : {}
|
|
8552
|
+
}
|
|
6891
8553
|
};
|
|
6892
8554
|
}
|
|
8555
|
+
function normalizeCwd(value) {
|
|
8556
|
+
return path7.resolve(value).replace(/\\/g, "/");
|
|
8557
|
+
}
|
|
8558
|
+
function buildTestStatusBaselineIdentity(args) {
|
|
8559
|
+
const cwd = normalizeCwd(args.cwd);
|
|
8560
|
+
const command = args.command ?? buildCachedCommand({
|
|
8561
|
+
shellCommand: args.shellCommand,
|
|
8562
|
+
command: args.shellCommand ? void 0 : args.commandPreview?.split(" ")
|
|
8563
|
+
});
|
|
8564
|
+
const mode = command?.mode ?? (args.shellCommand ? "shell" : "argv");
|
|
8565
|
+
const normalizedCommand = command?.mode === "argv" ? command.argv.join("") : command?.mode === "shell" ? command.shellCommand.trim().replace(/\s+/g, " ") : (args.commandPreview ?? "").trim().replace(/\s+/g, " ");
|
|
8566
|
+
return [cwd, args.runner, mode, normalizedCommand].join("");
|
|
8567
|
+
}
|
|
6893
8568
|
function buildTestStatusCommandKey(args) {
|
|
6894
|
-
return
|
|
8569
|
+
return buildTestStatusBaselineIdentity({
|
|
8570
|
+
cwd: args.cwd ?? process.cwd(),
|
|
8571
|
+
runner: args.runner ?? "unknown",
|
|
8572
|
+
command: args.command,
|
|
8573
|
+
commandPreview: args.commandPreview,
|
|
8574
|
+
shellCommand: args.shellCommand
|
|
8575
|
+
});
|
|
6895
8576
|
}
|
|
6896
8577
|
function snapshotTestStatusAnalysis(analysis) {
|
|
6897
8578
|
return {
|
|
@@ -6917,13 +8598,22 @@ function createCachedTestStatusRun(args) {
|
|
|
6917
8598
|
command: args.command,
|
|
6918
8599
|
shellCommand: args.shellCommand
|
|
6919
8600
|
});
|
|
8601
|
+
const runnerName = args.analysis.runner !== "unknown" ? args.analysis.runner : detectRunnerFromCommand(command);
|
|
8602
|
+
const commandPreview = args.commandPreview ?? args.shellCommand ?? (args.command ?? []).join(" ");
|
|
8603
|
+
const commandKey = args.commandKey ?? buildTestStatusBaselineIdentity({
|
|
8604
|
+
cwd: args.cwd,
|
|
8605
|
+
runner: runnerName,
|
|
8606
|
+
command,
|
|
8607
|
+
commandPreview,
|
|
8608
|
+
shellCommand: args.shellCommand
|
|
8609
|
+
});
|
|
6920
8610
|
return {
|
|
6921
|
-
version:
|
|
8611
|
+
version: 2,
|
|
6922
8612
|
timestamp: args.timestamp ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
6923
8613
|
presetName: "test-status",
|
|
6924
8614
|
cwd: args.cwd,
|
|
6925
|
-
commandKey
|
|
6926
|
-
commandPreview
|
|
8615
|
+
commandKey,
|
|
8616
|
+
commandPreview,
|
|
6927
8617
|
command,
|
|
6928
8618
|
detail: args.detail,
|
|
6929
8619
|
exitCode: args.exitCode,
|
|
@@ -6933,13 +8623,61 @@ function createCachedTestStatusRun(args) {
|
|
|
6933
8623
|
truncatedApplied: args.truncatedApplied
|
|
6934
8624
|
},
|
|
6935
8625
|
analysis: snapshotTestStatusAnalysis(args.analysis),
|
|
6936
|
-
|
|
8626
|
+
runner: buildCachedRunnerState({
|
|
6937
8627
|
command,
|
|
6938
|
-
analysis: args.analysis
|
|
6939
|
-
remainingNodeIds: args.remainingNodeIds
|
|
8628
|
+
analysis: args.analysis
|
|
6940
8629
|
})
|
|
6941
8630
|
};
|
|
6942
8631
|
}
|
|
8632
|
+
function migrateCachedTestStatusRun(state) {
|
|
8633
|
+
if (state.version === 2) {
|
|
8634
|
+
return state;
|
|
8635
|
+
}
|
|
8636
|
+
const runnerFromOutput = detectTestRunner(state.rawOutput);
|
|
8637
|
+
const runner = runnerFromOutput !== "unknown" ? runnerFromOutput : detectRunnerFromCommand(state.command);
|
|
8638
|
+
const storedCommand = state.command;
|
|
8639
|
+
const fallbackBaseArgv = !storedCommand && state.pytest?.baseArgv ? {
|
|
8640
|
+
mode: "argv",
|
|
8641
|
+
argv: [...state.pytest.baseArgv]
|
|
8642
|
+
} : void 0;
|
|
8643
|
+
const baselineCommand = storedCommand ?? fallbackBaseArgv;
|
|
8644
|
+
const commandPreview = state.commandPreview ?? (baselineCommand?.mode === "argv" ? baselineCommand.argv.join(" ") : baselineCommand?.mode === "shell" ? baselineCommand.shellCommand : "");
|
|
8645
|
+
const commandKey = buildTestStatusBaselineIdentity({
|
|
8646
|
+
cwd: state.cwd,
|
|
8647
|
+
runner,
|
|
8648
|
+
command: baselineCommand,
|
|
8649
|
+
commandPreview
|
|
8650
|
+
});
|
|
8651
|
+
return {
|
|
8652
|
+
version: 2,
|
|
8653
|
+
timestamp: state.timestamp,
|
|
8654
|
+
presetName: state.presetName,
|
|
8655
|
+
cwd: state.cwd,
|
|
8656
|
+
commandKey,
|
|
8657
|
+
commandPreview,
|
|
8658
|
+
command: state.command,
|
|
8659
|
+
detail: state.detail,
|
|
8660
|
+
exitCode: state.exitCode,
|
|
8661
|
+
rawOutput: state.rawOutput,
|
|
8662
|
+
capture: state.capture,
|
|
8663
|
+
analysis: state.analysis,
|
|
8664
|
+
runner: {
|
|
8665
|
+
name: runner,
|
|
8666
|
+
failingTargets: [...new Set((state.pytest?.failingNodeIds ?? []).map(
|
|
8667
|
+
(target) => normalizeFailingTarget(target, runner)
|
|
8668
|
+
))],
|
|
8669
|
+
baselineCommand,
|
|
8670
|
+
subset: {
|
|
8671
|
+
available: runner === "pytest" && Boolean(state.pytest?.baseArgv),
|
|
8672
|
+
strategy: runner === "pytest" && state.pytest?.baseArgv ? "pytest-node-ids" : "none",
|
|
8673
|
+
...runner === "pytest" && state.pytest?.baseArgv ? {
|
|
8674
|
+
baseArgv: [...state.pytest.baseArgv]
|
|
8675
|
+
} : {}
|
|
8676
|
+
}
|
|
8677
|
+
},
|
|
8678
|
+
...fallbackBaseArgv ? { runnerMigrationFallbackUsed: true } : {}
|
|
8679
|
+
};
|
|
8680
|
+
}
|
|
6943
8681
|
function readCachedTestStatusRun(statePath = getDefaultTestStatusStatePath()) {
|
|
6944
8682
|
let raw = "";
|
|
6945
8683
|
try {
|
|
@@ -6951,7 +8689,7 @@ function readCachedTestStatusRun(statePath = getDefaultTestStatusStatePath()) {
|
|
|
6951
8689
|
throw new InvalidCachedTestStatusRunError();
|
|
6952
8690
|
}
|
|
6953
8691
|
try {
|
|
6954
|
-
return cachedRunSchema.parse(JSON.parse(raw));
|
|
8692
|
+
return migrateCachedTestStatusRun(cachedRunSchema.parse(JSON.parse(raw)));
|
|
6955
8693
|
} catch {
|
|
6956
8694
|
throw new InvalidCachedTestStatusRunError();
|
|
6957
8695
|
}
|
|
@@ -6980,15 +8718,7 @@ function getNextEscalationDetail(detail) {
|
|
|
6980
8718
|
return null;
|
|
6981
8719
|
}
|
|
6982
8720
|
function buildTargetDelta(args) {
|
|
6983
|
-
if (args.previous.presetName !== "test-status" || args.current.presetName !== "test-status" || args.previous.cwd !== args.current.cwd || args.previous.commandKey !== args.current.commandKey) {
|
|
6984
|
-
return {
|
|
6985
|
-
comparable: false,
|
|
6986
|
-
resolved: [],
|
|
6987
|
-
remaining: [],
|
|
6988
|
-
introduced: []
|
|
6989
|
-
};
|
|
6990
|
-
}
|
|
6991
|
-
if (!args.previous.pytest || !args.current.pytest) {
|
|
8721
|
+
if (args.previous.presetName !== "test-status" || args.current.presetName !== "test-status" || args.previous.cwd !== args.current.cwd || args.previous.commandKey !== args.current.commandKey || args.previous.runner.name !== args.current.runner.name || args.previous.runner.name === "unknown") {
|
|
6992
8722
|
return {
|
|
6993
8723
|
comparable: false,
|
|
6994
8724
|
resolved: [],
|
|
@@ -6996,8 +8726,8 @@ function buildTargetDelta(args) {
|
|
|
6996
8726
|
introduced: []
|
|
6997
8727
|
};
|
|
6998
8728
|
}
|
|
6999
|
-
const previousTargets = args.previous.
|
|
7000
|
-
const currentTargets = args.current.
|
|
8729
|
+
const previousTargets = args.previous.runner.failingTargets;
|
|
8730
|
+
const currentTargets = args.current.runner.failingTargets;
|
|
7001
8731
|
const currentTargetSet = new Set(currentTargets);
|
|
7002
8732
|
const previousTargetSet = new Set(previousTargets);
|
|
7003
8733
|
return {
|
|
@@ -7010,8 +8740,11 @@ function buildTargetDelta(args) {
|
|
|
7010
8740
|
function diffTestStatusTargets(args) {
|
|
7011
8741
|
return buildTargetDelta(args);
|
|
7012
8742
|
}
|
|
8743
|
+
function isRemainingSubsetAvailable(state) {
|
|
8744
|
+
return state.runner.name === "pytest" && state.runner.subset.available;
|
|
8745
|
+
}
|
|
7013
8746
|
function getRemainingPytestNodeIds(state) {
|
|
7014
|
-
return state.pytest
|
|
8747
|
+
return state.runner.name === "pytest" ? state.runner.failingTargets : [];
|
|
7015
8748
|
}
|
|
7016
8749
|
function diffTestStatusRuns(args) {
|
|
7017
8750
|
const targetDelta = buildTargetDelta(args);
|
|
@@ -7022,21 +8755,45 @@ function diffTestStatusRuns(args) {
|
|
|
7022
8755
|
args.current.analysis.buckets.map((bucket) => [buildBucketSignature(bucket), bucket])
|
|
7023
8756
|
);
|
|
7024
8757
|
const lines = [];
|
|
7025
|
-
|
|
7026
|
-
|
|
7027
|
-
|
|
7028
|
-
|
|
7029
|
-
|
|
7030
|
-
|
|
7031
|
-
|
|
7032
|
-
|
|
7033
|
-
)
|
|
7034
|
-
|
|
7035
|
-
|
|
8758
|
+
const resolvedSummary = buildTestTargetSummary(targetDelta.resolved);
|
|
8759
|
+
const remainingSummary = buildTestTargetSummary(targetDelta.remaining);
|
|
8760
|
+
const introducedSummary = buildTestTargetSummary(targetDelta.introduced);
|
|
8761
|
+
const pushTargetLine = (args2) => {
|
|
8762
|
+
if (args2.summary.count === 0) {
|
|
8763
|
+
return;
|
|
8764
|
+
}
|
|
8765
|
+
const summaryText = describeTargetSummary(args2.summary);
|
|
8766
|
+
if (summaryText) {
|
|
8767
|
+
lines.push(
|
|
8768
|
+
`- ${args2.kind}: ${formatCount3(args2.summary.count, args2.countLabel, `${args2.countLabel}s`)} ${args2.verb} ${summaryText}.`
|
|
8769
|
+
);
|
|
8770
|
+
return;
|
|
8771
|
+
}
|
|
7036
8772
|
lines.push(
|
|
7037
|
-
`-
|
|
8773
|
+
`- ${args2.kind}: ${formatCount3(args2.summary.count, args2.countLabel, `${args2.countLabel}s`)} ${args2.verb}${appendPreview(args2.fallbackValues)}.`
|
|
7038
8774
|
);
|
|
7039
|
-
}
|
|
8775
|
+
};
|
|
8776
|
+
pushTargetLine({
|
|
8777
|
+
kind: "Resolved",
|
|
8778
|
+
summary: resolvedSummary,
|
|
8779
|
+
countLabel: "failing target",
|
|
8780
|
+
fallbackValues: targetDelta.resolved,
|
|
8781
|
+
verb: "no longer appear"
|
|
8782
|
+
});
|
|
8783
|
+
pushTargetLine({
|
|
8784
|
+
kind: "Remaining",
|
|
8785
|
+
summary: remainingSummary,
|
|
8786
|
+
countLabel: "failing target",
|
|
8787
|
+
fallbackValues: targetDelta.remaining,
|
|
8788
|
+
verb: "still appear"
|
|
8789
|
+
});
|
|
8790
|
+
pushTargetLine({
|
|
8791
|
+
kind: "New",
|
|
8792
|
+
summary: introducedSummary,
|
|
8793
|
+
countLabel: "failing target",
|
|
8794
|
+
fallbackValues: targetDelta.introduced,
|
|
8795
|
+
verb: "appeared"
|
|
8796
|
+
});
|
|
7040
8797
|
for (const bucket of args.current.analysis.buckets) {
|
|
7041
8798
|
const signature = buildBucketSignature(bucket);
|
|
7042
8799
|
const previous = previousBuckets.get(signature);
|
|
@@ -7064,19 +8821,19 @@ function diffTestStatusRuns(args) {
|
|
|
7064
8821
|
}
|
|
7065
8822
|
}
|
|
7066
8823
|
return {
|
|
7067
|
-
lines: lines.slice(0, 4)
|
|
7068
|
-
remainingNodeIds: targetDelta.comparable ? targetDelta.remaining : void 0
|
|
8824
|
+
lines: lines.slice(0, 4)
|
|
7069
8825
|
};
|
|
7070
8826
|
}
|
|
7071
8827
|
function getCachedRerunCommand(state) {
|
|
7072
|
-
|
|
8828
|
+
const baselineCommand = state.runner.baselineCommand ?? state.command;
|
|
8829
|
+
if (baselineCommand?.mode === "argv") {
|
|
7073
8830
|
return {
|
|
7074
|
-
command: [...
|
|
8831
|
+
command: [...baselineCommand.argv]
|
|
7075
8832
|
};
|
|
7076
8833
|
}
|
|
7077
|
-
if (
|
|
8834
|
+
if (baselineCommand?.mode === "shell") {
|
|
7078
8835
|
return {
|
|
7079
|
-
shellCommand:
|
|
8836
|
+
shellCommand: baselineCommand.shellCommand
|
|
7080
8837
|
};
|
|
7081
8838
|
}
|
|
7082
8839
|
throw new Error(
|
|
@@ -7084,13 +8841,13 @@ function getCachedRerunCommand(state) {
|
|
|
7084
8841
|
);
|
|
7085
8842
|
}
|
|
7086
8843
|
function getRemainingPytestRerunCommand(state) {
|
|
7087
|
-
if (!state
|
|
8844
|
+
if (!isRemainingSubsetAvailable(state) || !state.runner.subset.baseArgv) {
|
|
7088
8845
|
throw new Error(
|
|
7089
8846
|
"Cached test-status run cannot use `sift rerun --remaining`. Automatic remaining-subset reruns currently support only argv-mode `pytest ...` or `python -m pytest ...` commands. Run a narrowed command manually with `sift exec --preset test-status -- <narrowed pytest command>`."
|
|
7090
8847
|
);
|
|
7091
8848
|
}
|
|
7092
8849
|
const remainingNodeIds = getRemainingPytestNodeIds(state);
|
|
7093
|
-
return [...state.
|
|
8850
|
+
return [...state.runner.subset.baseArgv, ...remainingNodeIds];
|
|
7094
8851
|
}
|
|
7095
8852
|
|
|
7096
8853
|
// src/core/escalate.ts
|
|
@@ -7114,7 +8871,7 @@ async function runEscalate(request) {
|
|
|
7114
8871
|
const detail = resolveEscalationDetail(state, request.detail, request.showRaw);
|
|
7115
8872
|
if (request.verbose) {
|
|
7116
8873
|
process.stderr.write(
|
|
7117
|
-
`${
|
|
8874
|
+
`${pc4.dim("sift")} escalate detail=${detail} cached_detail=${state.detail} command=${state.commandPreview}
|
|
7118
8875
|
`
|
|
7119
8876
|
);
|
|
7120
8877
|
}
|
|
@@ -7124,7 +8881,7 @@ async function runEscalate(request) {
|
|
|
7124
8881
|
process.stderr.write("\n");
|
|
7125
8882
|
}
|
|
7126
8883
|
}
|
|
7127
|
-
|
|
8884
|
+
const result = await runSiftWithStats({
|
|
7128
8885
|
question: request.question,
|
|
7129
8886
|
format: request.format,
|
|
7130
8887
|
goal: request.goal,
|
|
@@ -7138,9 +8895,11 @@ async function runEscalate(request) {
|
|
|
7138
8895
|
outputContract: request.outputContract,
|
|
7139
8896
|
fallbackJson: request.fallbackJson,
|
|
7140
8897
|
testStatusContext: {
|
|
7141
|
-
remainingSubsetAvailable:
|
|
8898
|
+
remainingSubsetAvailable: isRemainingSubsetAvailable(state) && state.runner.failingTargets.length > 0,
|
|
8899
|
+
remainingMode: "none"
|
|
7142
8900
|
}
|
|
7143
8901
|
});
|
|
8902
|
+
let output = result.output;
|
|
7144
8903
|
if (isInsufficientSignalOutput(output)) {
|
|
7145
8904
|
output = buildInsufficientSignalOutput({
|
|
7146
8905
|
presetName: "test-status",
|
|
@@ -7151,6 +8910,10 @@ async function runEscalate(request) {
|
|
|
7151
8910
|
}
|
|
7152
8911
|
process.stdout.write(`${output}
|
|
7153
8912
|
`);
|
|
8913
|
+
emitStatsFooter({
|
|
8914
|
+
stats: result.stats,
|
|
8915
|
+
quiet: Boolean(request.quiet)
|
|
8916
|
+
});
|
|
7154
8917
|
try {
|
|
7155
8918
|
writeCachedTestStatusRun({
|
|
7156
8919
|
...state,
|
|
@@ -7159,7 +8922,7 @@ async function runEscalate(request) {
|
|
|
7159
8922
|
} catch (error) {
|
|
7160
8923
|
if (request.verbose) {
|
|
7161
8924
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
7162
|
-
process.stderr.write(`${
|
|
8925
|
+
process.stderr.write(`${pc4.dim("sift")} cache_write=failed reason=${reason}
|
|
7163
8926
|
`);
|
|
7164
8927
|
}
|
|
7165
8928
|
}
|
|
@@ -7169,7 +8932,7 @@ async function runEscalate(request) {
|
|
|
7169
8932
|
// src/core/exec.ts
|
|
7170
8933
|
import { spawn } from "child_process";
|
|
7171
8934
|
import { constants as osConstants } from "os";
|
|
7172
|
-
import
|
|
8935
|
+
import pc5 from "picocolors";
|
|
7173
8936
|
|
|
7174
8937
|
// src/core/gate.ts
|
|
7175
8938
|
var FAIL_ON_SUPPORTED_PRESETS = /* @__PURE__ */ new Set(["infra-risk", "audit-critical"]);
|
|
@@ -7347,8 +9110,9 @@ async function runTestStatusWatch(request, cycles) {
|
|
|
7347
9110
|
testStatusContext: {
|
|
7348
9111
|
...request.testStatusContext,
|
|
7349
9112
|
resolvedTests: targetDelta?.resolved ?? request.testStatusContext?.resolvedTests,
|
|
7350
|
-
remainingTests: targetDelta?.remaining ?? currentRun.
|
|
7351
|
-
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? (
|
|
9113
|
+
remainingTests: targetDelta?.remaining ?? currentRun.runner.failingTargets ?? request.testStatusContext?.remainingTests,
|
|
9114
|
+
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? (isRemainingSubsetAvailable(currentRun) && currentRun.runner.failingTargets.length > 0),
|
|
9115
|
+
remainingMode: request.testStatusContext?.remainingMode ?? "none"
|
|
7352
9116
|
}
|
|
7353
9117
|
});
|
|
7354
9118
|
if (request.goal === "diagnose" && request.format === "json") {
|
|
@@ -7495,11 +9259,13 @@ async function runExec(request) {
|
|
|
7495
9259
|
const shellPath = process.env.SHELL || "/bin/bash";
|
|
7496
9260
|
const commandPreview = buildCommandPreview(request);
|
|
7497
9261
|
const commandCwd = request.cwd ?? process.cwd();
|
|
7498
|
-
const
|
|
7499
|
-
const
|
|
9262
|
+
const isTestStatusPreset = request.presetName === "test-status";
|
|
9263
|
+
const readCachedBaseline = isTestStatusPreset && (request.readCachedBaseline ?? true);
|
|
9264
|
+
const writeCachedBaselineRequested = isTestStatusPreset && (request.writeCachedBaseline ?? (request.skipCacheWrite ? false : true));
|
|
9265
|
+
const previousCachedRun = readCachedBaseline ? tryReadCachedTestStatusRun() : null;
|
|
7500
9266
|
if (request.config.runtime.verbose) {
|
|
7501
9267
|
process.stderr.write(
|
|
7502
|
-
`${
|
|
9268
|
+
`${pc5.dim("sift")} exec mode=${hasShellCommand ? "shell" : "argv"} command=${commandPreview}
|
|
7503
9269
|
`
|
|
7504
9270
|
);
|
|
7505
9271
|
}
|
|
@@ -7528,7 +9294,7 @@ async function runExec(request) {
|
|
|
7528
9294
|
}
|
|
7529
9295
|
bypassed = true;
|
|
7530
9296
|
if (request.config.runtime.verbose) {
|
|
7531
|
-
process.stderr.write(`${
|
|
9297
|
+
process.stderr.write(`${pc5.dim("sift")} bypass=interactive-prompt
|
|
7532
9298
|
`);
|
|
7533
9299
|
}
|
|
7534
9300
|
process.stderr.write(capture.render());
|
|
@@ -7554,18 +9320,20 @@ async function runExec(request) {
|
|
|
7554
9320
|
const capturedOutput = capture.render();
|
|
7555
9321
|
const autoWatchDetected = !request.watch && looksLikeWatchStream(capturedOutput);
|
|
7556
9322
|
const useWatchFlow = Boolean(request.watch) || autoWatchDetected;
|
|
7557
|
-
const
|
|
9323
|
+
const shouldBuildTestStatusState = isTestStatusPreset && !useWatchFlow;
|
|
9324
|
+
const shouldWriteCachedBaseline = writeCachedBaselineRequested && !useWatchFlow;
|
|
7558
9325
|
if (request.config.runtime.verbose) {
|
|
7559
9326
|
process.stderr.write(
|
|
7560
|
-
`${
|
|
9327
|
+
`${pc5.dim("sift")} child_exit=${exitCode} captured_chars=${capture.getTotalChars()} capture_truncated=${capture.wasTruncated()}
|
|
7561
9328
|
`
|
|
7562
9329
|
);
|
|
7563
9330
|
}
|
|
7564
9331
|
if (autoWatchDetected) {
|
|
7565
|
-
process.stderr.write(`${
|
|
9332
|
+
process.stderr.write(`${pc5.dim("sift")} auto-watch=detected
|
|
7566
9333
|
`);
|
|
7567
9334
|
}
|
|
7568
9335
|
if (!bypassed) {
|
|
9336
|
+
const reductionStartedAt = Date.now();
|
|
7569
9337
|
if (request.showRaw && capturedOutput.length > 0) {
|
|
7570
9338
|
process.stderr.write(capturedOutput);
|
|
7571
9339
|
if (!capturedOutput.endsWith("\n")) {
|
|
@@ -7580,12 +9348,22 @@ async function runExec(request) {
|
|
|
7580
9348
|
if (execSuccessShortcut && !request.dryRun) {
|
|
7581
9349
|
if (request.config.runtime.verbose) {
|
|
7582
9350
|
process.stderr.write(
|
|
7583
|
-
`${
|
|
9351
|
+
`${pc5.dim("sift")} exec_shortcut=${request.presetName}
|
|
7584
9352
|
`
|
|
7585
9353
|
);
|
|
7586
9354
|
}
|
|
7587
9355
|
process.stdout.write(`${execSuccessShortcut}
|
|
7588
9356
|
`);
|
|
9357
|
+
emitStatsFooter({
|
|
9358
|
+
stats: {
|
|
9359
|
+
layer: "heuristic",
|
|
9360
|
+
providerCalled: false,
|
|
9361
|
+
totalTokens: null,
|
|
9362
|
+
durationMs: Date.now() - reductionStartedAt,
|
|
9363
|
+
presetName: request.presetName
|
|
9364
|
+
},
|
|
9365
|
+
quiet: Boolean(request.quiet)
|
|
9366
|
+
});
|
|
7589
9367
|
return exitCode;
|
|
7590
9368
|
}
|
|
7591
9369
|
if (useWatchFlow) {
|
|
@@ -7598,17 +9376,27 @@ async function runExec(request) {
|
|
|
7598
9376
|
presetName: request.presetName,
|
|
7599
9377
|
originalLength: capture.getTotalChars(),
|
|
7600
9378
|
truncatedApplied: capture.wasTruncated(),
|
|
7601
|
-
exitCode
|
|
9379
|
+
exitCode,
|
|
9380
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
7602
9381
|
});
|
|
7603
9382
|
}
|
|
7604
9383
|
process.stdout.write(`${output2}
|
|
7605
9384
|
`);
|
|
7606
9385
|
return exitCode;
|
|
7607
9386
|
}
|
|
7608
|
-
const analysis =
|
|
7609
|
-
let currentCachedRun =
|
|
9387
|
+
const analysis = shouldBuildTestStatusState ? analyzeTestStatus(capturedOutput) : null;
|
|
9388
|
+
let currentCachedRun = shouldBuildTestStatusState && analysis ? createCachedTestStatusRun({
|
|
7610
9389
|
cwd: commandCwd,
|
|
7611
9390
|
commandKey: buildTestStatusCommandKey({
|
|
9391
|
+
cwd: commandCwd,
|
|
9392
|
+
runner: analysis.runner,
|
|
9393
|
+
command: Array.isArray(request.command) && request.command.length > 0 ? {
|
|
9394
|
+
mode: "argv",
|
|
9395
|
+
argv: [...request.command]
|
|
9396
|
+
} : request.shellCommand ? {
|
|
9397
|
+
mode: "shell",
|
|
9398
|
+
shellCommand: request.shellCommand
|
|
9399
|
+
} : void 0,
|
|
7612
9400
|
commandPreview,
|
|
7613
9401
|
shellCommand: request.shellCommand
|
|
7614
9402
|
}),
|
|
@@ -7622,36 +9410,39 @@ async function runExec(request) {
|
|
|
7622
9410
|
truncatedApplied: capture.wasTruncated(),
|
|
7623
9411
|
analysis
|
|
7624
9412
|
}) : null;
|
|
7625
|
-
const targetDelta = request.diff && !request.dryRun && previousCachedRun && currentCachedRun ? diffTestStatusTargets({
|
|
9413
|
+
const targetDelta = (request.diff || request.testStatusContext?.remainingMode === "subset_rerun" || request.testStatusContext?.remainingMode === "full_rerun_diff") && !request.dryRun && previousCachedRun && currentCachedRun ? diffTestStatusTargets({
|
|
7626
9414
|
previous: previousCachedRun,
|
|
7627
9415
|
current: currentCachedRun
|
|
7628
9416
|
}) : null;
|
|
7629
|
-
|
|
9417
|
+
const result = await runSiftWithStats({
|
|
7630
9418
|
...request,
|
|
7631
9419
|
stdin: capturedOutput,
|
|
7632
|
-
analysisContext: request.
|
|
9420
|
+
analysisContext: request.testStatusContext?.remainingMode && request.testStatusContext.remainingMode !== "none" && request.presetName === "test-status" ? [
|
|
7633
9421
|
request.analysisContext,
|
|
7634
9422
|
"Zoom context:",
|
|
7635
9423
|
"- This pass is remaining-only.",
|
|
7636
9424
|
"- The full-suite truth already exists from the cached full run.",
|
|
7637
9425
|
"- Do not reintroduce resolved tests into the diagnosis."
|
|
7638
9426
|
].filter((value) => Boolean(value)).join("\n") : request.analysisContext,
|
|
7639
|
-
testStatusContext:
|
|
9427
|
+
testStatusContext: shouldBuildTestStatusState && analysis ? {
|
|
7640
9428
|
...request.testStatusContext,
|
|
7641
9429
|
resolvedTests: targetDelta?.resolved ?? request.testStatusContext?.resolvedTests,
|
|
7642
|
-
remainingTests: targetDelta?.remaining ?? currentCachedRun?.
|
|
9430
|
+
remainingTests: targetDelta?.remaining ?? currentCachedRun?.runner.failingTargets ?? request.testStatusContext?.remainingTests,
|
|
7643
9431
|
remainingSubsetAvailable: request.testStatusContext?.remainingSubsetAvailable ?? Boolean(
|
|
7644
|
-
currentCachedRun
|
|
7645
|
-
)
|
|
9432
|
+
currentCachedRun && isRemainingSubsetAvailable(currentCachedRun) && (targetDelta?.remaining ?? currentCachedRun?.runner.failingTargets ?? []).length > 0
|
|
9433
|
+
),
|
|
9434
|
+
remainingMode: request.testStatusContext?.remainingMode ?? "none"
|
|
7646
9435
|
} : request.testStatusContext
|
|
7647
9436
|
});
|
|
7648
|
-
|
|
9437
|
+
let output = result.output;
|
|
9438
|
+
if (shouldBuildTestStatusState) {
|
|
7649
9439
|
if (isInsufficientSignalOutput(output)) {
|
|
7650
9440
|
output = buildInsufficientSignalOutput({
|
|
7651
9441
|
presetName: request.presetName,
|
|
7652
9442
|
originalLength: capture.getTotalChars(),
|
|
7653
9443
|
truncatedApplied: capture.wasTruncated(),
|
|
7654
|
-
exitCode
|
|
9444
|
+
exitCode,
|
|
9445
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
7655
9446
|
});
|
|
7656
9447
|
}
|
|
7657
9448
|
if (request.diff && !request.dryRun && previousCachedRun && currentCachedRun) {
|
|
@@ -7659,32 +9450,18 @@ async function runExec(request) {
|
|
|
7659
9450
|
previous: previousCachedRun,
|
|
7660
9451
|
current: currentCachedRun
|
|
7661
9452
|
});
|
|
7662
|
-
currentCachedRun = createCachedTestStatusRun({
|
|
7663
|
-
cwd: commandCwd,
|
|
7664
|
-
commandKey: currentCachedRun.commandKey,
|
|
7665
|
-
commandPreview,
|
|
7666
|
-
command: request.command,
|
|
7667
|
-
shellCommand: request.shellCommand,
|
|
7668
|
-
detail: request.detail ?? "standard",
|
|
7669
|
-
exitCode,
|
|
7670
|
-
rawOutput: capturedOutput,
|
|
7671
|
-
originalChars: capture.getTotalChars(),
|
|
7672
|
-
truncatedApplied: capture.wasTruncated(),
|
|
7673
|
-
analysis,
|
|
7674
|
-
remainingNodeIds: delta.remainingNodeIds
|
|
7675
|
-
});
|
|
7676
9453
|
if (delta.lines.length > 0) {
|
|
7677
9454
|
output = `${delta.lines.join("\n")}
|
|
7678
9455
|
${output}`;
|
|
7679
9456
|
}
|
|
7680
9457
|
}
|
|
7681
|
-
if (currentCachedRun) {
|
|
9458
|
+
if (currentCachedRun && shouldWriteCachedBaseline) {
|
|
7682
9459
|
try {
|
|
7683
9460
|
writeCachedTestStatusRun(currentCachedRun);
|
|
7684
9461
|
} catch (error) {
|
|
7685
9462
|
if (request.config.runtime.verbose) {
|
|
7686
9463
|
const reason = error instanceof Error ? error.message : "unknown_error";
|
|
7687
|
-
process.stderr.write(`${
|
|
9464
|
+
process.stderr.write(`${pc5.dim("sift")} cache_write=failed reason=${reason}
|
|
7688
9465
|
`);
|
|
7689
9466
|
}
|
|
7690
9467
|
}
|
|
@@ -7694,11 +9471,16 @@ ${output}`;
|
|
|
7694
9471
|
presetName: request.presetName,
|
|
7695
9472
|
originalLength: capture.getTotalChars(),
|
|
7696
9473
|
truncatedApplied: capture.wasTruncated(),
|
|
7697
|
-
exitCode
|
|
9474
|
+
exitCode,
|
|
9475
|
+
recognizedRunner: detectTestRunner(capturedOutput)
|
|
7698
9476
|
});
|
|
7699
9477
|
}
|
|
7700
9478
|
process.stdout.write(`${output}
|
|
7701
9479
|
`);
|
|
9480
|
+
emitStatsFooter({
|
|
9481
|
+
stats: result.stats,
|
|
9482
|
+
quiet: Boolean(request.quiet)
|
|
9483
|
+
});
|
|
7702
9484
|
if (request.failOn && !request.dryRun && exitCode === 0 && supportsFailOnPreset(request.presetName) && evaluateGate({
|
|
7703
9485
|
presetName: request.presetName,
|
|
7704
9486
|
output
|
|
@@ -7720,25 +9502,60 @@ async function runRerun(request) {
|
|
|
7720
9502
|
diff: true,
|
|
7721
9503
|
presetName: "test-status",
|
|
7722
9504
|
detail: "standard",
|
|
7723
|
-
showRaw: false
|
|
9505
|
+
showRaw: false,
|
|
9506
|
+
readCachedBaseline: true,
|
|
9507
|
+
writeCachedBaseline: true,
|
|
9508
|
+
testStatusContext: {
|
|
9509
|
+
...request.testStatusContext,
|
|
9510
|
+
remainingMode: "none"
|
|
9511
|
+
}
|
|
7724
9512
|
});
|
|
7725
9513
|
}
|
|
7726
|
-
|
|
7727
|
-
|
|
7728
|
-
|
|
7729
|
-
|
|
9514
|
+
if (state.runner.name === "pytest") {
|
|
9515
|
+
const remainingNodeIds = getRemainingPytestNodeIds(state);
|
|
9516
|
+
if (remainingNodeIds.length === 0) {
|
|
9517
|
+
process.stdout.write("No remaining failing pytest targets.\n");
|
|
9518
|
+
return 0;
|
|
9519
|
+
}
|
|
9520
|
+
return runExec({
|
|
9521
|
+
...request,
|
|
9522
|
+
command: getRemainingPytestRerunCommand(state),
|
|
9523
|
+
cwd: state.cwd,
|
|
9524
|
+
diff: false,
|
|
9525
|
+
presetName: "test-status",
|
|
9526
|
+
readCachedBaseline: true,
|
|
9527
|
+
writeCachedBaseline: false,
|
|
9528
|
+
testStatusContext: {
|
|
9529
|
+
...request.testStatusContext,
|
|
9530
|
+
remainingSubsetAvailable: isRemainingSubsetAvailable(state),
|
|
9531
|
+
remainingMode: "subset_rerun"
|
|
9532
|
+
}
|
|
9533
|
+
});
|
|
7730
9534
|
}
|
|
7731
|
-
|
|
7732
|
-
|
|
7733
|
-
|
|
7734
|
-
|
|
7735
|
-
|
|
7736
|
-
presetName: "test-status",
|
|
7737
|
-
skipCacheWrite: true,
|
|
7738
|
-
testStatusContext: {
|
|
7739
|
-
remainingSubsetAvailable: true
|
|
9535
|
+
if (state.runner.name === "vitest" || state.runner.name === "jest") {
|
|
9536
|
+
if (!state.runner.baselineCommand || state.runnerMigrationFallbackUsed) {
|
|
9537
|
+
throw new Error(
|
|
9538
|
+
"Cached test-status run cannot use `sift rerun --remaining` yet because the original full command is unavailable from cache. Refresh the baseline with `sift exec --preset test-status -- <test command>` and retry."
|
|
9539
|
+
);
|
|
7740
9540
|
}
|
|
7741
|
-
|
|
9541
|
+
return runExec({
|
|
9542
|
+
...request,
|
|
9543
|
+
...getCachedRerunCommand(state),
|
|
9544
|
+
cwd: state.cwd,
|
|
9545
|
+
diff: false,
|
|
9546
|
+
presetName: "test-status",
|
|
9547
|
+
readCachedBaseline: true,
|
|
9548
|
+
writeCachedBaseline: false,
|
|
9549
|
+
testStatusContext: {
|
|
9550
|
+
...request.testStatusContext,
|
|
9551
|
+
remainingSubsetAvailable: false,
|
|
9552
|
+
remainingMode: "full_rerun_diff"
|
|
9553
|
+
}
|
|
9554
|
+
});
|
|
9555
|
+
}
|
|
9556
|
+
throw new Error(
|
|
9557
|
+
"Cached test-status run cannot use `sift rerun --remaining` for this runner. Refresh with `sift exec --preset test-status -- <test command>` or rerun a narrowed command manually."
|
|
9558
|
+
);
|
|
7742
9559
|
}
|
|
7743
9560
|
|
|
7744
9561
|
// src/core/stdin.ts
|
|
@@ -7788,6 +9605,7 @@ var defaultCliDeps = {
|
|
|
7788
9605
|
evaluateGate,
|
|
7789
9606
|
readStdin,
|
|
7790
9607
|
runSift,
|
|
9608
|
+
runSiftWithStats,
|
|
7791
9609
|
runWatch,
|
|
7792
9610
|
looksLikeWatchStream,
|
|
7793
9611
|
getPreset
|
|
@@ -7896,7 +9714,7 @@ function applySharedOptions(command) {
|
|
|
7896
9714
|
).option(
|
|
7897
9715
|
"--fail-on",
|
|
7898
9716
|
"Fail with exit code 1 when a supported built-in preset produces a blocking result"
|
|
7899
|
-
).option("--config <path>", "Path to config file").option("--verbose", "Enable verbose stderr logging");
|
|
9717
|
+
).option("--config <path>", "Path to config file").option("--quiet", "Suppress the stats footer on stderr").option("--verbose", "Enable verbose stderr logging");
|
|
7900
9718
|
}
|
|
7901
9719
|
function normalizeDetail(value) {
|
|
7902
9720
|
if (value === void 0 || value === null || value === "") {
|
|
@@ -7995,21 +9813,25 @@ function createCliApp(args = {}) {
|
|
|
7995
9813
|
stderr.write("\n");
|
|
7996
9814
|
}
|
|
7997
9815
|
}
|
|
7998
|
-
const
|
|
7999
|
-
|
|
8000
|
-
|
|
8001
|
-
|
|
8002
|
-
|
|
8003
|
-
|
|
8004
|
-
|
|
8005
|
-
|
|
8006
|
-
|
|
8007
|
-
|
|
8008
|
-
|
|
8009
|
-
|
|
8010
|
-
|
|
8011
|
-
|
|
8012
|
-
|
|
9816
|
+
const isWatchStream = deps.looksLikeWatchStream(stdin);
|
|
9817
|
+
const result = isWatchStream ? {
|
|
9818
|
+
output: await deps.runWatch({
|
|
9819
|
+
question: input.question,
|
|
9820
|
+
format: input.format,
|
|
9821
|
+
goal: input.goal,
|
|
9822
|
+
stdin,
|
|
9823
|
+
config,
|
|
9824
|
+
dryRun: Boolean(input.options.dryRun),
|
|
9825
|
+
showRaw: Boolean(input.options.showRaw),
|
|
9826
|
+
includeTestIds: Boolean(input.options.includeTestIds),
|
|
9827
|
+
detail: input.detail,
|
|
9828
|
+
presetName: input.presetName,
|
|
9829
|
+
policyName: input.policyName,
|
|
9830
|
+
outputContract: input.outputContract,
|
|
9831
|
+
fallbackJson: input.fallbackJson
|
|
9832
|
+
}),
|
|
9833
|
+
stats: null
|
|
9834
|
+
} : await deps.runSiftWithStats({
|
|
8013
9835
|
question: input.question,
|
|
8014
9836
|
format: input.format,
|
|
8015
9837
|
goal: input.goal,
|
|
@@ -8024,8 +9846,13 @@ function createCliApp(args = {}) {
|
|
|
8024
9846
|
outputContract: input.outputContract,
|
|
8025
9847
|
fallbackJson: input.fallbackJson
|
|
8026
9848
|
});
|
|
9849
|
+
const output = result.output;
|
|
8027
9850
|
stdout.write(`${output}
|
|
8028
9851
|
`);
|
|
9852
|
+
emitStatsFooter({
|
|
9853
|
+
stats: result.stats,
|
|
9854
|
+
quiet: Boolean(input.options.quiet)
|
|
9855
|
+
});
|
|
8029
9856
|
if (Boolean(input.options.failOn) && !Boolean(input.options.dryRun) && input.presetName && deps.evaluateGate({
|
|
8030
9857
|
presetName: input.presetName,
|
|
8031
9858
|
output
|
|
@@ -8055,6 +9882,7 @@ function createCliApp(args = {}) {
|
|
|
8055
9882
|
dryRun: Boolean(input.options.dryRun),
|
|
8056
9883
|
diff: input.diff,
|
|
8057
9884
|
failOn: Boolean(input.options.failOn),
|
|
9885
|
+
quiet: Boolean(input.options.quiet),
|
|
8058
9886
|
showRaw: Boolean(input.options.showRaw),
|
|
8059
9887
|
includeTestIds: Boolean(input.options.includeTestIds),
|
|
8060
9888
|
watch: Boolean(input.options.watch),
|
|
@@ -8234,13 +10062,20 @@ function createCliApp(args = {}) {
|
|
|
8234
10062
|
outputContract: preset.outputContract,
|
|
8235
10063
|
fallbackJson: preset.fallbackJson,
|
|
8236
10064
|
detail: normalizeEscalateDetail(options.detail),
|
|
10065
|
+
quiet: Boolean(options.quiet),
|
|
8237
10066
|
showRaw: Boolean(options.showRaw),
|
|
8238
10067
|
verbose: Boolean(options.verbose)
|
|
8239
10068
|
});
|
|
8240
10069
|
});
|
|
8241
10070
|
applySharedOptions(
|
|
8242
|
-
cli.command(
|
|
8243
|
-
|
|
10071
|
+
cli.command(
|
|
10072
|
+
"rerun",
|
|
10073
|
+
"Rerun the cached test-status command or focus on what still fails from the cached baseline"
|
|
10074
|
+
)
|
|
10075
|
+
).usage("rerun [options]").example("rerun").example("rerun --remaining").example("rerun --remaining --detail focused").example("rerun --remaining --detail verbose --show-raw").option(
|
|
10076
|
+
"--remaining",
|
|
10077
|
+
"Focus on what still fails from the cached baseline; narrows automatically for pytest and diffs a full rerun for vitest/jest"
|
|
10078
|
+
).action(async (options) => {
|
|
8244
10079
|
const remaining = Boolean(options.remaining);
|
|
8245
10080
|
if (!remaining && Boolean(options.showRaw)) {
|
|
8246
10081
|
throw new Error("--show-raw is supported only with `sift rerun --remaining`.");
|