/** * EntityExtractor — "discovery writer" (pantser) support * * Scans ALREADY-DRAFTED chapter prose and proposes NEW story entities * (characters and locations) that are not yet recorded in the project's * `characters/*.yml` / `locations/*.yml` files. * * This is a purely deterministic, heuristic analyser — there is NO LLM call. * The goal is to give a writer who "just writes" some structured value without * forcing them to define every entity up-front. * * Philosophy: under-propose rather than flood. Every threshold below is a * named constant and deliberately conservative — a missed candidate is cheap * (the writer can `create` it manually), whereas a wall of false positives * erodes trust in the command. */ /** A single proposed entity with how many times it appears in the chapter. */ export interface EntityCandidate { /** Canonical (capitalised) name as it appears in the prose. */ name: string; /** Number of whole-word occurrences of `name` in the cleaned chapter text. */ mentions: number; } /** Result of scanning one chapter for new entities. */ export interface ExtractionResult { /** Proposed NEW characters, sorted by mention count (descending). */ characters: EntityCandidate[]; /** Proposed NEW locations, sorted by mention count (descending). */ locations: EntityCandidate[]; } export declare class EntityExtractor { private readonly projectPath; constructor(projectPath: string); /** * Scan a single chapter file and propose NEW characters and locations. * * @param chapterFile - Absolute path, or a path relative to * `/chapters/`. */ extractFromChapter(chapterFile: string): Promise; /** * Scan an arbitrary prose file — typically a freeform outline or treatment the * author wrote before populating `characters/` / `plots/`. Use this to * bootstrap structured entities from an existing plan. * * @param file - Absolute path, or a path relative to `projectPath`. */ extractFromFile(file: string): Promise; /** * Core extraction over raw text (markup is stripped internally). Cross-checks * against the names already recorded under `characters/` and `locations/`. */ extractFromText(raw: string): Promise; /** * Build the character candidate list from two signals: * 1. Dialogue speakers identified via attribution patterns (strong signal — * always proposed). * 2. Capitalised tokens that recur in non-sentence-start positions at least * `MIN_CHARACTER_MENTIONS` times (proper-noun heuristic). * * Candidates already present in `known` (case-insensitive) are excluded. */ private _extractCharacters; /** * Collect every dialogue speaker named via an attribution pattern. * Sentence-starter words (e.g. "Then said the man") are filtered out. */ private static _collectSpeakers; /** * Collect capitalised single-word proper-noun candidates that recur in * NON-sentence-start positions at least `MIN_CHARACTER_MENTIONS` times. */ private static _collectRecurringProperNouns; /** * Propose NEW locations: capitalised phrases that follow a locational * preposition. A candidate is accepted when it is either multi-word, ends in a * known place suffix, or is a single proper noun that is NOT already a * character candidate. Known locations are excluded. */ private _extractLocations; /** * Load the canonical names already recorded under `//`. * Parses `name` and `fullName` from each `*.yml` / `*.yaml` file, and also * adds each individual word token so that e.g. an existing "Mara Quill" * suppresses a bare "Mara" candidate. All entries are lower-cased. * * File-based and best-effort: a missing directory or malformed YAML simply * yields fewer known names rather than throwing. */ private _loadKnownNames; /** * Remove all non-prose markup so the heuristics only see narrative text: * - YAML frontmatter (leading `--- … ---`) * - HTML / scene comments (``) * - `[MARKER: …]` annotations (TK, TODO, NOTE, …) * - `--- Scene N ---` delimiters and bare horizontal rules * - Markdown headings, emphasis markers, and link syntax */ static stripMarkup(raw: string): string; /** * Split text into sentences for sentence-start detection. Splits on * end-of-sentence punctuation followed by whitespace, and on line breaks * (a new line conservatively begins a new "sentence" for capitalisation * purposes). */ private static _splitSentences; /** * Count whole-word, case-insensitive occurrences of `term` in `text`. * Multi-word terms are matched as a contiguous phrase. */ private static _countOccurrences; } //# sourceMappingURL=entity-extractor.d.ts.map