// engine/living-spec/hash-tracker.ts — File hash tracking for spec drift detection
// Stores SHA-256 hashes of files referenced in spec's technical.md File Plan section.
import { createHash } from 'node:crypto';
import { readFile, stat } from 'node:fs/promises';
import { join } from 'node:path';
/**
 * Compute SHA-256 hash of a file's content.
 */
export async function computeFileHash(filePath) {
    const content = await readFile(filePath, 'utf-8');
    return createHash('sha256').update(content).digest('hex');
}
/**
 * Create a hash entry for a single file.
 */
export async function createFileHashEntry(projectPath, relativePath) {
    const fullPath = join(projectPath, relativePath);
    try {
        const hash = await computeFileHash(fullPath);
        const fileStat = await stat(fullPath);
        return {
            file: relativePath,
            hash,
            size: fileStat.size,
            snapshotAt: new Date().toISOString(),
        };
    }
    catch {
        return null;
    }
}
/**
 * Create hash entries for multiple files.
 */
export async function createFileHashEntries(projectPath, relativePaths) {
    const entries = [];
    for (const relativePath of relativePaths) {
        const entry = await createFileHashEntry(projectPath, relativePath);
        if (entry) {
            entries.push(entry);
        }
    }
    return entries;
}
/**
 * Compare current file hashes against a stored snapshot.
 * Returns a list of changed, added, and removed files.
 */
export async function diffFileHashes(projectPath, previousSnapshot, currentFiles) {
    const diffs = [];
    const previousMap = new Map();
    for (const entry of previousSnapshot.files) {
        previousMap.set(entry.file, entry.hash);
    }
    const currentSet = new Set(currentFiles);
    // Check current files against previous
    for (const file of currentFiles) {
        const previousHash = previousMap.get(file) ?? '';
        try {
            const currentHash = await computeFileHash(join(projectPath, file));
            if (currentHash !== previousHash) {
                diffs.push({
                    file,
                    previousHash,
                    currentHash,
                    linkedCriteria: [],
                });
            }
        }
        catch {
            // File unreadable — treat as changed if it had a previous hash
            if (previousHash) {
                diffs.push({
                    file,
                    previousHash,
                    currentHash: '',
                    linkedCriteria: [],
                });
            }
        }
    }
    // Check for removed files (in previous but not in current)
    for (const [file, hash] of previousMap) {
        if (!currentSet.has(file)) {
            diffs.push({
                file,
                previousHash: hash,
                currentHash: '',
                linkedCriteria: [],
            });
        }
    }
    return diffs;
}
/**
 * Extract file paths from a technical.md File Plan section.
 * Looks for markdown table rows or list items with file path patterns.
 */
export function extractFilePlanPaths(technicalContent) {
    const paths = [];
    const seen = new Set();
    // Match paths in table cells: | src/foo/bar.ts | or | `src/foo/bar.ts` |
    const tablePattern = /\|\s*`?([a-zA-Z][\w/.\\-]+\.\w{1,10})`?\s*\|/g;
    let match;
    while ((match = tablePattern.exec(technicalContent)) !== null) {
        const path = match[1];
        if (path && !seen.has(path)) {
            seen.add(path);
            paths.push(path);
        }
    }
    // Match paths in list items: - src/foo/bar.ts or - `src/foo/bar.ts`
    const listPattern = /[-*]\s+`?([a-zA-Z][\w/.\\-]+\.\w{1,10})`?/g;
    while ((match = listPattern.exec(technicalContent)) !== null) {
        const path = match[1];
        if (path && !seen.has(path)) {
            seen.add(path);
            paths.push(path);
        }
    }
    return paths;
}
//# sourceMappingURL=hash-tracker.js.map