// engine/spec-registry/scorer.ts — Spec completeness scorer for SPEC-128.
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { readSpecTechnicalSection } from '../spec-format/read-technical-section.js';
// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------
async function readFileOrEmpty(path) {
    try {
        return await readFile(path, 'utf-8');
    }
    catch {
        return '';
    }
}
/** Count lines matching `- [ ]` or `- [x]` (acceptance criteria checkboxes). */
function scoreAcceptanceCriteria(specContent) {
    const matches = specContent.match(/^[\s]*- \[[x ]\]/gm);
    const count = matches?.length ?? 0;
    if (count >= 8) {
        return 20;
    }
    if (count >= 4) {
        return 15;
    }
    if (count >= 1) {
        return 10;
    }
    return 0;
}
/** Check for a risk section header and whether it has content. */
function scoreRisks(specContent) {
    const riskHeaderIdx = specContent.search(/^#{1,4}\s+.*(?:riesgo|risk)/im);
    if (riskHeaderIdx === -1) {
        return 0;
    }
    // Grab text after the risk header until the next header or end
    const afterHeader = specContent.slice(riskHeaderIdx);
    const nextHeader = afterHeader.search(/\n#{1,4}\s+/);
    const section = nextHeader === -1 ? afterHeader : afterHeader.slice(0, nextHeader);
    const bodyLines = section
        .split('\n')
        .slice(1)
        .filter((l) => l.trim().length > 0);
    return bodyLines.length > 0 ? 15 : 5;
}
/** Check for testing strategy mentions. */
function scoreTestingStrategy(specContent, technicalContent) {
    const combined = `${specContent}\n${technicalContent}`;
    // Look for a dedicated testing section header
    const hasTestSection = /^#{1,4}\s+.*(?:test|prueba)/im.test(combined);
    if (hasTestSection) {
        return 15;
    }
    // Look for inline test references
    const testMentions = combined.match(/\b(?:test|prueba|jest|vitest|coverage)\b/gi);
    if (testMentions && testMentions.length >= 2) {
        return 8;
    }
    return 0;
}
/** Check for a files/archivos listing. */
function scoreKeyFiles(specContent, technicalContent) {
    const combined = `${specContent}\n${technicalContent}`;
    const filesHeaderIdx = combined.search(/^#{1,4}\s+.*(?:archivos|files)/im);
    if (filesHeaderIdx === -1) {
        return 0;
    }
    const afterHeader = combined.slice(filesHeaderIdx);
    const nextHeader = afterHeader.search(/\n#{1,4}\s+/);
    const section = nextHeader === -1 ? afterHeader : afterHeader.slice(0, nextHeader);
    // Count file-like entries (lines with paths or table rows with file refs)
    const fileEntries = section.match(/[`|]\S+\.\w{1,5}[`|]|^\s*[-|]\s*\S+\.\w{1,5}/gm);
    const count = fileEntries?.length ?? 0;
    if (count >= 3) {
        return 15;
    }
    if (count >= 1) {
        return 8;
    }
    /* v8 ignore next -- files header present but no file-like entries */
    return 0;
}
/** Score inline technical content based on existence and length. */
function scoreTechnicalCompleteness(technicalContent) {
    if (!technicalContent) {
        return 0;
    }
    const lines = technicalContent.split('\n');
    const lineCount = lines.length;
    // Check for metadata table (lines with `|`)
    const hasMetaTable = lines.some((l) => /^\|.*\|.*\|/.test(l));
    if (hasMetaTable && lineCount > 50) {
        return 20;
    }
    if (lineCount > 50) {
        return 15;
    }
    if (lineCount > 10) {
        return 10;
    }
    return 5;
}
/** Parse YAML frontmatter and check for required fields. */
function scoreFrontmatter(specContent) {
    const fmMatch = /^---\r?\n([\s\S]*?)\r?\n---/.exec(specContent);
    if (!fmMatch) {
        return 0;
    }
    const fmBlock = fmMatch[1] ?? '';
    const requiredFields = ['id', 'title', 'type', 'status', 'priority', 'difficulty'];
    const presentCount = requiredFields.filter((field) => new RegExp(`^${field}\\s*:`, 'm').test(fmBlock)).length;
    if (presentCount >= 6) {
        return 15;
    }
    if (presentCount >= 4) {
        return 10;
    }
    if (presentCount >= 2) {
        return 5;
    }
    return 0;
}
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
/**
 * Calculate a completeness score (0-100) for a spec directory.
 * Reads canonical spec.md, evaluates 6 quality categories.
 */
export async function calculateCompletenessScore(specDir) {
    const specPath = join(specDir, 'spec.md');
    const [specContent, technicalContent] = await Promise.all([
        readFileOrEmpty(specPath),
        readSpecTechnicalSection({ specPath }),
    ]);
    const breakdown = {
        acceptanceCriteria: scoreAcceptanceCriteria(specContent),
        risksDocumented: scoreRisks(specContent),
        testingStrategy: scoreTestingStrategy(specContent, technicalContent),
        keyFiles: scoreKeyFiles(specContent, technicalContent),
        technicalCompleteness: scoreTechnicalCompleteness(technicalContent),
        frontmatterFields: scoreFrontmatter(specContent),
    };
    const score = breakdown.acceptanceCriteria +
        breakdown.risksDocumented +
        breakdown.testingStrategy +
        breakdown.keyFiles +
        breakdown.technicalCompleteness +
        breakdown.frontmatterFields;
    return { score, breakdown };
}
//# sourceMappingURL=scorer.js.map