/**
 * Per-owner LLM emit hook — Phase 1 of the 2026-05-11-PER-OWNER-VS-PER-ACTION
 * proposal.
 *
 * Sibling of `per-action-llm-emit.ts`. SAME structural-validator and
 * parse-check infrastructure, DIFFERENT granularity: one LLM call per
 * controller / service instead of one per action. Restores the
 * class-level context (constructor injection, shared helpers, sibling
 * methods) that per-action emission engineered out and that surfaced
 * empirically as the 31% LLM-decline rate on idle-meta sonnet (the
 * LLM correctly refused to invent imports it couldn't see were
 * available).
 *
 * Three quality gates run on the emitted file:
 *   1. Markdown extraction — pull the ```typescript fenced block out
 *      of the LLM response. Empty / missing block → null (caller
 *      falls through to per-action emit OR engine γ-stub).
 *   2. Structural preservation (DR3) — every pre-baked snippet from
 *      every action's per-step matches must appear verbatim somewhere
 *      in the emitted file. A rewritten snippet is a loud failure;
 *      we throw with a diff.
 *   3. esbuild parse check — the file must be syntactically valid TS.
 *      Surgical retry once on parse failure; if it fails again we
 *      return null (caller falls through).
 *
 * The realize-owner.prompt.yaml asset ships at:
 * `@specverse/assets/prompts/core/standard/default/realize-owner.prompt.yaml`.
 * Until assets is republished with it, a filesystem fallback under
 * `specverse-self/assets/` is searched.
 */
import { readFileSync, existsSync } from 'fs';
import { dirname, join, resolve } from 'path';
import { fileURLToPath } from 'url';
import { createRequire } from 'module';
import yaml from 'js-yaml';
import { loadPrompt, promptTag, runPrompt as runPromptFn, assembleSystem, assembleUser, } from '../ai/prompt-runner.js';
import { validateLlmOutputPreservesConventions, DEFAULT_STRUCTURAL_DROP_THRESHOLD, } from './structural-validator.js';
import { extractTypescriptBlock, } from './owner-emit-shared.js';
import { rulesApplicableTo, forbiddenApisForRuntime, renderConstraintsForPrompt, } from './realize-rules.js';
const require = createRequire(import.meta.url);
/**
 * Build a {@link PerOwnerEmitFn} for use in per-owner realize emission.
 *
 * Returned function:
 *   1. Builds the prompt input from the per-owner request.
 *   2. Calls runPrompt({ operation: 'realize-owner', ... }).
 *   3. Extracts the ```typescript block from the response.
 *   4. Runs the structural validator (DR3); throws on rewrite.
 *   5. Runs an esbuild parse check; surgical-retries once on failure.
 *   6. Returns the file contents string, or null when all attempts
 *      fail (caller falls through).
 */
export function createRealizeOwnerLlmEmit(opts = {}) {
    const runPrompt = opts.runPromptOverride ?? runPromptFn;
    const loadPromptResolved = opts.loadPromptOverride ?? loadRealizeOwnerPrompt;
    const parseCheck = opts.parseCheckOverride ?? esbuildFileParseCheck;
    return async (request) => {
        // Telemetry state (always-on; gated only by opts.telemetry being
        // passed). Tracks the journey through retries so downstream can
        // answer "did Lever 1 fire" / "did surgical retry recover" without
        // log parsing. See PerOwnerEmitTelemetry doc for full schema.
        const telemetryStart = Date.now();
        let attemptsMade = 0;
        let structuralRetryFired = false;
        let parseRetryFired = false;
        let ruleRetryFired = false;
        let structuralMissingSteps;
        const recordTelemetry = (outcome) => {
            if (!opts.telemetry)
                return;
            opts.telemetry.record({
                ownerName: request.ownerName,
                attempts: attemptsMade,
                outcome,
                structuralRetryFired,
                parseRetryFired,
                ruleRetryFired,
                ...(structuralMissingSteps !== undefined ? { structuralMissingSteps } : {}),
                durationMs: Date.now() - telemetryStart,
            });
        };
        // 1. Pre-bake snippets across all actions for the structural
        //    validator. Each matched step from any action contributes.
        const prebakedSnippets = [];
        for (const action of request.actions) {
            action.perStepMatches.forEach((match, idx) => {
                if (match?.matched && match.call) {
                    prebakedSnippets.push({
                        stepNum: idx + 1,
                        stepText: action.spec.steps[idx] ?? '',
                        snippet: match.call,
                    });
                }
            });
        }
        // 2. Build prompt values.
        const promptValues = buildPromptValues(request);
        // 3. Surgical retry loop (cap at 1).
        //
        // Three classes of retry-eligible failure share the single retry
        // slot — first-fail-wins, retry prompt is composed from that
        // failure's diagnostic:
        //   - structural-validator drop > threshold (Tier 1 retry — Lever 1
        //     of `2026-05-15-TIER1-EMISSION-QUALITY.md`)
        //   - rules violation (`realize-rules.ts`)
        //   - esbuild parse error
        // Tier 1 retry is on by default; opt out with
        // `SPECVERSE_REALIZE_NO_TIER1_RETRY=1` to restore the old
        // throw-immediately-on-structural-fail behaviour (debug only).
        const maxAttempts = opts.disableSurgicalRetry ? 1 : 2;
        const tier1RetryEnabled = process.env.SPECVERSE_REALIZE_NO_TIER1_RETRY !== '1';
        let lastParseError = null;
        let lastStructuralMissing = null;
        for (let attempt = 0; attempt < maxAttempts; attempt++) {
            attemptsMade++;
            const userPrefix = lastStructuralMissing
                ? buildStructuralRetryPrefix(lastStructuralMissing, request.ownerName)
                : lastParseError
                    ? buildSurgicalRetryPrefix(lastParseError, request.ownerName)
                    : '';
            let runResult;
            try {
                // Per-owner calls produce whole-file emissions (5-15K tokens
                // per call vs 1.5-3K per-action) and routinely run 60-180s
                // for big owners on sonnet. The default 5-minute timeout was
                // sufficient for per-action but cut off large per-owner emits
                // (the 2026-05-11 trial saw GamesV3Controller, 6 actions,
                // time out at exactly 300s). Bump the default to 15min for
                // per-owner; callers can still tighten via opts.timeoutMs.
                const timeoutMs = opts.timeoutMs ?? 900_000;
                const runOpts = {
                    operation: 'realize-owner',
                    values: promptValues,
                    model: opts.model,
                    timeoutMs,
                    userPrefix,
                    // The `realize-emit` skill carries the per-owner emission
                    // CONSTRAINTS / GUIDANCE / EXAMPLE. claude-cli auto-loads it
                    // via filesystem-based skill resolution; other providers
                    // (anthropic, openai-compatible, stub) have no such mechanism
                    // — prompt-runner inlines the skill content into the system
                    // prompt for them. Single source of truth, two delivery paths.
                    skills: ['realize-emit'],
                };
                runResult = await callRunPromptWithFallback(runPrompt, runOpts, loadPromptResolved);
            }
            catch (e) {
                if (process.env.SPECVERSE_VERBOSE) {
                    const cls = (e && e.constructor && e.constructor.name) || 'Error';
                    const msg = (e && e.message) || String(e);
                    const head = String(msg).slice(0, 240).replace(/\s+/g, ' ');
                    console.error(`   ✗ per-owner LLM emit threw for ${request.ownerName} [${cls}]: ${head}`);
                }
                recordTelemetry('threw-from-llm');
                return null;
            }
            // 4. Extract the ```typescript fenced block.
            const fileContents = extractTypescriptBlock(runResult.text);
            if (!fileContents || fileContents.trim().length === 0) {
                if (process.env.SPECVERSE_VERBOSE) {
                    const reason = fileContents === undefined || fileContents === null
                        ? 'no typescript code block in LLM response'
                        : 'typescript block was empty';
                    console.error(`   ✗ per-owner LLM emit empty for ${request.ownerName}: ${reason}`);
                }
                recordTelemetry('empty-block');
                return null;
            }
            // 5. Structural validator — DR3. A MAJORITY of rewritten pre-baked
            //    snippets is a LOUD failure (→ γ-fallback). A minority is
            //    tolerated and surfaced as a warning: the owner's output is
            //    kept and rides the normal tsc + stub-completeness gates.
            //    Threshold tunable via SPECVERSE_REALIZE_STRUCTURAL_DROP_THRESHOLD
            //    (Phase 5 first target — see structural-validator.ts header).
            const dropThresholdRaw = process.env.SPECVERSE_REALIZE_STRUCTURAL_DROP_THRESHOLD;
            const dropThreshold = dropThresholdRaw !== undefined && !Number.isNaN(parseFloat(dropThresholdRaw))
                ? parseFloat(dropThresholdRaw)
                : DEFAULT_STRUCTURAL_DROP_THRESHOLD;
            const structural = validateLlmOutputPreservesConventions(fileContents, prebakedSnippets, dropThreshold);
            if (structural.ok === false) {
                // Tier 1 retry (Lever 1 of 2026-05-15-TIER1-EMISSION-QUALITY).
                // When retries remain AND opt-out env isn't set, capture the
                // dropped snippets, build a retry prompt that surfaces them
                // verbatim, and loop. Wider context wins are preserved (still
                // a per-owner emit, not Tier 2 per-action) but the model gets
                // concrete feedback about what to fix.
                if (tier1RetryEnabled && attempt < maxAttempts - 1) {
                    structuralRetryFired = true;
                    structuralMissingSteps = structural.missing.map((m) => m.stepNum);
                    lastStructuralMissing = structural.missing;
                    // Clear any pending parse-retry — structural wins precedence;
                    // the next prompt will surface the dropped snippets, not the
                    // parse error from a prior attempt.
                    lastParseError = null;
                    if (process.env.SPECVERSE_VERBOSE) {
                        const steps = structural.missing.map((m) => m.stepNum).join(', ');
                        console.error(`   ↻ per-owner LLM emit [${request.ownerName}] structural-fail attempt ${attempt + 1}; ` +
                            `retrying with ${structural.missing.length} dropped snippet(s) (steps: ${steps})`);
                    }
                    continue;
                }
                // Out of retries OR opt-out: throw (caller treats this as Tier 1
                // failure → Tier 2 per-action recovery fall-through).
                // structural-exhausted: Lever 1 retry tried once and the retry
                // also failed. structural-immediate: Lever 1 disabled by env, so
                // the first failure throws without retry.
                // Capture the missing steps from this final failure (when Lever
                // 1 was disabled and this is the first attempt — otherwise the
                // retry-fired branch above already set it).
                if (!structuralMissingSteps) {
                    structuralMissingSteps = structural.missing.map((m) => m.stepNum);
                }
                recordTelemetry(tier1RetryEnabled && structuralRetryFired
                    ? 'structural-exhausted'
                    : 'structural-immediate');
                throw new Error(`[realize-owner] structural-preservation failed for ${request.ownerName}:\n${structural.reason}`);
            }
            // Structural retry recovered (attempt > 0 and we had pending
            // dropped snippets that are no longer flagged).
            if (lastStructuralMissing && process.env.SPECVERSE_VERBOSE) {
                console.error(`   ✓ per-owner LLM emit [${request.ownerName}] structural retry recovered`);
            }
            lastStructuralMissing = null;
            if (structural.warnings && structural.warnings.length > 0 && process.env.SPECVERSE_VERBOSE) {
                const steps = structural.warnings.map((w) => w.stepNum).join(', ');
                console.error(`   ⚠ per-owner LLM emit [${request.ownerName}] tolerated ${structural.warnings.length} ` +
                    `dropped pre-baked snippet(s) under threshold (steps: ${steps})`);
            }
            // 5b. RULES manifest pipeline. Each rule in `realize-rules.ts`
            //     declares its own detect() + correction() + ordering. We
            //     iterate in pipeline order; first violating rule wins (one
            //     correction per surgical-retry pass). On exhausted retries
            //     we return null and the caller emits the γ stub.
            //
            //     Adding a new constraint is one place: append to RULES.
            //     See `2026-05-12-VALIDATOR-MANIFEST-AND-TEST-CORPUS.md`.
            const ruleCtx = {
                ownerName: request.ownerName,
                targetRuntime: request.targetRuntime,
            };
            let ruleViolated = false;
            let ruleOutOfRetries = false;
            for (const rule of rulesApplicableTo(ruleCtx)) {
                const violations = rule.detect(fileContents, ruleCtx);
                if (violations.length === 0)
                    continue;
                if (process.env.SPECVERSE_VERBOSE) {
                    const head = violations.slice(0, 3).join(', ');
                    console.error(`   ✗ per-owner LLM emit [${rule.id}] for ${request.ownerName}: ${head}`);
                }
                if (attempt < maxAttempts - 1) {
                    lastParseError = rule.correction(violations, ruleCtx);
                    ruleViolated = true;
                    ruleRetryFired = true;
                }
                else {
                    ruleOutOfRetries = true;
                }
                break;
            }
            if (ruleOutOfRetries) {
                recordTelemetry('rule-exhausted');
                return null;
            }
            if (ruleViolated)
                continue;
            // 6. esbuild parse check — surgical retry once on parse failure.
            const parseError = await parseCheck(fileContents);
            if (parseError === null) {
                // All gates green — return the file.
                // Outcome priority: structural retry > rule retry > parse retry
                // > success. Only one retry slot exists, so at most one of the
                // *RetryFired flags can be true.
                recordTelemetry(structuralRetryFired
                    ? 'structural-retry-recovered'
                    : ruleRetryFired
                        ? 'rule-retry-recovered'
                        : parseRetryFired
                            ? 'parse-retry-recovered'
                            : 'success');
                return fileContents;
            }
            // Parse error → mark parse-retry as fired (if we have retries left)
            // and queue the parse error for the next attempt's prefix.
            if (attempt < maxAttempts - 1) {
                parseRetryFired = true;
            }
            lastParseError = parseError;
        }
        // Exhausted retries — fall through.
        if (process.env.SPECVERSE_VERBOSE) {
            const head = String(lastParseError ?? 'unknown').slice(0, 240).replace(/\s+/g, ' ');
            console.error(`   ✗ per-owner LLM emit parse-failed after ${maxAttempts} attempt(s) for ${request.ownerName}: ${head}`);
        }
        recordTelemetry('parse-exhausted');
        return null;
    };
}
// ─── Prompt loading + assets fallback ────────────────────────────────
/** Default prompt loader. Tries the installed @specverse/assets path
 *  first; falls back to the in-repo specverse-self/assets/ tree for
 *  dev cycles where assets isn't yet republished. Mirrors the
 *  per-action emitter's fallback strategy.  */
function loadRealizeOwnerPrompt() {
    // Installed path: shipped from @specverse/assets via loadPrompt.
    try {
        return loadPrompt('realize-owner');
    }
    catch (e) {
        // Try filesystem fallbacks below.
    }
    const fallback = loadRealizeOwnerPromptFromFallback();
    if (fallback)
        return fallback;
    throw new Error('realize-owner.prompt.yaml not found in @specverse/assets nor the ' +
        'specverse-self/assets/ filesystem fallback. Has the assets ' +
        'package been republished since 2026-05-11?');
}
/** Filesystem fallback search for the realize-owner prompt. Mirrors
 *  per-action-llm-emit.ts:loadRealizeActionPromptFromFallback's three-
 *  tier search: @specverse/assets package path, engine-bundled assets,
 *  worktree-sibling specverse-self/assets. */
export function loadRealizeOwnerPromptFromFallback() {
    // Phase 1 (2026-05-22): prefer the composed bundle; fall back to the
    // version-less source dir. The stale `v9/` paths are retired.
    const tag = promptTag();
    const candidates = [];
    // 1. @specverse/assets package (covers the post-publish case).
    try {
        const pkg = require.resolve('@specverse/assets/package.json');
        candidates.push(join(dirname(pkg), 'prompts/_composed', tag, 'prompts/realize-owner.prompt.yaml'), join(dirname(pkg), 'prompts/core/standard/default/realize-owner.prompt.yaml'));
    }
    catch {
        /* assets not installed */
    }
    // 2. Engine-bundled assets (engines ≤ 6.0.2-style layouts).
    try {
        const enginesPkg = require.resolve('@specverse/engines/package.json');
        candidates.push(join(dirname(enginesPkg), 'assets/prompts/_composed', tag, 'prompts/realize-owner.prompt.yaml'), join(dirname(enginesPkg), 'assets/prompts/core/standard/default/realize-owner.prompt.yaml'));
    }
    catch {
        /* engines not installed at this path */
    }
    // 3. @specverse/self package (covers the post-publish case where
    //    self has the prompts bundled but @specverse/assets doesn't yet).
    //    Per-owner ships to specverse-self/assets/ first during the trial.
    try {
        const selfPkg = require.resolve('@specverse/self/package.json');
        candidates.push(join(dirname(selfPkg), 'assets/prompts/_composed', tag, 'prompts/realize-owner.prompt.yaml'), join(dirname(selfPkg), 'assets/prompts/core/standard/default/realize-owner.prompt.yaml'));
    }
    catch {
        /* self not installed at this path */
    }
    // 4. Worktree fallback — specverse-self lives next to specverse-engines.
    try {
        const here = dirname(fileURLToPath(import.meta.url));
        candidates.push(resolve(here, `../../../../specverse-self/assets/prompts/_composed/${tag}/prompts/realize-owner.prompt.yaml`), resolve(here, '../../../../specverse-self/assets/prompts/core/standard/default/realize-owner.prompt.yaml'), resolve(here, `../../../../../specverse-self/assets/prompts/_composed/${tag}/prompts/realize-owner.prompt.yaml`), resolve(here, '../../../../../specverse-self/assets/prompts/core/standard/default/realize-owner.prompt.yaml'));
    }
    catch {
        /* import.meta.url unavailable */
    }
    for (const p of candidates) {
        if (existsSync(p)) {
            try {
                return yaml.load(readFileSync(p, 'utf-8'));
            }
            catch {
                /* try next */
            }
        }
    }
    return null;
}
/** Wrap runPrompt with the assets-fallback prompt-loading strategy.
 *  Mirrors per-action-llm-emit's same-named helper: first try
 *  runPrompt's own loadPrompt path; on `prompt file not found`,
 *  fall back to the filesystem-loaded prompt and call ai-sdk
 *  generateText directly so we don't need a `prompt: PromptYaml`
 *  field on RunPromptOptions. */
async function callRunPromptWithFallback(runPromptImpl, opts, fallbackLoader) {
    // First try: let runPrompt do its own loadPrompt.
    try {
        return await runPromptImpl(opts);
    }
    catch (e) {
        const msg = e?.message ?? String(e);
        if (!/prompt file not found for "realize-owner"/i.test(msg)) {
            throw e;
        }
    }
    // Second try: load via fallback + call ai-sdk directly.
    const prompt = fallbackLoader();
    const system = assembleSystem(prompt);
    const baseUser = assembleUser(prompt, opts.values);
    const user = (opts.userPrefix || '') + baseUser;
    if (typeof opts.onAssembled === 'function') {
        opts.onAssembled({ system, user });
    }
    const aiSdk = await import('ai');
    const { resolveModel } = await import('../ai/model-resolver.js');
    const model = opts.model ?? resolveModel({ ...(opts.modelOptions ?? {}), timeout: opts.timeoutMs ?? 300_000 });
    const t0 = Date.now();
    const result = await aiSdk.generateText({
        model,
        system,
        prompt: user,
        abortSignal: AbortSignal.timeout(opts.timeoutMs ?? 300_000),
    });
    const duration_ms = Date.now() - t0;
    return {
        text: result.text,
        tokens: {
            input: result.usage?.inputTokens?.total ?? null,
            output: result.usage?.outputTokens?.total ?? null,
        },
        duration_ms,
        system,
        user,
        providerId: model.provider,
        modelId: model.modelId,
    };
}
// ─── Prompt input builder ────────────────────────────────────────────
/** Build the prompt-values map that the realize-owner.prompt.yaml
 *  user template references. */
function buildPromptValues(request) {
    const ownerSpecYaml = yamlDumpOwner(request.ownerSpec, request.ownerName);
    const perActionScaffolding = buildPerActionScaffolding(request.actions);
    return {
        ownerName: request.ownerName,
        ownerKind: request.ownerKind,
        ownerSpec: ownerSpecYaml,
        perActionScaffolding,
        availableClients: request.availableClients ?? '(none)',
        availableCapabilities: request.availableCapabilities ?? '(none — only `input: any` is available)',
        availableSpecSurface: request.availableSpecSurface ?? '(no other services / controllers in this spec)',
        targetRuntime: request.targetRuntime ?? 'Node.js (backend)',
        forbiddenRuntimeApis: describeForbiddenApis(request.targetRuntime),
        constraints: renderConstraintsForPrompt({
            ownerName: request.ownerName,
            targetRuntime: request.targetRuntime,
        }),
        processContext: request.processContext ?? '(no related processes)',
    };
}
/** Render the owner's spec body as YAML. Top-level key = the owner
 *  name; nested body = the spec object (actions / operations / etc.). */
function yamlDumpOwner(ownerSpec, ownerName) {
    return yaml.dump({ [ownerName]: ownerSpec }, { lineWidth: 100, noRefs: true });
}
/** Build the per-action scaffolding block. One section per action,
 *  listing its steps with [PRE-BAKED] / [WRITE] markers. Exported for
 *  reuse by the realize-context-snapshot module (Phase 3 of
 *  2026-05-13-VERIFIER-DIAGNOSTIC-TREATMENT-SPLIT) — the reemit prompt
 *  embeds the same scaffolding the original per-owner prompt used. */
export function buildPerActionScaffolding(actions) {
    if (actions.length === 0)
        return '(no actions on this owner)';
    const sections = [];
    for (const action of actions) {
        sections.push(`### ${action.actionName}`);
        if (!action.spec.steps?.length) {
            sections.push('  (no steps declared — emit a bare body that fulfils the spec)');
            sections.push('');
            continue;
        }
        for (let i = 0; i < action.spec.steps.length; i++) {
            const stepText = action.spec.steps[i] ?? '';
            const match = action.perStepMatches[i];
            if (match?.matched && match.call) {
                sections.push(`  ${i + 1}. [PRE-BAKED] "${stepText}" →`);
                const snippetLines = match.call.split('\n');
                for (const snippetLine of snippetLines) {
                    sections.push(`       ${snippetLine}`);
                }
            }
            else {
                sections.push(`  ${i + 1}. [WRITE]      "${stepText}"`);
            }
        }
        sections.push('');
    }
    return sections.join('\n');
}
/** File-level esbuild parse check. Distinct from
 *  per-action-llm-emit's `esbuildParseCheck` which wraps the body in
 *  a function (correct for action-body emission but wrong for
 *  whole-file emission with top-of-file imports). Here we parse the
 *  file as a TS module directly. Returns null on success, an error
 *  message string on failure. */
async function esbuildFileParseCheck(fileContents) {
    try {
        const esbuild = await import('esbuild');
        await esbuild.transform(fileContents, {
            loader: 'ts',
            format: 'esm',
            target: 'es2022',
        });
        return null;
    }
    catch (err) {
        const msg = err?.errors?.[0]?.text || err?.message || 'unknown parse error';
        return msg;
    }
}
/** Render the surgical-retry prefix that frames the parse error for
 *  the LLM. Reuses the per-action-llm-emit convention. */
function buildSurgicalRetryPrefix(parseError, ownerName) {
    const trimmed = parseError.split('\n').slice(0, 4).join('\n');
    return [
        `Your previous emission for ${ownerName} failed an esbuild parse check:`,
        '',
        trimmed,
        '',
        'Re-emit the entire file with the parse error corrected. Preserve',
        'all [PRE-BAKED] snippets verbatim. If a specific action cannot be',
        'implemented coherently, use the γ-stub form for THAT action while',
        'keeping the others fully implemented.',
        '',
    ].join('\n');
}
/**
 * Build the retry prompt prefix for a Tier 1 structural failure.
 *
 * The structural validator flags an owner when the LLM dropped or
 * paraphrased more than `dropThreshold` of its [PRE-BAKED] snippets.
 * Before this retry path existed (engines 6.64.0+), structural failure
 * threw immediately and the owner went straight to Tier 2 per-action
 * recovery. Now we re-prompt Tier 1 once, surfacing the SPECIFIC
 * dropped snippets so the model has concrete structural feedback
 * instead of having to re-derive them.
 *
 * Up to 3 of the missing snippets are shown verbatim — the model sees
 * (a) the step number + text it dropped, (b) the exact expected
 * snippet shape. The rest are summarised. Preserves the wide-context
 * advantage Tier 1 has over Tier 2 (constructor, sibling methods,
 * cross-service surface all still in the prompt) while giving the
 * model the same concrete-snippet hint Tier 2 uses.
 *
 * Lever 1 of `specverse-self/docs/proposals/2026-05-15-TIER1-EMISSION-QUALITY.md`.
 */
function buildStructuralRetryPrefix(missing, ownerName) {
    const sampleSize = Math.min(3, missing.length);
    const samples = missing.slice(0, sampleSize).map((m) => {
        return [
            `Step ${m.stepNum}: "${m.stepText}"`,
            'Expected (verbatim):',
            m.expected,
        ].join('\n');
    }).join('\n\n');
    const moreNote = missing.length > sampleSize
        ? `\n\n(... and ${missing.length - sampleSize} more dropped snippet(s) not shown.)`
        : '';
    return [
        `Your previous emission for ${ownerName} dropped or paraphrased`,
        `${missing.length} [PRE-BAKED] snippet(s). These snippets are`,
        `DETERMINISTIC — they must appear in your output VERBATIM:`,
        '',
        samples + moreNote,
        '',
        'Re-emit the entire owner file with these snippets included exactly',
        'as shown. Do not paraphrase, reword, or "improve" them — they are',
        'the L2 convention output and downstream tooling depends on their',
        'exact text. The wider function body around them is yours to write,',
        'and any [WRITE] step bodies (steps without a pre-baked snippet)',
        'are entirely up to you.',
        '',
    ].join('\n');
}
// ─── Prompt rendering helpers ────────────────────────────────────────
/**
 * Human-readable description of the forbidden-API set for a given
 * runtime, suitable for the prompt's TARGET RUNTIME section. Consults
 * the same source-of-truth (`forbiddenApisForRuntime` in realize-rules)
 * that the `no-forbidden-runtime-apis` validator uses.
 */
function describeForbiddenApis(targetRuntime) {
    const list = forbiddenApisForRuntime(targetRuntime);
    if (list.length === 0)
        return '(none — all APIs available)';
    return list.map((s) => `  - ${s}`).join('\n');
}
//# sourceMappingURL=per-owner-emit.js.map