export const DEFAULT_GATES = {
    minSupport: 4,
    minDistinctSessions: 3,
    minDistinctCauseCommits: 3,
};
/** Template recommendations keyed on category — NOT free LLM text (§4.4 / BL-3). */
const RECOMMENDATION_BY_CATEGORY = {
    concurrency: 'Recurring concurrency failures — give the adversarial review pass a concurrency/race checklist and require a concurrent-path test.',
    'config-parse': 'Recurring config-parse failures — add a config-schema validation gate to the build skill before merge.',
    wiring: 'Recurring wiring failures — require a wiring-integrity test (assert the component is constructed AND started) for every dependency-injected feature.',
    logic: 'Recurring logic failures — strengthen the both-sides-of-the-boundary test requirement in the spec review.',
    migration: 'Recurring migration failures — add a migration-parity check (existing-agent path) to the review checklist.',
    'test-gap': 'Recurring test-gap failures — require the 3-tier test set (incl. the production storage path) before merge.',
    // Ingestion-sources spec §7: required because this is a total Record<FailureCategory,…>
    // — widening the enum without these entries fails tsc. (A totality test locks this.)
    'build-failure': 'Recurring build failures — check the build config / dependency or lint rule that keeps breaking, and add a pre-merge build gate for it.',
    'test-failure': 'Recurring test failures — require the 3-tier test set before merge and stabilize the flaky path before it lands.',
    regression: 'Recurring regressions — a shipped feature keeps breaking; add a regression guard (a test pinning the behavior) for this path.',
    unknown: 'Recurring uncategorized failures — improve the failure-categorization step so these become actionable.',
};
export class FailureAnalyzer {
    ledger;
    gates;
    constructor(ledger, gates = DEFAULT_GATES) {
        this.ledger = ledger;
        this.gates = gates;
    }
    /**
     * Scan attributed failures, detect category clusters that cross the gate, and
     * upsert an InsightRecord for each. Returns the insights touched. Re-running
     * is idempotent (upsert on a content-stable identityKey).
     */
    analyze() {
        const sinceMs = this.gates.windowDays && this.gates.windowDays > 0
            ? Date.now() - this.gates.windowDays * 86400_000
            : undefined;
        // Only attributed records feed a process-change recommendation (§4.3): a
        // guess (`inferred`) is excluded until confirmed.
        // Ingestion-sources spec §6.1 (implements parent §4.4 M6, specified-but-unbuilt):
        // `resolved` records (incl. those a revert closes) are excluded from active-rate
        // clustering — a fixed/reverted failure must not keep driving a recommendation.
        // `reopened` stays IN (it is active again); only `resolved` is excluded.
        const records = this.ledger
            .list({ sinceMs, limit: 1000 })
            .filter((r) => (r.attribution === 'automatic' || r.attribution === 'one-tap') && r.status !== 'resolved');
        const byCategory = new Map();
        for (const r of records) {
            const arr = byCategory.get(r.category) ?? [];
            arr.push(r);
            byCategory.set(r.category, arr);
        }
        const discovered = [];
        let belowThreshold = 0;
        for (const [category, cluster] of byCategory) {
            if (category === 'unknown')
                continue; // unknown is a coverage signal, not a pattern
            const distinctSessions = new Set(cluster.map((r) => r.filedBy)).size;
            const distinctCauseCommits = new Set(cluster.map((r) => r.causeCommitOid ?? r.id)).size;
            const crosses = cluster.length >= this.gates.minSupport &&
                distinctSessions >= this.gates.minDistinctSessions &&
                distinctCauseCommits >= this.gates.minDistinctCauseCommits;
            if (!crosses) {
                belowThreshold++;
                continue;
            }
            const insight = this.ledger.upsertInsight({
                identityKey: `category:${category}`,
                summary: `${cluster.length} attributed ${category} failures across ${distinctSessions} sessions / ${distinctCauseCommits} cause-commits`,
                recommendation: RECOMMENDATION_BY_CATEGORY[category],
                supportingFailureIds: cluster.map((r) => r.id),
                distinctSessions,
                distinctCauseCommits,
                targetCategory: category,
                baselineRate: cluster.length,
            });
            if (insight)
                discovered.push(insight);
        }
        return {
            insightsDiscovered: discovered,
            clustersConsidered: byCategory.size,
            clustersBelowThreshold: belowThreshold,
        };
    }
}
//# sourceMappingURL=FailureAnalyzer.js.map