// engine/hooks/handlers/on-save.ts — on-save hook handler (SPEC-129)
// Validates @spec annotations, computes SHA-256 hash, updates drift score.
import { createHash } from 'node:crypto';
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
// ---------------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------------
const SPEC_ANNOTATION_REGEX = /@spec\s+(SPEC-\d+)/;
const DRIFT_THRESHOLD = 0.3;
// ---------------------------------------------------------------------------
// Hash snapshots (in-memory store, keyed by file path)
// ---------------------------------------------------------------------------
const hashSnapshots = new Map();
/**
 * Compute SHA-256 hash of file content.
 */
function computeHash(content) {
    return createHash('sha256').update(content, 'utf-8').digest('hex');
}
/**
 * Extract @spec annotations from file content.
 */
export function extractSpecAnnotations(content) {
    const matches = [];
    const regex = new RegExp(SPEC_ANNOTATION_REGEX.source, 'g');
    let match = regex.exec(content);
    while (match !== null) {
        if (match[1]) {
            matches.push(match[1]);
        }
        match = regex.exec(content);
    }
    return matches;
}
/**
 * Validate that spec IDs from annotations exist in the known specs set.
 */
function validateAnnotations(specIds, knownSpecs) {
    if (specIds.length === 0) {
        return true;
    }
    return specIds.every((id) => knownSpecs.has(id));
}
// ---------------------------------------------------------------------------
// Handler
// ---------------------------------------------------------------------------
/**
 * Handle on-save event for a file.
 * - Validates @spec annotations
 * - Computes SHA-256 hash and compares with stored snapshot
 * - Reports drift delta
 */
export async function handleOnSave(relPath, ctx) {
    const fullPath = join(ctx.projectRoot, relPath);
    let content;
    try {
        content = await readFile(fullPath, 'utf-8');
    }
    catch {
        return {
            hook: 'on-save',
            specId: null,
            driftDelta: 0,
            hashChanged: false,
            annotationsValid: true,
        };
    }
    // Extract and validate annotations
    const specIds = extractSpecAnnotations(content);
    const annotationsValid = validateAnnotations(specIds, ctx.knownSpecs);
    const specId = (specIds.length > 0 ? specIds[0] : null) ?? null;
    // Compute hash and compare
    const newHash = computeHash(content);
    const previousHash = hashSnapshots.get(relPath);
    const hashChanged = previousHash !== undefined && previousHash !== newHash;
    // Update snapshot
    hashSnapshots.set(relPath, newHash);
    // Compute drift delta (simple: 0.1 if changed, 0 if not)
    // In a real implementation this would compare with spec baseline
    const driftDelta = hashChanged ? 0.1 : 0;
    // Warn if drift exceeds threshold
    if (driftDelta > DRIFT_THRESHOLD) {
        console.warn(`[hooks] Drift warning: ${relPath} drift=${String(driftDelta)} exceeds threshold=${String(DRIFT_THRESHOLD)}`);
    }
    return {
        hook: 'on-save',
        specId,
        driftDelta,
        hashChanged,
        annotationsValid,
    };
}
/**
 * Get the stored hash for a file path.
 */
export function getStoredHash(relPath) {
    return hashSnapshots.get(relPath);
}
/**
 * Set a hash snapshot (useful for initialization / testing).
 */
export function setStoredHash(relPath, hash) {
    hashSnapshots.set(relPath, hash);
}
/**
 * Remove a hash snapshot.
 */
export function removeStoredHash(relPath) {
    return hashSnapshots.delete(relPath);
}
/**
 * Clear all stored hashes.
 */
export function clearStoredHashes() {
    hashSnapshots.clear();
}
//# sourceMappingURL=on-save.js.map