/** * ThemeAnalyzer — deterministic theme / motif-density scanner * * A theme (e.g. "isolation") is registered with a set of motif words/phrases * (e.g. "cold", "mirror", "silence", "locked"). This analyser scans every * chapter's prose and reports, per theme, how densely those motifs appear in * each chapter — so an author can SEE where a theme lives, where it goes silent * (gaps), and where it spikes. * * Purely deterministic and heuristic — there is NO LLM call. Matching is * whole-word and case-insensitive, and chapter markup/frontmatter is stripped * first via {@link EntityExtractor.stripMarkup} so only narrative prose counts. */ /** On-disk YAML schema for a registered theme (`themes/.yml`). */ export interface ThemeYAML { /** Display name, e.g. "isolation". */ name: string; /** Motif words / phrases associated with the theme. */ motifs: string[]; /** Optional free-text note about the theme. */ description?: string; } /** A loaded theme definition with its source slug/file resolved. */ export interface ThemeDefinition { name: string; slug: string; motifs: string[]; description?: string; /** Absolute path of the YAML file it was loaded from. */ file: string; } /** Per-chapter motif statistics for one theme. */ export interface ChapterMotifStat { /** Display label (chapter filename without extension). */ chapter: string; /** Chapter filename (with extension). */ file: string; /** Number of prose words in the chapter (after stripping markup). */ wordCount: number; /** Total motif occurrences in the chapter. */ hits: number; /** Motif occurrences per 1000 prose words. */ density: number; /** Per-motif occurrence breakdown. */ byMotif: Record; /** True when the theme is entirely ABSENT from this chapter (`hits === 0`). */ isGap: boolean; /** True when the theme density spikes well above the manuscript mean. */ isSpike: boolean; } /** Result of tracing a single theme across all chapters. */ export interface ThemeTrace { theme: ThemeDefinition; chapters: ChapterMotifStat[]; /** Total motif occurrences across every chapter. */ totalHits: number; /** Mean density (per 1000 words) across all chapters. */ meanDensity: number; /** Maximum chapter density (per 1000 words). */ maxDensity: number; /** Labels of chapters where the theme is absent. */ gaps: string[]; /** Labels of chapters where the theme spikes. */ spikes: string[]; } /** Convert a theme name into a filesystem-safe slug (mirrors the builders). */ export declare function slugifyTheme(name: string): string; /** * Parse a comma-separated motif list into a trimmed, de-duplicated array. * Empty entries are dropped; duplicates are removed case-insensitively while * preserving the first-seen spelling. */ export declare function parseMotifs(raw: string): string[]; /** * Render a unicode sparkline for a series of non-negative values. Zero values * render as a gap marker so absent chapters are visually distinct; positive * values are scaled against the series maximum. */ export declare function renderSparkline(values: number[]): string; export declare class ThemeAnalyzer { private readonly projectPath; constructor(projectPath: string); /** * Load every registered theme from `/themes/*.yml`. Missing * directory or malformed/empty files are skipped rather than throwing. */ loadThemes(): Promise; /** * Trace one or all themes across every chapter. When `themeName` is given, * only the matching theme (case-insensitive name OR slug) is traced. * * Returns one {@link ThemeTrace} per matched theme. An empty array means * either no themes are registered or the named theme was not found — callers * disambiguate via {@link loadThemes}. */ trace(themeName?: string): Promise; /** Read and strip every `chapters/*.md` file once, sorted by filename. */ private _loadChapters; /** Compute per-chapter motif stats and gap/spike flags for one theme. */ private _traceTheme; } //# sourceMappingURL=theme-analyzer.d.ts.map