// engine/readiness-checker.ts — Spec readiness evaluation for implementation gate (SPEC-039)
import { readFile } from 'node:fs/promises';
import { stripFrontmatter } from './frontmatter-parser.js';
// ── Constants ────────────────────────────────────────────────────────────────
const VAGUE_WORDS = ['fast', 'easy', 'good', 'nice', 'quick', 'better', 'simple', 'clean'];
const STRICT_BLOCKER_THRESHOLD = 50;
const STRICT_CAUTION_THRESHOLD = 70;
const LENIENT_BLOCKER_THRESHOLD = 40;
const LENIENT_CAUTION_THRESHOLD = 60;
// ── Helpers ──────────────────────────────────────────────────────────────────
function scoreHuCompleteness(spec) {
    const blockers = [];
    const warnings = [];
    let points = 0;
    // title is required but may be empty
    if (spec.title.trim().length > 0) {
        points += 5;
    }
    else {
        blockers.push('Spec is missing a title');
    }
    // scope, target, type are non-optional on Spec — always award points
    points += 5; // scope
    points += 5; // target
    points += 5; // type
    // Only add a metadata warning if something major is obviously default/empty
    if (spec.scope === 'feature' && spec.target === 'backend' && spec.type === 'feature') {
        warnings.push('Spec metadata uses all default values — verify scope, target and type are intentional');
    }
    return { points, blockers, warnings };
}
async function readHuContent(spec) {
    if (!spec.specPath) {
        return '';
    }
    try {
        const raw = await readFile(spec.specPath, 'utf-8');
        return stripFrontmatter(raw);
    }
    catch {
        return '';
    }
}
async function readFichaContent(spec) {
    if (!spec.technicalPath) {
        return '';
    }
    try {
        const raw = await readFile(spec.technicalPath, 'utf-8');
        return stripFrontmatter(raw);
    }
    catch {
        return '';
    }
}
function extractCriteriaLines(huContent) {
    return huContent
        .split('\n')
        .filter((line) => /^- \[[ x]\]/.test(line.trim()))
        .map((line) => line.trim());
}
function scoreCriteria(criteriaLines) {
    const blockers = [];
    const warnings = [];
    const count = criteriaLines.length;
    let points;
    if (count >= 5) {
        points = 30;
    }
    else if (count >= 3) {
        points = 20;
    }
    else if (count >= 1) {
        points = 10;
    }
    else {
        blockers.push('No acceptance criteria found — add at least 3 testable criteria');
        return { points: 0, blockers, warnings };
    }
    // Vague criteria detection
    for (const line of criteriaLines) {
        const lower = line.toLowerCase();
        const vagueFound = VAGUE_WORDS.filter((word) => {
            const pattern = new RegExp(`\\b${word}\\b`);
            return pattern.test(lower);
        });
        if (vagueFound.length > 0) {
            warnings.push(`Vague criterion detected (words: ${vagueFound.join(', ')}): "${line.slice(0, 80)}"`);
        }
    }
    return { points, blockers, warnings };
}
function scoreFilesIdentified(spec, fichaContent) {
    const blockers = [];
    const warnings = [];
    const hasFicha = fichaContent.trim().length > 0;
    const hasTagsWithFiles = spec.tags.some((tag) => tag.includes('src/') || tag.includes('.ts') || tag.includes('.js'));
    let points;
    if (hasFicha) {
        points = 20;
    }
    else if (hasTagsWithFiles) {
        points = 10;
    }
    else {
        points = 0;
        warnings.push('No FICHA-TECNICA.md found — files to create/modify are not identified. Run create_spec_tech first.');
    }
    return { points, blockers, warnings };
}
function scoreDependencies(spec) {
    const blockers = [];
    const warnings = [];
    if (spec.dependencies.length === 0) {
        return { points: 30, blockers, warnings };
    }
    warnings.push(`Spec has ${spec.dependencies.length} dependencies (${spec.dependencies.join(', ')}). Verify they are implemented before starting.`);
    return { points: 20, blockers, warnings };
}
function buildRecommendations(score, huPoints, criteriaPoints, filesPoints, depsPoints) {
    const recs = [];
    if (score >= 70) {
        return recs;
    }
    if (huPoints < 20) {
        recs.push('Complete the spec metadata: title, scope, target, type');
    }
    if (criteriaPoints < 30) {
        recs.push('Add more acceptance criteria (aim for 5+ testable criteria)');
    }
    if (filesPoints < 20) {
        recs.push('Generate FICHA-TECNICA.md with create_spec_tech to identify key files');
    }
    if (depsPoints < 30) {
        recs.push('Verify all spec dependencies are in "done" status');
    }
    if (criteriaPoints > 0 && criteriaPoints < 30) {
        recs.push('Review criteria for vague language — each criterion should be independently testable');
    }
    return recs;
}
// ── Public API ───────────────────────────────────────────────────────────────
export async function checkSpecReadiness(spec, mode) {
    const huContent = await readHuContent(spec);
    const fichaContent = await readFichaContent(spec);
    const criteriaLines = extractCriteriaLines(huContent);
    const hu = scoreHuCompleteness(spec);
    const criteria = scoreCriteria(criteriaLines);
    const files = scoreFilesIdentified(spec, fichaContent);
    const deps = scoreDependencies(spec);
    const totalScore = hu.points + criteria.points + files.points + deps.points;
    const allBlockers = [...hu.blockers, ...criteria.blockers, ...files.blockers, ...deps.blockers];
    const allWarnings = [...hu.warnings, ...criteria.warnings, ...files.warnings, ...deps.warnings];
    const breakdown = {
        hu: hu.points,
        criteria: criteria.points,
        files: files.points,
        dependencies: deps.points,
    };
    const blockerThreshold = mode === 'strict' ? STRICT_BLOCKER_THRESHOLD : LENIENT_BLOCKER_THRESHOLD;
    const cautionThreshold = mode === 'strict' ? STRICT_CAUTION_THRESHOLD : LENIENT_CAUTION_THRESHOLD;
    if (totalScore < blockerThreshold) {
        allBlockers.push(`ReadinessScore ${totalScore} is below ${blockerThreshold} — spec is not ready for implementation`);
    }
    else if (totalScore < cautionThreshold) {
        allWarnings.push(`ReadinessScore ${totalScore} is below ${cautionThreshold} — proceed with caution`);
    }
    const readinessScore = {
        score: totalScore,
        breakdown,
        blockers: allBlockers,
        warnings: allWarnings,
    };
    const isReady = totalScore >= cautionThreshold && allBlockers.length === 0;
    const recommendations = buildRecommendations(totalScore, hu.points, criteria.points, files.points, deps.points);
    return {
        specId: spec.id,
        score: totalScore,
        ready: isReady,
        issues: readinessScore,
        recommendations,
        mode,
    };
}
//# sourceMappingURL=readiness-checker.js.map