// engine/living-spec/annotation-parser.ts — Parse @spec annotations from source code
// Supports: // @spec, # @spec, -- @spec, /* @spec */ (language-agnostic)
// Performance target: <100ms for 10,000-line files
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
const SOURCE_EXTS = '*.{ts,tsx,js,jsx,py,go,rs,java,kt,rb,php,vue,svelte,sql,lua,sh,bash,zsh}';
const IGNORE_DIRS = ['node_modules/**', 'dist/**', 'build/**', '.git/**', 'vendor/**'];
/**
 * Regex that captures @spec annotations in any comment style.
 * Groups: 1=specId, 2=criteria (optional, e.g. ":AC-1,AC-3"), 3=description (optional)
 */
const ANNOTATION_REGEX = /(?:\/\/|#|--|\/\*)\s*@spec\s+(SPEC-\d+)(?::([A-Za-z0-9,-]+))?\s*(?:—\s*(.+?))?\s*(?:\*\/)?$/;
/**
 * Parse a single line for a @spec annotation.
 */
export function parseAnnotationLine(line, file, lineNumber) {
    const match = ANNOTATION_REGEX.exec(line.trim());
    if (!match?.[1]) {
        return null;
    }
    const specId = match[1];
    const criteriaStr = match[2] ?? '';
    const description = match[3]?.trim() ?? '';
    const criteria = parseCriteriaList(criteriaStr);
    return {
        specId,
        criteria,
        description,
        file,
        line: lineNumber,
        raw: line.trim(),
    };
}
/**
 * Parse criteria string like "AC-1,AC-3,AC-5" into [1, 3, 5].
 */
export function parseCriteriaList(criteriaStr) {
    if (!criteriaStr) {
        return [];
    }
    const parts = criteriaStr.split(',');
    const result = [];
    for (const part of parts) {
        const cleaned = part.trim().replace(/^AC-/i, '');
        const num = Number.parseInt(cleaned, 10);
        if (!Number.isNaN(num) && num > 0) {
            result.push(num);
        }
    }
    return result;
}
/**
 * Parse all @spec annotations from a file's content.
 */
export function parseAnnotationsFromContent(content, file) {
    const annotations = [];
    const lines = content.split('\n');
    for (let i = 0; i < lines.length; i++) {
        const line = lines[i];
        if (line === undefined) {
            continue;
        }
        const annotation = parseAnnotationLine(line, file, i + 1);
        if (annotation) {
            annotations.push(annotation);
        }
    }
    return annotations;
}
/**
 * Parse all @spec annotations from a project directory.
 */
export async function parseAnnotationsFromProject(projectPath, filterSpecId) {
    const annotations = [];
    let fileCount = 0;
    let annotatedFileCount = 0;
    const files = await glob(`**/${SOURCE_EXTS}`, {
        cwd: projectPath,
        nodir: true,
        ignore: IGNORE_DIRS,
        maxDepth: 8,
    });
    for (const file of files) {
        fileCount++;
        try {
            const content = await readFile(join(projectPath, file), 'utf-8');
            const fileAnnotations = parseAnnotationsFromContent(content, file);
            if (fileAnnotations.length > 0) {
                annotatedFileCount++;
                if (filterSpecId) {
                    annotations.push(...fileAnnotations.filter((a) => a.specId === filterSpecId));
                }
                else {
                    annotations.push(...fileAnnotations);
                }
            }
        }
        catch {
            // Skip unreadable files
        }
    }
    return { annotations, fileCount, annotatedFileCount };
}
//# sourceMappingURL=annotation-parser.js.map