/**
 * Diagnostic layer — runs stub-completeness (and future observe-only
 * verifiers) on the realized output and writes a `realize-quality.json`
 * sidecar. Runs independently of the feedback runner.
 *
 * Why this exists (Phase 1 of 2026-05-13-VERIFIER-DIAGNOSTIC-TREATMENT-SPLIT):
 *   The realize pipeline used to print `✅ All code generated` and the
 *   user would only discover stub bodies at runtime. The post-emit-verify
 *   framework can detect stubs but only fires opt-in via
 *   `SPECVERSE_REALIZE_POST_VERIFY_ENABLE=stub-completeness`, and even
 *   then the findings are buried in pass logs. This module promotes the
 *   diagnostic to a first-class artifact: a sidecar JSON + a CLI summary
 *   line at end of realize.
 *
 * The diagnostic layer:
 *   - Runs ALWAYS when there are emitted `.ai.ts` files
 *   - Calls stub-completeness directly (bypasses the manifest's
 *     enabledByDefault gate — diagnostic should run regardless of whether
 *     the verifier is wired into the auto-fix loop)
 *   - Writes `<outputDir>/realize-quality.json`
 *   - Returns a summary suitable for CLI display
 *
 * Costs: regex-only scan of `.ai.ts` files. Milliseconds. No LLM calls.
 * Disabled via `SPECVERSE_REALIZE_NO_DIAGNOSTICS=1` for users who really
 * don't want the sidecar.
 */
import { existsSync, readFileSync, writeFileSync } from 'fs';
import { join } from 'path';
import { STUB_COMPLETENESS_VERIFIER } from './verifiers/stub-completeness.js';
import { defaultMapFileToOwner } from './feedback-runner.js';
/**
 * Run the diagnostic verifiers on the realized output, write the
 * sidecar, return a summary for the CLI.
 *
 * Returns null when there's nothing to report (e.g. no `.ai.ts` files
 * at all — typical for backend-only validate-only specs). Caller
 * suppresses CLI output in that case.
 */
export async function runDiagnostics(ctx) {
    if (!STUB_COMPLETENESS_VERIFIER.applies(ctx))
        return null;
    const stubResult = await STUB_COMPLETENESS_VERIFIER.verify(ctx);
    const byOwner = {};
    const stubsByCode = {};
    for (const e of stubResult.errors) {
        const ownerName = defaultMapFileToOwner(e.file) ?? '__unmapped__';
        if (!byOwner[ownerName]) {
            byOwner[ownerName] = { ownerName, stubs: [] };
        }
        byOwner[ownerName].stubs.push({
            code: e.code,
            file: e.file,
            line: e.line,
            col: e.col,
            message: e.message,
        });
        stubsByCode[e.code] = (stubsByCode[e.code] ?? 0) + 1;
    }
    const sidecar = {
        schemaVersion: '1.0',
        ranAt: new Date().toISOString(),
        ctx: {
            outputDir: ctx.outputDir,
            ...(ctx.targetLanguage ? { targetLanguage: ctx.targetLanguage } : {}),
            ...(ctx.subpath ? { subpath: ctx.subpath } : {}),
        },
        totals: {
            stubs: stubResult.errors.length,
            stubsByCode,
            ownersWithStubs: Object.keys(byOwner).length,
        },
        byOwner,
        notes: stubResult.notes,
    };
    const sidecarPath = join(ctx.outputDir, 'realize-quality.json');
    try {
        writeFileSync(sidecarPath, JSON.stringify(sidecar, null, 2));
    }
    catch {
        return null;
    }
    return {
        totalStubs: sidecar.totals.stubs,
        ownersWithStubs: sidecar.totals.ownersWithStubs,
        sidecarPath,
    };
}
/**
 * Format the one-line CLI summary for `spv realize` output.
 *
 *   ⚠ realize-quality: 5 stubs across 3 owners (see realize-quality.json)
 *
 * Returns null when there's nothing to flag — caller should not print
 * anything in that case (no false-positive noise on clean realizes).
 */
export function formatSummaryLine(summary) {
    if (!summary || summary.totalStubs === 0)
        return null;
    const ownerWord = summary.ownersWithStubs === 1 ? 'owner' : 'owners';
    const stubWord = summary.totalStubs === 1 ? 'stub' : 'stubs';
    return (`   ⚠ realize-quality: ${summary.totalStubs} ${stubWord} across ` +
        `${summary.ownersWithStubs} ${ownerWord} (see realize-quality.json)`);
}
/**
 * Read the existing `realize-quality.json` sidecar, attach the
 * feedback runner's `specGaps`, and rewrite. Idempotent — safe to call
 * multiple times. Skips silently when the sidecar doesn't exist
 * (diagnostics never ran or wrote it).
 *
 * Phase 2 of 2026-05-13-VERIFIER-DIAGNOSTIC-TREATMENT-SPLIT: the
 * feedback runner returns specGaps; realize's index.ts calls this to
 * merge them into the sidecar so users have one place to inspect
 * realize quality.
 */
export function appendSpecGaps(outputDir, specGaps) {
    if (specGaps.length === 0)
        return;
    const path = join(outputDir, 'realize-quality.json');
    if (!existsSync(path))
        return;
    try {
        const raw = readFileSync(path, 'utf8');
        const sidecar = JSON.parse(raw);
        sidecar.specGaps = specGaps;
        sidecar.totals.specGaps = specGaps.length;
        writeFileSync(path, JSON.stringify(sidecar, null, 2));
    }
    catch {
        // best-effort sidecar — don't crash realize on a parse failure
    }
}
/**
 * Format the spec-gap one-liner for `spv realize` output. Distinct from
 * the stub summary because spec gaps indicate the user needs to act
 * (adjust spec/manifest/provider), not just inspect.
 *
 *   ⚠ realize-quality: 3 spec gap(s) surfaced; review realize-quality.json::specGaps
 *
 * Returns null when there are no gaps.
 */
export function formatSpecGapLine(specGapCount) {
    if (specGapCount === 0)
        return null;
    const word = specGapCount === 1 ? 'spec gap' : 'spec gaps';
    return (`   ⚠ realize-quality: ${specGapCount} ${word} surfaced; ` +
        `review realize-quality.json::specGaps`);
}
//# sourceMappingURL=diagnostics.js.map