/**
 * @module _shared/compose-scenes-skills
 *
 * Single-beat Claude composer for the `compose-scenes-with-skills` v0.59
 * pipeline action. Per beat:
 *
 *   1. Compute a sha256 cache key over (skill-bundle hash, DESIGN.md,
 *      storyboard global direction, beat body, model id, retry-feedback).
 *   2. Read the user cache's `compose-scenes/<key>.html` if present.
 *   3. Otherwise call Claude Sonnet 4.6 with the skill bundle as system
 *      prompt and the beat as user prompt; parse HTML out of a fenced
 *      code block; persist to cache.
 *
 * Pre-flight (PR #111) validated this exact prompt shape at 5/5 lint pass,
 * $0.058/scene, 8.4 s. The diff between runs is high (~33 % line-Jaccard)
 * which is precisely *why* we cache on input rather than output: same
 * input prompts always return the same cached HTML.
 *
 * C3 ships:
 *   - `composeBeatHtml(ctx)` — single shot, no retry
 *   - Cache I/O (read + write) by sha256 input key
 *   - Anthropic SDK call with model selection + cost reporting
 *   - Pure prompt construction helpers (testable without API key)
 *
 * Lint feedback retry loop comes in C4 (extends this module).
 */
import { type LintFinding } from "./scene-lint.js";
import { type Beat } from "./storyboard-parse.js";
import { type ComposerProvider } from "./composer-resolve.js";
/** Effort level → token caps. Model is per-provider (see MODEL_SETTINGS_BY_PROVIDER). */
export type ComposeEffort = "low" | "medium" | "high";
export interface ComposeBeatContext {
    /** The beat to compose, from `parseStoryboard()`. */
    beat: Beat;
    /** Project's `DESIGN.md` content (visual identity hard-gate). */
    designMd: string;
    /** Storyboard's global direction (everything before the first `## Beat`). */
    storyboardGlobal: string;
    /** Hyperframes skill bundle content + hash, from `loadHyperframesSkillBundle()`. */
    skillBundle: {
        content: string;
        hash: string;
    };
    /** Effort level — picks model + token caps. Defaults to "medium". */
    effort?: ComposeEffort;
    /**
     * Optional lint feedback to append to the user prompt, used by C4's retry
     * loop. When non-empty, the cache key includes it so retries don't return
     * the cached first-shot HTML.
     */
    retryFeedback?: string;
    /**
     * LLM provider that powers the composer. Picked by `resolveComposer()` at
     * the entry point (CLI flag → env auto-resolve). The `apiKey` field below
     * is the matching key for this provider.
     */
    provider: ComposerProvider;
    /** API key for {@link ComposeBeatContext.provider}. */
    apiKey: string;
    /**
     * Cache directory override. Defaults to `~/.vibeframe/cache/compose-scenes/`.
     * Tests pass a temp dir.
     */
    cacheDir?: string;
    /**
     * Narration-synced FINAL beat duration in seconds. The root timeline
     * stretches each beat to cover its generated narration
     * (see resolveSyncedBeatDuration in root-sync.ts); when set, the prompt
     * pins `data-duration` and every timeline anchor to this exact value so the
     * scene cannot end early and render a black tail. The storyboard cue
     * duration is then only the minimum and is annotated as superseded.
     */
    finalDurationSec?: number;
}
export interface ComposeBeatResult {
    /** Generated HTML (from cache or fresh API call). */
    html: string;
    /** True when the HTML came from cache (no API call made). */
    cached: boolean;
    /** Cache key used (sha256 hex). */
    cacheKey: string;
    /** Provider input tokens (only set on fresh API call). */
    inputTokens?: number;
    /** Provider output tokens (only set on fresh API call). */
    outputTokens?: number;
    /** USD cost of this beat (only set on fresh API call). */
    costUsd?: number;
    /** Wall-clock latency in ms (only set on fresh API call). */
    latencyMs?: number;
    /** Resolved model id (e.g. "claude-sonnet-4-6"). */
    model: string;
    /** Composer provider that produced this beat. */
    provider: ComposerProvider;
}
/** Build the system prompt — skill bundle + DESIGN.md hard-gate. */
export declare function buildSystemPrompt(ctx: Pick<ComposeBeatContext, "skillBundle" | "designMd">): string;
/** Build the user prompt — instructions + storyboard global + beat body. */
export declare function buildUserPrompt(ctx: Pick<ComposeBeatContext, "beat" | "storyboardGlobal" | "retryFeedback" | "finalDurationSec">): string;
/**
 * Compute the sha256 cache key for a (provider, model, system, user) tuple.
 * Provider id is folded in so switching `--composer` doesn't serve cached
 * HTML produced by a different model.
 */
export declare function computeCacheKey(parts: {
    provider: ComposerProvider;
    systemPrompt: string;
    userPrompt: string;
    model: string;
}): string;
/** Default cache root: `~/.vibeframe/cache/compose-scenes/`. */
export declare function defaultCacheDir(): string;
/**
 * Pull the HTML out of Claude's response. Prefers a fenced ```html block;
 * accepts a bare HTML response as fallback (some completions skip the fence
 * when the entire reply is the document).
 */
export declare function extractHtml(responseText: string): string;
export declare class ComposeBeatError extends Error {
    readonly code: string;
    readonly cause?: unknown | undefined;
    constructor(code: string, message: string, cause?: unknown | undefined);
}
/** Raw text-completion result from any of the three providers. */
export interface LLMCallResult {
    text: string;
    inputTokens: number;
    outputTokens: number;
}
/** Injectable LLM call function — used by tests to mock the SDK calls. */
export type LLMCallFn = (req: {
    provider: ComposerProvider;
    apiKey: string;
    model: string;
    maxTokens: number;
    systemPrompt: string;
    userPrompt: string;
}) => Promise<LLMCallResult>;
/**
 * Compose one beat into HTML. Cache-first; on miss, call the configured
 * composer provider (Claude / OpenAI / Gemini — picked at the entry point
 * via `resolveComposer()` and threaded through `ctx.provider`).
 *
 * @param overrides allows tests to inject a mocked LLM call. In prod we use
 *   `defaultCallLLM` which dispatches to the matching SDK.
 */
export declare function composeBeatHtml(ctx: ComposeBeatContext, overrides?: {
    callLLM?: LLMCallFn;
    now?: () => number;
}): Promise<ComposeBeatResult>;
export interface BeatLintResult {
    errorCount: number;
    warningCount: number;
    findings: LintFinding[];
}
/**
 * Lint a single beat's HTML in-memory using `@hyperframes/producer`'s
 * `runHyperframeLint` directly — no temp project shell, no subprocess.
 *
 * Sub-composition false-positives are filtered (matches what the
 * `vibe scene lint` command does). The result is the same set of
 * findings the user would see if they ran `vibe scene lint` after
 * the beat HTML landed in a project.
 */
export declare function lintBeatHtml(html: string, beatId: string): BeatLintResult;
/**
 * Format lint findings into a feedback block for the retry prompt.
 * Errors only — warnings don't block, so we don't burn the LLM's
 * attention budget on them.
 */
export declare function formatLintFeedback(findings: LintFinding[]): string;
export interface ComposeBeatWithRetryResult extends ComposeBeatResult {
    /** 1 if first-shot HTML linted clean; 2 if retry succeeded. */
    lintAttempts: 1 | 2;
    /** Lint result from the *final* (winning) attempt. */
    lint: BeatLintResult;
}
/**
 * `composeBeatHtml` + lint feedback retry loop, capped at 1 retry.
 *
 * Pre-flight (PR #111) showed 5/5 first-pass success at $0.058/scene; deeper
 * retry loops just burn budget. If the second attempt also fails lint,
 * throws `ComposeBeatError("lint-failed-after-retry", ...)` with the final
 * findings — caller (C5 pipeline action) decides whether to fall back to
 * the 5-preset emit path or surface the failure to the user.
 *
 * Cache contract preserved: retries use a different `retryFeedback`, which
 * is folded into the cache key, so retries don't accidentally serve the
 * cached first-shot HTML.
 */
export declare function composeBeatWithRetry(ctx: ComposeBeatContext, overrides?: {
    callLLM?: LLMCallFn;
    now?: () => number;
}): Promise<ComposeBeatWithRetryResult>;
/**
 * YAML pipeline action: read DESIGN.md + STORYBOARD.md, compose each beat
 * via Claude, write `compositions/scene-<id>.html` per beat. Sequential
 * fanout; C6 swaps in `Promise.all` parallelism.
 *
 * Wired into `pipeline/executor.ts` as the `compose-scenes-with-skills`
 * action. YAML shape:
 *
 *     - id: compose
 *       action: compose-scenes-with-skills
 *       design: DESIGN.md          # required (relative to project)
 *       storyboard: STORYBOARD.md  # required
 *       project: .                 # optional, default outputDir
 *       effort: medium             # optional, low|medium|high
 *
 * Returns the project root as `output` and per-beat metadata in `data`.
 */
/** Per-beat lifecycle event emitted to the optional `onProgress` callback. */
export type ComposeProgressEvent = {
    type: "beat-start";
    beatId: string;
    beatIndex: number;
    totalBeats: number;
} | {
    type: "beat-cached";
    beatId: string;
    beatIndex: number;
    totalBeats: number;
    lintAttempts: 1 | 2;
} | {
    type: "beat-fresh";
    beatId: string;
    beatIndex: number;
    totalBeats: number;
    lintAttempts: 1 | 2;
    costUsd?: number;
    latencyMs?: number;
} | {
    type: "beat-failed";
    beatId: string;
    beatIndex: number;
    totalBeats: number;
    error: string;
};
export interface ComposeScenesParams {
    /** Path to DESIGN.md, relative to project root. */
    design?: string;
    /** Path to STORYBOARD.md, relative to project root. */
    storyboard?: string;
    /** Scene project root, relative to outputDir. Defaults to outputDir. */
    project?: string;
    /** Effort tier — low/medium/high. Defaults to medium. */
    effort?: ComposeEffort;
    /**
     * Composer provider override. When undefined, `resolveComposer()` picks
     * one based on env keys (`claude > gemini > openai` order).
     */
    composer?: ComposerProvider;
    /** Override the cache directory (tests). */
    cacheDir?: string;
    /** Optional per-beat progress callback (CLI spinner / pipeline reporter). */
    onProgress?: (event: ComposeProgressEvent) => void;
}
export interface ComposeScenesActionResult {
    success: boolean;
    /** Project root (where compositions/ lives). */
    outputPath?: string;
    /** Aggregated metadata for the dry-run / final report. */
    data?: {
        beats: number;
        written: Array<{
            beatId: string;
            path: string;
            cached: boolean;
            cacheKey: string;
            lintAttempts: 1 | 2;
            costUsd?: number;
        }>;
        totalCostUsd: number;
        totalTokensIn: number;
        totalTokensOut: number;
        cacheHits: number;
    };
    error?: string;
}
/**
 * Execute the action. Per-beat fanout via `Promise.allSettled` — all beats
 * compose in parallel, all errors surface together (rather than fail-fast,
 * which would still pay for in-flight API calls without surfacing their
 * findings). Tests inject `overrides` to mock the SDK + clock.
 *
 * Wall-clock for an N-beat compose ≈ slowest single beat (~8s @ Sonnet 4.6
 * per pre-flight PR #111), regardless of N. Cost still scales with N.
 */
export declare function executeComposeScenesWithSkills(params: ComposeScenesParams, outputDir: string, overrides?: {
    callLLM?: LLMCallFn;
    now?: () => number;
}): Promise<ComposeScenesActionResult>;
