import type { ThemeSlotValues, ThemeSummary } from "./extract-theme.js"; /** * Theme provenance — how `vendo sync` re-extracts a rebrand without ever * clobbering a hand edit. * * `.vendo/theme.json` is the editable source of truth and stays exactly the * frozen `VendoTheme` shape, so provenance rides a sibling merge base: * `.vendo/theme.extracted.json` records what the DETERMINISTIC scan produced * the last time it ran. That mirrors the split `.vendo/` already lives by — * `tools.json`/`judgments.json` are the machine layer, `overrides.json` is * "what a person decided" — instead of inventing a second convention. * * The law is one line: a slot is machine-owned ONLY when the base records it * and `theme.json` still holds exactly that value. Everything else is pinned. * • recorded and unchanged → machine-extracted; a new extraction updates it * • recorded and different → hand-edited; pinned and reported * • not recorded at all (no base file yet, or a token init never saw) → * pinned, because there is no evidence about who chose the value on disk * * That last rule is deliberately conservative. An earlier draft treated "the * value equals Vendo's neutral default" as proof the machine wrote it — but * the neutral defaults are ordinary Tailwind palette values (`#2563eb` is * blue-600, the greys are the slate ramp), so a human who picks blue-600 would * have had it silently overwritten. Unprovable ownership is never ownership. * * The base only advances on an unambiguous run (no pinned slots), so an * install from before the base existed warns with the diff on every sync until * a human resolves it with `--theme-refresh` — never quietly adopting a stale * value as the new truth. */ export declare const THEME_EXTRACTED_FILE = "theme.extracted.json"; export interface ExtractedThemeBase { format: string; /** Only the slots the deterministic scan had host evidence for (exact token reads, plus the values derived from them). Slots that fell back to a neutral default are absent — Vendo never claims to have read them. Deliberately the whole file: no timestamp, because a timestamp carries no decision and would make the committed artifact churn on every sync. */ slots: Partial>; } /** The deterministic scan's evidence, as the merge base. Built from the EXACT-ONLY summary (before any model fill or `--theme` answer): those are human/model decisions, and pinning them is the point. */ export declare function baseFrom(summary: ThemeSummary): ExtractedThemeBase; /** Write the base only when its slots actually changed. `.vendo/` is committed and sync runs from `predev`, so a base that rewrote itself on every run would dirty every contributor's tree on every `npm run dev` — the exact churn the hookless `--no-ai` flag exists to prevent. */ export declare function writeBase(vendoDir: string, base: ExtractedThemeBase): Promise; /** The recorded base, or null when absent/unreadable (both mean "no recorded provenance" — never a reason to fail a sync). */ export declare function readBase(vendoDir: string): Promise; export interface ThemeMerge { /** The theme document to write; null when nothing changed. */ theme: unknown | null; /** Slots this sync actually wrote into `theme.json` — never a slot that was merely reconsidered, because the summary line names these to the user. */ updated: string[]; /** Slots the extraction disagrees with but a human owns, carrying BOTH values so the report can show the choice instead of implying one. */ pinned: Array<{ slot: string; mine: string; theirs: string; }>; } /** * Merge a fresh deterministic extraction into the host's `theme.json`. * `force` (sync `--theme-refresh`) takes every disagreement, pinned or not. */ export declare function mergeExtraction(args: { theme: unknown; base: ExtractedThemeBase | null; summary: ThemeSummary; force?: boolean; }): ThemeMerge;