// engine/impact-detector/test-break-predictor.ts — SPEC-586: Predict test breaks
import { readdir, readFile, stat } from 'node:fs/promises';
import { join, relative } from 'node:path';
const MAX_TEST_FILES = 200;
const BREAK_PATTERNS = [
    {
        pattern: /toHaveBeenCalledTimes\s*\(\s*\d+\s*\)/,
        kind: 'tool-count',
        remediation: 'Update the count assertion to reflect new tool count',
    },
    {
        pattern: /toMatchSnapshot|toMatchInlineSnapshot/,
        kind: 'snapshot',
        remediation: 'Regenerate snapshot with: pnpm test -- -u',
    },
    {
        pattern: /STREAMS-ANCHOR/,
        kind: 'streams-anchor',
        remediation: 'Run scripts/sync-website-counts.sh --apply and commit website/',
    },
    {
        pattern: /license-plans\.json/,
        kind: 'license-plans-sync',
        remediation: 'Add new tool name to src/config/license-plans.json',
    },
];
async function collectTestFiles(dir, results, budget, depth) {
    if (budget.remaining <= 0 || depth > 5) {
        return;
    }
    let entries;
    try {
        entries = await readdir(dir);
    }
    catch {
        return;
    }
    for (const entry of entries) {
        if (budget.remaining <= 0) {
            break;
        }
        if (entry.startsWith('.') || entry === 'node_modules') {
            continue;
        }
        const full = join(dir, entry);
        let s;
        try {
            s = await stat(full);
        }
        catch {
            continue;
        }
        if (s.isDirectory()) {
            await collectTestFiles(full, results, budget, depth + 1);
        }
        else if (entry.endsWith('.test.ts') || entry.endsWith('.test.js')) {
            const b = budget;
            b.remaining -= 1;
            results.push(full);
        }
    }
}
async function scanFileForBreaks(filePath, projectPath) {
    let content;
    try {
        content = await readFile(filePath, 'utf-8');
    }
    catch {
        return [];
    }
    const breaks = [];
    const rel = relative(projectPath, filePath);
    for (const rule of BREAK_PATTERNS) {
        if (rule.pattern.test(content)) {
            breaks.push({
                testPath: rel,
                pattern: rule.kind,
                remediation: rule.remediation,
            });
        }
    }
    return breaks;
}
export async function predictTestBreaks(projectPath) {
    const testsDir = join(projectPath, 'tests');
    const testFiles = [];
    const budget = { remaining: MAX_TEST_FILES };
    await collectTestFiles(testsDir, testFiles, budget, 0);
    const allBreaks = [];
    for (const file of testFiles) {
        const fileBreaks = await scanFileForBreaks(file, projectPath);
        allBreaks.push(...fileBreaks);
    }
    const seen = new Set();
    return allBreaks.filter((b) => {
        const key = `${b.testPath}:${b.pattern}`;
        if (seen.has(key)) {
            return false;
        }
        seen.add(key);
        return true;
    });
}
//# sourceMappingURL=test-break-predictor.js.map