const TRAILER_FEATURE = /^\s*Fixes-Feature:\s*(\S+)\s*$/im;
const TRAILER_FAIL = /^\s*Fixes:\s*(FAIL-\S+)\s*$/im;
export class FailureAttributionEngine {
    deps;
    constructor(deps) {
        this.deps = deps;
    }
    /** Parse the bugfix-commit trailers from a commit message. */
    static parseTrailers(commitMessage) {
        const out = {};
        const f = commitMessage.match(TRAILER_FEATURE);
        if (f)
            out.fixesFeature = f[1].trim();
        const x = commitMessage.match(TRAILER_FAIL);
        if (x)
            out.fixesFailId = x[1].trim();
        return out;
    }
    /** Normalize a path for overlap comparison (strip leading ./, lowercase drive-irrelevant). */
    static norm(p) {
        return p.replace(/^\.\//, '').trim();
    }
    /**
     * Attribute a bugfix commit. The trailer is a HINT that must be reconciled
     * against reality: the cited initiative must exist AND the fix commit's
     * touched files must intersect its coveredFiles. Verified overlap → automatic;
     * trailer-without-overlap (or missing initiative) → inferred (needs attribution).
     */
    attributeBugfixCommit(input) {
        const trailers = FailureAttributionEngine.parseTrailers(input.commitMessage);
        if (!trailers.fixesFeature) {
            // Trailer omission is a measured coverage gap, not a silent drop (§4.2 #A).
            return {
                attribution: 'inferred',
                attributionConfidence: 0,
                causeCommitOid: input.commitOid,
                noFeatureLink: true,
                note: 'no Fixes-Feature trailer — counted in the no-feature-link coverage bucket',
            };
        }
        const initiative = this.deps.getInitiative(trailers.fixesFeature);
        if (!initiative) {
            return {
                attribution: 'inferred',
                attributionConfidence: 0.1,
                causeCommitOid: input.commitOid,
                note: `trailer cites unknown initiative "${trailers.fixesFeature}" — needs attribution`,
            };
        }
        const touched = new Set(this.deps.commitTouchedFiles(input.commitOid).map(FailureAttributionEngine.norm));
        const covered = (initiative.coveredFiles ?? []).map(FailureAttributionEngine.norm);
        const overlap = covered.some((f) => touched.has(f));
        if (!overlap) {
            // Forged/mistaken trailer: the fix doesn't touch the feature's code (§4.2 M7).
            return {
                attribution: 'inferred',
                attributionConfidence: 0.2,
                initiativeId: initiative.id,
                projectId: initiative.parentProjectId,
                specPath: initiative.specPath,
                causeCommitOid: input.commitOid,
                note: `trailer cites "${initiative.id}" but fix touches none of its coveredFiles — needs attribution (possible mis-blame)`,
            };
        }
        return {
            attribution: 'automatic',
            attributionConfidence: 0.9,
            initiativeId: initiative.id,
            projectId: initiative.parentProjectId,
            specPath: initiative.specPath,
            causeCommitOid: input.commitOid,
            note: `trailer cross-checked: fix overlaps ${initiative.id} coveredFiles`,
        };
    }
    /**
     * Validate an agent-diagnosed one-tap filing. The cited initiative must
     * exist. A caller-supplied causeCommitOid NEVER upgrades to automatic (B6);
     * the verdict stays one-tap.
     */
    validateAgentDiagnosed(input) {
        const initiative = this.deps.getInitiative(input.initiativeId);
        if (!initiative) {
            return { ok: false, reason: `initiative "${input.initiativeId}" does not exist` };
        }
        return {
            ok: true,
            verdict: {
                attribution: 'one-tap',
                attributionConfidence: 0.5,
                initiativeId: initiative.id,
                projectId: initiative.parentProjectId,
                specPath: initiative.specPath,
                causeCommitOid: input.causeCommitOid, // recorded but does NOT raise confidence/attribution
                note: 'agent-diagnosed one-tap — excluded from toolchain-blame aggregates',
            },
        };
    }
    /** Pick a category enum value defensively — never trust free text (§4.4). */
    static coerceCategory(value) {
        const allowed = [
            'concurrency', 'config-parse', 'wiring', 'logic', 'migration', 'test-gap',
            'build-failure', 'test-failure', 'regression', 'unknown',
        ];
        return allowed.includes(value) ? value : 'unknown';
    }
}
//# sourceMappingURL=FailureAttributionEngine.js.map