// engine/spec-audit/index.ts — SPEC-744
// Orchestrates the two-tier spec drift audit.
import { join } from 'node:path';
import { listSpecs } from '../../storage/spec-store.js';
import { hashProjectPath, projectDataDir } from '../../storage/base-store.js';
import { buildSpecGraph } from '../spec-dependency-graph/graph-builder.js';
import { getSupersededSet, getSupersededMap } from '../spec-dependency-graph/superseded-set.js';
import { scanSpecTier1 } from './tier1-scanner.js';
import { classifyAmbiguous, createBudgetTracker } from './tier2-classifier.js';
import { writeAuditReport, appendDriftReviewToPending } from './report-writer.js';
// ---------------------------------------------------------------------------
// Public API
// ---------------------------------------------------------------------------
export async function auditSpecsDrift(opts) {
    const { projectRoot, budget = 20, model = 'haiku' } = opts;
    const projectId = hashProjectPath(projectRoot);
    // Load all specs
    const allSpecs = await listSpecs(projectId);
    // Build dependency graph to identify superseded specs (SPEC-746)
    const graph = buildSpecGraph(allSpecs);
    const supersededSet = getSupersededSet(graph);
    const supersededMap = getSupersededMap(graph);
    // Filter to done specs only, excluding superseded ones
    const doneSpecs = allSpecs.filter((s) => s.status === 'done' && !supersededSet.has(s.id));
    // Build superseded list for reporting
    const skippedSuperseded = [];
    for (const [superseded, superseder] of supersededMap.entries()) {
        skippedSuperseded.push(`${superseded} → ${superseder}`);
    }
    // Initialize budget tracker
    const tracker = createBudgetTracker(budget);
    // Run tier-1 scan on each done spec
    const findings = [];
    let scanned = 0;
    for (const spec of doneSpecs) {
        if (!spec.specPath) {
            continue;
        }
        scanned++;
        const result = scanSpecTier1(spec.id, spec.specPath, projectRoot);
        findings.push(...result.findings);
        // Run tier-2 on ambiguous cases (budget-gated)
        for (const amb of result.ambiguous) {
            if (tracker.exhausted) {
                // Report as P1 needs human triage
                findings.push({
                    specId: amb.specId,
                    path: amb.path,
                    priority: 'P1',
                    reason: `Budget exhausted — symbol "${amb.symbol}" needs human triage`,
                    evidence: `Possible candidates: ${amb.candidates.join(', ')}`,
                    tier: 1,
                });
                continue;
            }
            const t2Result = await classifyAmbiguous(amb, tracker, model);
            if (t2Result?.finding) {
                findings.push(t2Result.finding);
            }
        }
    }
    // Generate report path
    const ts = new Date().toISOString().replace(/[:.]/g, '').slice(0, 15);
    const reportPath = join(projectDataDir(projectId), 'research', `audit-full-${ts}.md`);
    const report = {
        generatedAt: new Date().toISOString(),
        totalSpecs: allSpecs.length,
        scanned,
        skippedSuperseded,
        findings,
        budgetUsedUsd: tracker.usedUsd,
        budgetRemainingUsd: Math.max(0, budget - tracker.usedUsd),
        reportPath,
    };
    // Write report and pending.json entry
    await writeAuditReport(report, projectRoot);
    await appendDriftReviewToPending({
        kind: 'drift_review',
        reportPath,
        counts: {
            p0: findings.filter((f) => f.priority === 'P0').length,
            p1: findings.filter((f) => f.priority === 'P1').length,
            p2: findings.filter((f) => f.priority === 'P2').length,
        },
        ts: report.generatedAt,
    }, projectRoot);
    return report;
}
//# sourceMappingURL=index.js.map