// engine/verifier/compliance-scorer.ts — Spec compliance scoring (SPEC-406)
import { readFile } from 'node:fs/promises';
import { glob } from 'glob';
import { matchCriterion } from './criterion-matcher.js';
/**
 * Determines compliance level based on score.
 */
function toLevel(score) {
    if (score >= 80) {
        return 'compliant';
    }
    if (score >= 60) {
        return 'partial';
    }
    return 'non-compliant';
}
/**
 * Parses acceptance criteria lines from spec.md content.
 * Recognizes both "- [ ]" (unchecked) and "- [x]" (checked) task list items.
 */
export function parseCriteria(content) {
    const lines = content.split('\n');
    const criteria = [];
    for (const line of lines) {
        const trimmed = line.trim();
        // Match "- [ ] ..." or "- [x] ..." or "- [X] ..."
        if (/^-\s*\[[xX ]\]\s+/.test(trimmed)) {
            const text = trimmed.replace(/^-\s*\[[xX ]\]\s+/, '').trim();
            if (text.length > 0) {
                criteria.push(text);
            }
        }
    }
    return criteria;
}
/**
 * Locates the spec.md file for a given specId within the projectPath.
 * Searches in common locations: planu/specs/SPEC-NNN-slug or specs/SPEC-NNN-slug.
 */
async function findSpecFile(specId, projectPath) {
    const patterns = [
        `planu/specs/${specId}-*/spec.md`,
        `specs/${specId}-*/spec.md`,
        `planu/specs/${specId}/spec.md`,
        `specs/${specId}/spec.md`,
    ];
    for (const pattern of patterns) {
        const matches = await glob(pattern, { cwd: projectPath, absolute: true });
        if (matches.length > 0 && matches[0] !== undefined) {
            return matches[0];
        }
    }
    return null;
}
/**
 * Computes a compliance score for a spec by matching each acceptance criterion
 * against the codebase.
 *
 * Scoring formula:
 *   - verified criterion = 100/N points
 *   - partial criterion = 50/N points
 *   - unverified criterion = 0 points
 */
export async function scoreCompliance(specId, projectPath) {
    const checkedAt = new Date().toISOString();
    // Locate spec.md
    const specFile = await findSpecFile(specId, projectPath);
    if (specFile === null) {
        return {
            specId,
            score: 0,
            level: 'non-compliant',
            verifiedCriteria: 0,
            partialCriteria: 0,
            unverifiedCriteria: 0,
            totalCriteria: 0,
            criteriaDetails: [],
            checkedAt,
        };
    }
    // Read and parse acceptance criteria
    const content = await readFile(specFile, 'utf-8');
    const criteria = parseCriteria(content);
    if (criteria.length === 0) {
        return {
            specId,
            score: 0,
            level: 'non-compliant',
            verifiedCriteria: 0,
            partialCriteria: 0,
            unverifiedCriteria: 0,
            totalCriteria: 0,
            criteriaDetails: [],
            checkedAt,
        };
    }
    // Match each criterion against the codebase
    const criteriaDetails = await Promise.all(criteria.map((criterion) => matchCriterion(criterion, projectPath)));
    // Compute score
    const N = criteria.length;
    let rawScore = 0;
    let verifiedCriteria = 0;
    let partialCriteria = 0;
    let unverifiedCriteria = 0;
    for (const detail of criteriaDetails) {
        if (detail.status === 'verified') {
            rawScore += 100 / N;
            verifiedCriteria++;
        }
        else if (detail.status === 'partial') {
            rawScore += 50 / N;
            partialCriteria++;
        }
        else {
            unverifiedCriteria++;
        }
    }
    const score = Math.round(rawScore * 10) / 10;
    return {
        specId,
        score,
        level: toLevel(score),
        verifiedCriteria,
        partialCriteria,
        unverifiedCriteria,
        totalCriteria: N,
        criteriaDetails,
        checkedAt,
    };
}
//# sourceMappingURL=compliance-scorer.js.map