llm-wiki-compiler 0.7.0 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +251 -0
- package/README.md +71 -9
- package/dist/cli.js +3072 -387
- package/dist/cli.js.map +1 -1
- package/dist/viewer/assets/THIRD_PARTY_NOTICES.txt +22 -0
- package/dist/viewer/assets/d3.min.js +2 -0
- package/dist/viewer/assets/index.html +4 -2
- package/dist/viewer/assets/viewer-graph.js +369 -0
- package/dist/viewer/assets/viewer-rail.js +115 -39
- package/dist/viewer/assets/viewer-search.js +48 -16
- package/dist/viewer/assets/viewer-sidebar.js +105 -35
- package/dist/viewer/assets/viewer.css +68 -1
- package/dist/viewer/assets/viewer.js +232 -115
- package/package.json +6 -3
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli.ts","../src/commands/ingest.ts","../src/utils/markdown.ts","../src/utils/source-writer.ts","../src/utils/constants.ts","../src/utils/output.ts","../src/ingest/web.ts","../src/ingest/file.ts","../src/ingest/shared.ts","../src/ingest/pdf.ts","../src/ingest/image.ts","../src/providers/anthropic.ts","../src/utils/claude-settings.ts","../src/ingest/transcript.ts","../src/commands/ingest-session.ts","../src/adapters/claude.ts","../src/adapters/utils.ts","../src/adapters/codex.ts","../src/adapters/cursor.ts","../src/adapters/registry.ts","../src/commands/view.ts","../src/viewer/server.ts","../src/linter/cache.ts","../src/viewer/health.ts","../src/viewer/shell.ts","../src/viewer/static-assets.ts","../src/viewer/path-safety.ts","../src/viewer/render.ts","../src/wiki/collect.ts","../src/viewer/collect.ts","../src/viewer/markdown-it-helpers.ts","../src/viewer/wikilink-rule.ts","../src/viewer/citation-rule.ts","../src/viewer/search.ts","../src/viewer/snapshot.ts","../src/compiler/candidates.ts","../src/utils/state.ts","../src/commands/compile.ts","../src/compiler/index.ts","../src/compiler/source-state.ts","../src/compiler/hasher.ts","../src/providers/openai.ts","../src/providers/ollama.ts","../src/providers/minimax.ts","../src/providers/copilot.ts","../src/utils/provider.ts","../src/utils/llm.ts","../src/utils/lock.ts","../src/utils/output-language.ts","../src/compiler/prompts.ts","../src/schema/types.ts","../src/schema/defaults.ts","../src/schema/loader.ts","../src/schema/helpers.ts","../src/compiler/deps.ts","../src/compiler/orphan.ts","../src/compiler/resolver.ts","../src/compiler/indexgen.ts","../src/compiler/prompt-budget.ts","../src/compiler/obsidian.ts","../src/utils/embeddings.ts","../src/utils/retrieval.ts","../src/linter/rules.ts","../src/compiler/page-renderer.ts","../src/compiler/provenance.ts","../src/commands/query.ts","../src/commands/watch.ts","../src/linter/index.ts","../src/commands/lint.ts","../src/commands/export.ts","../src/export/collect.ts","../src/export/llms-txt.ts","../src/export/json-export.ts","../src/export/json-ld.ts","../src/export/graphml.ts","../src/export/marp.ts","../src/export/types.ts","../src/commands/schema.ts","../src/commands/review-list.ts","../src/commands/review-show.ts","../src/commands/review-approve.ts","../src/commands/review-helpers.ts","../src/commands/review-reject.ts","../src/mcp/server.ts","../src/mcp/tools.ts","../src/mcp/provider-check.ts","../src/mcp/resources.ts"],"sourcesContent":["/**\n * CLI entry point for llmwiki — the knowledge compiler.\n *\n * Registers all commands (ingest, compile, query, watch, lint) via Commander.\n * Validates the correct API key for the selected LLM provider.\n * Designed for `npx llmwiki` or global install via `npm install -g llm-wiki-compiler`.\n */\n\nimport \"dotenv/config\";\nimport { createRequire } from \"module\";\nimport { Command } from \"commander\";\nimport ingestCommand from \"./commands/ingest.js\";\nimport ingestSessionCommand from \"./commands/ingest-session.js\";\nimport viewCommand from \"./commands/view.js\";\nimport compileCommand from \"./commands/compile.js\";\nimport queryCommand from \"./commands/query.js\";\nimport watchCommand from \"./commands/watch.js\";\nimport lintCommand from \"./commands/lint.js\";\nimport exportCommand from \"./commands/export.js\";\nimport { schemaInitCommand, schemaShowCommand } from \"./commands/schema.js\";\nimport reviewListCommand from \"./commands/review-list.js\";\nimport reviewShowCommand from \"./commands/review-show.js\";\nimport reviewApproveCommand from \"./commands/review-approve.js\";\nimport reviewRejectCommand from \"./commands/review-reject.js\";\nimport { startMCPServer } from \"./mcp/server.js\";\nimport { DEFAULT_PROVIDER } from \"./utils/constants.js\";\nimport { resolveAnthropicAuthFromEnv } from \"./utils/claude-settings.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\") as { version: string };\n\nconst program = new Command();\n\nprogram\n .name(\"llmwiki\")\n .description(\"The knowledge compiler — raw sources in, interlinked wiki out\")\n .version(version);\n\nprogram\n .command(\"ingest <source>\")\n .description(\"Ingest a URL or local file into sources/\")\n .action(async (source: string) => {\n try {\n await ingestCommand(source);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"ingest-session <path>\")\n .description(\"Ingest a coding-agent session export (Claude, Codex, Cursor) into sources/\")\n .action(async (targetPath: string) => {\n try {\n await ingestSessionCommand(targetPath);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"view\")\n .description(\"Start a local read-only web viewer for the current wiki project\")\n .option(\"--port <port>\", \"Port to bind (default 0 — OS-assigned)\")\n .option(\"--host <host>\", \"Host to bind (requires --allow-lan; default 127.0.0.1)\")\n .option(\"--allow-lan\", \"Bind beyond loopback (requires --host); off by default for privacy\")\n .option(\"--open\", \"Open the viewer in the default browser after startup\")\n .action(async (options: { port?: string; host?: string; allowLan?: boolean; open?: boolean }) => {\n try {\n await viewCommand(options);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"compile\")\n .description(\"Compile sources/ into an interlinked wiki\")\n .option(\n \"--review\",\n \"Write generated pages as review candidates under .llmwiki/candidates/ instead of mutating wiki/. Orphan-marking for deleted sources is deferred until the next non-review compile.\",\n )\n .option(\n \"--lang <code>\",\n \"Target language for generated wiki content (e.g. \\\"Chinese\\\", \\\"ja\\\", \\\"zh-CN\\\"). Equivalent to setting LLMWIKI_OUTPUT_LANG.\",\n )\n .action(async (options: { review?: boolean; lang?: string }) => {\n try {\n applyLanguageOption(options.lang);\n requireProvider();\n await compileCommand({ review: options.review });\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nconst reviewCommand = program\n .command(\"review\")\n .description(\"Inspect and act on pending compile review candidates\");\n\nreviewCommand\n .command(\"list\")\n .description(\"List pending review candidates\")\n .action(async () => {\n try {\n await reviewListCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nreviewCommand\n .command(\"show <id>\")\n .description(\"Print a single candidate's metadata and body\")\n .action(async (id: string) => {\n try {\n await reviewShowCommand(id);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nreviewCommand\n .command(\"approve <id>\")\n .description(\"Approve a candidate and promote it into wiki/concepts/\")\n .action(async (id: string) => {\n try {\n await reviewApproveCommand(id);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nreviewCommand\n .command(\"reject <id>\")\n .description(\"Reject a candidate and archive it without touching wiki/\")\n .action(async (id: string) => {\n try {\n await reviewRejectCommand(id);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"query <question>\")\n .description(\"Ask a question against the wiki\")\n .option(\"--save\", \"Save the answer as a wiki page\")\n .option(\"--debug\", \"Print which pages and chunks were selected and their scores\")\n .option(\n \"--lang <code>\",\n \"Target language for the answer (e.g. \\\"Chinese\\\", \\\"ja\\\", \\\"zh-CN\\\"). Equivalent to setting LLMWIKI_OUTPUT_LANG.\",\n )\n .action(\n async (\n question: string,\n options: { save?: boolean; debug?: boolean; lang?: string },\n ) => {\n try {\n applyLanguageOption(options.lang);\n requireProvider();\n await queryCommand(process.cwd(), question, options);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n },\n );\n\nprogram\n .command(\"watch\")\n .description(\"Watch sources/ and auto-recompile on changes\")\n .action(async () => {\n try {\n requireProvider();\n await watchCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"lint\")\n .description(\"Run rule-based quality checks against the wiki\")\n .action(async () => {\n try {\n await lintCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nconst schemaCmd = program\n .command(\"schema\")\n .description(\"Inspect or initialize the project's wiki schema config\");\n\nschemaCmd\n .command(\"init\")\n .description(\"Write a starter schema file to .llmwiki/schema.json\")\n .action(async () => {\n try {\n await schemaInitCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nschemaCmd\n .command(\"show\")\n .description(\"Print the resolved schema for this project\")\n .action(async () => {\n try {\n await schemaShowCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"export\")\n .description(\"Export wiki content to portable formats (llms.txt, JSON, GraphML, Marp, …)\")\n .option(\"--target <name>\", \"Limit export to a single target format\")\n .option(\n \"--source <kind>\",\n \"For marp target: which pages to include — concepts, queries, or all (default: all)\",\n )\n .action(async (options: { target?: string; source?: string }) => {\n try {\n await exportCommand(process.cwd(), options);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"serve\")\n .description(\"Start an MCP server exposing wiki tools and resources over stdio\")\n .option(\"--root <dir>\", \"Project root directory\", process.cwd())\n .action(async (options: { root: string }) => {\n try {\n // Per-tool credential checks happen inside the MCP layer so read-only\n // tools and ingest still work without an API key.\n await startMCPServer({ root: options.root, version });\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\n/**\n * Apply the --lang CLI option by setting LLMWIKI_OUTPUT_LANG so prompt\n * builders pick it up (issue #37). Single env slot keeps the resolution\n * order simple: explicit flag wins over the inherited environment.\n */\nfunction applyLanguageOption(lang: string | undefined): void {\n if (lang && lang.trim().length > 0) {\n process.env.LLMWIKI_OUTPUT_LANG = lang.trim();\n }\n}\n\n/** API key env var required per provider. Null means no key needed. */\nconst PROVIDER_KEY_VARS: Record<string, string | null> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n ollama: null,\n minimax: \"MINIMAX_API_KEY\",\n copilot: \"GITHUB_TOKEN\",\n};\n\n/** Exit with a helpful message if the selected provider's API key is missing. */\nfunction requireProvider(): void {\n const provider = process.env.LLMWIKI_PROVIDER ?? DEFAULT_PROVIDER;\n\n if (provider === \"anthropic\") {\n const auth = resolveAnthropicAuthFromEnv();\n if (!auth.apiKey && !auth.authToken) {\n console.error(\n `\\x1b[31mError:\\x1b[0m Anthropic credentials are required for the \"anthropic\" provider.\\n` +\n ` Set one of: export ANTHROPIC_API_KEY=<your-key> OR export ANTHROPIC_AUTH_TOKEN=<your-token>`,\n );\n process.exit(1);\n }\n return;\n }\n\n const keyVar = PROVIDER_KEY_VARS[provider];\n\n if (keyVar === undefined) {\n console.error(\n `\\x1b[31mError:\\x1b[0m Unknown provider \"${provider}\".\\n` +\n ` Supported: ${Object.keys(PROVIDER_KEY_VARS).join(\", \")}`,\n );\n process.exit(1);\n }\n\n if (keyVar && !process.env[keyVar]) {\n console.error(\n `\\x1b[31mError:\\x1b[0m ${keyVar} environment variable is required for the \"${provider}\" provider.\\n` +\n ` Set it with: export ${keyVar}=<your-key>`,\n );\n process.exit(1);\n }\n}\n\nprogram.parse();\n","/**\n * Commander action for `llmwiki ingest <source>`.\n *\n * Detects the source type (URL, image, PDF, transcript, or generic file),\n * delegates to the appropriate ingestion module, and saves the result as a\n * markdown file with YAML frontmatter in the sources/ directory.\n *\n * Source type is persisted in frontmatter under the `sourceType` key for\n * downstream tooling and human readers.\n */\n\nimport path from \"path\";\nimport { readFile } from \"fs/promises\";\nimport { buildFrontmatter } from \"../utils/markdown.js\";\nimport { saveSource } from \"../utils/source-writer.js\";\nimport { MAX_SOURCE_CHARS, MIN_SOURCE_CHARS, SOURCES_DIR, IMAGE_EXTENSIONS, TRANSCRIPT_EXTENSIONS } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\nimport ingestWeb from \"../ingest/web.js\";\nimport ingestFile from \"../ingest/file.js\";\nimport ingestPdf from \"../ingest/pdf.js\";\nimport ingestImage from \"../ingest/image.js\";\nimport ingestTranscript, { isYoutubeUrl } from \"../ingest/transcript.js\";\nimport type { IngestResult, SourceType } from \"../utils/types.js\";\n\n/** Check whether a source string looks like a URL. */\nfunction isUrl(source: string): boolean {\n return source.startsWith(\"http://\") || source.startsWith(\"https://\");\n}\n\n/** Number of bytes to peek at when sniffing .txt content for transcript signals. */\nconst TXT_SNIFF_BYTES = 2048;\n\n/**\n * Regex for a speaker-tag line: captures the speaker name before the colon.\n * Allows names up to ~40 chars with letters, spaces, dots, apostrophes, hyphens.\n * The `gm` flags let us find ALL occurrences in the sample.\n */\nconst SPEAKER_TAG_PATTERN = /^([A-Z][a-zA-Z .'-]{0,40}):\\s/gm;\n\n/**\n * Regex for a bare timestamp at the start of a line (allowing leading\n * whitespace): \"H:MM\", \"HH:MM\", or \"HH:MM:SS\". Anchored to line starts so\n * incidental times in prose (e.g. \"the meeting at 3:00 was productive\")\n * don't trip the transcript heuristic.\n */\nconst TIMESTAMP_PATTERN = /^\\s*\\d{1,2}:\\d{2}(:\\d{2})?/;\n\n/** Minimum number of timestamp-like matches to treat a file as a transcript. */\nconst MIN_TIMESTAMP_MATCHES = 3;\n\n/**\n * Minimum number of times a single speaker name must appear to signal dialogue\n * (rules out one-off section headers like \"Summary:\" that appear only once).\n */\nconst MIN_SPEAKER_REPEAT_COUNT = 2;\n\n/**\n * Minimum number of distinct speaker names required alongside the repeat\n * condition (rules out single-speaker monologues).\n */\nconst MIN_DISTINCT_SPEAKERS = 2;\n\n/**\n * Count how many times each speaker name appears in the collected tag matches.\n * Returns a Map from name → occurrence count.\n */\nfunction countSpeakerOccurrences(sample: string): Map<string, number> {\n const counts = new Map<string, number>();\n // Reset lastIndex since SPEAKER_TAG_PATTERN has the `g` flag.\n SPEAKER_TAG_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = SPEAKER_TAG_PATTERN.exec(sample)) !== null) {\n const name = match[1].trim();\n counts.set(name, (counts.get(name) ?? 0) + 1);\n }\n return counts;\n}\n\n/**\n * Decide whether speaker-tag occurrences in a sample look like dialogue.\n *\n * A file passes when both of the following are true:\n * - At least {@link MIN_DISTINCT_SPEAKERS} distinct speaker names appear.\n * - At least one name appears {@link MIN_SPEAKER_REPEAT_COUNT}+ times,\n * indicating back-and-forth turns rather than a list of section headers\n * (e.g. \"Summary: …\", \"Details: …\") where every label is unique.\n */\nfunction hasSpeakerDialoguePattern(sample: string): boolean {\n const counts = countSpeakerOccurrences(sample);\n\n const distinctSpeakers = counts.size;\n const hasEnoughSpeakers = distinctSpeakers >= MIN_DISTINCT_SPEAKERS;\n\n const hasRepeatedSpeaker = [...counts.values()].some(\n (n) => n >= MIN_SPEAKER_REPEAT_COUNT,\n );\n\n return hasEnoughSpeakers && hasRepeatedSpeaker;\n}\n\n/**\n * Peek at the first {@link TXT_SNIFF_BYTES} of a plain-text file and decide\n * whether it looks like a conversation transcript.\n *\n * Heuristic: at least one of the following must be true in the sampled content:\n *\n * 1. **Speaker-tag dialogue pattern** — lines of the form \"Name: …\" where:\n * - At least {@link MIN_DISTINCT_SPEAKERS} distinct names appear, AND\n * - At least one name appears {@link MIN_SPEAKER_REPEAT_COUNT}+ times.\n * This rejects lone section headers (\"Summary: …\") and lists of unique\n * labels (\"Summary:\", \"Details:\", \"Notes:\") that have no repetition, while\n * accepting real back-and-forth dialogue (\"Alice: …\\nBob: …\\nAlice: …\").\n *\n * 2. **Timestamp density** — three or more bare timestamp patterns (e.g.\n * \"01:23\" / \"1:23:45\"), the signature of time-coded scripts or subtitles.\n *\n * When neither signal fires the caller routes the file as a generic text file.\n *\n * @param filePath - Absolute or relative path to the .txt file.\n * @returns `true` when transcript signals are detected, `false` otherwise.\n */\nasync function looksLikeTxtTranscript(filePath: string): Promise<boolean> {\n const raw = await readFile(filePath, \"utf-8\");\n const sample = raw.slice(0, TXT_SNIFF_BYTES);\n\n if (hasSpeakerDialoguePattern(sample)) return true;\n\n const timestampMatches = sample.match(new RegExp(TIMESTAMP_PATTERN.source, \"gm\"));\n return (timestampMatches?.length ?? 0) >= MIN_TIMESTAMP_MATCHES;\n}\n\n/** Truncate result including whether truncation occurred and original length. */\ninterface TruncateResult {\n content: string;\n truncated: boolean;\n originalChars: number;\n}\n\n/** Truncate content if it exceeds the character limit, logging a warning. */\nexport function enforceCharLimit(content: string): TruncateResult {\n if (content.length <= MAX_SOURCE_CHARS) {\n return { content, truncated: false, originalChars: content.length };\n }\n\n output.status(\n \"!\",\n output.warn(\n `Content truncated from ${content.length.toLocaleString()} to ${MAX_SOURCE_CHARS.toLocaleString()} characters.`\n )\n );\n return {\n content: content.slice(0, MAX_SOURCE_CHARS),\n truncated: true,\n originalChars: content.length,\n };\n}\n\n/** Reject empty content and warn when content is trivially short. */\nfunction enforceMinContent(content: string): void {\n const length = content.trim().length;\n\n if (length === 0) {\n throw new Error(\n \"No readable content could be extracted from the source.\"\n );\n }\n\n if (length < MIN_SOURCE_CHARS) {\n output.status(\n \"!\",\n output.warn(\n `Content seems very short (${length} chars, minimum recommended is ${MIN_SOURCE_CHARS}).`\n )\n );\n }\n}\n\n/**\n * Determine the source type for a given source string.\n *\n * For `.txt` files, content-sniffing is used instead of a pure extension check.\n * The file's first {@link TXT_SNIFF_BYTES} bytes are inspected for transcript\n * signals (speaker-tag lines or repeated timestamps). Only when both heuristics\n * fail is the file routed to the generic `file` adapter. `.vtt` and `.srt` are\n * always treated as transcripts regardless of content.\n *\n * @param source - A URL, local file path, or image path.\n * @returns The detected SourceType.\n */\nexport async function detectSourceType(source: string): Promise<SourceType> {\n if (!isUrl(source)) {\n const ext = path.extname(source).toLowerCase();\n if (ext === \".pdf\") return \"pdf\";\n if (IMAGE_EXTENSIONS.has(ext)) return \"image\";\n if (TRANSCRIPT_EXTENSIONS.has(ext)) return \"transcript\";\n if (ext === \".txt\") {\n const isTranscript = await looksLikeTxtTranscript(source);\n return isTranscript ? \"transcript\" : \"file\";\n }\n return \"file\";\n }\n\n if (isYoutubeUrl(source)) return \"transcript\";\n return \"web\";\n}\n\n/** Build the full markdown document with frontmatter. */\nexport function buildDocument(\n title: string,\n source: string,\n result: TruncateResult,\n sourceType?: SourceType,\n): string {\n const meta: Record<string, unknown> = {\n title,\n source,\n ingestedAt: new Date().toISOString(),\n };\n if (sourceType !== undefined) {\n meta.sourceType = sourceType;\n }\n if (result.truncated) {\n meta.truncated = true;\n meta.originalChars = result.originalChars;\n }\n const frontmatter = buildFrontmatter(meta);\n\n return `${frontmatter}\\n\\n${result.content}\\n`;\n}\n\n/** Fetch content from the appropriate ingestion module based on source type. */\nasync function fetchContent(\n source: string,\n sourceType: SourceType,\n): Promise<{ title: string; content: string }> {\n switch (sourceType) {\n case \"web\":\n return ingestWeb(source);\n case \"pdf\":\n return ingestPdf(source);\n case \"image\":\n return ingestImage(source);\n case \"transcript\":\n return ingestTranscript(source);\n case \"file\":\n return ingestFile(source);\n }\n}\n\n/**\n * Programmatic ingest entry point. Identical fetch + write logic to the CLI\n * command but returns a structured IngestResult instead of writing to stdout.\n * Used by the MCP server's ingest_source tool.\n *\n * @param source - A URL (http/https), YouTube URL, local file, PDF, or image path.\n * @returns Saved filename, character count, truncation flag, source URI, and detected source type.\n */\nexport async function ingestSource(source: string): Promise<IngestResult> {\n const sourceType = await detectSourceType(source);\n output.status(\"*\", output.info(`Ingesting [${sourceType}]: ${source}`));\n\n const { title, content } = await fetchContent(source, sourceType);\n\n const result = enforceCharLimit(content);\n enforceMinContent(result.content);\n const document = buildDocument(title, source, result, sourceType);\n const savedPath = await saveSource(title, document, source);\n\n return {\n filename: path.basename(savedPath),\n charCount: result.content.length,\n truncated: result.truncated,\n source,\n sourceType,\n };\n}\n\n/**\n * Ingest a source and save it to the sources/ directory.\n * @param source - A URL (http/https), YouTube URL, local file, PDF, or image path.\n */\nexport default async function ingest(source: string): Promise<void> {\n const result = await ingestSource(source);\n const savedPath = path.join(SOURCES_DIR, result.filename);\n\n output.status(\n \"+\",\n output.success(`Saved ${output.bold(result.filename)} → ${output.source(savedPath)}`)\n );\n output.status(\"→\", output.dim(\"Next: llmwiki compile\"));\n}\n","/**\n * Markdown parsing and manipulation helpers.\n * Handles YAML frontmatter extraction, slugification, and atomic file writes\n * for wiki pages.\n */\n\nimport { writeFile, rename, readFile, mkdir } from \"fs/promises\";\nimport path from \"path\";\nimport yaml from \"js-yaml\";\nimport type {\n ClaimCitation,\n ContradictionRef,\n ProvenanceMetadata,\n ProvenanceState,\n SourceSpan,\n} from \"./types.js\";\n\n/** Regex matching `^[...]` citation markers (paragraph or claim-level). */\nconst CITATION_MARKER_PATTERN = /\\^\\[([^\\]]+)\\]/g;\n\n/** Regex matching the optional `:start-end` or `#Lstart-Lend` span suffix on a citation entry. */\nconst SPAN_SUFFIX_PATTERN = /^(?<file>[^:#]+)(?:(?::(?<colonStart>\\d+)(?:-(?<colonEnd>\\d+))?)|(?:#L(?<hashStart>\\d+)(?:-L(?<hashEnd>\\d+))?))?$/;\n\n/** The minimum valid line number in a source span (lines are 1-indexed). */\nconst MIN_LINE_NUMBER = 1;\n\n/** The set of valid provenance state strings, used to reject unknown values. */\nconst VALID_PROVENANCE_STATES: ReadonlySet<ProvenanceState> = new Set([\n \"extracted\",\n \"merged\",\n \"inferred\",\n \"ambiguous\",\n]);\n\n/**\n * Convert a human-readable concept title to a filename slug.\n *\n * Unicode-aware: keeps letters and numbers from any script (Latin, CJK,\n * Cyrillic, Greek, Arabic, etc.). Strips punctuation, emoji, and other\n * symbols. The previous implementation used `\\w` without the `u` flag,\n * which only matches `[A-Za-z0-9_]` — that silently dropped CJK titles\n * to the empty string and caused the bug fixed in #35.\n *\n * Returns an empty string when the title contains no letters or numbers\n * at all (callers that write files should detect this and fail loudly\n * instead of writing a dotfile).\n */\nexport function slugify(title: string): string {\n return title\n .toLowerCase()\n .replace(/['']/g, \"\")\n .replace(/[^\\p{L}\\p{N}\\s-]/gu, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\n/** Build YAML frontmatter string from key-value pairs. */\nexport function buildFrontmatter(fields: Record<string, unknown>): string {\n const dumped = yaml.dump(fields, { lineWidth: -1, quotingType: '\"' }).trimEnd();\n return `---\\n${dumped}\\n---`;\n}\n\n/** Parse YAML frontmatter from a markdown string. Returns { meta, body }. */\nexport function parseFrontmatter(content: string): {\n meta: Record<string, unknown>;\n body: string;\n} {\n const { meta, body } = parseFrontmatterStatus(content);\n return { meta, body };\n}\n\n/**\n * Like `parseFrontmatter` but also reports whether a frontmatter block was\n * present and whether the YAML inside it parsed cleanly. Callers that need\n * to distinguish \"no frontmatter block\" from \"malformed YAML\" (e.g. the\n * viewer collector, which surfaces these as different warnings) use this\n * variant; the plain `parseFrontmatter` stays a thin wrapper so existing\n * callers are unaffected.\n */\nexport function parseFrontmatterStatus(content: string): {\n meta: Record<string, unknown>;\n body: string;\n hasFrontmatterBlock: boolean;\n malformedFrontmatter: boolean;\n} {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n if (!match) {\n return { meta: {}, body: content, hasFrontmatterBlock: false, malformedFrontmatter: false };\n }\n\n let meta: Record<string, unknown> = {};\n let malformedFrontmatter = false;\n try {\n const parsed = yaml.load(match[1]);\n if (parsed && typeof parsed === \"object\") {\n meta = parsed as Record<string, unknown>;\n } else if (parsed !== null && parsed !== undefined) {\n // YAML parsed to a scalar/array — frontmatter must be a mapping.\n malformedFrontmatter = true;\n }\n } catch {\n malformedFrontmatter = true;\n }\n return { meta, body: match[2], hasFrontmatterBlock: true, malformedFrontmatter };\n}\n\n/** Atomically write a file (write to .tmp, then rename). */\nexport async function atomicWrite(filePath: string, content: string): Promise<void> {\n await mkdir(path.dirname(filePath), { recursive: true });\n const tmpPath = filePath + \".tmp\";\n await writeFile(tmpPath, content, \"utf-8\");\n await rename(tmpPath, filePath);\n}\n\n/**\n * Extract all source filenames from ^[filename.md] citation markers in a page body.\n * Handles paragraph form (`^[source.md]`), multi-source (`^[a.md, b.md]`), and the\n * claim-level extension that pins a line range (`^[source.md:42-58]` or\n * `^[source.md#L42-L58]`). Only the filename component is returned — span data is\n * discarded so existing callers continue to receive a flat filename list.\n * @param body - The markdown body text to parse.\n * @returns Array of unique source filenames.\n */\nexport function extractCitations(body: string): string[] {\n const filenames = new Set<string>();\n for (const citation of extractClaimCitations(body)) {\n for (const span of citation.spans) {\n if (span.file.length > 0) filenames.add(span.file);\n }\n }\n return [...filenames];\n}\n\n/**\n * Extract claim-level citations from a markdown body. Each `^[...]` marker\n * becomes one `ClaimCitation`; comma-separated entries inside a single marker\n * become multiple spans on that citation. Entries that fail to parse against\n * the span grammar are returned as bare-file spans so callers can still tell\n * the marker was present (the linter inspects `raw` to flag malformed forms).\n * @param body - The markdown body text to parse.\n * @returns Array of ClaimCitation objects in document order.\n */\nexport function extractClaimCitations(body: string): ClaimCitation[] {\n const citations: ClaimCitation[] = [];\n let match: RegExpExecArray | null;\n CITATION_MARKER_PATTERN.lastIndex = 0;\n while ((match = CITATION_MARKER_PATTERN.exec(body)) !== null) {\n const raw = match[1];\n const spans = parseCitationEntries(raw);\n if (spans.length > 0) citations.push({ raw, spans });\n }\n return citations;\n}\n\n/** Parse the inside of `^[...]` into one or more SourceSpan entries. */\nfunction parseCitationEntries(inner: string): SourceSpan[] {\n const spans: SourceSpan[] = [];\n for (const part of inner.split(\",\")) {\n const trimmed = part.trim();\n if (trimmed.length === 0) continue;\n const span = parseSpanEntry(trimmed);\n // Skip entries with invalid line ranges — the linter flags them separately.\n if (span !== undefined) spans.push(span);\n }\n return spans;\n}\n\n/**\n * Parse a single citation entry (`file.md` / `file.md:1-3` / `file.md#L1-L3`).\n * Returns undefined when the parsed line range is semantically invalid (line\n * numbers must be >= 1 and end must be >= start).\n */\nfunction parseSpanEntry(entry: string): SourceSpan | undefined {\n const match = SPAN_SUFFIX_PATTERN.exec(entry);\n if (!match || !match.groups) {\n return { file: entry };\n }\n const { file, colonStart, colonEnd, hashStart, hashEnd } = match.groups;\n const start = colonStart ?? hashStart;\n const end = colonEnd ?? hashEnd;\n if (start === undefined) return { file };\n const startLine = Number(start);\n const endLine = end === undefined ? startLine : Number(end);\n if (!isValidLineRange(startLine, endLine)) return undefined;\n return { file, lines: { start: startLine, end: endLine } };\n}\n\n/** Returns true when both lines are >= 1 and end is not before start. */\nfunction isValidLineRange(start: number, end: number): boolean {\n return start >= MIN_LINE_NUMBER && end >= start;\n}\n\n/**\n * Detect whether a citation entry is malformed: bracket text that contains\n * `:` or `#` characters but does not match the documented span grammar, or\n * contains a semantically invalid line range (line 0 or end before start).\n * Used by the linter to flag broken claim-level provenance markers.\n */\nexport function isMalformedCitationEntry(entry: string): boolean {\n const trimmed = entry.trim();\n if (trimmed.length === 0) return true;\n if (!trimmed.includes(\":\") && !trimmed.includes(\"#\")) return false;\n const match = SPAN_SUFFIX_PATTERN.exec(trimmed);\n if (!match || !match.groups) return true;\n const { colonStart, colonEnd, hashStart, hashEnd } = match.groups;\n const start = colonStart ?? hashStart;\n const end = colonEnd ?? hashEnd;\n if (start === undefined) return false;\n const startLine = Number(start);\n const endLine = end === undefined ? startLine : Number(end);\n return !isValidLineRange(startLine, endLine);\n}\n\n/**\n * Inspect provenance for a page body, grouping every parsed span by source file.\n * Useful for tooling that wants to render a \"this page draws from\" panel without\n * worrying about how the markers were formatted in source. Each filename maps to\n * a deduplicated list of `{start, end}` line ranges (paragraph-only citations\n * appear as the empty array, signalling \"no specific span\").\n */\nexport function inspectProvenance(body: string): Map<string, Array<{ start: number; end: number }>> {\n const grouped = new Map<string, Array<{ start: number; end: number }>>();\n for (const citation of extractClaimCitations(body)) {\n for (const span of citation.spans) {\n const ranges = grouped.get(span.file) ?? [];\n if (span.lines && !rangeAlreadyTracked(ranges, span.lines)) {\n ranges.push(span.lines);\n }\n grouped.set(span.file, ranges);\n }\n }\n return grouped;\n}\n\n/** Has this start/end pair already been recorded for a file? */\nfunction rangeAlreadyTracked(\n ranges: Array<{ start: number; end: number }>,\n candidate: { start: number; end: number },\n): boolean {\n return ranges.some((r) => r.start === candidate.start && r.end === candidate.end);\n}\n\n/** Read a file, returning empty string if it doesn't exist. */\nexport async function safeReadFile(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf-8\");\n } catch {\n return \"\";\n }\n}\n\n/** Parse a numeric confidence value, clamping to 0..1 and rejecting non-numbers. */\nfunction parseConfidence(raw: unknown): number | undefined {\n if (typeof raw !== \"number\" || !Number.isFinite(raw)) return undefined;\n if (raw < 0) return 0;\n if (raw > 1) return 1;\n return raw;\n}\n\n/** Parse a provenance state string, returning undefined for unknown values. */\nfunction parseProvenanceState(raw: unknown): ProvenanceState | undefined {\n if (typeof raw !== \"string\") return undefined;\n return VALID_PROVENANCE_STATES.has(raw as ProvenanceState)\n ? (raw as ProvenanceState)\n : undefined;\n}\n\n/** Coerce a single contradiction entry to a ContradictionRef, or null if invalid. */\nfunction coerceContradictionEntry(entry: unknown): ContradictionRef | null {\n if (typeof entry === \"string\" && entry.trim().length > 0) {\n return { slug: entry.trim() };\n }\n if (entry && typeof entry === \"object\" && \"slug\" in entry) {\n const obj = entry as { slug: unknown; reason?: unknown };\n if (typeof obj.slug !== \"string\" || obj.slug.trim().length === 0) return null;\n const ref: ContradictionRef = { slug: obj.slug.trim() };\n if (typeof obj.reason === \"string\") ref.reason = obj.reason;\n return ref;\n }\n return null;\n}\n\n/** Parse a contradictedBy array, accepting strings or objects with slug. */\nfunction parseContradictedBy(raw: unknown): ContradictionRef[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const refs = raw\n .map(coerceContradictionEntry)\n .filter((ref): ref is ContradictionRef => ref !== null);\n return refs.length > 0 ? refs : undefined;\n}\n\n/**\n * Extract provenance metadata fields from a parsed frontmatter record.\n * Defensively handles missing or malformed values so existing pages without\n * the new fields continue to parse correctly.\n *\n * Note: legacy pages may also carry an `inferredParagraphs` frontmatter\n * field from earlier compiles. It is intentionally not parsed here —\n * the lint rule derives the count from the rendered body instead, so\n * the cached field is ignored.\n * @param meta - Raw frontmatter object as returned by parseFrontmatter.\n * @returns Typed provenance metadata with only the fields that were present.\n */\nexport function parseProvenanceMetadata(\n meta: Record<string, unknown>,\n): ProvenanceMetadata {\n return {\n confidence: parseConfidence(meta.confidence),\n provenanceState: parseProvenanceState(meta.provenanceState),\n contradictedBy: parseContradictedBy(meta.contradictedBy),\n };\n}\n\n/**\n * Validate that a wiki page has non-empty content and valid frontmatter.\n * Returns true if the page is valid.\n */\nexport function validateWikiPage(content: string): boolean {\n if (!content || content.trim().length === 0) return false;\n\n const { meta, body } = parseFrontmatter(content);\n if (!meta.title) return false;\n if (body.trim().length === 0) return false;\n\n return true;\n}\n","/**\n * Shared writer for the `sources/` directory.\n *\n * Centralises the slug → filename resolution every ingest path needs, so\n * `ingest`, `ingest-session`, and any future ingester get the same\n * protections:\n *\n * - Empty-slug guard (#35): titles that strip to \"\" (e.g. pure emoji)\n * fail with an actionable error instead of writing `sources/.md`.\n * - Stable basename-collision suffix (#36): two distinct sources that\n * slugify to the same name coexist as `name.md` and\n * `name-<8-hex-of-source>.md`; re-ingesting the same source still\n * overwrites in place because the existing file's frontmatter\n * `source` field is consulted before suffixing.\n */\n\nimport { mkdir, readFile, writeFile } from \"fs/promises\";\nimport path from \"path\";\nimport { createHash } from \"crypto\";\nimport { parseFrontmatter, slugify } from \"./markdown.js\";\nimport { SOURCES_DIR } from \"./constants.js\";\n\n/** Length of the hex hash suffix appended to disambiguate basename collisions. */\nconst COLLISION_HASH_LEN = 8;\n\n/**\n * Compute a short, stable hex hash of a source identifier. Stability\n * matters — re-ingesting the same source must always produce the same\n * hash so existing files are overwritten cleanly rather than\n * accumulating duplicates.\n */\nfunction shortHashOfSource(source: string): string {\n return createHash(\"sha256\").update(source).digest(\"hex\").slice(0, COLLISION_HASH_LEN);\n}\n\n/**\n * Resolve the destination filename for a slug + source identity:\n *\n * - When `${slug}.md` does not exist, return `${slug}.md`.\n * - When it exists and its frontmatter `source` matches the incoming\n * source, return `${slug}.md` so re-ingest stays idempotent.\n * - Otherwise return `${slug}-<hash>.md` so two distinct sources that\n * share a basename coexist instead of one silently overwriting the\n * other.\n */\nasync function resolveCollisionFreeFilename(slug: string, source: string): Promise<string> {\n const candidate = `${slug}.md`;\n const candidatePath = path.join(SOURCES_DIR, candidate);\n let existing: string;\n try {\n existing = await readFile(candidatePath, \"utf-8\");\n } catch (err) {\n const e = err as { code?: string };\n if (e.code === \"ENOENT\") return candidate;\n throw err;\n }\n const { meta } = parseFrontmatter(existing);\n if (typeof meta.source === \"string\" && meta.source === source) {\n return candidate;\n }\n return `${slug}-${shortHashOfSource(source)}.md`;\n}\n\n/**\n * Write a markdown document into `sources/` under a slug derived from\n * the title, applying the empty-slug guard and basename-collision\n * disambiguation. Returns the resolved destination path.\n *\n * @param title - Human-readable title used to derive the filename.\n * @param document - Full markdown content (frontmatter + body) to write.\n * @param source - Source identity (URL, file path, etc.) used both for\n * collision disambiguation and idempotency on re-ingest.\n */\nexport async function saveSource(\n title: string,\n document: string,\n source: string,\n): Promise<string> {\n const slug = slugify(title);\n // Defense in depth — even with the Unicode-aware slugifier (#35), a\n // title made entirely of punctuation/emoji/symbols still slugifies to\n // \"\". Without this guard the file would land at sources/.md.\n if (!slug) {\n throw new Error(\n `Could not derive a filename from title \"${title}\". ` +\n `The title contains no letter or number characters. ` +\n `Rename the source file to one with at least one letter or digit.`,\n );\n }\n await mkdir(SOURCES_DIR, { recursive: true });\n const filename = await resolveCollisionFreeFilename(slug, source);\n const destPath = path.join(SOURCES_DIR, filename);\n await writeFile(destPath, document, \"utf-8\");\n return destPath;\n}\n","/**\n * Shared constants for the llmwiki knowledge compiler.\n * Centralized config values to avoid magic numbers scattered across the codebase.\n */\n\n/** Maximum source file size in characters before truncation. */\nexport const MAX_SOURCE_CHARS = 100_000;\n\n/** Minimum source content length to ingest without a warning. */\nexport const MIN_SOURCE_CHARS = 50;\n\n/**\n * Default character budget for the combined source content sent to the LLM\n * during page generation for a single concept (issue #39).\n *\n * Caps the per-prompt content at ~200,000 chars (~50k tokens). When two or\n * more sources contribute to the same concept and their combined raw size\n * exceeds this budget, each source's slice is proportionally truncated so\n * the prompt fits the model's context window. Without this cap, popular\n * concepts that appear in many overlapping documents reliably blow past\n * the LLM provider's context limit and the compile crashes.\n *\n * Override via the LLMWIKI_PROMPT_BUDGET_CHARS env var when running against\n * larger-context (raise) or smaller-context (lower) models.\n */\nexport const DEFAULT_PROMPT_BUDGET_CHARS = 200_000;\n\n/** Env var that overrides DEFAULT_PROMPT_BUDGET_CHARS at runtime. */\nexport const PROMPT_BUDGET_ENV_VAR = \"LLMWIKI_PROMPT_BUDGET_CHARS\";\n\n/** Number of most relevant wiki pages to load for query context. */\nexport const QUERY_PAGE_LIMIT = 5;\n\n/** Maximum concurrent API calls during page generation. */\nexport const COMPILE_CONCURRENCY = 5;\n\n/** API retry configuration. */\nexport const RETRY_COUNT = 3;\nexport const RETRY_BASE_MS = 1000;\nexport const RETRY_MULTIPLIER = 4;\n\n/** Default provider when LLMWIKI_PROVIDER is not set. */\nexport const DEFAULT_PROVIDER = \"anthropic\";\n\n/** Default model per provider. */\nexport const PROVIDER_MODELS: Record<string, string> = {\n anthropic: \"claude-sonnet-4-20250514\",\n openai: \"gpt-4o\",\n ollama: \"llama3.1\",\n minimax: \"MiniMax-M2.7\",\n copilot: \"gpt-4o\",\n};\n\n/** Default Ollama API base URL. */\nexport const OLLAMA_DEFAULT_HOST = \"http://localhost:11434/v1\";\n\n/** GitHub Copilot API base URL (OpenAI-compatible, requires OAuth token). */\nexport const COPILOT_BASE_URL = \"https://api.githubcopilot.com\";\n\n/**\n * Default request timeout for cloud OpenAI-compatible providers (10 minutes).\n * Matches the OpenAI SDK's own default; called out here so it's explicit.\n */\nexport const OPENAI_DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;\n\n/**\n * Default request timeout for Ollama (30 minutes). Local models on modest\n * hardware can take well over the cloud-provider default for a single\n * compile-time completion. Configurable via LLMWIKI_REQUEST_TIMEOUT_MS or\n * OLLAMA_TIMEOUT_MS env vars.\n */\nexport const OLLAMA_DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;\n\n/** Directory names relative to the project root. */\nexport const SOURCES_DIR = \"sources\";\nexport const CONCEPTS_DIR = \"wiki/concepts\";\nexport const QUERIES_DIR = \"wiki/queries\";\nexport const LLMWIKI_DIR = \".llmwiki\";\nexport const STATE_FILE = \".llmwiki/state.json\";\nexport const LOCK_FILE = \".llmwiki/lock\";\nexport const INDEX_FILE = \"wiki/index.md\";\nexport const MOC_FILE = \"wiki/MOC.md\";\nexport const EMBEDDINGS_FILE = \".llmwiki/embeddings.json\";\nexport const LAST_LINT_FILE = \".llmwiki/last-lint.json\";\n\n/** Supported image file extensions for vision-based ingest. */\nexport const IMAGE_EXTENSIONS = new Set([\".jpg\", \".jpeg\", \".png\", \".gif\", \".webp\"]);\n\n/** Supported transcript file extensions (content-sniff .txt separately). */\nexport const TRANSCRIPT_EXTENSIONS = new Set([\".vtt\", \".srt\"]);\n\n/** Max tokens for image-description completions. */\nexport const IMAGE_DESCRIBE_MAX_TOKENS = 2048;\n\n/** Pending review candidates awaiting approval/rejection. */\nexport const CANDIDATES_DIR = \".llmwiki/candidates\";\n\n/** Rejected review candidates archived for audit (not deleted). */\nexport const CANDIDATES_ARCHIVE_DIR = \".llmwiki/candidates/archive\";\n\n/** Number of most similar pages to return from embedding-based pre-filter. */\nexport const EMBEDDING_TOP_K = 15;\n\n/** Number of chunk candidates to retain after the semantic-similarity step. */\nexport const CHUNK_TOP_K = 30;\n\n/** Number of chunk candidates to keep after reranking. */\nexport const CHUNK_RERANK_KEEP = 12;\n\n/** Target chunk size in characters; chunks try to land near this length. */\nexport const CHUNK_TARGET_CHARS = 800;\n\n/** Hard upper bound on a single chunk's character length. */\nexport const CHUNK_MAX_CHARS = 1_400;\n\n/** Minimum standalone chunk size; smaller trailing fragments are merged back. */\nexport const CHUNK_MIN_CHARS = 200;\n\n/** Provenance metadata thresholds used by lint rules. */\nexport const LOW_CONFIDENCE_THRESHOLD = 0.5;\nexport const MAX_INFERRED_PARAGRAPHS_WITHOUT_CITATIONS = 2;\n\n/** Embedding model to use per provider. */\nexport const EMBEDDING_MODELS: Record<string, string> = {\n anthropic: \"voyage-3-lite\",\n openai: \"text-embedding-3-small\",\n ollama: \"nomic-embed-text\",\n};\n","/**\n * ANSI colored terminal output helpers.\n * Provides consistent styling for compilation progress, status messages,\n * and streaming token display.\n */\n\nconst RESET = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\nconst GREEN = \"\\x1b[32m\";\nconst YELLOW = \"\\x1b[33m\";\nconst BLUE = \"\\x1b[34m\";\nconst MAGENTA = \"\\x1b[35m\";\nconst CYAN = \"\\x1b[36m\";\nconst RED = \"\\x1b[31m\";\n\nexport function bold(text: string): string {\n return `${BOLD}${text}${RESET}`;\n}\n\nexport function dim(text: string): string {\n return `${DIM}${text}${RESET}`;\n}\n\nexport function success(text: string): string {\n return `${GREEN}${text}${RESET}`;\n}\n\nexport function warn(text: string): string {\n return `${YELLOW}${text}${RESET}`;\n}\n\nexport function info(text: string): string {\n return `${BLUE}${text}${RESET}`;\n}\n\nexport function error(text: string): string {\n return `${RED}${text}${RESET}`;\n}\n\nexport function source(text: string): string {\n return `${CYAN}${text}${RESET}`;\n}\n\n/** Print a status line with an icon. */\nexport function status(icon: string, message: string): void {\n console.log(`${icon} ${message}`);\n}\n\n/** Print a section header. */\nexport function header(title: string): void {\n console.log(`\\n${BOLD}${title}${RESET}`);\n console.log(dim(\"─\".repeat(Math.min(title.length + 4, 60))));\n}\n","/**\n * Web URL ingestion module.\n * Fetches a URL, extracts readable content using Mozilla Readability,\n * and converts the result to clean markdown via Turndown.\n *\n * Throws descriptive errors on network failures or when the page\n * cannot be parsed into readable content.\n */\n\nimport { JSDOM } from \"jsdom\";\nimport { Readability } from \"@mozilla/readability\";\nimport TurndownService from \"turndown\";\n\ninterface WebIngestResult {\n title: string;\n content: string;\n}\n\n/** Fetch a URL and return its readable content as markdown. */\nasync function fetchAndParse(url: string): Promise<Response> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: HTTP ${response.status}`);\n }\n return response;\n}\n\n/** Extract readable content from raw HTML using Readability. */\nfunction extractReadableContent(html: string, url: string): { title: string; htmlContent: string } {\n const dom = new JSDOM(html, { url });\n const reader = new Readability(dom.window.document);\n const article = reader.parse();\n\n if (!article || !article.content) {\n throw new Error(`Could not extract readable content from ${url}`);\n }\n\n return {\n title: article.title || \"Untitled\",\n htmlContent: article.content,\n };\n}\n\n/** Convert HTML to clean markdown using Turndown. */\nfunction convertToMarkdown(html: string): string {\n const turndown = new TurndownService({ headingStyle: \"atx\" });\n return turndown.turndown(html);\n}\n\n/**\n * Ingest a web URL and return its content as markdown.\n * @param url - The URL to fetch and convert.\n * @returns An object with the extracted title and markdown content.\n * @throws On network failure or unparseable content.\n */\nexport default async function ingestWeb(url: string): Promise<WebIngestResult> {\n const response = await fetchAndParse(url);\n const html = await response.text();\n const { title, htmlContent } = extractReadableContent(html, url);\n const content = convertToMarkdown(htmlContent);\n\n return { title, content };\n}\n","/**\n * Local file ingestion module.\n * Reads .md and .txt files from the local filesystem and returns their\n * content as markdown. Markdown files are returned as-is; plain text files\n * are wrapped in a markdown code block. All other extensions are rejected.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { titleFromFilename, type IngestedSource } from \"./shared.js\";\n\n/** Plain-text file extensions handled directly by this module. */\nconst SUPPORTED_EXTENSIONS = new Set([\".md\", \".txt\"]);\n\n/** Wrap plain text content in a markdown fenced block. */\nfunction wrapPlainText(text: string): string {\n return `\\`\\`\\`\\n${text}\\n\\`\\`\\``;\n}\n\n/**\n * Ingest a local file and return its content as markdown.\n * @param filePath - Absolute or relative path to a .md or .txt file.\n * @returns An object with a title derived from the filename and the markdown content.\n * @throws On unsupported file type or read failure.\n */\nexport default async function ingestFile(filePath: string): Promise<IngestedSource> {\n const ext = path.extname(filePath).toLowerCase();\n\n if (!SUPPORTED_EXTENSIONS.has(ext)) {\n throw new Error(\n `Unsupported file type \"${ext}\". Only .md and .txt files are supported.`\n );\n }\n\n const raw = await readFile(filePath, \"utf-8\");\n const title = titleFromFilename(filePath);\n const content = ext === \".md\" ? raw : wrapPlainText(raw);\n\n return { title, content };\n}\n","/**\n * Shared helpers used by every ingest module.\n *\n * Centralizes the IngestedSource result shape and title-derivation logic so\n * each per-format ingester (file, pdf, image, transcript, web) doesn't\n * reimplement the same primitives.\n */\n\nimport path from \"path\";\n\n/** Common shape returned by every ingest module. */\nexport interface IngestedSource {\n title: string;\n content: string;\n}\n\n/**\n * Derive a human-readable title from a filename.\n *\n * Strips the extension and converts dashes/underscores to spaces so that\n * \"quarterly_report.pdf\" becomes \"quarterly report\".\n *\n * @param filePath - Path to a source file.\n * @returns Humanized title (lowercase preserved, no extension).\n */\nexport function titleFromFilename(filePath: string): string {\n const basename = path.basename(filePath, path.extname(filePath));\n return basename.replace(/[-_]+/g, \" \").trim();\n}\n","/**\n * PDF ingestion module.\n *\n * Reads a local PDF file using the pdf-parse v2 PDFParse class, extracts the\n * text content via getText() and the document metadata via getInfo(). The\n * title comes from the PDF's Info dictionary when present, falling back to\n * the filename. Pages are joined into a single markdown body.\n *\n * pdf-parse (and its transitive pdfjs-dist) is imported dynamically so the\n * cost of loading the PDF parser is only paid when a PDF is actually being\n * ingested — `node dist/cli.js --help` and every other non-PDF code path\n * stays lean.\n */\n\nimport { readFile } from \"fs/promises\";\nimport { titleFromFilename, type IngestedSource } from \"./shared.js\";\n\n/** Extract the title from PDF metadata or fall back to the filename. */\nexport function resolveTitle(filePath: string, info: unknown): string {\n if (info && typeof info === \"object\") {\n const titleField = (info as Record<string, unknown>)[\"Title\"];\n if (typeof titleField === \"string\" && titleField.trim().length > 0) {\n return titleField.trim();\n }\n }\n return titleFromFilename(filePath);\n}\n\n/**\n * Ingest a local PDF file and return its text content with the document title.\n *\n * pdf-parse is imported dynamically so this module's load cost stays minimal\n * for non-PDF code paths (the parser pulls in pdfjs-dist which is sizeable).\n *\n * @param filePath - Absolute or relative path to a .pdf file.\n * @returns An object with the document title and extracted text content.\n * @throws On read failure or unparseable PDF.\n */\nexport default async function ingestPdf(filePath: string): Promise<IngestedSource> {\n const { PDFParse } = await import(\"pdf-parse\");\n\n const buffer = await readFile(filePath);\n const parser = new PDFParse({ data: new Uint8Array(buffer) });\n\n try {\n // Sequential calls are required: pdfjs-dist's LoopbackPort.postMessage\n // uses structuredClone internally; concurrent calls cause a DataCloneError\n // when the port tries to transfer the same underlying state simultaneously.\n const textResult = await parser.getText();\n const infoResult = await parser.getInfo();\n\n const title = resolveTitle(filePath, infoResult.info);\n const content = textResult.text.trim();\n return { title, content };\n } finally {\n await parser.destroy();\n }\n}\n","/**\n * Image ingestion module using LLM vision capabilities.\n *\n * Reads a local image file, encodes it as base64, and sends it to the\n * configured LLM provider's vision endpoint for OCR-plus-description\n * extraction. Requires the active provider to support image content blocks\n * (currently: Anthropic).\n *\n * Throws a clear error when the provider does not support vision, rather\n * than falling back silently.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { buildAnthropicClientOptions } from \"../providers/anthropic.js\";\nimport { IMAGE_DESCRIBE_MAX_TOKENS } from \"../utils/constants.js\";\nimport { resolveAnthropicAuthFromEnv, resolveAnthropicBaseURLFromEnv, resolveAnthropicModelFromEnv } from \"../utils/claude-settings.js\";\nimport { PROVIDER_MODELS } from \"../utils/constants.js\";\nimport { titleFromFilename, type IngestedSource } from \"./shared.js\";\n\n/** Mime types supported by Anthropic vision. */\ntype AnthropicImageMediaType = \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\";\n\nconst EXTENSION_TO_MIME: Record<string, AnthropicImageMediaType> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n};\n\n/** Return the MIME type for an image file, or throw on unknown extension. */\nfunction mimeTypeForExtension(ext: string): AnthropicImageMediaType {\n const mimeType = EXTENSION_TO_MIME[ext.toLowerCase()];\n if (!mimeType) {\n throw new Error(\n `Unsupported image extension \"${ext}\". Supported: ${Object.keys(EXTENSION_TO_MIME).join(\", \")}`,\n );\n }\n return mimeType;\n}\n\n/** Build an Anthropic client from the current environment config. */\nfunction buildClient(): Anthropic {\n const baseURL = resolveAnthropicBaseURLFromEnv();\n const auth = resolveAnthropicAuthFromEnv();\n return new Anthropic(buildAnthropicClientOptions({ baseURL, ...auth }));\n}\n\n/** Send an image to Anthropic vision and return the extracted description. */\nasync function describeImageWithVision(\n client: Anthropic,\n model: string,\n imageData: string,\n mimeType: AnthropicImageMediaType,\n): Promise<string> {\n const response = await client.messages.create({\n model,\n max_tokens: IMAGE_DESCRIBE_MAX_TOKENS,\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"image\",\n source: { type: \"base64\", media_type: mimeType, data: imageData },\n },\n {\n type: \"text\",\n text: \"Extract and transcribe all text visible in this image. Then provide a detailed description of any non-text visual content. Format your response as markdown.\",\n },\n ],\n },\n ],\n });\n\n const textBlock = response.content.find((block) => block.type === \"text\");\n return textBlock?.type === \"text\" ? textBlock.text : \"\";\n}\n\n/**\n * Ingest a local image file using LLM vision for OCR and description.\n *\n * Only Anthropic is supported for vision. The active provider must be\n * Anthropic; if not, a clear error is thrown rather than degrading silently.\n *\n * @param filePath - Absolute or relative path to an image file.\n * @returns An object with a title derived from the filename and the extracted content.\n * @throws When the provider does not support vision or on read/API failure.\n */\nexport default async function ingestImage(filePath: string): Promise<IngestedSource> {\n const providerName = process.env.LLMWIKI_PROVIDER ?? \"anthropic\";\n\n if (providerName !== \"anthropic\") {\n throw new Error(\n `Image ingest requires the Anthropic provider (vision). ` +\n `Current provider: \"${providerName}\". ` +\n `Set LLMWIKI_PROVIDER=anthropic and ANTHROPIC_API_KEY to use image ingest.`,\n );\n }\n\n const ext = path.extname(filePath).toLowerCase();\n const mimeType = mimeTypeForExtension(ext);\n const imageBuffer = await readFile(filePath);\n const imageData = imageBuffer.toString(\"base64\");\n\n const client = buildClient();\n const model = resolveAnthropicModelFromEnv() ?? PROVIDER_MODELS.anthropic;\n const content = await describeImageWithVision(client, model, imageData, mimeType);\n const title = titleFromFilename(filePath);\n\n return { title, content };\n}\n","/**\n * Anthropic LLM provider implementation.\n *\n * Wraps the @anthropic-ai/sdk to implement the LLMProvider interface.\n * Handles complete, streaming, and tool-use calls against Claude models.\n */\n\nimport Anthropic, { type ClientOptions } from \"@anthropic-ai/sdk\";\nimport type { LLMProvider, LLMMessage, LLMTool } from \"../utils/provider.js\";\nimport { EMBEDDING_MODELS } from \"../utils/constants.js\";\n\nconst VOYAGE_EMBEDDINGS_URL = \"https://api.voyageai.com/v1/embeddings\";\n\n/**\n * Builds the client options for the Anthropic SDK.\n *\n * Handles optional baseURL and filters out empty values so the SDK\n * can fall back to its internal defaults when not specified.\n */\ninterface AnthropicProviderOptions {\n apiKey?: string;\n authToken?: string;\n baseURL?: string;\n}\n\nexport function buildAnthropicClientOptions(\n options: AnthropicProviderOptions = {},\n): ClientOptions {\n const trimmedBaseURL = options.baseURL?.trim();\n const trimmedApiKey = options.apiKey?.trim();\n const trimmedAuthToken = options.authToken?.trim();\n\n const result: ClientOptions = {};\n\n if (trimmedApiKey) {\n result.apiKey = trimmedApiKey;\n }\n if (trimmedAuthToken) {\n result.authToken = trimmedAuthToken;\n }\n\n if (!trimmedBaseURL) {\n return result;\n }\n\n const normalizedBaseURL =\n trimmedBaseURL.endsWith(\"/\") && trimmedBaseURL.length > 1\n ? trimmedBaseURL.slice(0, -1)\n : trimmedBaseURL;\n\n result.baseURL = normalizedBaseURL;\n return result;\n}\n\n\n/** Anthropic-backed LLM provider using the official SDK. */\nexport class AnthropicProvider implements LLMProvider {\n private readonly client: Anthropic;\n private readonly model: string;\n\n constructor(model: string, options: AnthropicProviderOptions = {}) {\n this.model = model;\n this.client = new Anthropic(buildAnthropicClientOptions(options));\n }\n\n /** Send a single non-streaming completion request. */\n async complete(system: string, messages: LLMMessage[], maxTokens: number): Promise<string> {\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: maxTokens,\n system,\n messages,\n });\n\n const textBlock = response.content.find((block) => block.type === \"text\");\n return textBlock?.type === \"text\" ? textBlock.text : \"\";\n }\n\n /** Stream a completion, invoking onToken for each text chunk. */\n async stream(\n system: string,\n messages: LLMMessage[],\n maxTokens: number,\n onToken?: (text: string) => void,\n ): Promise<string> {\n const stream = this.client.messages.stream({\n model: this.model,\n max_tokens: maxTokens,\n system,\n messages,\n });\n\n let fullText = \"\";\n for await (const event of stream) {\n if (event.type === \"content_block_delta\" && event.delta.type === \"text_delta\") {\n fullText += event.delta.text;\n onToken?.(event.delta.text);\n }\n }\n\n return fullText;\n }\n\n /** Call Claude with tool definitions and return the parsed tool input as JSON. */\n async toolCall(\n system: string,\n messages: LLMMessage[],\n tools: LLMTool[],\n maxTokens: number,\n ): Promise<string> {\n const anthropicTools: Anthropic.Tool[] = tools.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.input_schema as Anthropic.Tool.InputSchema,\n }));\n\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: maxTokens,\n system,\n messages,\n tools: anthropicTools,\n });\n\n const toolBlock = response.content.find((block) => block.type === \"tool_use\");\n if (toolBlock?.type === \"tool_use\") {\n return JSON.stringify(toolBlock.input);\n }\n\n const textBlock = response.content.find((block) => block.type === \"text\");\n return textBlock?.type === \"text\" ? textBlock.text : \"\";\n }\n\n /**\n * Produce a single embedding vector via the Voyage API.\n *\n * Anthropic does not ship a first-party embeddings endpoint, so we delegate\n * to Voyage (their recommended partner). Requires VOYAGE_API_KEY.\n */\n async embed(text: string): Promise<number[]> {\n const apiKey = process.env.VOYAGE_API_KEY?.trim();\n if (!apiKey) {\n throw new Error(\n \"VOYAGE_API_KEY is not set. Anthropic embeddings use Voyage — set VOYAGE_API_KEY to enable semantic search.\",\n );\n }\n\n const response = await fetch(VOYAGE_EMBEDDINGS_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ input: text, model: EMBEDDING_MODELS.anthropic }),\n });\n\n if (!response.ok) {\n const detail = await response.text();\n throw new Error(`Voyage embeddings request failed (${response.status}): ${detail}`);\n }\n\n const json = (await response.json()) as { data?: Array<{ embedding?: number[] }> };\n const vector = json.data?.[0]?.embedding;\n if (!Array.isArray(vector)) {\n throw new Error(\"Voyage embeddings response did not include a vector.\");\n }\n return vector;\n }\n}\n","/**\n * Claude settings fallback helpers.\n *\n * Provides a narrow, read-only integration with `~/.claude/settings.json`.\n * We only read the `env` object and only extract Anthropic-related values that\n * llmwiki can safely consume. Explicit process env values remain higher priority.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nconst CLAUDE_SETTINGS_PATH_ENV = \"LLMWIKI_CLAUDE_SETTINGS_PATH\";\n\ninterface ClaudeSettingsEnv {\n ANTHROPIC_API_KEY?: string;\n ANTHROPIC_AUTH_TOKEN?: string;\n ANTHROPIC_BASE_URL?: string;\n ANTHROPIC_MODEL?: string;\n}\n\ninterface AnthropicAuthConfig {\n apiKey?: string;\n authToken?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction normalize(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction resolveClaudeSettingsPath(env: NodeJS.ProcessEnv): string {\n return env[CLAUDE_SETTINGS_PATH_ENV] ?? path.join(homedir(), \".claude\", \"settings.json\");\n}\n\nfunction readClaudeSettingsFile(settingsPath: string): string | undefined {\n try {\n return readFileSync(settingsPath, \"utf8\");\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") {\n return undefined;\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read Claude settings at \"${settingsPath}\": ${message}`);\n }\n}\n\nexport function readClaudeSettingsEnv(env: NodeJS.ProcessEnv = process.env): ClaudeSettingsEnv | undefined {\n const settingsPath = resolveClaudeSettingsPath(env);\n const raw = readClaudeSettingsFile(settingsPath);\n if (!raw) return undefined;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse Claude settings at \"${settingsPath}\": ${message}`);\n }\n\n if (!isRecord(parsed) || !isRecord(parsed.env)) {\n return undefined;\n }\n\n const values: ClaudeSettingsEnv = {\n ANTHROPIC_API_KEY: normalize(parsed.env.ANTHROPIC_API_KEY),\n ANTHROPIC_AUTH_TOKEN: normalize(parsed.env.ANTHROPIC_AUTH_TOKEN),\n ANTHROPIC_BASE_URL: normalize(parsed.env.ANTHROPIC_BASE_URL),\n ANTHROPIC_MODEL: normalize(parsed.env.ANTHROPIC_MODEL),\n };\n\n if (!values.ANTHROPIC_API_KEY && !values.ANTHROPIC_AUTH_TOKEN && !values.ANTHROPIC_BASE_URL && !values.ANTHROPIC_MODEL) {\n return undefined;\n }\n return values;\n}\n\nfunction tryReadClaudeSettingsEnv(env: NodeJS.ProcessEnv): ClaudeSettingsEnv | undefined {\n try {\n return readClaudeSettingsEnv(env);\n } catch {\n return undefined;\n }\n}\n\nfunction validateAnthropicBaseURL(value: string): string {\n const normalized = value.trim();\n try {\n const parsed = new URL(normalized);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(\"Must use http:// or https:// protocol.\");\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Must be a valid http(s) URL.\";\n throw new Error(`Invalid ANTHROPIC_BASE_URL: \"${normalized}\". ${message}`);\n }\n return normalized;\n}\n\nexport function resolveAnthropicAuthFromEnv(env: NodeJS.ProcessEnv = process.env): AnthropicAuthConfig {\n const explicitApiKey = normalize(env.ANTHROPIC_API_KEY);\n if (explicitApiKey) return { apiKey: explicitApiKey };\n\n const explicitAuthToken = normalize(env.ANTHROPIC_AUTH_TOKEN);\n if (explicitAuthToken) return { authToken: explicitAuthToken };\n\n const fallback = readClaudeSettingsEnv(env);\n if (fallback?.ANTHROPIC_API_KEY) return { apiKey: fallback.ANTHROPIC_API_KEY };\n if (fallback?.ANTHROPIC_AUTH_TOKEN) return { authToken: fallback.ANTHROPIC_AUTH_TOKEN };\n return {};\n}\n\nexport function resolveAnthropicModelFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const explicitModel = env.LLMWIKI_MODEL;\n if (explicitModel !== undefined) return explicitModel;\n return tryReadClaudeSettingsEnv(env)?.ANTHROPIC_MODEL;\n}\n\nexport function resolveAnthropicBaseURLFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const explicitBaseURL = normalize(env.ANTHROPIC_BASE_URL);\n if (explicitBaseURL) return validateAnthropicBaseURL(explicitBaseURL);\n\n const fallbackBaseURL = tryReadClaudeSettingsEnv(env)?.ANTHROPIC_BASE_URL;\n if (!fallbackBaseURL) return undefined;\n return validateAnthropicBaseURL(fallbackBaseURL);\n}\n","/**\n * Transcript ingestion module.\n *\n * Handles three transcript source types:\n * 1. YouTube URLs — fetched via the youtube-transcript package.\n * 2. WebVTT (.vtt) — speaker/time markers preserved in output.\n * 3. SubRip (.srt) — speaker/time markers preserved in output.\n * 4. Plain-text (.txt) with speaker tags (e.g. \"Speaker: text\").\n *\n * Speaker and timing metadata are kept in the output so downstream\n * compilation can reference them. Content is returned as markdown.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { titleFromFilename, type IngestedSource } from \"./shared.js\";\n// youtube-transcript exports proper ESM via its package.json `exports` map.\n// Import from the package root; the \"import\" condition resolves to\n// ./dist/esm/index.js and includes bundled .d.ts types.\nimport { YoutubeTranscript } from \"youtube-transcript\";\n\n/** Pattern that identifies a YouTube URL. */\nconst YOUTUBE_URL_PATTERN = /^https?:\\/\\/(www\\.)?(youtube\\.com\\/watch|youtu\\.be\\/)/;\n\n/** Pattern for SRT sequence number lines (numeric-only). */\nconst SRT_SEQUENCE_PATTERN = /^\\d+$/;\n\n/** Pattern for SRT/VTT timestamp lines. */\nconst TIMESTAMP_PATTERN = /\\d{2}:\\d{2}[:.]\\d{2}/;\n\n/** Number of milliseconds in one minute. */\nconst MS_PER_MINUTE = 60_000;\n\n/** Number of milliseconds in one second. */\nconst MS_PER_SECOND = 1_000;\n\n/** Check whether a source string is a YouTube URL. */\nexport function isYoutubeUrl(source: string): boolean {\n return YOUTUBE_URL_PATTERN.test(source);\n}\n\n/** Extract the YouTube video ID from a URL. */\nfunction extractVideoId(url: string): string {\n const match = url.match(/(?:v=|youtu\\.be\\/)([^&?/]+)/);\n if (!match) {\n throw new Error(`Could not extract video ID from YouTube URL: ${url}`);\n }\n return match[1];\n}\n\n/** Format a millisecond offset as a \"MM:SS\" timestamp. */\nfunction formatOffset(offsetMs: number): string {\n const minutes = Math.floor(offsetMs / MS_PER_MINUTE);\n const seconds = Math.floor((offsetMs % MS_PER_MINUTE) / MS_PER_SECOND);\n return `${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/** Fetch and format a YouTube transcript as markdown. */\nasync function fetchYoutubeTranscript(url: string): Promise<IngestedSource> {\n const videoId = extractVideoId(url);\n const segments = await YoutubeTranscript.fetchTranscript(videoId);\n\n if (!segments || segments.length === 0) {\n throw new Error(`No transcript available for YouTube video: ${url}`);\n }\n\n const lines = segments.map((seg) => `[${formatOffset(seg.offset)}] ${seg.text}`);\n\n return {\n title: `YouTube Transcript ${videoId}`,\n content: lines.join(\"\\n\"),\n };\n}\n\n/** Decide whether a trimmed line is a VTT/SRT cue timestamp marker. */\nfunction isCueTimestamp(trimmed: string): boolean {\n return TIMESTAMP_PATTERN.test(trimmed) && trimmed.includes(\"-->\");\n}\n\n/** Parse a VTT file, preserving speaker cues and timestamps. */\nfunction parseVtt(raw: string, filePath: string): IngestedSource {\n const lines = raw.split(\"\\n\");\n const output: string[] = [];\n let inCue = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === \"WEBVTT\" || trimmed === \"\") {\n inCue = false;\n continue;\n }\n if (isCueTimestamp(trimmed)) {\n output.push(`\\n**[${trimmed}]**`);\n inCue = true;\n continue;\n }\n if (inCue && trimmed.length > 0) {\n output.push(trimmed);\n }\n }\n\n return { title: titleFromFilename(filePath), content: output.join(\"\\n\").trim() };\n}\n\n/** Parse an SRT file, preserving speaker cues and timestamps. */\nfunction parseSrt(raw: string, filePath: string): IngestedSource {\n const lines = raw.split(\"\\n\");\n const output: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === \"\" || SRT_SEQUENCE_PATTERN.test(trimmed)) {\n continue;\n }\n if (isCueTimestamp(trimmed)) {\n output.push(`\\n**[${trimmed}]**`);\n continue;\n }\n if (trimmed.length > 0) {\n output.push(trimmed);\n }\n }\n\n return { title: titleFromFilename(filePath), content: output.join(\"\\n\").trim() };\n}\n\n/** Parse a plain-text transcript, preserving speaker tags. */\nfunction parsePlainTranscript(raw: string, filePath: string): IngestedSource {\n // Plain .txt transcripts are returned as-is; speaker lines like \"Alice: ...\"\n // are naturally readable.\n return { title: titleFromFilename(filePath), content: raw.trim() };\n}\n\n/**\n * Ingest a transcript source: a YouTube URL or a local .vtt/.srt/.txt file.\n *\n * @param source - YouTube URL or path to a transcript file.\n * @returns Title and markdown-formatted content with speaker/time markers.\n * @throws On network failure, missing transcript, or unsupported file type.\n */\nexport default async function ingestTranscript(source: string): Promise<IngestedSource> {\n if (isYoutubeUrl(source)) {\n return fetchYoutubeTranscript(source);\n }\n\n const ext = path.extname(source).toLowerCase();\n const raw = await readFile(source, \"utf-8\");\n\n if (ext === \".vtt\") return parseVtt(raw, source);\n if (ext === \".srt\") return parseSrt(raw, source);\n if (ext === \".txt\") return parsePlainTranscript(raw, source);\n\n throw new Error(\n `Unsupported transcript file type \"${ext}\". Supported: .vtt, .srt, .txt`,\n );\n}\n","/**\n * Commander action for `llmwiki ingest-session <path>`.\n *\n * Accepts a single session export file or a directory of session files.\n * Each file is detected, parsed, and written to `sources/` as a markdown\n * document with YAML frontmatter recording the adapter name and timestamps.\n *\n * Supported formats (auto-detected): Claude, Codex, Cursor.\n */\n\nimport path from \"path\";\nimport { readdir, stat } from \"fs/promises\";\nimport { buildFrontmatter } from \"../utils/markdown.js\";\nimport { saveSource } from \"../utils/source-writer.js\";\nimport * as output from \"../utils/output.js\";\nimport { parseSessionFile, formatSessionAsMarkdown } from \"../adapters/registry.js\";\nimport type { NormalizedSession } from \"../adapters/types.js\";\n\n/** Result of ingesting a single session file. */\ninterface SessionIngestResult {\n filename: string;\n adapter: string;\n title: string;\n source: string;\n}\n\n/** Build the YAML frontmatter for a session source. */\nfunction buildSessionFrontmatter(session: NormalizedSession, sourcePath: string): string {\n const meta: Record<string, unknown> = {\n title: session.title,\n source: sourcePath,\n adapter: session.adapter,\n ingestedAt: new Date().toISOString(),\n };\n if (session.startedAt) meta.sessionStartedAt = session.startedAt;\n if (session.endedAt) meta.sessionEndedAt = session.endedAt;\n if (session.participantIdentity) meta.participant = session.participantIdentity;\n\n return buildFrontmatter(meta);\n}\n\n/**\n * Write a session as a markdown file under `sources/` using the shared\n * source writer — gets the empty-slug guard (#35) and the\n * basename-collision suffix (#36) for free, so two sessions with the\n * same title from different transcript files coexist instead of one\n * silently overwriting the other.\n */\nasync function saveSessionSource(session: NormalizedSession, sourcePath: string): Promise<string> {\n const frontmatter = buildSessionFrontmatter(session, sourcePath);\n const body = formatSessionAsMarkdown(session);\n const document = `${frontmatter}\\n\\n${body}\\n`;\n return saveSource(session.title, document, sourcePath);\n}\n\n/**\n * Ingest a single session file.\n * @throws When the file is not recognised or is malformed.\n */\nexport async function ingestSessionFile(filePath: string): Promise<SessionIngestResult> {\n output.status(\"*\", output.info(`Ingesting session: ${filePath}`));\n\n const session = await parseSessionFile(filePath);\n const savedPath = await saveSessionSource(session, filePath);\n\n output.status(\n \"+\",\n output.success(\n `Saved ${output.bold(path.basename(savedPath))} [${session.adapter}] → ${output.source(savedPath)}`\n )\n );\n\n return {\n filename: path.basename(savedPath),\n adapter: session.adapter,\n title: session.title,\n source: filePath,\n };\n}\n\n/** Collect all files directly inside a directory (non-recursive). */\nasync function listDirectoryFiles(dirPath: string): Promise<string[]> {\n const entries = await readdir(dirPath);\n const files: string[] = [];\n\n for (const entry of entries) {\n const full = path.join(dirPath, entry);\n const info = await stat(full);\n if (info.isFile()) files.push(full);\n }\n\n return files;\n}\n\n/**\n * Ingest all session files in a directory. Recognised files import; the\n * rest are skipped with a warning so a single bad file doesn't abort\n * the whole batch.\n *\n * Throws when ZERO files imported successfully, even if the directory\n * contained candidate files. A bulk run with nothing usable is a\n * failure mode the user needs to know about — exiting 0 with \"Imported\n * 0 session(s), skipped N\" was easy to miss in scripts.\n */\nasync function ingestDirectory(dirPath: string): Promise<void> {\n const files = await listDirectoryFiles(dirPath);\n\n if (files.length === 0) {\n throw new Error(`No files found in directory: ${dirPath}`);\n }\n\n output.status(\"*\", output.info(`Scanning ${files.length} file(s) in: ${dirPath}`));\n\n let imported = 0;\n let skipped = 0;\n\n for (const file of files) {\n try {\n await ingestSessionFile(file);\n imported++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.warn(`Skipped ${path.basename(file)}: ${message}`));\n skipped++;\n }\n }\n\n if (imported === 0) {\n throw new Error(\n `No sessions imported from ${dirPath} (${skipped} file(s) skipped). ` +\n `Check that at least one file is in a supported session format.`,\n );\n }\n\n output.status(\n \"→\",\n output.dim(`Imported ${imported} session(s), skipped ${skipped}.`)\n );\n}\n\n/**\n * Entry point for `llmwiki ingest-session <pathOrDir>`.\n * Dispatches to single-file or directory import based on the target type.\n */\nexport default async function ingestSession(targetPath: string): Promise<void> {\n const info = await stat(targetPath).catch(() => {\n throw new Error(`Path not found: ${targetPath}`);\n });\n\n if (info.isDirectory()) {\n await ingestDirectory(targetPath);\n } else {\n await ingestSessionFile(targetPath);\n }\n\n output.status(\"→\", output.dim(\"Next: llmwiki compile\"));\n}\n","/**\n * Adapter for Claude Code session exports.\n *\n * Claude Code writes session transcripts as newline-delimited JSON (`.jsonl`)\n * under `~/.claude/projects/<project>/<session>.jsonl`. Each line is a JSON\n * object representing one event in the session.\n *\n * Supported event schemas (as observed in Claude Code v≥0.2):\n * - `{ type: \"user\", message: { role: \"user\", content: <string|array> } }`\n * - `{ type: \"assistant\", message: { role: \"assistant\", content: <string|array> } }`\n *\n * The adapter extracts the first user message as the session title and records\n * the timestamp range from `timestamp` fields when present.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport type { SessionAdapter, NormalizedSession, SessionTurn } from \"./types.js\";\nimport { resolveSessionTitle } from \"./utils.js\";\n\nconst CLAUDE_EXTENSION = \".jsonl\";\n\n/** Known marker strings in the first line that confirm Claude Code origin. */\nconst CLAUDE_TYPE_MARKERS = new Set([\"user\", \"assistant\", \"system\", \"tool_use\", \"tool_result\"]);\n\ninterface ClaudeContentBlock {\n type: string;\n text?: string;\n}\n\ninterface ClaudeMessage {\n role: \"user\" | \"assistant\";\n content: string | ClaudeContentBlock[];\n}\n\ninterface ClaudeEvent {\n type: string;\n message?: ClaudeMessage;\n timestamp?: string;\n}\n\n/** Extract plain text from a Claude content value (string or block array). */\nfunction extractText(content: string | ClaudeContentBlock[]): string {\n if (typeof content === \"string\") return content;\n return content\n .filter((b) => b.type === \"text\" && typeof b.text === \"string\")\n .map((b) => b.text as string)\n .join(\"\\n\");\n}\n\n/** Derive a title from the first non-empty user turn text. */\nfunction titleFromFirstUserMessage(turns: SessionTurn[]): string {\n const firstUser = turns.find((t) => t.role === \"user\" && t.content.trim().length > 0);\n return resolveSessionTitle(undefined, firstUser?.content, \"Claude Session\");\n}\n\n/** Parse a single JSONL line into a ClaudeEvent, returning null on failure. */\nfunction parseLine(line: string): ClaudeEvent | null {\n try {\n return JSON.parse(line) as ClaudeEvent;\n } catch {\n return null;\n }\n}\n\n/** Convert a ClaudeEvent into a SessionTurn, or null if not a conversation event. */\nfunction eventToTurn(event: ClaudeEvent): SessionTurn | null {\n if (!event.message || !event.message.role) return null;\n const role = event.message.role;\n if (role !== \"user\" && role !== \"assistant\") return null;\n\n const content = extractText(event.message.content);\n if (content.trim().length === 0) return null;\n\n return { role, content, timestamp: event.timestamp };\n}\n\nexport const claudeAdapter: SessionAdapter = {\n name: \"claude\",\n\n async detect(filePath: string): Promise<boolean> {\n if (path.extname(filePath).toLowerCase() !== CLAUDE_EXTENSION) return false;\n const raw = await readFile(filePath, \"utf-8\").catch(() => \"\");\n const firstLine = raw.split(\"\\n\")[0].trim();\n if (!firstLine.startsWith(\"{\")) return false;\n try {\n const obj = JSON.parse(firstLine) as ClaudeEvent;\n return typeof obj.type === \"string\" && CLAUDE_TYPE_MARKERS.has(obj.type);\n } catch {\n return false;\n }\n },\n\n async parse(filePath: string): Promise<NormalizedSession> {\n const raw = await readFile(filePath, \"utf-8\");\n const lines = raw.split(\"\\n\").filter((l) => l.trim().length > 0);\n\n if (lines.length === 0) {\n throw new Error(`Claude session file is empty: ${filePath}`);\n }\n\n const turns: SessionTurn[] = [];\n const timestamps: string[] = [];\n\n for (const [index, line] of lines.entries()) {\n const event = parseLine(line);\n if (event === null) {\n throw new Error(\n `Malformed JSON on line ${index + 1} of Claude session: ${filePath}`\n );\n }\n if (event.timestamp) timestamps.push(event.timestamp);\n const turn = eventToTurn(event);\n if (turn) turns.push(turn);\n }\n\n const title = titleFromFirstUserMessage(turns);\n\n return {\n title,\n adapter: \"claude\",\n startedAt: timestamps[0],\n endedAt: timestamps[timestamps.length - 1],\n participantIdentity: \"Claude Code\",\n turns,\n };\n },\n};\n","/**\n * Shared utilities for session adapters.\n *\n * These helpers are used by multiple adapters to avoid code duplication\n * for common operations like title truncation and JSON file parsing.\n */\n\n/** Maximum title characters before truncation with an ellipsis. */\nconst MAX_TITLE_CHARS = 80;\n\n/**\n * Truncate a string to `MAX_TITLE_CHARS` and append an ellipsis if needed.\n * @param text - The input string to truncate.\n */\nfunction truncateTitle(text: string): string {\n const trimmed = text.trim();\n return trimmed.length > MAX_TITLE_CHARS\n ? trimmed.slice(0, MAX_TITLE_CHARS).trimEnd() + \"…\"\n : trimmed;\n}\n\n/**\n * Derive a session title from an optional raw title or a fallback default.\n * Falls back to the first line of `firstUserContent` when `rawTitle` is absent,\n * and to `defaultTitle` when both are unavailable.\n *\n * @param rawTitle - Optional title from session metadata.\n * @param firstUserContent - Content of the first user turn (for fallback).\n * @param defaultTitle - Adapter-specific default (e.g. \"Claude Session\").\n */\nexport function resolveSessionTitle(\n rawTitle: string | undefined,\n firstUserContent: string | undefined,\n defaultTitle: string,\n): string {\n if (rawTitle && rawTitle.trim().length > 0) return truncateTitle(rawTitle);\n if (firstUserContent) {\n const firstLine = firstUserContent.split(\"\\n\")[0];\n if (firstLine.trim().length > 0) return truncateTitle(firstLine);\n }\n return defaultTitle;\n}\n\n/**\n * Parse JSON from `raw`, throwing an actionable error on failure.\n * @param raw - Raw JSON string.\n * @param filePath - Used in the error message to identify the file.\n */\nexport function parseJsonOrThrow(raw: string, filePath: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON in session file: ${filePath}`);\n }\n}\n","/**\n * Adapter for OpenAI Codex / ChatGPT session exports.\n *\n * OpenAI's conversation export format (`conversations.json`) is an array of\n * conversation objects. Each conversation contains a `mapping` of nodes, where\n * each node holds a `message` with `author.role` and `content.parts`.\n *\n * Supported schema (as documented in OpenAI's data export):\n * ```json\n * [{ \"title\": \"...\", \"create_time\": 1234, \"update_time\": 5678,\n * \"mapping\": { \"<id>\": { \"message\": { \"author\": { \"role\": \"user\" },\n * \"content\": { \"parts\": [\"...\"] },\n * \"create_time\": 1234 } } } }]\n * ```\n *\n * When the file contains multiple conversations, only the first is parsed.\n * For bulk import, callers should split the array and pass each element\n * as a separate file (or use the directory bulk-import path).\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport type { SessionAdapter, NormalizedSession, SessionTurn } from \"./types.js\";\nimport { resolveSessionTitle, parseJsonOrThrow } from \"./utils.js\";\n\nconst CODEX_EXTENSION = \".json\";\n\ninterface CodexAuthor {\n role: string;\n}\n\ninterface CodexContent {\n parts: string[];\n}\n\ninterface CodexMessage {\n author: CodexAuthor;\n content: CodexContent;\n create_time?: number;\n}\n\ninterface CodexNode {\n message?: CodexMessage | null;\n}\n\ninterface CodexConversation {\n title?: string;\n create_time?: number;\n update_time?: number;\n mapping?: Record<string, CodexNode>;\n}\n\n/** Convert a Unix timestamp (seconds) to an ISO-8601 string. */\nfunction unixToIso(ts: number): string {\n return new Date(ts * 1000).toISOString();\n}\n\n/** Extract conversation turns from the mapping, sorted by create_time. */\nfunction extractTurns(mapping: Record<string, CodexNode>): SessionTurn[] {\n const turns: SessionTurn[] = [];\n\n for (const node of Object.values(mapping)) {\n const msg = node.message;\n if (!msg) continue;\n const role = msg.author?.role;\n if (role !== \"user\" && role !== \"assistant\") continue;\n const content = (msg.content?.parts ?? []).join(\"\\n\").trim();\n if (content.length === 0) continue;\n turns.push({\n role,\n content,\n timestamp: msg.create_time != null ? unixToIso(msg.create_time) : undefined,\n });\n }\n\n // Sort by timestamp when available so turns appear in chronological order.\n turns.sort((a, b) => {\n if (!a.timestamp || !b.timestamp) return 0;\n return a.timestamp.localeCompare(b.timestamp);\n });\n\n return turns;\n}\n\n/** Return true if `value` looks like a Codex conversation export array. */\nfunction isCodexExport(value: unknown): value is CodexConversation[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n typeof (value[0] as CodexConversation).mapping === \"object\"\n );\n}\n\nexport const codexAdapter: SessionAdapter = {\n name: \"codex\",\n\n async detect(filePath: string): Promise<boolean> {\n if (path.extname(filePath).toLowerCase() !== CODEX_EXTENSION) return false;\n const raw = await readFile(filePath, \"utf-8\").catch(() => \"\");\n if (raw.trimStart()[0] !== \"[\") return false;\n try {\n return isCodexExport(JSON.parse(raw));\n } catch {\n return false;\n }\n },\n\n async parse(filePath: string): Promise<NormalizedSession> {\n const raw = await readFile(filePath, \"utf-8\");\n const parsed = parseJsonOrThrow(raw, filePath);\n\n if (!isCodexExport(parsed)) {\n throw new Error(\n `Codex session file does not contain a conversation array: ${filePath}`\n );\n }\n\n const conv = parsed[0];\n const turns = extractTurns(conv.mapping ?? {});\n const firstUser = turns.find((t) => t.role === \"user\");\n\n return {\n title: resolveSessionTitle(conv.title, firstUser?.content, \"Codex Session\"),\n adapter: \"codex\",\n startedAt: conv.create_time != null ? unixToIso(conv.create_time) : undefined,\n endedAt: conv.update_time != null ? unixToIso(conv.update_time) : undefined,\n participantIdentity: \"OpenAI Codex\",\n turns,\n };\n },\n};\n","/**\n * Adapter for Cursor conversation history exports.\n *\n * Cursor AI can export conversation history as JSON files. The schema\n * reflects Cursor's internal chat format:\n * ```json\n * { \"tabs\": [{ \"title\": \"...\",\n * \"messages\": [{ \"role\": \"user\"|\"assistant\",\n * \"content\": \"...\",\n * \"timestamp\": \"...\" }] }] }\n * ```\n *\n * When the export contains multiple tabs, only the first tab is parsed.\n * A top-level `messages` array (without `tabs`) is also supported for\n * simpler single-conversation exports.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport type { SessionAdapter, NormalizedSession, SessionTurn } from \"./types.js\";\nimport { resolveSessionTitle, parseJsonOrThrow } from \"./utils.js\";\n\nconst CURSOR_EXTENSION = \".json\";\n\ninterface CursorMessage {\n role: string;\n content: string;\n timestamp?: string;\n}\n\ninterface CursorTab {\n title?: string;\n messages: CursorMessage[];\n}\n\ninterface CursorTabsExport {\n tabs: CursorTab[];\n}\n\ninterface CursorFlatExport {\n messages: CursorMessage[];\n title?: string;\n}\n\ntype CursorExport = CursorTabsExport | CursorFlatExport;\n\n/** Guard: does the value look like a Cursor tabs export? */\nfunction isTabsExport(value: unknown): value is CursorTabsExport {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"tabs\" in value &&\n Array.isArray((value as CursorTabsExport).tabs)\n );\n}\n\n/** Guard: does the value look like a Cursor flat messages export? */\nfunction isFlatExport(value: unknown): value is CursorFlatExport {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"messages\" in value &&\n Array.isArray((value as CursorFlatExport).messages)\n );\n}\n\n/** Extract the raw messages and optional title from a Cursor export. */\nfunction extractMessagesAndTitle(\n data: CursorExport\n): { messages: CursorMessage[]; title?: string } {\n if (isTabsExport(data)) {\n const tab = data.tabs[0];\n return { messages: tab?.messages ?? [], title: tab?.title };\n }\n return { messages: data.messages, title: data.title };\n}\n\n/** Convert raw Cursor messages to normalised SessionTurns. */\nfunction toTurns(messages: CursorMessage[]): SessionTurn[] {\n const turns: SessionTurn[] = [];\n for (const msg of messages) {\n const role = msg.role;\n if (role !== \"user\" && role !== \"assistant\") continue;\n const content = (msg.content ?? \"\").trim();\n if (content.length === 0) continue;\n turns.push({ role, content, timestamp: msg.timestamp });\n }\n return turns;\n}\n\nexport const cursorAdapter: SessionAdapter = {\n name: \"cursor\",\n\n async detect(filePath: string): Promise<boolean> {\n if (path.extname(filePath).toLowerCase() !== CURSOR_EXTENSION) return false;\n const raw = await readFile(filePath, \"utf-8\").catch(() => \"\");\n if (raw.trimStart()[0] !== \"{\") return false;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isTabsExport(parsed) || isFlatExport(parsed);\n } catch {\n return false;\n }\n },\n\n async parse(filePath: string): Promise<NormalizedSession> {\n const raw = await readFile(filePath, \"utf-8\");\n const parsed = parseJsonOrThrow(raw, filePath);\n\n if (!isTabsExport(parsed) && !isFlatExport(parsed)) {\n throw new Error(\n `Cursor session file does not match a known Cursor export schema: ${filePath}`\n );\n }\n\n const { messages, title: rawTitle } = extractMessagesAndTitle(parsed as CursorExport);\n const turns = toTurns(messages);\n const firstUser = turns.find((t) => t.role === \"user\");\n\n const timestamps = turns\n .filter((t) => t.timestamp != null)\n .map((t) => t.timestamp as string);\n\n return {\n title: resolveSessionTitle(rawTitle, firstUser?.content, \"Cursor Session\"),\n adapter: \"cursor\",\n startedAt: timestamps[0],\n endedAt: timestamps[timestamps.length - 1],\n participantIdentity: \"Cursor AI\",\n turns,\n };\n },\n};\n","/**\n * Adapter registry and auto-detection for session files.\n *\n * `detectAdapter` probes a file against each registered adapter in priority\n * order and returns the first match. New adapters are added to `ADAPTERS`.\n */\n\nimport type { SessionAdapter, NormalizedSession } from \"./types.js\";\nimport { claudeAdapter } from \"./claude.js\";\nimport { codexAdapter } from \"./codex.js\";\nimport { cursorAdapter } from \"./cursor.js\";\n\n/** All registered session adapters, checked in order during detection. */\nexport const ADAPTERS: SessionAdapter[] = [claudeAdapter, codexAdapter, cursorAdapter];\n\n/**\n * Probe `filePath` against each adapter and return the first match.\n * Returns `null` when no adapter recognises the file.\n */\nexport async function detectAdapter(filePath: string): Promise<SessionAdapter | null> {\n for (const adapter of ADAPTERS) {\n if (await adapter.detect(filePath)) return adapter;\n }\n return null;\n}\n\n/**\n * Parse a session file using automatic adapter detection.\n *\n * After parsing, requires the session to contain at least one user or\n * assistant turn with non-empty content. Detection is intentionally\n * shape-based and lenient (file extension + first-line/JSON-shape match)\n * to avoid false-negatives on slightly-malformed-but-intelligible\n * exports — but a \"recognised-looking\" file with zero valid turns is\n * almost always a corrupted or empty export and should fail loudly,\n * not import as a content-free `sources/` page.\n *\n * @throws When no adapter recognises the file, the file is malformed,\n * or the parsed session has no usable turns.\n */\nexport async function parseSessionFile(filePath: string): Promise<NormalizedSession> {\n const adapter = await detectAdapter(filePath);\n if (!adapter) {\n throw new Error(\n `No session adapter recognised the file: ${filePath}\\n` +\n `Supported formats: ${ADAPTERS.map((a) => a.name).join(\", \")}`\n );\n }\n const session = await adapter.parse(filePath);\n assertSessionHasUsableTurns(session, filePath);\n return session;\n}\n\n/**\n * Reject sessions where every adapter-side filter dropped the input —\n * shape-based detection passed, but no usable user/assistant turn\n * survived. Throws with an actionable error rather than producing a\n * markdown file with \"No conversation turns found\".\n */\nfunction assertSessionHasUsableTurns(session: NormalizedSession, filePath: string): void {\n const hasUsableTurn = session.turns.some(\n (t) => (t.role === \"user\" || t.role === \"assistant\") && t.content.trim().length > 0,\n );\n if (!hasUsableTurn) {\n throw new Error(\n `${session.adapter} session has no usable turns: ${filePath}\\n` +\n `The file matches the ${session.adapter} export shape, but no user or ` +\n `assistant message with content was found. Re-export the session or ` +\n `delete the file if it is empty.`,\n );\n }\n}\n\n/**\n * Format a normalised session as a markdown document body.\n * Each turn is rendered as a level-3 heading plus the turn's content.\n *\n * Note: callers should obtain `session` via {@link parseSessionFile},\n * which enforces ≥1 usable turn. Direct construction with an empty\n * turns array would render as nothing — there is no fallback line\n * because the empty case should fail before reaching here.\n */\nexport function formatSessionAsMarkdown(session: NormalizedSession): string {\n const lines: string[] = [];\n\n for (const turn of session.turns) {\n const label = turn.role === \"user\" ? \"User\" : session.participantIdentity ?? \"Assistant\";\n const heading = turn.timestamp\n ? `### ${label} _(${turn.timestamp})_`\n : `### ${label}`;\n lines.push(heading);\n lines.push(\"\");\n lines.push(turn.content);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trimEnd();\n}\n","/**\n * CLI handler for `llmwiki view` — the local read-only web viewer.\n *\n * Responsibilities:\n * - parse and validate the host/port/allow-lan symmetry (spec\n * §Non-Negotiable Security Requirements: non-loopback bind requires\n * BOTH `--allow-lan` AND `--host <host>`; either alone exits 1)\n * - build the frozen `ViewerSnapshot` once at startup\n * - start the HTTP server and emit a parseable readiness line so\n * test fixtures (and the user) know what URL to point a browser at\n * - register SIGINT / SIGTERM handlers for graceful shutdown\n */\n\nimport { spawn } from \"child_process\";\nimport { startViewerServer } from \"../viewer/server.js\";\nimport { buildViewerSnapshot } from \"../viewer/snapshot.js\";\n\nconst LOOPBACK_HOST = \"127.0.0.1\";\n\n/**\n * Bind hosts that listen on every interface. We reject these in v1\n * because the Host / Origin / Sec-Fetch-Site checks in the server are\n * built around a single canonical bind address; a wildcard bind would\n * mean any reachable interface's IP serves as a valid Host, which\n * defeats the DNS-rebind protection. A future PR can add a real\n * allowed-host model; for now, fail closed with a clear CLI error.\n */\nconst WILDCARD_HOSTS = new Set([\n \"0.0.0.0\",\n \"::\",\n \"0:0:0:0:0:0:0:0\",\n \"0000:0000:0000:0000:0000:0000:0000:0000\",\n \"*\",\n]);\n\n/** Parsed CLI options. */\ninterface ViewCommandOptions {\n port?: string | number;\n host?: string;\n allowLan?: boolean;\n open?: boolean;\n}\n\n/**\n * Run `llmwiki view`. Resolves once the server has bound; the returned\n * promise stays unresolved for the lifetime of the listening process so\n * Commander's exit semantics keep the event loop alive until the signal\n * handler closes the server.\n */\nexport default async function viewCommand(options: ViewCommandOptions): Promise<void> {\n const { host, port } = resolveBindConfig(options);\n const root = process.cwd();\n const snapshot = await buildViewerSnapshot(root);\n const handle = await startViewerServer(snapshot, { host, port });\n const url = buildReadyUrl(handle.host, handle.port);\n process.stdout.write(`Viewer ready at ${url}\\n`);\n if (options.open) openInBrowser(url);\n registerShutdown(handle.close);\n}\n\n/**\n * Fire-and-forget native-shell open of the viewer URL. Failures here are\n * intentionally swallowed: a broken browser launch must not prevent the\n * server from keeping the readiness it just announced.\n */\nfunction openInBrowser(url: string): void {\n const command =\n process.platform === \"darwin\" ? \"open\"\n : process.platform === \"win32\" ? \"cmd\"\n : \"xdg-open\";\n const args = process.platform === \"win32\" ? [\"/c\", \"start\", \"\", url] : [url];\n const child = spawn(command, args, { stdio: \"ignore\", detached: true });\n child.on(\"error\", () => undefined);\n child.unref();\n}\n\n/**\n * Apply the spec's host/port symmetry: `--allow-lan` and `--host`\n * together unlock non-loopback bind; either alone is a fatal error.\n */\nfunction resolveBindConfig(options: ViewCommandOptions): { host: string; port: number } {\n const hostFlag = typeof options.host === \"string\" && options.host.length > 0;\n const allowLan = options.allowLan === true;\n if (hostFlag !== allowLan) {\n throw new Error(\n \"Privacy gate: --host and --allow-lan must be supplied together. \" +\n \"Use both to bind beyond loopback, or neither to keep the viewer on 127.0.0.1.\",\n );\n }\n const host = hostFlag ? (options.host as string) : LOOPBACK_HOST;\n if (WILDCARD_HOSTS.has(host)) {\n throw new Error(\n `--host ${host} is not supported: wildcard binds defeat the viewer's DNS-rebind protection. ` +\n \"Use a specific interface IP (e.g. 192.168.1.10) instead.\",\n );\n }\n const port = parsePort(options.port);\n return { host, port };\n}\n\n/**\n * Build the readiness-line URL. IPv6 literal hosts must be bracketed\n * (`http://[::1]:PORT/`) per RFC 3986 — bare `http://::1:PORT/` is a\n * malformed URL and won't open in a browser. Heuristic for \"literal\n * IPv6\": a colon in the host portion (domain names + IPv4 dotted\n * quads never contain `:`).\n */\nfunction buildReadyUrl(host: string, port: number): string {\n if (host.includes(\":\")) return `http://[${host}]:${port}`;\n return `http://${host}:${port}`;\n}\n\n/** Coerce the optional --port string into a non-negative integer. */\nfunction parsePort(raw: string | number | undefined): number {\n if (raw === undefined) return 0;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n if (!Number.isInteger(value) || value < 0 || value > 65535) {\n throw new Error(`Invalid --port value: ${raw}`);\n }\n return value;\n}\n\n/** Install SIGINT/SIGTERM handlers that close the server gracefully. */\nfunction registerShutdown(close: () => Promise<void>): void {\n const shutdown = async (): Promise<void> => {\n try {\n await close();\n process.exit(0);\n } catch {\n process.exit(1);\n }\n };\n process.once(\"SIGINT\", () => void shutdown());\n process.once(\"SIGTERM\", () => void shutdown());\n}\n","/**\n * Local read-only HTTP server for the llmwiki viewer.\n *\n * Built on Node's `http` module (no framework). The spec's mandatory\n * security headers (CSP, CORP, nosniff, Referrer-Policy) and the\n * Host / Origin / Sec-Fetch-Site rejection rules from\n * §Non-Negotiable Security Requirements apply to **every response**,\n * including 404s for unregistered paths and 403s for bad origin — see\n * `handleRequest` for the ordering rationale.\n *\n * The server reads from the frozen `ViewerSnapshot` for every request.\n * The single exception is `/api/health`, which calls `readLintCache`\n * per request — that's a documented cheap atomic-JSON contract, not a\n * filesystem rescan of the wiki.\n */\n\nimport http from \"http\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { AddressInfo } from \"net\";\nimport { buildHealthResponse } from \"./health.js\";\nimport { loadShellTemplate, substitutePageIndex } from \"./shell.js\";\nimport { ASSETS_DIR, handleAsset } from \"./static-assets.js\";\nimport { renderPageHtml } from \"./render.js\";\nimport { searchPages } from \"./search.js\";\nimport type { PageDirectory } from \"../export/types.js\";\nimport type { ViewerSnapshot, ViewerPage } from \"./types.js\";\nimport { assertSafeSlug, PathSafetyError } from \"./path-safety.js\";\n\nconst LOOPBACK_HOSTS = new Set([\"127.0.0.1\", \"::1\"]);\n\n/** Exact CSP string the spec mandates. Pinned here to keep the test contract obvious. */\nconst CONTENT_SECURITY_POLICY =\n \"default-src 'self'; script-src 'self'; style-src 'self'; \" +\n \"img-src 'self' data:; font-src 'self'; connect-src 'self'; \" +\n \"frame-ancestors 'none'; base-uri 'none'; object-src 'none'; form-action 'none'\";\n\n/** Configuration knobs accepted by `startViewerServer`. */\ninterface ViewerServerConfig {\n /** Listening host. `--allow-lan` callers set this to a non-loopback bind address. */\n host: string;\n /** Listening port. `0` lets the OS pick a free port. */\n port: number;\n}\n\n/** Handle returned by `startViewerServer`. */\ninterface ViewerServerHandle {\n /** Actual port the server bound to (resolves `port: 0`). */\n port: number;\n /** Actual host the server bound to. */\n host: string;\n /** Graceful shutdown — closes the listener and resolves when all sockets drain. */\n close(): Promise<void>;\n}\n\n/**\n * Bind the configured server to its host/port and resolve once `listen`\n * fires. Errors during bind (occupied port, invalid host) reject so the\n * CLI surfaces a clean failure instead of hanging. The internal config\n * the request handler uses is the actually-bound port — not the one the\n * caller passed in — so `--port 0` correctly accepts Host headers that\n * carry the OS-assigned port.\n */\nexport async function startViewerServer(\n snapshot: ViewerSnapshot,\n config: ViewerServerConfig,\n): Promise<ViewerServerHandle> {\n const boundConfig: ViewerServerConfig = { ...config };\n const server = http.createServer((req, res) => {\n handleRequest(req, res, snapshot, boundConfig).catch((err) => {\n // Per spec: never return raw thrown error text to the client.\n // The per-route handlers catch render/sanitize failures locally\n // and emit `render_failed`; reaching here means a genuinely\n // unexpected bug, so surface a generic envelope.\n void err;\n if (!res.headersSent) {\n writeJsonError(res, 500, \"internal_error\", \"Unexpected server error.\");\n }\n });\n });\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error): void => {\n server.off(\"listening\", onListening);\n reject(err);\n };\n const onListening = (): void => {\n server.off(\"error\", onError);\n resolve();\n };\n server.once(\"error\", onError);\n server.once(\"listening\", onListening);\n server.listen(config.port, config.host);\n });\n const address = server.address() as AddressInfo | null;\n if (!address) throw new Error(\"server bound but address is null\");\n boundConfig.port = address.port;\n return {\n host: config.host,\n port: address.port,\n close: () => new Promise<void>((resolve) => server.close(() => resolve())),\n };\n}\n\n/**\n * Dispatch a single request. The order matters:\n * 1. Set the mandatory security headers — every response carries them,\n * including 404s for unknown paths and 403s for bad Host/Origin.\n * 2. Validate Host / Origin / Sec-Fetch-Site. Hostile-origin requests\n * to unknown paths must still return 403, not a header-less 404.\n * 3. Dispatch to a registered route, or surface a JSON 404 envelope\n * for anything else.\n * That ordering closes the DNS-rebind / cross-site leakage gap the\n * naive \"404 first, then security\" flow would leave behind.\n */\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n snapshot: ViewerSnapshot,\n config: ViewerServerConfig,\n): Promise<void> {\n applySecurityHeaders(res);\n if (!validateOriginHeaders(req, config)) {\n writeJsonError(res, 403, \"forbidden\", \"rejected by origin policy\");\n return;\n }\n const url = new URL(req.url ?? \"/\", buildOriginBase(config));\n if (!isRouteRegistered(req.method, url.pathname)) {\n writeJsonError(res, 404, \"not_found\", `${req.method ?? \"?\"} ${url.pathname}`);\n return;\n }\n await routeRegistered(req, res, url, snapshot, LOOPBACK_HOSTS.has(config.host));\n}\n\n/**\n * Dispatch the request to whichever registered handler owns this path.\n * `isLoopback` controls whether the rendered citation chips include\n * `absolutePath` / editor-link payloads — non-loopback binds suppress\n * both per spec §Support Rail.\n */\nasync function routeRegistered(\n req: IncomingMessage,\n res: ServerResponse,\n parsedUrl: URL,\n snapshot: ViewerSnapshot,\n isLoopback: boolean,\n): Promise<void> {\n if (parsedUrl.pathname === \"/\") return handleShell(res, snapshot);\n if (parsedUrl.pathname.startsWith(\"/assets/\")) return handleAsset(res, parsedUrl.pathname);\n if (parsedUrl.pathname === \"/api/pages\") return handleApiPages(res, snapshot);\n if (parsedUrl.pathname === \"/api/index\") return handleApiIndex(res, snapshot, isLoopback);\n if (parsedUrl.pathname === \"/api/health\") return handleApiHealth(res, snapshot);\n if (parsedUrl.pathname === \"/api/search\") return handleApiSearch(res, parsedUrl, snapshot);\n if (parsedUrl.pathname.startsWith(\"/api/page/\")) {\n return handleApiPage(res, parsedUrl.pathname, snapshot, isLoopback);\n }\n // Unreachable: `isRouteRegistered` is the gate, and every branch\n // there has a matching dispatch above. If it ever fires, the two\n // functions have drifted — fail loudly rather than silently 404.\n throw new Error(`route registration drift: no handler for ${parsedUrl.pathname}`);\n}\n\n/** True when (method, path) is one of the v1 registered routes. */\nfunction isRouteRegistered(method: string | undefined, pathname: string): boolean {\n if (method !== \"GET\") return false;\n if (pathname === \"/\") return true;\n if (pathname.startsWith(\"/assets/\")) return true;\n if (pathname === \"/api/pages\") return true;\n if (pathname === \"/api/index\") return true;\n if (pathname === \"/api/health\") return true;\n if (pathname === \"/api/search\") return true;\n if (pathname.startsWith(\"/api/page/\")) return true;\n return false;\n}\n\n/**\n * Stamp every response with the mandatory security headers. Called\n * first in `handleRequest` so unregistered-route 404s and bad-origin\n * 403s carry the same hardening as the v1 API responses.\n */\nfunction applySecurityHeaders(res: ServerResponse): void {\n res.setHeader(\"Content-Security-Policy\", CONTENT_SECURITY_POLICY);\n res.setHeader(\"Cross-Origin-Resource-Policy\", \"same-origin\");\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n res.setHeader(\"Referrer-Policy\", \"no-referrer\");\n}\n\n/**\n * Apply the Host / Origin / Sec-Fetch-Site rejection rules from\n * §Non-Negotiable Security Requirements. Returns false when a request\n * should be rejected with 403; the caller writes the error envelope.\n */\nfunction validateOriginHeaders(req: IncomingMessage, config: ViewerServerConfig): boolean {\n const host = req.headers.host;\n if (!host || !isAcceptableHost(host, config)) return false;\n const origin = req.headers.origin;\n if (typeof origin === \"string\" && origin.length > 0) {\n if (!isSameOrigin(origin, config)) return false;\n }\n const fetchSite = req.headers[\"sec-fetch-site\"];\n if (fetchSite === \"cross-site\") return false;\n return true;\n}\n\n/**\n * True when the incoming `Host` header matches the configured bind.\n * Handles IPv4 (`127.0.0.1:PORT`), IPv6 (`[::1]:PORT` — clients always\n * bracket the host portion when the Host header carries a literal IPv6\n * address per RFC 3986/7230), and the `localhost` alias on both\n * loopback families.\n */\nfunction isAcceptableHost(hostHeader: string, config: ViewerServerConfig): boolean {\n for (const acceptable of buildAcceptableHostHeaders(config)) {\n if (hostHeader === acceptable) return true;\n }\n return false;\n}\n\n/** Every Host header value we accept for the current bind. */\nfunction buildAcceptableHostHeaders(config: ViewerServerConfig): string[] {\n const formattedBind = formatHostHeader(config.host, config.port);\n const accepted = [formattedBind];\n if (config.host === \"127.0.0.1\" || config.host === \"::1\") {\n accepted.push(`localhost:${config.port}`);\n }\n return accepted;\n}\n\n/** True when the incoming `Origin` resolves to our own host:port. */\nfunction isSameOrigin(origin: string, config: ViewerServerConfig): boolean {\n try {\n const parsed = new URL(origin);\n const expectedHostname = normalizeHostnameForOrigin(config.host);\n const originHostname = normalizeHostnameForOrigin(parsed.hostname);\n return originHostname === expectedHostname && Number(parsed.port) === config.port;\n } catch {\n return false;\n }\n}\n\n/**\n * Format a Host header value for the given bind. IPv6 addresses must\n * be bracketed (`[::1]:54391`); IPv4 and named hosts go in bare. The\n * heuristic for \"literal IPv6\" is a colon in the host portion — domain\n * names and IPv4 dotted-quads never contain `:`.\n */\nfunction formatHostHeader(host: string, port: number): string {\n if (host.includes(\":\")) return `[${host}]:${port}`;\n return `${host}:${port}`;\n}\n\n/**\n * Build a URL base suitable for the `new URL(req.url, base)` resolver.\n * IPv6 literal hosts must be bracketed inside a URL — `http://::1:PORT/`\n * is malformed and `new URL` throws. The bracketed form is the only\n * legal way to express a literal IPv6 host in a URL.\n */\nfunction buildOriginBase(config: ViewerServerConfig): string {\n if (config.host.includes(\":\")) return `http://[${config.host}]:${config.port}`;\n return `http://${config.host}:${config.port}`;\n}\n\n/**\n * `URL.hostname` strips the brackets from a parsed IPv6 origin\n * (`new URL(\"http://[::1]/\").hostname === \"::1\"`), so compare against\n * the bare form. Lowercased for case-insensitive equality (RFC 3986\n * says the host is case-insensitive).\n */\nfunction normalizeHostnameForOrigin(host: string): string {\n let h = host.toLowerCase();\n if (h.startsWith(\"[\") && h.endsWith(\"]\")) h = h.slice(1, -1);\n return h;\n}\n\n/**\n * Serve the templated viewer shell. Reads `index.html` lazily through\n * `loadShellTemplate` (process-cached), substitutes the page-index JSON\n * blob, and returns the result with `Content-Type: text/html`. A missing\n * template surfaces as a 500 `shell_missing` so the rest of the routes\n * stay usable when the asset bundle is incomplete.\n */\nasync function handleShell(res: ServerResponse, snapshot: ViewerSnapshot): Promise<void> {\n const template = await loadShellTemplate(ASSETS_DIR);\n if (template === null) {\n writeJsonError(res, 500, \"shell_missing\", \"Viewer shell template not found on disk.\");\n return;\n }\n const body = substitutePageIndex(template, snapshot.pages);\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(body);\n}\n\n/** `/api/pages` — full envelope with counts, recent pages, and page list. */\nfunction handleApiPages(res: ServerResponse, snapshot: ViewerSnapshot): void {\n writeJson(res, 200, {\n project: snapshot.project,\n counts: {\n concepts: snapshot.counts.concepts,\n queries: snapshot.counts.queries,\n sourceFiles: snapshot.counts.sourceFiles,\n pendingReviews: snapshot.counts.pendingReviews,\n },\n index: { available: snapshot.index.available, href: snapshot.index.href },\n recentPages: snapshot.recentPages,\n pages: snapshot.pages.map(pageListRow),\n updatedAt: snapshot.generatedAt,\n });\n}\n\n/** Per-page row shape returned in `/api/pages.pages`. */\nfunction pageListRow(page: ViewerPage): Record<string, unknown> {\n return {\n id: page.id,\n pageDirectory: page.pageDirectory,\n slug: page.slug,\n title: page.title,\n kind: typeof page.frontmatter.kind === \"string\" ? page.frontmatter.kind : \"concept\",\n summary: typeof page.frontmatter.summary === \"string\" ? page.frontmatter.summary : \"\",\n updatedAt:\n typeof page.frontmatter.updatedAt === \"string\" ? (page.frontmatter.updatedAt as string) : \"\",\n warnings: page.warnings,\n };\n}\n\n/** `/api/index` — rendered `wiki/index.md` with resolved outgoing links. */\nfunction handleApiIndex(\n res: ServerResponse,\n snapshot: ViewerSnapshot,\n isLoopback: boolean,\n): void {\n if (!snapshot.index.available) {\n writeJsonError(res, 404, \"index_unavailable\", \"wiki/index.md is not present.\");\n return;\n }\n const rendered = tryRenderBody(snapshot.index.body, snapshot, isLoopback);\n if (rendered === null) {\n writeRenderFailed(res);\n return;\n }\n writeJson(res, 200, {\n html: rendered.html,\n outgoingLinks: snapshot.index.outgoingLinks,\n generatedAt: snapshot.generatedAt,\n });\n}\n\n/** `/api/health` — cheap status summary. */\nasync function handleApiHealth(res: ServerResponse, snapshot: ViewerSnapshot): Promise<void> {\n const health = await buildHealthResponse(snapshot);\n writeJson(res, 200, health);\n}\n\n/**\n * `/api/search?q=...` — substring search over the startup snapshot. The\n * query string is read directly from the parsed URL (Node's URL parser\n * has already percent-decoded it); `searchPages` does its own length\n * cap and tokenization. An empty or missing `q` returns an empty\n * results array, consistent with the no-tokens case.\n */\nfunction handleApiSearch(\n res: ServerResponse,\n parsedUrl: URL,\n snapshot: ViewerSnapshot,\n): void {\n const query = parsedUrl.searchParams.get(\"q\") ?? \"\";\n writeJson(res, 200, searchPages(snapshot, query));\n}\n\n/**\n * `/api/page/:directory/:slug` — single page payload with server-rendered\n * sanitized HTML. The `render_pending` Slice-2 placeholder is gone; any\n * remaining warnings come from the collector (missing/malformed\n * frontmatter, missing title).\n */\nfunction handleApiPage(\n res: ServerResponse,\n pathname: string,\n snapshot: ViewerSnapshot,\n isLoopback: boolean,\n): void {\n const segments = pathname.replace(/^\\/api\\/page\\//, \"\").split(\"/\");\n if (segments.length !== 2) {\n writeJsonError(res, 400, \"bad_request\", \"Expected /api/page/:directory/:slug\");\n return;\n }\n const [directorySegment, encodedSlug] = segments;\n const decodedSlug = safeDecodeSlug(directorySegment, encodedSlug);\n if (!decodedSlug) {\n writeJsonError(res, 400, \"bad_request\", \"Invalid directory or slug.\");\n return;\n }\n const page = snapshot.pages.find(\n (p) => p.pageDirectory === decodedSlug.directory && p.slug === decodedSlug.slug,\n );\n if (!page) {\n writeJsonError(res, 404, \"page_not_found\", `${decodedSlug.directory}/${decodedSlug.slug}`);\n return;\n }\n const rendered = tryRenderBody(page.body, snapshot, isLoopback);\n if (rendered === null) {\n writeRenderFailed(res);\n return;\n }\n writeJson(res, 200, pagePayload(page, snapshot, rendered.html));\n}\n\n/**\n * Decode the directory and slug segments together so a bad input on\n * either fails with a uniform 400. Resolves with `null` for any\n * structural rejection.\n */\nfunction safeDecodeSlug(\n directorySegment: string,\n encodedSlug: string,\n): { directory: PageDirectory; slug: string } | null {\n if (directorySegment !== \"concepts\" && directorySegment !== \"queries\") return null;\n let decoded: string;\n try {\n decoded = decodeURIComponent(encodedSlug);\n } catch {\n return null;\n }\n try {\n assertSafeSlug(decoded);\n } catch (err) {\n if (err instanceof PathSafetyError) return null;\n throw err;\n }\n return { directory: directorySegment, slug: decoded };\n}\n\n/** Build the JSON payload for `/api/page/:dir/:slug`. */\nfunction pagePayload(\n page: ViewerPage,\n snapshot: ViewerSnapshot,\n renderedHtml: string,\n): Record<string, unknown> {\n return {\n id: page.id,\n title: page.title,\n pageDirectory: page.pageDirectory,\n slug: page.slug,\n html: renderedHtml,\n citations: page.citations,\n outgoingLinks: page.outgoingLinks,\n frontmatter: page.frontmatter,\n warnings: page.warnings,\n updatedAt:\n typeof page.frontmatter.updatedAt === \"string\" ? (page.frontmatter.updatedAt as string) : \"\",\n createdAt:\n typeof page.frontmatter.createdAt === \"string\" ? (page.frontmatter.createdAt as string) : \"\",\n generatedAt: snapshot.generatedAt,\n };\n}\n\n/**\n * Wrap the renderer in a catch and return null on any thrown error.\n * Render or sanitize failures must emit the spec's `render_failed`\n * envelope rather than leak the raw thrown text — see `writeRenderFailed`.\n */\nfunction tryRenderBody(\n body: string,\n snapshot: ViewerSnapshot,\n isLoopback: boolean,\n): { html: string } | null {\n try {\n return renderPageHtml(body, snapshot, { isLoopback });\n } catch {\n return null;\n }\n}\n\n/** Write the spec's exact `render_failed` 500 envelope. */\nfunction writeRenderFailed(res: ServerResponse): void {\n writeJsonError(res, 500, \"render_failed\", \"Could not render page.\");\n}\n\n/** Write a JSON response body with the given status. */\nfunction writeJson(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n res.end(JSON.stringify(body));\n}\n\n/** Standard `{ error: { code, message } }` envelope. */\nfunction writeJsonError(\n res: ServerResponse,\n status: number,\n code: string,\n message: string,\n): void {\n writeJson(res, status, { error: { code, message } });\n}\n","/**\n * Persistent cache of the most recent `llmwiki lint` run.\n *\n * Written by the lint command after a completed run, before any non-zero exit\n * for lint findings, so the cache always reflects the run the user just saw.\n * Crashed or partial runs leave the prior cache untouched.\n *\n * Consumers (e.g., the upcoming viewer's /api/health endpoint) read the cache\n * to surface lint counts without re-running lint per request. A missing or\n * malformed cache reads as null, which means \"lint has not been run yet.\"\n */\n\nimport { mkdir, readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { atomicWrite } from \"../utils/markdown.js\";\nimport { LLMWIKI_DIR, LAST_LINT_FILE } from \"../utils/constants.js\";\nimport type { LintSummary } from \"./types.js\";\n\n/** One persisted lint summary. Shape is part of the public viewer-cache contract. */\nexport interface LintCacheEntry {\n warnings: number;\n errors: number;\n /** ISO-8601 timestamp of the run that produced these counts. */\n at: string;\n}\n\n/**\n * The exact ISO-8601 shape `writeLintCache` produces and `readLintCache` accepts.\n * Exported so tests can assert against the same regex the validator enforces and\n * never drift from the documented contract.\n */\nexport const LINT_CACHE_TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/;\n\n/**\n * Persist a lint summary to `.llmwiki/last-lint.json` after a completed run.\n * Creates the `.llmwiki/` directory if missing. Overwrites any prior entry so\n * the cache reflects the most recent run, including zero-issue runs.\n */\nexport async function writeLintCache(root: string, summary: LintSummary): Promise<void> {\n await mkdir(path.join(root, LLMWIKI_DIR), { recursive: true });\n const entry: LintCacheEntry = {\n warnings: summary.warnings,\n errors: summary.errors,\n at: new Date().toISOString(),\n };\n await atomicWrite(path.join(root, LAST_LINT_FILE), `${JSON.stringify(entry, null, 2)}\\n`);\n}\n\n/**\n * Read the cached lint summary, returning null for missing or malformed files.\n * Validation is strict: every field must have its expected type, otherwise the\n * cache is treated as absent so callers do not surface garbage counts.\n */\nexport async function readLintCache(root: string): Promise<LintCacheEntry | null> {\n let raw: string;\n try {\n raw = await readFile(path.join(root, LAST_LINT_FILE), \"utf-8\");\n } catch {\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!isValidEntry(parsed)) return null;\n return { warnings: parsed.warnings, errors: parsed.errors, at: parsed.at };\n}\n\n/** True for finite non-negative integers, including zero. NaN and Infinity fail Number.isInteger. */\nfunction isNonNegativeInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n\n/**\n * Strict type guard for the persisted cache entry.\n *\n * Counts must be finite non-negative integers (the writer only ever persists\n * `LintSummary` severity counts, which originate from a length on an array, so\n * anything else means the file was hand-edited or corrupted). The timestamp\n * must match the exact ISO-8601 shape the writer produces, otherwise downstream\n * consumers risk surfacing values like \"2026-01-01\" as full timestamps.\n */\nfunction isValidEntry(value: unknown): value is LintCacheEntry {\n if (typeof value !== \"object\" || value === null) return false;\n const candidate = value as Record<string, unknown>;\n return (\n isNonNegativeInteger(candidate.warnings) &&\n isNonNegativeInteger(candidate.errors) &&\n typeof candidate.at === \"string\" &&\n LINT_CACHE_TIMESTAMP_PATTERN.test(candidate.at)\n );\n}\n","/**\n * Build the `/api/health` response from the frozen startup snapshot.\n *\n * The only per-request filesystem read is `readLintCache(root)` — every\n * other count is captured at startup in `ViewerSnapshot`. The lint cache\n * is intentionally read per-request because PR #57 already designed it\n * as a tiny atomic JSON file the viewer's contract treats as a stable\n * surface (returns `null` when no lint run has completed yet).\n */\n\nimport { readLintCache } from \"../linter/cache.js\";\nimport type { LintCacheEntry } from \"../linter/cache.js\";\nimport type { ViewerSnapshot } from \"./types.js\";\n\n/**\n * Cheap health summary surfacing the same count fields MCP `wiki_status`\n * uses, plus the lint cache. Shapes diverge intentionally — MCP returns\n * an envelope with nested `pages`, the viewer returns a flat record for\n * the dashboard.\n */\ninterface ViewerHealthResponse {\n pendingReviews: number;\n sources: number;\n sourceFiles: number;\n concepts: number;\n queries: number;\n lint: LintCacheEntry | null;\n}\n\n/**\n * Assemble the health response. Reads `.llmwiki/last-lint.json` via\n * `readLintCache`; every other value is derived from the snapshot's\n * frozen counts.\n */\nexport async function buildHealthResponse(\n snapshot: ViewerSnapshot,\n): Promise<ViewerHealthResponse> {\n const lint = await readLintCache(snapshot.root);\n return {\n pendingReviews: snapshot.counts.pendingReviews,\n sources: snapshot.counts.compiledSources,\n sourceFiles: snapshot.counts.sourceFiles,\n concepts: snapshot.counts.concepts,\n queries: snapshot.counts.queries,\n lint,\n };\n}\n","/**\n * Shell-template loading, in-memory caching, and page-index substitution\n * for the viewer's `GET /` handler.\n *\n * The template lives at `dist/viewer/assets/index.html` (copied there by\n * `scripts/copy-viewer-assets.mjs`) and contains a literal `<!--PAGE_INDEX-->`\n * marker the server replaces per-request with a `<script type=\"application/json\"\n * id=\"page-index\">…</script>` blob carrying a trimmed page list. The JSON is\n * escaped so `</`, `<!`, and bare `<` cannot break out of the embedded script\n * tag — this is the spec's \"embed only as escaped JSON\" allowance, executed\n * server-side rather than client-side.\n *\n * Lazy-read with process-local cache: a missing template is a per-request\n * 500 (`shell_missing`), not a startup failure. The viewer's API endpoints\n * stay usable even if the asset bundle is incomplete; only `GET /` degrades.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport type { ViewerPage } from \"./types.js\";\n\nconst PAGE_INDEX_MARKER = \"<!--PAGE_INDEX-->\";\n\n/** Per-`assetsDir` template cache. `null` is cached too so the missing-template path doesn't hammer the disk. */\nconst templateCache = new Map<string, string | null>();\n\n/** Page-index entry shape embedded in the shell. Excludes page bodies per spec. */\ninterface EmbeddedPage {\n id: string;\n pageDirectory: ViewerPage[\"pageDirectory\"];\n slug: string;\n title: string;\n /** Frontmatter `kind`, used by the sidebar to group concepts on first paint. */\n kind: string;\n}\n\n/**\n * Read the shell template from `assetsDir/index.html`. Returns null when the\n * file is missing — the caller turns that into a `shell_missing` 500 so the\n * server keeps serving the rest of its routes. Caches the file bytes per\n * `assetsDir` in process memory; the cache is invalidated only by process\n * restart (consistent with the v1 \"no live-watch\" snapshot lifecycle).\n */\nexport async function loadShellTemplate(assetsDir: string): Promise<string | null> {\n const cached = templateCache.get(assetsDir);\n if (cached !== undefined) return cached;\n let bytes: string | null;\n try {\n bytes = await readFile(path.join(assetsDir, \"index.html\"), \"utf-8\");\n } catch {\n bytes = null;\n }\n templateCache.set(assetsDir, bytes);\n return bytes;\n}\n\n/** Clear the in-memory template cache. Tests use this between scenarios. */\nexport function resetShellTemplateCache(): void {\n templateCache.clear();\n}\n\n/**\n * Substitute the `<!--PAGE_INDEX-->` marker in `template` with a JSON-escaped\n * `<script type=\"application/json\">` block carrying the trimmed page list.\n *\n * The embedded payload is a subset of `/api/pages.pages` — only the fields\n * the client needs for first-paint sidebar rendering. Page bodies are never\n * included. JSON serialization is followed by an HTML-safety pass that\n * replaces every literal less-than character with the JSON unicode escape\n * for less-than (backslash-u-003c), so a `</script>` substring, a `<!`\n * sequence, or bare angle brackets in any page title cannot break out of\n * the embedded tag. `JSON.parse` on the client round-trips that escape\n * back into a literal less-than character.\n */\nexport function substitutePageIndex(template: string, pages: ViewerPage[]): string {\n const embedded: EmbeddedPage[] = pages.map((page) => ({\n id: page.id,\n pageDirectory: page.pageDirectory,\n slug: page.slug,\n title: page.title,\n kind:\n typeof page.frontmatter.kind === \"string\" && page.frontmatter.kind.length > 0\n ? (page.frontmatter.kind as string)\n : \"concept\",\n }));\n const json = JSON.stringify({ pages: embedded }).replace(/</g, \"\\\\u003c\");\n const block = `<script type=\"application/json\" id=\"page-index\">${json}</script>`;\n return template.replace(PAGE_INDEX_MARKER, block);\n}\n","/**\n * Path-confined static file server for the viewer's bundled assets.\n *\n * Owns three things:\n * - `ASSETS_DIR` — the absolute filesystem path to the asset bundle,\n * resolved once at module load from `import.meta.url`. The tsup\n * `onSuccess` hook copies `src/viewer/assets/` here at build time.\n * - The route handler for `GET /assets/*`: decode → `assertSafeSlug` →\n * `realpath` → confine under `ASSETS_DIR` → extension allowlist.\n * - The `ASSET_CONTENT_TYPES` allowlist that doubles as the\n * served-extensions filter.\n *\n * `ASSETS_DIR` is exported because `src/viewer/server.ts`'s shell handler\n * also reads from the same directory via `loadShellTemplate`. Sharing the\n * constant keeps the two surfaces honest about pointing at one place.\n */\n\nimport { readFile, realpath } from \"fs/promises\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport type { ServerResponse } from \"http\";\nimport { assertSafeSlug, PathSafetyError } from \"./path-safety.js\";\n\n/**\n * Resolve the directory the viewer's static assets live in. Computed once\n * at module load, relative to wherever this file ended up after tsup\n * bundling — i.e. `<dist>/viewer/assets/` next to `dist/cli.js`. The\n * `copy-viewer-assets.mjs` tsup `onSuccess` hook is responsible for\n * populating that location; if it failed to run, the shell handler in\n * `server.ts` surfaces a per-request `shell_missing` 500 and this module\n * surfaces `asset_not_found` 404s instead of crashing startup.\n */\nexport const ASSETS_DIR = path.join(\n path.dirname(fileURLToPath(import.meta.url)),\n \"viewer/assets\",\n);\n\n/** Allowlist of asset extensions the static handler is willing to serve. */\nconst ASSET_CONTENT_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".js\": \"application/javascript; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n};\n\n/**\n * Serve a single asset file from `ASSETS_DIR`. Path-confined to the\n * canonical assets dir (catching symlinked entries that try to escape)\n * and limited to the small extension allowlist in `ASSET_CONTENT_TYPES`.\n */\nexport async function handleAsset(res: ServerResponse, pathname: string): Promise<void> {\n const segments = decodeAssetSegments(pathname);\n if (!segments) {\n writeAssetError(res, 400, \"bad_asset_path\", \"Bad asset path.\");\n return;\n }\n if (segments.length === 0) {\n writeAssetError(res, 404, \"asset_not_found\", \"Asset not found.\");\n return;\n }\n const contentType = ASSET_CONTENT_TYPES[\n path.extname(segments[segments.length - 1]).toLowerCase()\n ];\n if (!contentType) {\n writeAssetError(res, 404, \"asset_not_found\", \"Asset not found.\");\n return;\n }\n const resolved = await resolveAssetPath(segments);\n if (!resolved) {\n writeAssetError(res, 404, \"asset_not_found\", \"Asset not found.\");\n return;\n }\n try {\n const body = await readFile(resolved);\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", contentType);\n res.end(body);\n } catch {\n writeAssetError(res, 404, \"asset_not_found\", \"Asset not found.\");\n }\n}\n\n/**\n * Split the `/assets/...` URL path into decoded, structurally-safe\n * segments. Returns null when any segment is malformed (invalid\n * percent-encoding, separator, NUL, or traversal). An empty asset\n * path (`/assets/`) returns an empty array — the caller decides\n * whether to 404.\n */\nfunction decodeAssetSegments(pathname: string): string[] | null {\n const trimmed = pathname.replace(/^\\/assets\\//, \"\");\n if (trimmed.length === 0) return [];\n const decoded: string[] = [];\n for (const raw of trimmed.split(\"/\")) {\n let segment: string;\n try {\n segment = decodeURIComponent(raw);\n } catch {\n return null;\n }\n try {\n assertSafeSlug(segment);\n } catch (err) {\n if (err instanceof PathSafetyError) return null;\n throw err;\n }\n decoded.push(segment);\n }\n return decoded;\n}\n\n/**\n * Join `segments` under `ASSETS_DIR`, `realpath` both sides, and return\n * the resolved path only when it stays inside the canonical assets dir.\n * Returns null when the file is missing or escapes confinement.\n */\nasync function resolveAssetPath(segments: string[]): Promise<string | null> {\n const candidate = path.join(ASSETS_DIR, ...segments);\n let resolved: string;\n try {\n resolved = await realpath(candidate);\n } catch {\n return null;\n }\n const baseReal = await realpath(ASSETS_DIR).catch(() => ASSETS_DIR);\n if (resolved === baseReal) return resolved;\n const prefix = baseReal.endsWith(path.sep) ? baseReal : baseReal + path.sep;\n return resolved.startsWith(prefix) ? resolved : null;\n}\n\n/**\n * Write a `{ error: { code, message } }` JSON envelope for asset\n * failures. `message` is a hardcoded human string, never the request\n * pathname — reflecting untrusted input into the response body is\n * uneven with the rest of the server's error contract and would let\n * a noisy client write garbage into downstream response logs.\n */\nfunction writeAssetError(\n res: ServerResponse,\n status: number,\n code: string,\n message: string,\n): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n res.end(JSON.stringify({ error: { code, message } }));\n}\n","/**\n * Path-safety primitives for the local web viewer.\n *\n * Three layered checks form the v1 path-confinement chain. The HTTP layer\n * (Slice 2) is responsible for calling them in order at route entry:\n *\n * 1. Decode the URL path segment exactly once with `decodeURIComponent`.\n * 2. `assertSafeSlug(decoded)` — reject separators, NUL, traversal-as-slug.\n * 3. `resolveUnderRoot(root, ...segments)` — `realpath` both sides, confirm\n * the joined target stays inside `realpath(root)`. Catches symlink\n * escapes that survive a clean slug.\n * 4. `assertViewerSubtree(root, resolved)` — named-allowlist confinement\n * to `wiki/`, `sources/`, and `.llmwiki/last-lint.json` only. Anything\n * else under root (including other `.llmwiki/*`, `.git/`, `node_modules/`)\n * is rejected even though `realpath` resolved it cleanly.\n */\n\nimport { realpath } from \"fs/promises\";\nimport path from \"path\";\nimport {\n CONCEPTS_DIR,\n QUERIES_DIR,\n SOURCES_DIR,\n LAST_LINT_FILE,\n} from \"../utils/constants.js\";\n\n/** Error thrown when any path-safety check rejects an input. */\nexport class PathSafetyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PathSafetyError\";\n }\n}\n\n/**\n * Reject decoded slug values that would let a request escape its intended\n * file. Run AFTER `decodeURIComponent` — percent-encoded traversal becomes\n * literal `..` here and is caught. Unicode slugs (any letter or number\n * code point) are accepted; only structural metacharacters are rejected.\n */\nexport function assertSafeSlug(decodedSlug: string): void {\n if (typeof decodedSlug !== \"string\") {\n throw new PathSafetyError(\"slug must be a string\");\n }\n if (decodedSlug.length === 0) {\n throw new PathSafetyError(\"slug must not be empty\");\n }\n if (decodedSlug === \".\" || decodedSlug === \"..\") {\n throw new PathSafetyError(`slug must not be \"${decodedSlug}\"`);\n }\n if (decodedSlug.includes(\"/\") || decodedSlug.includes(\"\\\\\")) {\n throw new PathSafetyError(\"slug must not contain path separators\");\n }\n if (decodedSlug.includes(\"\\0\")) {\n throw new PathSafetyError(\"slug must not contain NUL bytes\");\n }\n if (path.sep !== \"/\" && decodedSlug.includes(path.sep)) {\n throw new PathSafetyError(`slug must not contain platform separator \"${path.sep}\"`);\n }\n}\n\n/**\n * Join `root` and `safeSegments`, resolve symlinks on both ends, and\n * confirm the target stays inside `realpath(root)`. Absolute segments are\n * rejected up-front so a single bad input cannot pivot the join away from\n * `root`. Callers MUST run `assertSafeSlug` on every untrusted segment\n * before this — `resolveUnderRoot` does not re-validate slug shape.\n */\nexport async function resolveUnderRoot(\n root: string,\n ...safeSegments: string[]\n): Promise<string> {\n for (const segment of safeSegments) {\n if (typeof segment !== \"string\" || segment.length === 0) {\n throw new PathSafetyError(\"path segment must be a non-empty string\");\n }\n if (path.isAbsolute(segment)) {\n throw new PathSafetyError(\"path segment must not be absolute\");\n }\n }\n const joined = path.join(root, ...safeSegments);\n const realRoot = await realpath(root);\n const realTarget = await realpath(joined);\n const rootWithSep = realRoot.endsWith(path.sep) ? realRoot : realRoot + path.sep;\n if (realTarget !== realRoot && !realTarget.startsWith(rootWithSep)) {\n throw new PathSafetyError(\"resolved path escapes project root\");\n }\n return realTarget;\n}\n\n/**\n * Confine an already-resolved path to the viewer's read allowlist. The\n * spec says \"read-only metadata under `.llmwiki/`\" — encoded here as a\n * named allowlist (the single file `.llmwiki/last-lint.json`) rather than\n * the full subtree, so a future viewer addition cannot accidentally\n * expose arbitrary `.llmwiki/*` contents over HTTP.\n *\n * The function is async because both `root` and the allowlist entries\n * are canonicalized via `fs.realpath` before comparison — without that,\n * a project root that itself is a symlink (a common workflow: cloning\n * into `~/code/project` which is a symlink to `/Volumes/Work/project`)\n * would false-reject every legitimate file. Callers may pass the raw\n * project root from `process.cwd()` and trust this helper to do the\n * canonicalization. Allowlist entries that do not exist on disk fall\n * back to a non-canonicalized join, so an empty project (no `sources/`\n * yet) still has a sensible allowlist.\n */\nexport async function assertViewerSubtree(root: string, resolvedPath: string): Promise<void> {\n const canonicalRoot = await canonicalizeOrFallback(root);\n const allowlistDirs = await Promise.all([\n canonicalizeOrFallback(path.join(canonicalRoot, \"wiki\")),\n canonicalizeOrFallback(path.join(canonicalRoot, CONCEPTS_DIR)),\n canonicalizeOrFallback(path.join(canonicalRoot, QUERIES_DIR)),\n canonicalizeOrFallback(path.join(canonicalRoot, SOURCES_DIR)),\n ]);\n const lintCachePath = await canonicalizeOrFallback(path.join(canonicalRoot, LAST_LINT_FILE));\n\n for (const dir of allowlistDirs) {\n if (isInsideOrEqual(resolvedPath, dir)) return;\n }\n if (resolvedPath === lintCachePath) return;\n\n throw new PathSafetyError(\"path is outside the viewer-approved subtrees\");\n}\n\n/**\n * Canonicalize `candidate` via `realpath`. Falls back to a normalized\n * stripped-trailing-separator string when the entry does not exist —\n * the allowlist tolerates absent directories (an empty project may have\n * no `sources/` yet) without rejecting every later check.\n */\nasync function canonicalizeOrFallback(candidate: string): Promise<string> {\n try {\n return await realpath(candidate);\n } catch {\n return candidate.endsWith(path.sep) ? candidate.slice(0, -1) : candidate;\n }\n}\n\n/** True when `candidate` equals `parent` or sits beneath it. */\nfunction isInsideOrEqual(candidate: string, parent: string): boolean {\n if (candidate === parent) return true;\n const parentWithSep = parent.endsWith(path.sep) ? parent : parent + path.sep;\n return candidate.startsWith(parentWithSep);\n}\n","/**\n * Server-side markdown → sanitized HTML renderer for the viewer.\n *\n * Pipeline:\n * 1. `markdown-it` with raw HTML disabled parses the body.\n * 2. Two custom inline rules (`wikilink-rule`, `citation-rule`) are\n * registered AFTER the built-in `link` rule so a wikilink or\n * citation embedded in markdown link text (`[outer [[alpha]] text](url)`)\n * gets folded into the outer link's text rather than emitting a\n * nested anchor. Inside that recursive parse the `linkLevel`\n * guard (and the silent-mode decline) keep the custom rules from\n * firing, so `[[…]]` and `^[…]` markers in link text, code spans,\n * fenced code blocks, and escaped sequences all render as literal\n * text. See `markdown-it-helpers.ts::shouldDeferInlineRule`.\n * 3. `sanitize-html` enforces the spec's tag/attribute/protocol\n * allowlist. The same policy applies to every rendered surface\n * (`/api/page/...` and `/api/index`).\n *\n * Returns HTML only. The structured `citations: ClaimCitation[]` field\n * on every page payload comes from `ViewerPage.citations` (produced by\n * Slice 1's `extractClaimCitations`), never from the renderer — so the\n * page record and the rendered HTML cannot drift on what the page cites.\n */\n\nimport MarkdownIt from \"markdown-it\";\nimport sanitizeHtml from \"sanitize-html\";\nimport type { IOptions } from \"sanitize-html\";\nimport { registerWikilink } from \"./wikilink-rule.js\";\nimport { registerCitation } from \"./citation-rule.js\";\nimport type { ViewerSnapshot } from \"./types.js\";\n\n/** Per-render configuration the server passes in. */\ninterface RenderOptions {\n /**\n * True when the viewer is bound to loopback (`127.0.0.1` or `::1`).\n * Controls whether citation chips include `data-absolute-path` and\n * editor-link payloads — both omitted on LAN binds per spec\n * §Support Rail.\n */\n isLoopback: boolean;\n}\n\n/**\n * Render a page body to sanitized HTML. The renderer is constructed per\n * call so the wikilink and citation rules can capture the current\n * snapshot in their closures without leaking across requests.\n */\nexport function renderPageHtml(\n body: string,\n snapshot: ViewerSnapshot,\n options: RenderOptions,\n): { html: string } {\n const md = buildMarkdownIt(snapshot, options);\n const rendered = md.render(body);\n const html = sanitizeHtml(rendered, buildSanitizerPolicy(options));\n return { html };\n}\n\n/** Construct a fresh markdown-it instance with the viewer's inline rules wired in. */\nfunction buildMarkdownIt(snapshot: ViewerSnapshot, options: RenderOptions): MarkdownIt {\n const md = new MarkdownIt({\n html: false,\n linkify: false,\n breaks: false,\n });\n registerWikilink(md, { pages: snapshot.pages });\n registerCitation(md, {\n root: snapshot.root,\n sourceFiles: new Set(snapshot.sourceFilenames),\n isLoopback: options.isLoopback,\n });\n return md;\n}\n\n/**\n * Build the sanitize-html policy. The spec's allowlist is encoded here\n * exactly once; every test that asserts a policy decision points at this\n * one source of truth so future changes show up in a single diff.\n *\n * Exported so the defense-in-depth security tests can exercise\n * `sanitizeHtml` directly against raw HTML the markdown parser would\n * normally escape — the sanitizer is the last line if a future change\n * ever flips the parser's `html` flag or admits an HTML-emitting plugin.\n */\nexport function buildSanitizerPolicy(options: RenderOptions): IOptions {\n const allowedSchemes = [\"http\", \"https\", \"mailto\"];\n const allowedSchemesAppliedToAttributes = [\"href\", \"src\", \"cite\"];\n return {\n allowedTags: [\n \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\",\n \"p\", \"br\", \"hr\",\n \"ul\", \"ol\", \"li\",\n \"blockquote\",\n \"strong\", \"em\", \"b\", \"i\", \"s\", \"u\",\n \"code\", \"pre\",\n \"table\", \"thead\", \"tbody\", \"tfoot\", \"tr\", \"th\", \"td\",\n \"a\", \"img\", \"span\", \"div\",\n ],\n disallowedTagsMode: \"discard\",\n allowedAttributes: {\n a: [\"href\", \"title\", \"class\", \"id\", \"data-*\", \"aria-*\"],\n img: [\"src\", \"alt\", \"title\", \"class\", \"id\"],\n span: [\"class\", \"id\", \"data-*\", \"aria-*\"],\n div: [\"class\", \"id\", \"data-*\", \"aria-*\"],\n th: [\"scope\", \"colspan\", \"rowspan\", \"class\", \"id\"],\n td: [\"colspan\", \"rowspan\", \"class\", \"id\"],\n table: [\"class\", \"id\"],\n code: [\"class\"],\n \"*\": [\"class\", \"id\"],\n },\n allowedSchemes,\n allowedSchemesByTag: {\n a: buildAnchorSchemes(),\n img: [\"http\", \"https\", \"data\"],\n },\n allowedSchemesAppliedToAttributes,\n allowProtocolRelative: false,\n // `allowedAttributes` above whitelists `class` everywhere via `*`,\n // so no further class-name allowlist is needed; leaving\n // `allowedClasses` unset lets every class value through.\n allowedStyles: {},\n allowedIframeHostnames: [],\n transformTags: {\n a: filterAnchorHref(),\n img: filterImgSrc,\n span: filterSpanForLanBind(options),\n },\n // sanitize-html's URL filter does not enforce hash-only links by\n // default; the anchor transform above whitelists `#/…` explicitly.\n };\n}\n\n/**\n * On non-loopback binds, strip `data-absolute-path` and\n * `data-editor-href` from any `<span>` regardless of who produced them.\n * The citation rule already gates these at the producer; this transform\n * is defense-in-depth so a future markdown-it plugin or hand-crafted\n * raw HTML can't smuggle the user's filesystem layout onto a LAN\n * surface. On loopback binds the attributes pass through untouched\n * (citation chips need them to render the editor link).\n */\nfunction filterSpanForLanBind(options: RenderOptions) {\n return function transformSpan(tagName: string, attribs: Record<string, string>): {\n tagName: string;\n attribs: Record<string, string>;\n } {\n if (options.isLoopback) return { tagName, attribs };\n if (!(\"data-absolute-path\" in attribs) && !(\"data-editor-href\" in attribs)) {\n return { tagName, attribs };\n }\n const stripped: Record<string, string> = {};\n for (const [key, value] of Object.entries(attribs)) {\n if (key === \"data-absolute-path\" || key === \"data-editor-href\") continue;\n stripped[key] = value;\n }\n return { tagName, attribs: stripped };\n };\n}\n\n/**\n * Anchor protocols allowed in the rendered output. Intentionally does\n * NOT include `vscode://` even on loopback: citation chips emit the\n * editor link on a `<span data-editor-href>`, not on an `<a href>`, so\n * markdown-authored anchors like `[click](vscode://file//etc/passwd)`\n * get their href stripped and cannot trick the user into opening\n * arbitrary local files in their editor.\n */\nfunction buildAnchorSchemes(): string[] {\n return [\"http\", \"https\", \"mailto\"];\n}\n\n/**\n * Filter anchor `href` values. Allows http/https/mailto, the viewer's\n * `#/…` hash links, and bare-fragment anchors. Anything else\n * (including any `vscode://` URI — see `buildAnchorSchemes`) loses the\n * `href` attribute entirely.\n */\nfunction filterAnchorHref() {\n return function transformAnchor(tagName: string, attribs: Record<string, string>): {\n tagName: string;\n attribs: Record<string, string>;\n } {\n const href = attribs.href;\n if (typeof href !== \"string\" || href.length === 0) return { tagName, attribs };\n if (isAllowedAnchorHref(href)) return { tagName, attribs };\n const stripped = { ...attribs };\n delete stripped.href;\n return { tagName, attribs: stripped };\n };\n}\n\n/** Filter `img` src to image-typed `data:` URIs and http(s) only. */\nfunction filterImgSrc(tagName: string, attribs: Record<string, string>): {\n tagName: string;\n attribs: Record<string, string>;\n} {\n const src = attribs.src;\n if (typeof src !== \"string\" || src.length === 0) return { tagName, attribs };\n if (isAllowedImgSrc(src)) return { tagName, attribs };\n const stripped = { ...attribs };\n delete stripped.src;\n return { tagName, attribs: stripped };\n}\n\n/**\n * Allow `http://`, `https://`, `mailto:`, and bare-fragment `#…`/`#/…`.\n * On loopback binds, additionally allow `vscode://file/<path>` (the\n * spec's \"safe local editor link\" carve-out for citation chips). The\n * carve-out is intentionally narrow — arbitrary `vscode://` URIs can\n * invoke commands (e.g. `vscode://vscode.git/...`), so a tighter prefix\n * keeps a hostile markdown source from smuggling command invocations\n * through user-authored anchors.\n */\nfunction isAllowedAnchorHref(href: string): boolean {\n if (href.startsWith(\"#\")) return true;\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\")) return true;\n if (href.startsWith(\"mailto:\")) return true;\n return false;\n}\n\n/** Allow http(s) and `data:image/...` only. */\nfunction isAllowedImgSrc(src: string): boolean {\n if (src.startsWith(\"http://\") || src.startsWith(\"https://\")) return true;\n if (src.startsWith(\"data:image/\")) return true;\n return false;\n}\n","/**\n * Shared low-level wiki page collector.\n *\n * Walks `wiki/concepts/` and `wiki/queries/`, derives the slug from each\n * filename stem (NOT through `slugify()` — filename slugs are the canonical\n * filesystem-truth identifier; slugifying them would shift routes, exports,\n * and citation lookups), parses frontmatter via `parseFrontmatterStatus`,\n * and returns one `RawWikiPage` per readable `.md` file with a `parseStatus`\n * field describing structural problems.\n *\n * Content semantics: this layer does not drop pages for parse-level\n * failures (missing frontmatter, malformed YAML, missing title, orphaned\n * flag). Those are surfaced as `parseStatus` flags so the caller decides.\n *\n * Path-safety: this layer DOES drop entries that fail confinement to\n * their expected canonical directory. Specifically — a symlinked\n * `wiki/concepts/` directory (even pointing in-root), a symlinked\n * `.md` file whose `realpath` resolves anywhere other than under the\n * expected concepts/queries directory, and any unreadable entry — are\n * silently excluded. Two callers consume it:\n *\n * - `src/export/collect.ts` filters on `parseStatus.orphaned` and\n * `parseStatus.hasTitle` to preserve the existing export semantics.\n * - `src/viewer/collect.ts` retains every record and maps `parseStatus`\n * flags into `ViewerWarning` objects so users can diagnose malformed\n * pages in the UI.\n */\n\nimport { readdir, readFile, realpath } from \"fs/promises\";\nimport path from \"path\";\nimport { parseFrontmatterStatus, slugify } from \"../utils/markdown.js\";\nimport { CONCEPTS_DIR, QUERIES_DIR } from \"../utils/constants.js\";\nimport type { PageDirectory } from \"../export/types.js\";\n\n/** Regex that matches `[[wikilink]]` or `[[wikilink|alias]]` patterns. */\nconst WIKILINK_RE = /\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]/g;\n\n/**\n * Structural status of a single page's frontmatter, surfaced to callers so\n * they can decide whether to filter, warn, or pass through.\n */\ninterface RawPageParseStatus {\n /** True when the file begins with a `---\\n…\\n---` block. */\n hasFrontmatterBlock: boolean;\n /** True when the frontmatter block exists but YAML failed to parse. */\n malformedFrontmatter: boolean;\n /** True when frontmatter contains a non-empty string `title`. */\n hasTitle: boolean;\n /** True when frontmatter explicitly sets `orphaned: true`. */\n orphaned: boolean;\n}\n\n/**\n * Raw page record returned by the shared collector. Lower-level than\n * `ExportPage` or `ViewerPage`: no decoration, no filtering, no warnings.\n */\nexport interface RawWikiPage {\n /** Filename stem (filename without the trailing `.md`). */\n slug: string;\n /** Which wiki/ subdirectory the page came from. */\n pageDirectory: PageDirectory;\n /** Absolute path on disk, useful for diagnostics and editor links. */\n filePath: string;\n /** Title from frontmatter when present; undefined otherwise. */\n title?: string;\n /** Parsed frontmatter (empty object when missing or malformed). */\n frontmatter: Record<string, unknown>;\n /** Markdown body with the frontmatter block stripped. */\n body: string;\n /** Structural status flags consumed by export and viewer callers. */\n parseStatus: RawPageParseStatus;\n}\n\n/**\n * Extract the slugs of all pages linked via `[[wikilinks]]` in the body.\n * Wikilink targets ARE slugified — the human-typed link text may not match\n * the on-disk filename verbatim, so we normalize to the same shape `slugify`\n * produces. Returns deduplicated targets.\n */\nexport function extractWikilinkSlugs(body: string): string[] {\n const slugs = new Set<string>();\n WIKILINK_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK_RE.exec(body)) !== null) {\n slugs.add(slugify(match[1].trim()));\n }\n return [...slugs];\n}\n\n/**\n * `realpath` wrapper that returns null instead of throwing on missing\n * files. Used everywhere we resolve a possibly-absent or possibly-broken\n * symlink and want to fall through to \"skip this entry.\"\n */\nasync function safeRealpath(p: string): Promise<string | null> {\n try {\n return await realpath(p);\n } catch {\n return null;\n }\n}\n\n/** True when `child` equals `dir` or sits beneath it. */\nfunction isInsideDir(child: string, dir: string): boolean {\n if (child === dir) return true;\n const prefix = dir.endsWith(path.sep) ? dir : dir + path.sep;\n return child.startsWith(prefix);\n}\n\n/**\n * Parse a single markdown file into a `RawWikiPage`. Returns null only when\n * the file cannot be read — every other failure mode (missing frontmatter,\n * malformed YAML, missing title, orphaned flag) is preserved as a\n * `parseStatus` flag so the caller decides how to handle it.\n */\nasync function parsePageFile(\n filePath: string,\n slug: string,\n pageDirectory: PageDirectory,\n): Promise<RawWikiPage | null> {\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf-8\");\n } catch {\n return null;\n }\n\n const { meta, body, hasFrontmatterBlock, malformedFrontmatter } = parseFrontmatterStatus(raw);\n const title = typeof meta.title === \"string\" && meta.title.length > 0 ? meta.title : undefined;\n return {\n slug,\n pageDirectory,\n filePath,\n title,\n frontmatter: meta,\n body,\n parseStatus: {\n hasFrontmatterBlock,\n malformedFrontmatter,\n hasTitle: title !== undefined,\n orphaned: meta.orphaned === true,\n },\n };\n}\n\n/**\n * Collect every readable `.md` file from a single wiki subdirectory.\n *\n * Confinement is stricter than \"stays under project root\": the\n * directory itself must resolve via `realpath` to the exact expected\n * path under `canonicalRoot` (so a symlinked `wiki/concepts/` is\n * skipped wholesale even when its target is also inside the project),\n * and each `.md` entry must resolve to a path under that canonical\n * expected directory (so a symlinked `wiki/concepts/leak.md` pointing\n * at `<root>/README.md` or `<root>/wiki/queries/x.md` is dropped).\n */\nasync function collectFromDir(\n canonicalRoot: string,\n pageDirectory: PageDirectory,\n subdir: string,\n): Promise<RawWikiPage[]> {\n const expectedDir = path.join(canonicalRoot, subdir);\n const realDir = await safeRealpath(expectedDir);\n if (realDir !== expectedDir) return [];\n let files: string[];\n try {\n files = await readdir(realDir);\n } catch {\n return [];\n }\n const pages: RawWikiPage[] = [];\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const candidate = path.join(realDir, file);\n const resolved = await safeRealpath(candidate);\n if (!resolved || !isInsideDir(resolved, realDir)) continue;\n const slug = file.replace(/\\.md$/, \"\");\n const page = await parsePageFile(resolved, slug, pageDirectory);\n if (page) pages.push(page);\n }\n return pages;\n}\n\n/**\n * Collect all readable wiki pages from `wiki/concepts/` and `wiki/queries/`.\n * Entries dropped for path-safety reasons (see `collectFromDir`) are\n * silently excluded. Pages are returned in filesystem order within each\n * directory, with concepts before queries; callers that need a stable\n * total order should sort.\n */\nexport async function collectRawWikiPages(root: string): Promise<RawWikiPage[]> {\n const canonicalRoot = await safeRealpath(root);\n if (!canonicalRoot) return [];\n const [concepts, queries] = await Promise.all([\n collectFromDir(canonicalRoot, \"concepts\", CONCEPTS_DIR),\n collectFromDir(canonicalRoot, \"queries\", QUERIES_DIR),\n ]);\n return [...concepts, ...queries];\n}\n","/**\n * Viewer-facing page collector.\n *\n * Consumes the structural records produced by `src/wiki/collect.ts` and\n * decorates each one with the fields the HTTP server needs:\n * - namespaced `id` (`concepts/<slug>` or `queries/<slug>`)\n * - `outgoingLinks` resolved against the in-memory page list using the\n * bare-slug precedence rule (concepts win over queries)\n * - `citations` extracted via `extractClaimCitations`\n * - stable `ViewerWarning` objects derived from `parseStatus` flags\n *\n * Unlike the export collector, this layer never drops a page: pages with\n * missing or malformed frontmatter are retained with a warning so users\n * can navigate to them and see what is wrong.\n */\n\nimport { collectRawWikiPages, extractWikilinkSlugs } from \"../wiki/collect.js\";\nimport type { RawWikiPage } from \"../wiki/collect.js\";\nimport { extractClaimCitations } from \"../utils/markdown.js\";\nimport type { PageId, ViewerPage, ViewerWarning } from \"./types.js\";\n\n/** Minimal page shape `resolveBareSlug` needs to find a target. */\ntype PageIndexEntry = { id: PageId; pageDirectory: ViewerPage[\"pageDirectory\"]; slug: string };\n\n/**\n * Build the decorated page list for a project root. Each `ViewerPage`\n * carries its namespaced id, resolved outgoing links, citations, and any\n * `ViewerWarning` objects derived from the underlying `parseStatus` flags.\n * Returns pages in collector order (concepts then queries).\n */\nexport async function collectViewerPages(root: string): Promise<ViewerPage[]> {\n const raw = await collectRawWikiPages(root);\n return decoratePages(raw);\n}\n\n/**\n * Resolve a bare-slug wikilink target to a namespaced `PageId`. The\n * precedence rule (concepts before queries) matches the spec and is the\n * same logic used for both per-page outgoing links and `/api/index` link\n * resolution; exporting it here keeps callers from re-implementing the\n * order and accidentally diverging.\n */\nexport function resolveBareSlug(\n slug: string,\n pages: ReadonlyArray<PageIndexEntry>,\n): PageId | null {\n if (slug.length === 0) return null;\n const concept = pages.find((p) => p.pageDirectory === \"concepts\" && p.slug === slug);\n if (concept) return concept.id;\n const query = pages.find((p) => p.pageDirectory === \"queries\" && p.slug === slug);\n if (query) return query.id;\n return null;\n}\n\n/**\n * Resolve a list of bare-slug wikilink targets against an in-memory page\n * index and deduplicate the resulting `PageId`s while preserving first-\n * occurrence order. Unresolved targets are dropped.\n */\nexport function resolveBareSlugList(\n targets: string[],\n pages: ReadonlyArray<PageIndexEntry>,\n): PageId[] {\n const seen = new Set<PageId>();\n const ordered: PageId[] = [];\n for (const target of targets) {\n const resolved = resolveBareSlug(target, pages);\n if (resolved && !seen.has(resolved)) {\n seen.add(resolved);\n ordered.push(resolved);\n }\n }\n return ordered;\n}\n\n/**\n * Two-pass decoration: build the namespaced id/title/warnings shell for\n * every page first, then resolve wikilink targets against the completed\n * shell. Single-pass would let a page miss links to pages later in the\n * list; the index has to be complete before resolution begins.\n */\nfunction decoratePages(raw: RawWikiPage[]): ViewerPage[] {\n const shells = raw.map(buildPageShell);\n for (const page of shells) {\n const targets = extractWikilinkSlugs(page.body);\n page.outgoingLinks = resolveBareSlugList(targets, shells);\n }\n return shells;\n}\n\n/**\n * Build the parts of a `ViewerPage` that do not need cross-page resolution\n * (id, title, citations, warnings). `outgoingLinks` starts empty and is\n * filled in once every shell is built.\n */\nfunction buildPageShell(page: RawWikiPage): ViewerPage {\n const id: PageId = `${page.pageDirectory}/${page.slug}`;\n return {\n id,\n slug: page.slug,\n pageDirectory: page.pageDirectory,\n title: page.title ?? page.slug,\n filePath: page.filePath,\n frontmatter: page.frontmatter,\n body: page.body,\n outgoingLinks: [],\n citations: extractClaimCitations(page.body),\n warnings: warningsFromParseStatus(page),\n };\n}\n\n/**\n * Map the structural `parseStatus` flags from `src/wiki/collect.ts` into\n * stable viewer warnings. Multiple conditions on one page produce\n * multiple warnings; the order here is the order they appear on the\n * page's `warnings[]`.\n */\nfunction warningsFromParseStatus(page: RawWikiPage): ViewerWarning[] {\n const warnings: ViewerWarning[] = [];\n if (!page.parseStatus.hasFrontmatterBlock) {\n warnings.push({\n code: \"missing_frontmatter\",\n message: `Page \"${page.slug}\" has no frontmatter block.`,\n });\n } else if (page.parseStatus.malformedFrontmatter) {\n warnings.push({\n code: \"malformed_frontmatter\",\n message: `Page \"${page.slug}\" has malformed YAML frontmatter.`,\n });\n }\n if (!page.parseStatus.hasTitle) {\n warnings.push({\n code: \"missing_title\",\n message: `Page \"${page.slug}\" has no frontmatter title; displaying slug.`,\n });\n }\n return warnings;\n}\n\n","/**\n * Shared internal helpers for the viewer's markdown-it inline rules.\n *\n * Both `wikilink-rule.ts` and `citation-rule.ts` need the same two\n * concerns: a minimal HTML escape for their render output (the rules\n * emit raw HTML strings that the sanitizer later validates), and a safe\n * read of `state.linkLevel` (a runtime property markdown-it sets while\n * parsing inside link text but `@types/markdown-it` 14 does not expose\n * on `StateInline`).\n */\n\nimport type StateInline from \"markdown-it/lib/rules_inline/state_inline.mjs\";\n\n/**\n * Minimal HTML attribute/text escape. Used by inline-rule renderers\n * that emit attribute values or visible text inside the HTML string\n * they hand back to markdown-it. The downstream sanitizer enforces the\n * tag/attribute allowlist; this escape just prevents structural breaks\n * (closing the tag early, breaking an attribute quote).\n */\nexport function escapeHtml(input: string): string {\n return input\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n/**\n * Read `state.linkLevel`, which markdown-it sets while parsing inside\n * link text but `@types/markdown-it` 14 does not surface on\n * `StateInline`. Falls back to 0 if the property is absent so the rule\n * still parses on older typings.\n */\nfunction currentLinkLevel(state: StateInline): number {\n const lifted = state as unknown as { linkLevel?: number };\n return typeof lifted.linkLevel === \"number\" ? lifted.linkLevel : 0;\n}\n\n/**\n * True when a custom inline rule should decline to match at the current\n * state. Two reasons it might:\n *\n * - `state.linkLevel > 0`: markdown-it is recursively parsing inline\n * content inside link text. Custom rules that emit anchors or\n * other interactive elements must not fire there, or the rendered\n * HTML carries nested anchors (invalid + a focus-trap accessibility\n * hazard).\n * - `silent === true`: markdown-it's link-label scanner uses\n * `skipToken` in silent mode to count nested `[`/`]` for bracket\n * matching. Consuming a nested `[` while silent trips the\n * `disableNested` guard inside the link rule and breaks otherwise-\n * valid markdown links of the shape `[outer [[alpha]] text](url)`.\n */\nexport function shouldDeferInlineRule(state: StateInline, silent: boolean): boolean {\n if (currentLinkLevel(state) > 0) return true;\n if (silent) return true;\n return false;\n}\n","/**\n * markdown-it inline rule for `[[wikilink]]` and `[[wikilink|alias]]`.\n *\n * Resolved wikilinks become hash-routed anchors carrying a `data-page-id`\n * attribute the client uses to mark the active sidebar entry. Unresolved\n * wikilinks render as a visible `<span data-missing=\"true\">[[slug]]</span>`\n * so the user can see (and fix) broken provenance instead of silently\n * dropping the target.\n *\n * Rule placement (handled by `registerWikilink`): registered AFTER the\n * built-in `link` rule. The link rule needs first crack at `[ … ](url)`\n * so a `[[wikilink]]` embedded in link text gets folded into the outer\n * link's text rather than emitting a nested anchor; the recursive parse\n * that happens inside link text is then suppressed by `shouldDeferInlineRule`\n * (link-level + silent-mode guards). Code spans and fenced code blocks\n * are handled earlier by markdown-it's own rules; escaped sequences are\n * stripped by the `escape` rule before this rule sees them. All four\n * contexts render the `[[…]]` marker as literal text per the spec's\n * §Slice 4 \"code-span / fenced / escaped / link-text\" audit item.\n */\n\nimport type MarkdownIt from \"markdown-it\";\nimport type StateInline from \"markdown-it/lib/rules_inline/state_inline.mjs\";\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport { resolveBareSlug } from \"./collect.js\";\nimport { slugify } from \"../utils/markdown.js\";\nimport { escapeHtml, shouldDeferInlineRule } from \"./markdown-it-helpers.js\";\nimport type { PageId, ViewerPage } from \"./types.js\";\n\nconst OPEN = \"[\";\nconst CHAR_OPEN_BRACKET = 0x5b; // \"[\"\n\n/** Internal context the parser and renderer share for a single render call. */\ninterface WikilinkContext {\n pages: ReadonlyArray<ViewerPage>;\n}\n\n/**\n * Register the wikilink inline rule and its renderer on `md`. The\n * `context` is captured by closure so the parser/renderer functions stay\n * pure of markdown-it's plugin-options shape.\n *\n * Registered AFTER the built-in `link` rule (not before): the link rule\n * needs first crack at `[ … ](url)` so a wikilink embedded in link text\n * like `[See [[alpha]] reference](url)` is consumed as part of the outer\n * link, with our wikilink later inhibited by the `linkLevel` guard while\n * the link's recursive inline parse runs.\n */\nexport function registerWikilink(md: MarkdownIt, context: WikilinkContext): void {\n md.inline.ruler.after(\"link\", \"wikilink\", buildParser(context));\n md.renderer.rules.wikilink = (tokens: Token[], idx: number): string =>\n renderWikilinkToken(tokens[idx]);\n}\n\n/** Build the inline parser closure capturing the snapshot context. */\nfunction buildParser(context: WikilinkContext) {\n return function parseWikilink(state: StateInline, silent: boolean): boolean {\n if (state.src.charCodeAt(state.pos) !== CHAR_OPEN_BRACKET) return false;\n if (state.src.charCodeAt(state.pos + 1) !== CHAR_OPEN_BRACKET) return false;\n if (shouldDeferInlineRule(state, silent)) return false;\n const closeAt = state.src.indexOf(\"]]\", state.pos + 2);\n if (closeAt < 0) return false;\n const inner = state.src.slice(state.pos + 2, closeAt);\n // Markdown convention: forbid newlines inside a single wikilink span.\n if (inner.includes(\"\\n\") || inner.includes(OPEN)) return false;\n const { rawTarget, display } = splitTargetAndAlias(inner);\n const slug = slugify(rawTarget.trim());\n const resolved = resolveBareSlug(slug, context.pages);\n pushWikilinkToken(state, resolved, slug, display);\n state.pos = closeAt + 2;\n return true;\n };\n}\n\n/** Split the inside-brackets text into a raw target and a display label. */\nfunction splitTargetAndAlias(inner: string): { rawTarget: string; display: string } {\n const pipe = inner.indexOf(\"|\");\n if (pipe < 0) return { rawTarget: inner, display: inner.trim() };\n return {\n rawTarget: inner.slice(0, pipe),\n display: inner.slice(pipe + 1).trim() || inner.slice(0, pipe).trim(),\n };\n}\n\n/** Push a single wikilink token onto the parser state. */\nfunction pushWikilinkToken(\n state: StateInline,\n resolved: PageId | null,\n slug: string,\n display: string,\n): void {\n const token = state.push(\"wikilink\", \"\", 0);\n token.meta = { resolved, slug, display };\n}\n\n/** Render a wikilink token as either an anchor or a missing-link span. */\nfunction renderWikilinkToken(token: Token): string {\n const meta = token.meta as { resolved: PageId | null; slug: string; display: string };\n const display = escapeHtml(meta.display || meta.slug);\n if (!meta.resolved) {\n return `<span data-missing=\"true\">[[${display}]]</span>`;\n }\n const href = `#/${encodeUriSegment(meta.resolved)}`;\n return `<a class=\"wikilink\" data-page-id=\"${escapeHtml(meta.resolved)}\" href=\"${escapeHtml(href)}\">${display}</a>`;\n}\n\n/** Encode a `concepts/<slug>` PageId into the URI form used by the hash router. */\nfunction encodeUriSegment(id: PageId): string {\n const [directory, slug] = id.split(\"/\");\n return `${encodeURIComponent(directory)}/${encodeURIComponent(slug)}`;\n}\n","/**\n * markdown-it inline rule for `^[source.md]` citation markers.\n *\n * Each marker can carry one or more comma-separated source entries; the\n * rule emits ONE chip per parsed span (matching the spec's\n * \"`^[a.md, b.md]` renders two chips\" rule). Span suffixes are parsed in\n * both flavours: `:42-58` and `#L42-L58`. Malformed entries (a colon\n * with no line numbers, end-before-start, etc.) are dropped per\n * `extractClaimCitations`'s contract.\n *\n * Each chip carries the source filename, optional line range,\n * resolvability flag (true iff the source filename is present in the\n * snapshot's source-file list), and — on loopback binds only —\n * `data-absolute-path` plus an editor `data-editor-href`. Non-loopback\n * binds intentionally omit both so LAN viewers cannot learn the user's\n * filesystem layout, per the spec's §Support Rail rules.\n *\n * Rule placement (handled by `registerCitation`): registered AFTER the\n * built-in `link` rule so a `^[…]` embedded in link text gets folded\n * into the outer link's text rather than emitting a chip next to a\n * nested anchor; `shouldDeferInlineRule` then suppresses the rule\n * during the link's recursive inline parse. Code spans and fenced\n * blocks are handled earlier by markdown-it's own rules; escaped\n * `\\^[…]` is stripped by the `escape` rule before this rule sees it.\n */\n\nimport type MarkdownIt from \"markdown-it\";\nimport type StateInline from \"markdown-it/lib/rules_inline/state_inline.mjs\";\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport { extractClaimCitations } from \"../utils/markdown.js\";\nimport { escapeHtml, shouldDeferInlineRule } from \"./markdown-it-helpers.js\";\nimport type { ClaimCitation, SourceSpan } from \"../utils/types.js\";\n\nconst CHAR_CARET = 0x5e; // \"^\"\nconst CHAR_OPEN_BRACKET = 0x5b; // \"[\"\n\n/** Shared context the parser and renderer use to decorate each chip. */\ninterface CitationContext {\n /** Project root, used to compute `data-absolute-path` on loopback binds. */\n root: string;\n /** Filenames present under `sources/`, used to set the resolvability flag. */\n sourceFiles: ReadonlySet<string>;\n /** When false, omit `absolutePath` and editor links per §Support Rail. */\n isLoopback: boolean;\n}\n\n/** One chip's render-ready data. */\ninterface ChipMeta {\n file: string;\n lineStart?: number;\n lineEnd?: number;\n resolved: boolean;\n absolutePath?: string;\n editorHref?: string;\n}\n\n/**\n * Register the rule and renderer on `md`. Registered AFTER the `link`\n * rule for the same reason as the wikilink rule (so a `^[…]` embedded in\n * link text gets included in the outer link's text, with the inner\n * recursive parse blocked by the `linkLevel` guard).\n */\nexport function registerCitation(md: MarkdownIt, context: CitationContext): void {\n md.inline.ruler.after(\"link\", \"citation\", buildParser(context));\n md.renderer.rules.citation = (tokens: Token[], idx: number): string =>\n renderCitationToken(tokens[idx]);\n}\n\n/** Build the parser closure capturing the citation context. */\nfunction buildParser(context: CitationContext) {\n return function parseCitation(state: StateInline, silent: boolean): boolean {\n if (state.src.charCodeAt(state.pos) !== CHAR_CARET) return false;\n if (state.src.charCodeAt(state.pos + 1) !== CHAR_OPEN_BRACKET) return false;\n if (shouldDeferInlineRule(state, silent)) return false;\n const closeAt = state.src.indexOf(\"]\", state.pos + 2);\n if (closeAt < 0) return false;\n const inner = state.src.slice(state.pos + 2, closeAt);\n if (inner.includes(\"\\n\")) return false;\n const citations = extractClaimCitations(`^[${inner}]`);\n pushChipTokens(state, citations, context);\n state.pos = closeAt + 1;\n return true;\n };\n}\n\n/** Emit one `citation` token per parsed span (multi-source marker → multiple chips). */\nfunction pushChipTokens(\n state: StateInline,\n citations: ClaimCitation[],\n context: CitationContext,\n): void {\n for (const citation of citations) {\n for (const span of citation.spans) {\n const token = state.push(\"citation\", \"\", 0);\n token.meta = buildChipMeta(span, context);\n }\n }\n}\n\n/** Construct the chip-meta record for a single source span. */\nfunction buildChipMeta(span: SourceSpan, context: CitationContext): ChipMeta {\n const meta: ChipMeta = {\n file: span.file,\n lineStart: span.lines?.start,\n lineEnd: span.lines?.end,\n resolved: context.sourceFiles.has(span.file),\n };\n if (context.isLoopback && meta.resolved && isBareFilename(span.file)) {\n const absolutePath = path.join(context.root, \"sources\", span.file);\n meta.absolutePath = absolutePath;\n meta.editorHref = buildEditorHref(absolutePath, meta.lineStart);\n }\n return meta;\n}\n\n/**\n * Build the `vscode://file/...` editor href, percent-encoding the path\n * portion so URI delimiters inside source filenames (spaces, `#`, `?`,\n * `&`, `=`, `;`, etc.) cannot turn part of the filename into a URI\n * fragment, query, or parameter. Uses Node's `pathToFileURL().pathname`\n * because it percent-encodes every char that would change URI structure\n * while preserving path separators. Plain `encodeURI` preserves several\n * of those delimiters, so a filename like `notes #1?.md` would emit a\n * malformed href there.\n *\n * The `pathname` already starts with `/` (it is the absolute path), so\n * concatenating directly onto `vscode://file` yields a well-formed URI\n * with `file` as the authority and the encoded absolute path as the\n * path (e.g. `vscode://file/tmp/x.md`). The optional `:<line>` suffix\n * is appended AFTER encoding so vscode parses it as a line index, not\n * part of the path.\n */\nfunction buildEditorHref(absolutePath: string, lineStart: number | undefined): string {\n const encodedPath = pathToFileURL(absolutePath).pathname;\n if (lineStart === undefined) return `vscode://file${encodedPath}`;\n return `vscode://file${encodedPath}:${lineStart}`;\n}\n\n/**\n * Conservative filename check: bare basename only, no separators, no\n * traversal segments. The citation rule never trusts a path-shaped\n * filename to begin with — if the source has slashes in it, we keep the\n * chip but skip the editor-link payload.\n */\nfunction isBareFilename(file: string): boolean {\n if (file.length === 0) return false;\n if (file.includes(\"/\") || file.includes(\"\\\\\") || file.includes(\"\\0\")) return false;\n if (file === \".\" || file === \"..\") return false;\n return true;\n}\n\n/** Render one citation chip token. */\nfunction renderCitationToken(token: Token): string {\n const meta = token.meta as ChipMeta;\n const label = formatChipLabel(meta);\n const attrs = chipAttributes(meta);\n return `<span ${attrs}>${escapeHtml(label)}</span>`;\n}\n\n/** Build the chip's data-* attribute string. */\nfunction chipAttributes(meta: ChipMeta): string {\n const parts = [\n `class=\"citation-chip\"`,\n `data-file=\"${escapeHtml(meta.file)}\"`,\n `data-resolved=\"${meta.resolved ? \"true\" : \"false\"}\"`,\n ];\n if (meta.lineStart !== undefined) {\n parts.push(`data-line-start=\"${meta.lineStart}\"`);\n }\n if (meta.lineEnd !== undefined) {\n parts.push(`data-line-end=\"${meta.lineEnd}\"`);\n }\n if (meta.absolutePath !== undefined) {\n parts.push(`data-absolute-path=\"${escapeHtml(meta.absolutePath)}\"`);\n }\n if (meta.editorHref !== undefined) {\n parts.push(`data-editor-href=\"${escapeHtml(meta.editorHref)}\"`);\n }\n return parts.join(\" \");\n}\n\n/** Human-visible chip label: filename plus optional line range. */\nfunction formatChipLabel(meta: ChipMeta): string {\n if (meta.lineStart === undefined) return meta.file;\n if (meta.lineEnd === undefined || meta.lineEnd === meta.lineStart) {\n return `${meta.file}:${meta.lineStart}`;\n }\n return `${meta.file}:${meta.lineStart}-${meta.lineEnd}`;\n}\n\n","/**\n * Server-side title/body search over the startup `ViewerSnapshot`.\n *\n * V1 semantics (spec §Slice 5 \"Search semantics\"):\n * - case-insensitive\n * - whitespace-tokenized\n * - multi-token AND: every token must appear in either title or body\n * - title matches rank before body matches\n * - 200-char query cap, 50-result cap\n * - concept and query pages only — `wiki/index.md` is excluded by\n * construction (it never lives in `snapshot.pages`)\n * - no fuzzy matching, stemming, regex, or client-side search\n *\n * The search reads from the snapshot exclusively — no per-request disk\n * I/O — so it inherits the same \"frozen at startup, restart to refresh\"\n * lifecycle as the rest of the viewer's API.\n */\n\nimport type { PageId, ViewerPage, ViewerSnapshot } from \"./types.js\";\nimport type { PageDirectory } from \"../export/types.js\";\n\nconst MAX_QUERY_LENGTH = 200;\nconst MAX_RESULTS = 50;\nconst SNIPPET_RADIUS = 60;\nconst SNIPPET_ELLIPSIS = \"…\";\n\n/** Where the query matched in a result page. */\ntype SearchMatch = \"title\" | \"body\";\n\n/** One row in the `/api/search` response. */\ninterface SearchResult {\n id: PageId;\n pageDirectory: PageDirectory;\n title: string;\n snippet: string;\n matchedIn: SearchMatch;\n}\n\n/**\n * Run a search over the snapshot and return the results envelope. Pure\n * over `(snapshot, rawQuery)` — the same inputs always produce the same\n * output, which lets the route handler stay a one-line adapter.\n */\nexport function searchPages(\n snapshot: ViewerSnapshot,\n rawQuery: string,\n): { results: SearchResult[] } {\n const tokens = tokenizeQuery(rawQuery);\n if (tokens.length === 0) return { results: [] };\n const matches = collectMatches(snapshot.pages, tokens);\n matches.sort(compareResults);\n return { results: matches.slice(0, MAX_RESULTS) };\n}\n\n/**\n * Trim, lowercase, cap at 200 characters, then split on any run of\n * whitespace. Empty tokens are dropped so trailing/leading spaces or\n * a runaway over-cap query still produce sensible tokens.\n */\nfunction tokenizeQuery(rawQuery: string): string[] {\n if (typeof rawQuery !== \"string\") return [];\n const trimmed = rawQuery.trim();\n if (trimmed.length === 0) return [];\n const capped = trimmed.slice(0, MAX_QUERY_LENGTH).toLowerCase();\n return capped.split(/\\s+/).filter((t) => t.length > 0);\n}\n\n/** Iterate the snapshot pages and emit one result per match. */\nfunction collectMatches(pages: ReadonlyArray<ViewerPage>, tokens: string[]): SearchResult[] {\n const matches: SearchResult[] = [];\n for (const page of pages) {\n const result = matchPage(page, tokens);\n if (result) matches.push(result);\n }\n return matches;\n}\n\n/**\n * Decide whether `page` matches `tokens`. Per spec §Slice 5 Search\n * Semantics: \"every token must appear in title or body\" — each token\n * individually must appear in the title-or-body union. Classification\n * (`matchedIn`) is \"title\" only when every token is found in the title;\n * any token that only matched the body downgrades the page to a body\n * hit, which then ranks below title hits.\n */\nfunction matchPage(page: ViewerPage, tokens: string[]): SearchResult | null {\n const titleLower = page.title.toLowerCase();\n const bodyLower = page.body.toLowerCase();\n for (const token of tokens) {\n if (!titleLower.includes(token) && !bodyLower.includes(token)) return null;\n }\n const allInTitle = tokens.every((t) => titleLower.includes(t));\n if (allInTitle) return rowFromPage(page, page.title, \"title\");\n const snippet = buildBodySnippet(page.body, bodyLower, tokens);\n return rowFromPage(page, snippet, \"body\");\n}\n\n/** Assemble a SearchResult from a page + computed snippet. */\nfunction rowFromPage(page: ViewerPage, snippet: string, matchedIn: SearchMatch): SearchResult {\n return {\n id: page.id,\n pageDirectory: page.pageDirectory,\n title: page.title,\n snippet,\n matchedIn,\n };\n}\n\n/**\n * Extract ±SNIPPET_RADIUS chars around the earliest token match in the\n * body. Newlines are flattened to single spaces so the snippet renders\n * inline in the results panel. `…` is prepended/appended when the\n * window was truncated at either end.\n */\nfunction buildBodySnippet(body: string, bodyLower: string, tokens: string[]): string {\n const matchPos = earliestTokenPosition(bodyLower, tokens);\n const start = Math.max(0, matchPos - SNIPPET_RADIUS);\n const end = Math.min(body.length, matchPos + SNIPPET_RADIUS);\n const cleaned = stripInlineMarkdownNoise(body.slice(start, end))\n .replace(/\\s+/g, \" \")\n .trim();\n const prefix = start > 0 ? SNIPPET_ELLIPSIS : \"\";\n const suffix = end < body.length ? SNIPPET_ELLIPSIS : \"\";\n return `${prefix}${cleaned}${suffix}`;\n}\n\n/**\n * Strip common inline-markdown markers from a snippet so the search\n * results panel shows readable prose rather than `**keyword**` and\n * `[label](url)` noise. Intentionally narrow: only handles the\n * inline-marker forms a reader would mistake for typos. Block-level\n * markers (`#` headings, `>` blockquotes) are left alone — they sit at\n * the start of a line and rarely land inside a ±60-char window.\n */\nfunction stripInlineMarkdownNoise(text: string): string {\n return text\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\")\n .replace(/\\[([^\\]]+)\\]\\([^)]*\\)/g, \"$1\")\n .replace(/\\[\\[([^\\]|\\n]+)\\|([^\\]\\n]+)\\]\\]/g, \"$2\")\n .replace(/\\[\\[([^\\]\\n]+)\\]\\]/g, \"$1\")\n .replace(/\\*\\*([^*]+)\\*\\*/g, \"$1\")\n .replace(/__([^_]+)__/g, \"$1\")\n .replace(/(?<!\\w)\\*([^*\\n]+)\\*(?!\\w)/g, \"$1\")\n .replace(/(?<!\\w)_([^_\\n]+)_(?!\\w)/g, \"$1\")\n .replace(/`([^`\\n]+)`/g, \"$1\")\n .replace(/~~([^~\\n]+)~~/g, \"$1\");\n}\n\n/** Earliest index where any token first appears in the body. */\nfunction earliestTokenPosition(bodyLower: string, tokens: string[]): number {\n let earliest = bodyLower.length;\n for (const token of tokens) {\n const idx = bodyLower.indexOf(token);\n if (idx >= 0 && idx < earliest) earliest = idx;\n }\n return earliest;\n}\n\n/**\n * Title hits sort before body hits. Within the same `matchedIn` bucket\n * the order is stable by title (alphabetical, locale-aware) so the\n * result list does not shift between requests against the same snapshot.\n */\nfunction compareResults(a: SearchResult, b: SearchResult): number {\n if (a.matchedIn !== b.matchedIn) {\n return a.matchedIn === \"title\" ? -1 : 1;\n }\n return a.title.localeCompare(b.title);\n}\n","/**\n * Build the frozen-at-startup `ViewerSnapshot` consumed by every viewer\n * endpoint. Every count, page list, and index payload that the HTTP\n * layer needs is captured here exactly once — v1 deliberately does not\n * live-watch the filesystem, so post-startup mutations are intentionally\n * invisible to the running viewer until it restarts.\n *\n * The snapshot consolidates four data sources:\n * - `collectViewerPages` for the decorated page list AND the\n * concept/query counts (deriving counts from the already-confined\n * page list means symlinked entries dropped by the collector\n * cannot quietly inflate the counts via a second unconfined scan)\n * - `readState` for the compiled-source count\n * - `countCandidates` for the pending-reviews count\n * - `readdir(sources/)` for the cheap source-file count\n */\n\nimport { readdir, readFile, realpath } from \"fs/promises\";\nimport path from \"path\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport { countCandidates } from \"../compiler/candidates.js\";\nimport { readState } from \"../utils/state.js\";\nimport { collectViewerPages, resolveBareSlugList } from \"./collect.js\";\nimport { extractWikilinkSlugs } from \"../wiki/collect.js\";\nimport { isMalformedCitationEntry } from \"../utils/markdown.js\";\nimport type {\n ViewerCounts,\n ViewerIndex,\n ViewerPage,\n ViewerProject,\n ViewerRecentPage,\n ViewerSnapshot,\n ViewerWarning,\n} from \"./types.js\";\n\nconst RECENT_PAGES_LIMIT = 8;\nconst INDEX_HREF = \"/#/index\";\n\n/**\n * Build the immutable startup snapshot for a project root. Reads pages,\n * counts, source state, candidates, and the optional `wiki/index.md`\n * exactly once and returns a fully populated `ViewerSnapshot`. Callers\n * must NOT re-derive any of these from disk on a per-request path —\n * `readLintCache` in `src/viewer/health.ts` is the sole exception.\n */\nexport async function buildViewerSnapshot(root: string): Promise<ViewerSnapshot> {\n const [pages, state, pendingReviews, sourceFilenames, index] = await Promise.all([\n collectViewerPages(root),\n readState(root),\n countCandidates(root),\n listSourceFiles(root),\n readIndexFile(root),\n ]);\n const project = buildProject(root);\n // Concept/query counts are derived from `pages`, the already-confined\n // viewer page list, NOT from a second unconfined directory scan.\n // Anything the collector dropped for path-safety reasons (symlinked\n // file or directory) is therefore also excluded from the counts.\n const counts: ViewerCounts = {\n concepts: pages.filter((p) => p.pageDirectory === \"concepts\").length,\n queries: pages.filter((p) => p.pageDirectory === \"queries\").length,\n sourceFiles: sourceFilenames.length,\n pendingReviews,\n compiledSources: Object.keys(state.sources).length,\n };\n const fullIndex: ViewerIndex = {\n available: index.available,\n href: INDEX_HREF,\n body: index.body,\n outgoingLinks: resolveBareSlugList(extractWikilinkSlugs(index.body), pages),\n };\n const sourceFileSet = new Set(sourceFilenames);\n const annotatedPages = pages.map((page) => annotateCitationWarnings(page, sourceFileSet));\n return {\n root,\n generatedAt: new Date().toISOString(),\n project,\n counts,\n index: fullIndex,\n recentPages: buildRecentPages(annotatedPages),\n pages: annotatedPages,\n sourceFilenames,\n };\n}\n\n/**\n * Append `unresolved_citation` and `malformed_citation` warnings to a\n * page based on its parsed citations and the project's source-file\n * list. Slice 1 only produced parser-level warnings; citation\n * resolvability needs the snapshot's source-file list, so this is the\n * earliest layer that can decide.\n *\n * The body is re-scanned for raw `^[…]` markers (rather than iterating\n * `page.citations`) because `extractClaimCitations` drops citations\n * whose ONLY entry has an invalid line range — but those still need a\n * `malformed_citation` warning. Scanning the body gives every marker a\n * chance to be classified.\n */\nfunction annotateCitationWarnings(page: ViewerPage, sourceFiles: ReadonlySet<string>): ViewerPage {\n const extra: ViewerWarning[] = [];\n const markerPattern = /\\^\\[([^\\]\\n]+)\\]/g;\n let match: RegExpExecArray | null;\n while ((match = markerPattern.exec(page.body)) !== null) {\n appendCitationWarningsForMarker(match[1], sourceFiles, extra);\n }\n if (extra.length === 0) return page;\n return { ...page, warnings: [...page.warnings, ...extra] };\n}\n\n/** Classify every comma-separated entry inside one `^[…]` marker. */\nfunction appendCitationWarningsForMarker(\n raw: string,\n sourceFiles: ReadonlySet<string>,\n into: ViewerWarning[],\n): void {\n for (const entry of raw.split(\",\")) {\n const trimmed = entry.trim();\n if (trimmed.length === 0) continue;\n if (isMalformedCitationEntry(trimmed)) {\n into.push({\n code: \"malformed_citation\",\n message: `Malformed citation entry: ${trimmed}`,\n });\n continue;\n }\n const file = trimmed.split(/[:#]/)[0];\n if (file.length > 0 && !sourceFiles.has(file)) {\n into.push({\n code: \"unresolved_citation\",\n message: `Source not found: ${file}`,\n });\n }\n }\n}\n\n\n/** Project title and bare directory name for the dashboard header. */\nfunction buildProject(root: string): ViewerProject {\n const rootName = path.basename(root);\n return { title: rootName, rootName };\n}\n\n/**\n * List filenames directly under `sources/`. Returns an empty array when\n * the directory is missing. The Slice 4 citation renderer uses this list\n * to mark each chip `data-resolved` without per-request directory scans;\n * `counts.sourceFiles` is the cheap `.length` of the same list.\n *\n * Stricter than \"stays under project root\": `realpath(<root>/sources)`\n * must equal the literal canonical path `<canonicalRoot>/sources`. A\n * symlinked `sources/` directory — even pointing in-root — returns an\n * empty list, matching the same containment posture the wiki collector\n * uses for `wiki/concepts/` and `wiki/queries/`. Symlinked entries\n * inside the directory are excluded by `Dirent.isFile()` (which returns\n * false for symlinks since `withFileTypes` does not follow them).\n */\nasync function listSourceFiles(root: string): Promise<string[]> {\n let canonicalRoot: string;\n try {\n canonicalRoot = await realpath(root);\n } catch {\n return [];\n }\n const expectedDir = path.join(canonicalRoot, SOURCES_DIR);\n let realDir: string;\n try {\n realDir = await realpath(expectedDir);\n } catch {\n return [];\n }\n if (realDir !== expectedDir) return [];\n try {\n const entries = await readdir(realDir, { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\n/**\n * Read `wiki/index.md` if present. Missing index is not an error: many\n * projects compile without an index page, and the viewer renders an\n * \"index unavailable\" placeholder for the `/#/index` route.\n *\n * Stricter than \"stays under project root\": `realpath(wiki/index.md)`\n * must equal the literal canonical path `<root>/wiki/index.md`. A\n * symlinked `wiki/index.md` is treated as unavailable, even when the\n * link target also lives inside the project — pointing the index at\n * (say) `<root>/README.md` would let the index endpoint render\n * content that has no business being the project's compiled index.\n * A symlinked `wiki/` directory is dropped by the same equality check.\n */\nasync function readIndexFile(root: string): Promise<{ available: boolean; body: string }> {\n let canonicalRoot: string;\n try {\n canonicalRoot = await realpath(root);\n } catch {\n return { available: false, body: \"\" };\n }\n const expectedIndex = path.join(canonicalRoot, \"wiki\", \"index.md\");\n let resolved: string;\n try {\n resolved = await realpath(expectedIndex);\n } catch {\n return { available: false, body: \"\" };\n }\n if (resolved !== expectedIndex) {\n return { available: false, body: \"\" };\n }\n try {\n const body = await readFile(resolved, \"utf-8\");\n return { available: true, body };\n } catch {\n return { available: false, body: \"\" };\n }\n}\n\n/**\n * Top-N recently updated pages for the dashboard. Pages without an\n * `updatedAt` frontmatter field sort to the end with an empty string so\n * the list remains deterministic.\n */\nfunction buildRecentPages(pages: ViewerPage[]): ViewerRecentPage[] {\n const rows: ViewerRecentPage[] = pages.map((page) => ({\n id: page.id,\n pageDirectory: page.pageDirectory,\n slug: page.slug,\n title: page.title,\n updatedAt:\n typeof page.frontmatter.updatedAt === \"string\" ? (page.frontmatter.updatedAt as string) : \"\",\n }));\n rows.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));\n return rows.slice(0, RECENT_PAGES_LIMIT);\n}\n\n","/**\n * Review candidate persistence for the llmwiki compile pipeline.\n *\n * When `llmwiki compile --review` runs, generated wiki pages are routed\n * here as JSON candidate records under `.llmwiki/candidates/` instead of\n * being written directly to `wiki/`. Reviewers then approve or reject the\n * proposals via the `llmwiki review` subcommands.\n *\n * Candidates are deliberately kept as standalone JSON so they survive across\n * compile runs and can be inspected manually without the CLI. Each record\n * stores the full page body so approval is a pure copy — the LLM is never\n * called again at approval time.\n */\n\nimport { readdir, rename, unlink, writeFile, mkdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { randomBytes } from \"crypto\";\nimport { atomicWrite, safeReadFile } from \"../utils/markdown.js\";\nimport * as output from \"../utils/output.js\";\nimport {\n CANDIDATES_DIR,\n CANDIDATES_ARCHIVE_DIR,\n} from \"../utils/constants.js\";\nimport type { ReviewCandidate, SourceState } from \"../utils/types.js\";\nimport type { LintResult } from \"../linter/types.js\";\n\n/** Length (bytes) of the random suffix appended to candidate ids. */\nconst ID_SUFFIX_BYTES = 4;\n\n/** Filesystem extension used for candidate JSON files. */\nconst CANDIDATE_EXT = \".json\";\n\n/** Input shape for creating a new candidate (id + timestamp generated here). */\ninterface CandidateDraft {\n title: string;\n slug: string;\n summary: string;\n sources: string[];\n body: string;\n /**\n * Per-source state entries to persist into `.llmwiki/state.json` when this\n * candidate is approved. Keyed by source filename. Optional so callers that\n * never need incremental tracking (legacy / tests) can omit it.\n */\n sourceStates?: Record<string, SourceState>;\n /**\n * Schema lint violations for the candidate body detected at compile time.\n * Omit (or pass `undefined`) when the candidate body is clean.\n */\n schemaViolations?: LintResult[];\n /**\n * Provenance lint violations for the candidate body — malformed claim\n * citations, out-of-bounds spans, or missing source files. Surfaced\n * alongside schema violations so reviewers see citation issues before\n * approving.\n */\n provenanceViolations?: LintResult[];\n}\n\n/** Build a deterministic-but-unique id from a slug and a short random suffix. */\nfunction buildCandidateId(slug: string): string {\n const suffix = randomBytes(ID_SUFFIX_BYTES).toString(\"hex\");\n return `${slug}-${suffix}`;\n}\n\n/** Absolute path to a candidate's JSON file. */\nfunction candidatePath(root: string, id: string): string {\n return path.join(root, CANDIDATES_DIR, `${id}${CANDIDATE_EXT}`);\n}\n\n/** Absolute path to the archived JSON file for a rejected candidate. */\nfunction archivePath(root: string, id: string): string {\n return path.join(root, CANDIDATES_ARCHIVE_DIR, `${id}${CANDIDATE_EXT}`);\n}\n\n/**\n * Persist a new candidate record and return it. The id is generated from the\n * slug plus a short random suffix so multiple compile runs can co-exist.\n * @param root - Project root directory.\n * @param draft - The candidate fields to persist.\n * @returns The full ReviewCandidate (with id + generatedAt populated).\n */\nexport async function writeCandidate(\n root: string,\n draft: CandidateDraft,\n): Promise<ReviewCandidate> {\n const candidate: ReviewCandidate = {\n id: buildCandidateId(draft.slug),\n title: draft.title,\n slug: draft.slug,\n summary: draft.summary,\n sources: draft.sources,\n body: draft.body,\n generatedAt: new Date().toISOString(),\n ...(draft.sourceStates ? { sourceStates: draft.sourceStates } : {}),\n ...(draft.schemaViolations ? { schemaViolations: draft.schemaViolations } : {}),\n ...(draft.provenanceViolations ? { provenanceViolations: draft.provenanceViolations } : {}),\n };\n\n await atomicWrite(candidatePath(root, candidate.id), JSON.stringify(candidate, null, 2));\n return candidate;\n}\n\n/**\n * Emit a CLI error, set exit code 1, and return null. Used by candidate load\n * helpers to avoid duplicating the error-path boilerplate.\n * @param message - Error message to display.\n */\nfunction failWithError(message: string): null {\n output.status(\"!\", output.error(message));\n process.exitCode = 1;\n return null;\n}\n\n/**\n * Load a candidate by id and, if missing, emit the standard \"not found\" CLI\n * error and set process.exitCode = 1. Returns null when the candidate is\n * missing so callers can early-return without re-implementing the same\n * error block in every review subcommand.\n * @param root - Project root directory.\n * @param id - Candidate id to look up.\n */\nexport async function loadCandidateOrFail(\n root: string,\n id: string,\n): Promise<ReviewCandidate | null> {\n const candidate = await readCandidate(root, id);\n if (!candidate) return failWithError(`Candidate not found: ${id}`);\n return candidate;\n}\n\n/**\n * Re-read a candidate under the lock and abort if it has disappeared.\n *\n * This is the authoritative TOCTOU guard: a concurrent approve or reject may\n * have removed the candidate after the pre-lock fast-fail but before the lock\n * was acquired. Returning `null` signals the caller to abort without writing\n * any output artefact.\n * @param root - Project root directory.\n * @param id - Candidate id to load.\n * @returns The candidate if still present, or `null` after setting exit code 1.\n */\nexport async function loadCandidateUnderLockOrFail(\n root: string,\n id: string,\n): Promise<ReviewCandidate | null> {\n const candidate = await readCandidate(root, id);\n if (!candidate) {\n return failWithError(`Candidate ${id} was removed by another process during review.`);\n }\n return candidate;\n}\n\n/** Parse a single candidate JSON file. Returns null when the file is missing or malformed. */\nexport async function readCandidate(\n root: string,\n id: string,\n): Promise<ReviewCandidate | null> {\n const raw = await safeReadFile(candidatePath(root, id));\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as ReviewCandidate;\n if (!isValidCandidate(parsed)) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\n/** Defensive type-guard so corrupted candidate files don't blow up the CLI. */\nfunction isValidCandidate(value: unknown): value is ReviewCandidate {\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as Record<string, unknown>;\n return (\n typeof candidate.id === \"string\" &&\n typeof candidate.title === \"string\" &&\n typeof candidate.slug === \"string\" &&\n typeof candidate.body === \"string\" &&\n Array.isArray(candidate.sources)\n );\n}\n\n/**\n * List every candidate currently pending review, sorted by generation time.\n * Skips files that aren't candidate JSON (e.g. the archive subdirectory).\n * @param root - Project root directory.\n * @returns All pending review candidates.\n */\nexport async function listCandidates(root: string): Promise<ReviewCandidate[]> {\n const dir = path.join(root, CANDIDATES_DIR);\n if (!existsSync(dir)) return [];\n\n const entries = await readdir(dir, { withFileTypes: true });\n const candidates: ReviewCandidate[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(CANDIDATE_EXT)) continue;\n const id = entry.name.slice(0, -CANDIDATE_EXT.length);\n const candidate = await readCandidate(root, id);\n if (candidate) candidates.push(candidate);\n }\n\n candidates.sort((a, b) => a.generatedAt.localeCompare(b.generatedAt));\n return candidates;\n}\n\n/**\n * Count pending candidates using the same validity filter as listCandidates,\n * so consumers (e.g. `wiki_status.pendingCandidates`) never report counts\n * that disagree with what `review list` actually shows. Malformed JSON files\n * are skipped here exactly as they are by listCandidates.\n */\nexport async function countCandidates(root: string): Promise<number> {\n const candidates = await listCandidates(root);\n return candidates.length;\n}\n\n/** Remove a pending candidate from disk. Returns false when nothing existed to remove. */\nexport async function deleteCandidate(root: string, id: string): Promise<boolean> {\n const filePath = candidatePath(root, id);\n if (!existsSync(filePath)) return false;\n await unlink(filePath);\n return true;\n}\n\n/**\n * Move a candidate from the pending area into the archive subdirectory so\n * rejected proposals stay auditable without touching `wiki/`.\n * @param root - Project root directory.\n * @param id - Candidate id to archive.\n * @returns True when the candidate was found and archived.\n */\nexport async function archiveCandidate(root: string, id: string): Promise<boolean> {\n const sourcePath = candidatePath(root, id);\n if (!existsSync(sourcePath)) return false;\n\n const target = archivePath(root, id);\n await mkdir(path.dirname(target), { recursive: true });\n // Copy via writeFile + unlink to support cross-filesystem rename failures.\n try {\n await rename(sourcePath, target);\n } catch {\n const raw = await safeReadFile(sourcePath);\n await writeFile(target, raw, \"utf-8\");\n await unlink(sourcePath);\n }\n return true;\n}\n","/**\n * Manages .llmwiki/state.json — the persistent compilation state that tracks\n * source file hashes and their compiled concepts. Enables incremental\n * compilation by detecting which sources have changed since last compile.\n *\n * Uses atomic writes (write to .tmp, then rename) to prevent corruption\n * from interrupted compiles.\n */\n\nimport { readFile, writeFile, rename, mkdir, copyFile } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { LLMWIKI_DIR, STATE_FILE } from \"./constants.js\";\nimport type { WikiState, SourceState } from \"./types.js\";\n\nfunction emptyState(): WikiState {\n return { version: 1, indexHash: \"\", sources: {} };\n}\n\n/** Read .llmwiki/state.json, recovering from corruption gracefully. */\nexport async function readState(root: string): Promise<WikiState> {\n const filePath = path.join(root, STATE_FILE);\n\n if (!existsSync(filePath)) {\n return emptyState();\n }\n\n try {\n const raw = await readFile(filePath, \"utf-8\");\n return JSON.parse(raw) as WikiState;\n } catch {\n const bakPath = filePath + \".bak\";\n console.warn(`⚠ Corrupt state.json — backed up to ${bakPath}, starting fresh.`);\n await copyFile(filePath, bakPath);\n return emptyState();\n }\n}\n\n/** Atomically write state.json (write .tmp then rename). */\nexport async function writeState(root: string, state: WikiState): Promise<void> {\n const dir = path.join(root, LLMWIKI_DIR);\n await mkdir(dir, { recursive: true });\n\n const filePath = path.join(root, STATE_FILE);\n const tmpPath = filePath + \".tmp\";\n\n await writeFile(tmpPath, JSON.stringify(state, null, 2), \"utf-8\");\n await rename(tmpPath, filePath);\n}\n\n/**\n * Update a single source's entry in state after successful compilation.\n * Per-source granularity means interrupted compiles only reprocess incomplete sources.\n */\nexport async function updateSourceState(\n root: string,\n sourceFile: string,\n entry: SourceState,\n): Promise<void> {\n const state = await readState(root);\n state.sources[sourceFile] = entry;\n await writeState(root, state);\n}\n\n/** Remove a source entry from state (for deleted sources). */\nexport async function removeSourceState(\n root: string,\n sourceFile: string,\n): Promise<void> {\n const state = await readState(root);\n delete state.sources[sourceFile];\n await writeState(root, state);\n}\n","/**\n * Commander action for `llmwiki compile`.\n * Checks that sources exist, then delegates to the compilation orchestrator\n * to process all new and changed source files into wiki pages.\n */\n\nimport { existsSync } from \"fs\";\nimport { compile } from \"../compiler/index.js\";\nimport * as output from \"../utils/output.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport type { CompileOptions } from \"../utils/types.js\";\n\n/**\n * Run the compile command from the current working directory.\n * Exits early if no sources directory exists yet.\n * @param options - Optional behaviour overrides forwarded from the CLI flag set.\n */\nexport default async function compileCommand(options: CompileOptions = {}): Promise<void> {\n if (!existsSync(SOURCES_DIR)) {\n output.status(\n \"!\",\n output.warn('No sources found. Run `llmwiki ingest <url>` first.'),\n );\n return;\n }\n\n await compile(process.cwd(), options);\n}\n","/**\n * Compilation orchestrator for the llmwiki knowledge compiler.\n *\n * Coordinates the full pipeline: lock acquisition, change detection,\n * concept extraction via LLM, wiki page generation with streaming output,\n * orphan marking for deleted sources, interlink resolution, and index\n * generation. Supports incremental compilation — only new or changed\n * sources are processed through the LLM pipeline.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { readState, updateSourceState } from \"../utils/state.js\";\nimport {\n buildExtractionSourceStates,\n pickStatesForSources,\n} from \"./source-state.js\";\nimport {\n atomicWrite,\n buildFrontmatter,\n parseFrontmatter,\n safeReadFile,\n validateWikiPage,\n slugify,\n} from \"../utils/markdown.js\";\nimport { callClaude } from \"../utils/llm.js\";\nimport { acquireLock, releaseLock } from \"../utils/lock.js\";\nimport {\n CONCEPT_EXTRACTION_TOOL,\n buildExtractionPrompt,\n buildSeedPagePrompt,\n parseConcepts,\n} from \"./prompts.js\";\nimport { loadSchema, type SchemaConfig, type SeedPage } from \"../schema/index.js\";\nimport { detectChanges, hashFile } from \"./hasher.js\";\nimport {\n findAffectedSources,\n findFrozenSlugs,\n findLateAffectedSources,\n freezeFailedExtractions,\n persistFrozenSlugs,\n type ExtractionResult,\n} from \"./deps.js\";\nimport { markOrphaned, orphanUnownedFrozenPages } from \"./orphan.js\";\nimport { resolveLinks } from \"./resolver.js\";\nimport { generateIndex } from \"./indexgen.js\";\nimport { buildBudgetedCombinedContent, type SourceSlice } from \"./prompt-budget.js\";\nimport { addObsidianMeta, generateMOC } from \"./obsidian.js\";\nimport { updateEmbeddings } from \"../utils/embeddings.js\";\nimport { writeCandidate } from \"./candidates.js\";\nimport {\n checkPageBrokenCitations,\n checkPageCrossLinks,\n checkPageMalformedCitations,\n} from \"../linter/rules.js\";\nimport type { LintResult } from \"../linter/types.js\";\nimport { renderMergedPageContent } from \"./page-renderer.js\";\nimport * as output from \"../utils/output.js\";\nimport {\n COMPILE_CONCURRENCY,\n CONCEPTS_DIR,\n INDEX_FILE,\n SOURCES_DIR,\n} from \"../utils/constants.js\";\nimport pLimit from \"p-limit\";\nimport type {\n CompileOptions,\n CompileResult,\n ExtractedConcept,\n ReviewCandidate,\n SourceChange,\n SourceState,\n WikiFrontmatter,\n WikiState,\n} from \"../utils/types.js\";\n\n/** Per-source state snapshots keyed by source filename. */\ntype SourceStateMap = Record<string, SourceState>;\n\n/** Empty CompileResult used when no pipeline work runs (e.g. lock contention). */\nfunction emptyCompileResult(): CompileResult {\n return { compiled: 0, skipped: 0, deleted: 0, concepts: [], pages: [], errors: [] };\n}\n\n/**\n * Run the full compilation pipeline with lock protection.\n * Acquires .llmwiki/lock, detects changes, compiles new/changed sources,\n * marks orphaned pages, resolves interlinks, and rebuilds the index.\n * @param root - Project root directory.\n * @param options - Optional pipeline overrides (e.g. --review mode).\n */\nexport async function compile(root: string, options: CompileOptions = {}): Promise<void> {\n await compileAndReport(root, options);\n}\n\n/**\n * Run the full compilation pipeline and return a structured result.\n * Same behaviour as compile() but exposes counts, slugs, and errors so\n * non-CLI consumers (the MCP server, programmatic callers) can report\n * meaningful data without scraping terminal output.\n * @param root - Project root directory.\n * @param options - Optional pipeline overrides (e.g. --review mode).\n * @returns Structured result describing what was compiled.\n */\nexport async function compileAndReport(\n root: string,\n options: CompileOptions = {},\n): Promise<CompileResult> {\n output.header(\"llmwiki compile\");\n\n const locked = await acquireLock(root);\n if (!locked) {\n output.status(\"!\", output.error(\"Could not acquire lock. Try again later.\"));\n return {\n ...emptyCompileResult(),\n errors: [\"Could not acquire .llmwiki/lock — another compile is in progress.\"],\n };\n }\n\n try {\n return await runCompilePipeline(root, options);\n } finally {\n await releaseLock(root);\n }\n}\n\n/** Buckets of source changes used by the compile pipeline. */\ninterface ChangeBuckets {\n toCompile: SourceChange[];\n deleted: SourceChange[];\n unchanged: SourceChange[];\n}\n\n/** Sort source changes into the buckets the pipeline acts on. */\nfunction bucketChanges(changes: SourceChange[]): ChangeBuckets {\n return {\n toCompile: changes.filter((c) => c.status === \"new\" || c.status === \"changed\"),\n deleted: changes.filter((c) => c.status === \"deleted\"),\n unchanged: changes.filter((c) => c.status === \"unchanged\"),\n };\n}\n\n/** Result of phase 2: page writes plus any errors collected along the way. */\ninterface PageGenerationResult {\n pages: MergedConcept[];\n errors: string[];\n /** Candidate ids written when running in --review mode. Empty otherwise. */\n candidates: string[];\n /**\n * Slugs of seed pages written this run (overview / comparison / entity).\n * Concept pages live on `pages`; seed pages don't fit MergedConcept's\n * source-list shape, so they're tracked separately here. summarizeCompile\n * concatenates these into CompileResult.pages so downstream consumers\n * (MCP, embeddings, programmatic callers) see seed-page changes too.\n */\n seedSlugs: string[];\n}\n\n/** Phase 2: generate pages for merged concepts in parallel, capturing errors. */\nasync function generatePagesPhase(\n root: string,\n extractions: ExtractionResult[],\n frozenSlugs: Set<string>,\n schema: SchemaConfig,\n options: CompileOptions,\n): Promise<PageGenerationResult> {\n const merged = mergeExtractions(extractions, frozenSlugs);\n // Build the per-source state snapshot once so each candidate can carry the\n // exact data needed to mark its sources compiled on approval.\n const sourceStates = options.review\n ? await buildExtractionSourceStates(root, extractions)\n : {};\n const limit = pLimit(COMPILE_CONCURRENCY);\n const errors: string[] = [];\n const candidates: string[] = [];\n const pages = await Promise.all(\n merged.map((entry) => limit(async () => {\n const result = await generateMergedPage(root, entry, schema, options, sourceStates);\n if (result.error) errors.push(result.error);\n if (result.candidateId) candidates.push(result.candidateId);\n return entry;\n })),\n );\n return { pages, errors, candidates, seedSlugs: [] };\n}\n\n/** Persist source state for every extraction that produced concepts. */\nasync function persistExtractionStates(\n root: string,\n extractions: ExtractionResult[],\n): Promise<void> {\n for (const result of extractions) {\n if (result.concepts.length === 0) continue;\n await persistSourceState(root, result.sourcePath, result.sourceFile, result.concepts);\n }\n}\n\n/** Build the structured CompileResult and emit the CLI completion banner. */\nfunction summarizeCompile(\n buckets: ChangeBuckets,\n generation: PageGenerationResult,\n extractions: ExtractionResult[],\n options: CompileOptions,\n): CompileResult {\n output.header(\"Compilation complete\");\n output.status(\"✓\", output.success(\n `${buckets.toCompile.length} compiled, ${buckets.unchanged.length} skipped, ${buckets.deleted.length} deleted`,\n ));\n if (options.review && generation.candidates.length > 0) {\n output.status(\"?\", output.info(\n `${generation.candidates.length} candidate(s) awaiting review — run \\`llmwiki review list\\``,\n ));\n } else if (buckets.toCompile.length > 0) {\n output.status(\"→\", output.dim('Next: llmwiki query \"your question here\"'));\n }\n\n const errors = [...generation.errors];\n for (const result of extractions) {\n if (result.concepts.length === 0) {\n errors.push(`No concepts extracted from ${result.sourceFile}`);\n }\n }\n\n // Concept-page slugs first, then seed-page slugs from the same run, so\n // downstream consumers see every page the compile actually produced.\n // Seed pages are deterministic schema-driven writes; before this they\n // landed on disk silently and never appeared on CompileResult.pages.\n const conceptSlugs = generation.pages.map((entry) => entry.slug);\n const baseResult: CompileResult = {\n compiled: buckets.toCompile.length,\n skipped: buckets.unchanged.length,\n deleted: buckets.deleted.length,\n concepts: generation.pages.map((entry) => entry.concept.concept),\n pages: [...conceptSlugs, ...generation.seedSlugs],\n errors,\n };\n if (options.review) {\n baseResult.candidates = generation.candidates;\n }\n return baseResult;\n}\n\n/** Inner pipeline, runs under lock protection. Returns structured CompileResult. */\nasync function runCompilePipeline(\n root: string,\n options: CompileOptions,\n): Promise<CompileResult> {\n const schema = await loadSchema(root);\n reportSchemaStatus(schema);\n const state = await readState(root);\n const changes = await detectChanges(root, state);\n augmentWithAffectedSources(changes, findAffectedSources(state, changes));\n\n const buckets = bucketChanges(changes);\n if (buckets.toCompile.length === 0 && buckets.deleted.length === 0) {\n output.status(\"✓\", output.success(\"Nothing to compile — all sources up to date.\"));\n // Seed pages are cheap deterministic writes — always run them even when\n // no source files changed, so adding a seed page to schema.json takes\n // effect on the next compile without needing a source file edit.\n if (!options.review) {\n const emptyGeneration: PageGenerationResult = {\n pages: [],\n errors: [],\n candidates: [],\n seedSlugs: [],\n };\n await generateSeedPages(root, schema, emptyGeneration);\n // Rebuild index/MOC so the newly-written seed pages become discoverable,\n // and propagate any seed-page validation errors into the returned result.\n await finalizeWiki(root, emptyGeneration.pages, emptyGeneration.seedSlugs);\n return {\n ...emptyCompileResult(),\n skipped: buckets.unchanged.length,\n // Surface seed-page slugs alongside any errors so downstream\n // consumers (MCP, embeddings, programmatic callers) can see what\n // landed even on the no-source-changes early-return path.\n pages: [...emptyGeneration.seedSlugs],\n errors: emptyGeneration.errors,\n };\n }\n return { ...emptyCompileResult(), skipped: buckets.unchanged.length };\n }\n\n printChangesSummary(changes);\n // In review mode the pipeline contract is \"write candidates instead of\n // mutating wiki/\". Deletion bookkeeping (orphan marking + frozen-slug\n // persistence) writes directly into wiki/ and updates state.json, so we\n // defer it to the next non-review compile pass. Source-state persistence\n // for compiled sources is also review-deferred — those entries land at\n // approve time so unapproved candidates remain re-detectable on subsequent\n // compiles.\n if (!options.review) {\n await markDeletedAsOrphaned(root, buckets.deleted, state);\n }\n\n const frozenSlugs = findFrozenSlugs(state, changes);\n reportFrozenSlugs(frozenSlugs);\n\n const extractions = await runExtractionPhases(root, buckets.toCompile, state, changes);\n if (!options.review) {\n await freezeFailedExtractions(root, extractions, frozenSlugs);\n }\n\n const generation = await generatePagesPhase(root, extractions, frozenSlugs, schema, options);\n\n if (!options.review) {\n await persistExtractionStates(root, extractions);\n if (frozenSlugs.size > 0) {\n await orphanUnownedFrozenPages(root, frozenSlugs);\n }\n await persistFrozenSlugs(root, frozenSlugs, extractions);\n // Seed pages write directly into wiki/, so skip them in review mode\n // to honour the \"no wiki/ mutation\" contract of that mode.\n await generateSeedPages(root, schema, generation);\n await finalizeWiki(root, generation.pages, generation.seedSlugs);\n }\n return summarizeCompile(buckets, generation, extractions, options);\n}\n\n/** Log where the schema was loaded from so the user can confirm it was picked up. */\nfunction reportSchemaStatus(schema: SchemaConfig): void {\n if (schema.loadedFrom) {\n output.status(\"i\", output.dim(`Schema: ${schema.loadedFrom}`));\n }\n}\n\n/** Append affected-source changes (logging each addition) to the change list. */\nfunction augmentWithAffectedSources(changes: SourceChange[], affected: string[]): void {\n for (const file of affected) {\n output.status(\"~\", output.info(`${file} [affected by shared concept]`));\n changes.push({ file, status: \"changed\" });\n }\n}\n\n/** Mark wiki pages owned solely by deleted sources as orphaned. */\nasync function markDeletedAsOrphaned(\n root: string,\n deleted: SourceChange[],\n state: WikiState,\n): Promise<void> {\n for (const del of deleted) {\n await markOrphaned(root, del.file, state);\n }\n}\n\n/** Log frozen slugs (shared concepts whose deletion-pinned content must persist). */\nfunction reportFrozenSlugs(frozenSlugs: Set<string>): void {\n for (const slug of frozenSlugs) {\n output.status(\"i\", output.dim(`Frozen: ${slug} (shared with deleted source)`));\n }\n}\n\n/**\n * Phase 1: extract concepts for the directly-changed batch, then expand to\n * any unchanged sources whose concepts overlap with newly extracted slugs.\n */\nasync function runExtractionPhases(\n root: string,\n toCompile: SourceChange[],\n state: WikiState,\n allChanges: SourceChange[],\n): Promise<ExtractionResult[]> {\n const extractions: ExtractionResult[] = [];\n for (const change of toCompile) {\n extractions.push(await extractForSource(root, change.file));\n }\n\n const lateAffected = findLateAffectedSources(extractions, state, allChanges);\n for (const file of lateAffected) {\n output.status(\"~\", output.info(`${file} [shares concept with new source]`));\n extractions.push(await extractForSource(root, file));\n }\n\n return extractions;\n}\n\n/**\n * Resolve interlinks, regenerate index/MOC, refresh embeddings\n * post-write. Seed-page slugs are folded into both the changed-slug\n * set (so embeddings refresh covers them) and the new-slug set (so\n * inbound-link resolution scans existing pages for mentions of seed\n * titles). Without that, schema-declared seed pages would land on\n * disk but stay unlinked and absent from the embedding store.\n */\nasync function finalizeWiki(\n root: string,\n pages: MergedConcept[],\n seedSlugs: string[] = [],\n): Promise<void> {\n const conceptChangedSlugs = pages.map((entry) => entry.slug);\n const conceptNewSlugs = pages\n .filter((entry) => entry.concept.is_new)\n .map((entry) => entry.slug);\n const allChangedSlugs = [...conceptChangedSlugs, ...seedSlugs];\n const allNewSlugs = [...conceptNewSlugs, ...seedSlugs];\n\n if (allChangedSlugs.length > 0) {\n output.status(\"🔗\", output.info(\"Resolving interlinks...\"));\n await resolveLinks(root, allChangedSlugs, allNewSlugs);\n }\n\n await generateIndex(root);\n await generateMOC(root);\n await safelyUpdateEmbeddings(root, allChangedSlugs);\n}\n\n/** Print a summary of detected source file changes. */\nfunction printChangesSummary(changes: SourceChange[]): void {\n const iconMap: Record<string, string> = {\n new: \"+\", changed: \"~\", unchanged: \".\", deleted: \"-\",\n };\n const fmtMap: Record<string, (s: string) => string> = {\n new: output.success, changed: output.warn, unchanged: output.dim, deleted: output.error,\n };\n\n for (const c of changes) {\n const icon = iconMap[c.status] ?? \"?\";\n const fmt = fmtMap[c.status] ?? output.dim;\n output.status(icon, fmt(`${c.file} [${c.status}]`));\n }\n}\n\n/**\n * Phase 1: Extract concepts from a source without generating pages.\n * Returns extraction data for the generation phase.\n */\nasync function extractForSource(\n root: string,\n sourceFile: string,\n): Promise<ExtractionResult> {\n output.status(\"*\", output.info(`Extracting: ${sourceFile}`));\n\n const sourcePath = path.join(root, SOURCES_DIR, sourceFile);\n const sourceContent = await readFile(sourcePath, \"utf-8\");\n const existingIndex = await safeReadFile(path.join(root, INDEX_FILE));\n const concepts = await extractConcepts(sourceContent, existingIndex);\n\n if (concepts.length > 0) {\n const names = concepts.map((c) => c.concept).join(\", \");\n output.status(\"*\", output.dim(` Found ${concepts.length} concepts: ${names}`));\n }\n return { sourceFile, sourcePath, sourceContent, concepts };\n}\n\n/** A concept with all contributing sources merged for generation. */\ninterface MergedConcept {\n slug: string;\n concept: ExtractedConcept;\n sourceFiles: string[];\n combinedContent: string;\n}\n\n/**\n * Reconcile metadata from a later-extracted concept into an existing merged entry.\n * Called when multiple sources contribute the same slug — produces the most\n * pessimistic aggregate view of confidence, provenance, and contradictions.\n *\n * Rules:\n * - confidence: min (most pessimistic value wins)\n * - provenanceState: always 'merged' once two sources are involved\n * - contradictedBy: union by slug (deduplicating on slug identity)\n *\n * `inferredParagraphs` is no longer reconciled — it is derived from the\n * rendered page body at lint time, not from extraction metadata.\n */\nexport function reconcileConceptMetadata(\n existing: ExtractedConcept,\n incoming: ExtractedConcept,\n): ExtractedConcept {\n const reconciled = { ...existing };\n\n // Minimum confidence — the weaker source's score governs the whole page.\n if (typeof incoming.confidence === \"number\") {\n reconciled.confidence = typeof existing.confidence === \"number\"\n ? Math.min(existing.confidence, incoming.confidence)\n : incoming.confidence;\n }\n\n // Merged state is the canonical answer when multiple sources contribute.\n reconciled.provenanceState = \"merged\";\n\n // Union contradictedBy entries, deduplicating by slug.\n const refs = [...(existing.contradictedBy ?? [])];\n const seenSlugs = new Set(refs.map((r) => r.slug));\n for (const ref of incoming.contradictedBy ?? []) {\n if (!seenSlugs.has(ref.slug)) {\n refs.push(ref);\n seenSlugs.add(ref.slug);\n }\n }\n reconciled.contradictedBy = refs.length > 0 ? refs : undefined;\n\n return reconciled;\n}\n\n/**\n * Merge extractions so each concept slug maps to ALL contributing sources.\n * When sources A and B both extract concept X, the LLM receives combined\n * content from both sources, producing a single page that reflects all\n * contributing material rather than just the last source processed.\n * Metadata is reconciled across all contributing concepts via\n * reconcileConceptMetadata so contradictions from later sources are not lost.\n *\n * Combined content is then run through {@link buildBudgetedCombinedContent}\n * so popular concepts that appear in many overlapping sources do not blow\n * past the LLM provider's context window (issue #39). When the raw total\n * fits the budget, the output is byte-identical to the previous unbudgeted\n * concatenation.\n */\nfunction mergeExtractions(\n extractions: ExtractionResult[],\n frozenSlugs: Set<string>,\n): MergedConcept[] {\n const bySlug = new Map<string, MergedConcept>();\n const slicesBySlug = new Map<string, SourceSlice[]>();\n\n for (const result of extractions) {\n if (result.concepts.length === 0) continue;\n\n for (const concept of result.concepts) {\n const slug = slugify(concept.concept);\n if (frozenSlugs.has(slug)) continue;\n\n const existing = bySlug.get(slug);\n if (existing) {\n existing.concept = reconcileConceptMetadata(existing.concept, concept);\n existing.sourceFiles.push(result.sourceFile);\n } else {\n bySlug.set(slug, {\n slug,\n concept,\n sourceFiles: [result.sourceFile],\n combinedContent: \"\",\n });\n slicesBySlug.set(slug, []);\n }\n slicesBySlug.get(slug)!.push({\n file: result.sourceFile,\n content: result.sourceContent,\n });\n }\n }\n\n for (const merged of bySlug.values()) {\n const slices = slicesBySlug.get(merged.slug) ?? [];\n merged.combinedContent = buildBudgetedCombinedContent(\n merged.concept.concept,\n slices,\n );\n }\n\n return Array.from(bySlug.values());\n}\n\n/** Outcome of generating a single merged concept page. */\ninterface MergedPageOutcome {\n error?: string;\n candidateId?: string;\n}\n\n/**\n * Generate a wiki page from merged source content.\n * For shared concepts, the LLM sees content from all contributing sources\n * and frontmatter records every source file. When `options.review` is set,\n * the rendered page is persisted as a review candidate instead of being\n * written into `wiki/`.\n */\nasync function generateMergedPage(\n root: string,\n entry: MergedConcept,\n schema: SchemaConfig,\n options: CompileOptions,\n sourceStates: SourceStateMap,\n): Promise<MergedPageOutcome> {\n const fullPage = await renderMergedPageContent(root, entry, schema);\n\n if (options.review) {\n return await persistReviewCandidate(root, entry, fullPage, sourceStates, schema);\n }\n\n const pagePath = path.join(root, CONCEPTS_DIR, `${entry.slug}.md`);\n const error = await writePageIfValid(pagePath, fullPage, entry.concept.concept);\n return { error: error ?? undefined };\n}\n\n/** Persist a candidate JSON record for later review and report it on stdout. */\nasync function persistReviewCandidate(\n root: string,\n entry: MergedConcept,\n fullPage: string,\n sourceStates: SourceStateMap,\n schema: SchemaConfig,\n): Promise<MergedPageOutcome> {\n // Run schema-aware AND provenance-aware lint against the candidate body so\n // both classes of violation are visible in `review show` before a reviewer\n // approves the page. The virtual file path uses the slug so diagnostics\n // are identifiable without a real disk path. Provenance lint covers the\n // citation rules that previously only ran on the post-promotion compile.\n const virtualPath = `wiki/concepts/${entry.slug}.md`;\n const schemaViolations = checkPageCrossLinks(fullPage, virtualPath, schema);\n const provenanceViolations = await collectCandidateProvenanceViolations(\n root,\n fullPage,\n virtualPath,\n );\n\n const candidate: ReviewCandidate = await writeCandidate(root, {\n title: entry.concept.concept,\n slug: entry.slug,\n summary: entry.concept.summary,\n sources: entry.sourceFiles,\n body: fullPage,\n sourceStates: pickStatesForSources(sourceStates, entry.sourceFiles),\n schemaViolations: schemaViolations.length > 0 ? schemaViolations : undefined,\n provenanceViolations:\n provenanceViolations.length > 0 ? provenanceViolations : undefined,\n });\n output.status(\"?\", output.info(`Candidate ready: ${candidate.id} (${entry.slug})`));\n return { candidateId: candidate.id };\n}\n\n/**\n * Run the in-memory provenance lint rules against a candidate body:\n * malformed claim citations + broken-source / out-of-bounds line spans.\n * Returns the combined diagnostics so writeCandidate can persist them.\n */\nasync function collectCandidateProvenanceViolations(\n root: string,\n fullPage: string,\n virtualPath: string,\n): Promise<LintResult[]> {\n const malformed = checkPageMalformedCitations(fullPage, virtualPath);\n const broken = await checkPageBrokenCitations(\n fullPage,\n virtualPath,\n path.join(root, SOURCES_DIR),\n );\n return [...malformed, ...broken];\n}\n\n/**\n * Materialise schema-declared seed pages (overview, comparison, entity).\n * Each seed page is written under wiki/concepts/ next to concept pages so\n * existing tooling (index, MOC, lint, embeddings) treats them uniformly.\n * Slugs from generated pages this run are added so seed pages can be linked\n * deterministically without waiting for a second compile pass.\n * @param root - Project root directory.\n * @param schema - Resolved schema config.\n * @param generation - Result of the concept-page generation phase.\n */\nasync function generateSeedPages(\n root: string,\n schema: SchemaConfig,\n generation: PageGenerationResult,\n): Promise<void> {\n if (schema.seedPages.length === 0) return;\n for (const seed of schema.seedPages) {\n const result = await generateSingleSeedPage(root, schema, seed);\n if (result.error) {\n generation.errors.push(result.error);\n continue;\n }\n generation.seedSlugs.push(result.slug);\n }\n}\n\n/** Outcome of a single seed-page generation: slug always, error when the write failed validation. */\ninterface SeedPageOutcome {\n slug: string;\n error?: string;\n}\n\n/** Build, prompt, and persist a single seed page. */\nasync function generateSingleSeedPage(\n root: string,\n schema: SchemaConfig,\n seed: SeedPage,\n): Promise<SeedPageOutcome> {\n const slug = slugify(seed.title);\n const pagePath = path.join(root, CONCEPTS_DIR, `${slug}.md`);\n const relatedContent = await loadSeedRelatedPages(root, seed.relatedSlugs ?? []);\n const rule = schema.kinds[seed.kind];\n const system = buildSeedPagePrompt(seed, rule, relatedContent);\n const pageBody = await callClaude({\n system,\n messages: [{ role: \"user\", content: `Write the ${seed.kind} page titled \"${seed.title}\".` }],\n });\n\n const now = new Date().toISOString();\n const existing = await safeReadFile(pagePath);\n const existingMeta = existing ? parseFrontmatter(existing).meta : null;\n const createdAt = typeof existingMeta?.createdAt === \"string\" ? existingMeta.createdAt : now;\n const typedFields: WikiFrontmatter = {\n title: seed.title,\n summary: seed.summary,\n sources: [],\n kind: seed.kind,\n createdAt,\n updatedAt: now,\n };\n const frontmatterFields: Record<string, unknown> = { ...typedFields };\n addObsidianMeta(frontmatterFields, seed.title, []);\n const frontmatter = buildFrontmatter(frontmatterFields);\n const error = await writePageIfValid(pagePath, `${frontmatter}\\n\\n${pageBody}\\n`, seed.title);\n return error ? { slug, error } : { slug };\n}\n\n/** Load the bodies of the related concept pages a seed page should weave together. */\nasync function loadSeedRelatedPages(root: string, slugs: string[]): Promise<string> {\n if (slugs.length === 0) return \"\";\n const contents: string[] = [];\n for (const slug of slugs) {\n const pagePath = path.join(root, CONCEPTS_DIR, `${slug}.md`);\n const content = await safeReadFile(pagePath);\n if (content) contents.push(content);\n }\n return contents.join(\"\\n\\n---\\n\\n\");\n}\n\n/**\n * Call Claude to extract concepts from a source document.\n * @param sourceContent - Full source document text.\n * @param existingIndex - Current wiki index for deduplication.\n * @returns Parsed array of extracted concepts.\n */\nasync function extractConcepts(\n sourceContent: string,\n existingIndex: string,\n): Promise<ExtractedConcept[]> {\n const system = buildExtractionPrompt(sourceContent, existingIndex);\n const rawOutput = await callClaude({\n system,\n messages: [{ role: \"user\", content: \"Extract the key concepts from this source.\" }],\n tools: [CONCEPT_EXTRACTION_TOOL],\n });\n\n return parseConcepts(rawOutput);\n}\n\n/**\n * Validate and atomically write a wiki page, logging the result.\n * @param pagePath - Absolute path to write the page.\n * @param content - Full page content including frontmatter.\n * @param conceptTitle - Title for logging purposes.\n */\nasync function writePageIfValid(\n pagePath: string,\n content: string,\n conceptTitle: string,\n): Promise<string | null> {\n if (!validateWikiPage(content)) {\n output.status(\"!\", output.warn(`Invalid page for \"${conceptTitle}\" — skipped.`));\n return `Invalid page for \"${conceptTitle}\" — failed validation`;\n }\n\n await atomicWrite(pagePath, content);\n return null;\n}\n\n/**\n * Refresh the embeddings store without failing compilation.\n * Semantic search is a non-critical enhancement — missing API keys or\n * transient provider errors should produce a warning, not a broken build.\n */\nasync function safelyUpdateEmbeddings(root: string, changedSlugs: string[]): Promise<void> {\n try {\n await updateEmbeddings(root, changedSlugs);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.warn(`Skipped embeddings update: ${message}`));\n }\n}\n\n/**\n * Update the persisted state for a compiled source file.\n * @param root - Project root directory.\n * @param sourcePath - Absolute path to the source file.\n * @param sourceFile - Filename within sources/.\n * @param concepts - Concepts extracted from this source.\n */\nasync function persistSourceState(\n root: string,\n sourcePath: string,\n sourceFile: string,\n concepts: ReturnType<typeof parseConcepts>,\n): Promise<void> {\n const hash = await hashFile(sourcePath);\n const entry: SourceState = {\n hash,\n concepts: concepts.map((c) => slugify(c.concept)),\n compiledAt: new Date().toISOString(),\n };\n\n await updateSourceState(root, sourceFile, entry);\n}\n","/**\n * Source-state snapshot helpers shared between the live compile path and the\n * review-candidate path.\n *\n * The compile pipeline normally persists a `SourceState` entry for every\n * extracted source so subsequent compiles can skip unchanged inputs. When\n * compile runs in `--review` mode, page writes are deferred — but the same\n * per-source state still needs to land on approval, otherwise approved\n * sources stay marked as \"new/changed\" forever and reproduce duplicate\n * candidates on every compile.\n *\n * This module produces a `Record<sourceFile, SourceState>` snapshot from the\n * extraction results so it can ride along inside each `ReviewCandidate` and\n * be flushed to `.llmwiki/state.json` at approval time.\n */\n\nimport path from \"path\";\nimport { hashFile } from \"./hasher.js\";\nimport { slugify } from \"../utils/markdown.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport type { ExtractionResult } from \"./deps.js\";\nimport type { SourceState } from \"../utils/types.js\";\n\n/**\n * Compute a per-source state snapshot keyed by source filename.\n *\n * Hashes every contributing source once so each candidate carries the\n * incremental-state payload required to mark its sources compiled on\n * approval. Sources with no extracted concepts are skipped — we only mark\n * sources compiled when extraction succeeded, mirroring the live path's\n * behaviour.\n *\n * @param root - Project root directory.\n * @param extractions - Extraction results from the compile pipeline.\n * @returns Map of source filename → SourceState ready for state.json.\n */\nexport async function buildExtractionSourceStates(\n root: string,\n extractions: ExtractionResult[],\n): Promise<Record<string, SourceState>> {\n const snapshot: Record<string, SourceState> = {};\n const compiledAt = new Date().toISOString();\n\n for (const result of extractions) {\n if (result.concepts.length === 0) continue;\n snapshot[result.sourceFile] = await buildEntry(root, result, compiledAt);\n }\n\n return snapshot;\n}\n\n/** Build a single SourceState entry for one extraction result. */\nasync function buildEntry(\n root: string,\n result: ExtractionResult,\n compiledAt: string,\n): Promise<SourceState> {\n const filePath = path.join(root, SOURCES_DIR, result.sourceFile);\n const hash = await hashFile(filePath);\n return {\n hash,\n concepts: result.concepts.map((concept) => slugify(concept.concept)),\n compiledAt,\n };\n}\n\n/**\n * Filter a global source-state snapshot down to entries relevant to a\n * specific candidate. A candidate carries only the source-state entries\n * for sources that actually contributed to it, so on approval we can\n * persist a minimal, accurate slice into state.json.\n *\n * @param allStates - Global per-source snapshot from buildExtractionSourceStates.\n * @param sourceFiles - Source filenames that contributed to the candidate.\n */\nexport function pickStatesForSources(\n allStates: Record<string, SourceState>,\n sourceFiles: string[],\n): Record<string, SourceState> {\n const picked: Record<string, SourceState> = {};\n for (const file of sourceFiles) {\n const entry = allStates[file];\n if (entry) picked[file] = entry;\n }\n return picked;\n}\n","/**\n * Source file hashing for change detection.\n * Computes SHA-256 hashes of source files and compares them against\n * previously stored state to determine which files need recompilation.\n * This enables incremental compilation — only changed or new sources\n * are sent through the LLM pipeline.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFile, readdir } from \"fs/promises\";\nimport path from \"path\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport type { WikiState, SourceChange } from \"../utils/types.js\";\n\n/**\n * Read a file and compute its SHA-256 hash.\n * @param filePath - Absolute path to the file to hash.\n * @returns Hex-encoded SHA-256 digest of the file contents.\n */\nexport async function hashFile(filePath: string): Promise<string> {\n const content = await readFile(filePath, \"utf-8\");\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\n/**\n * Scan the sources/ directory and compare file hashes against previous state\n * to identify new, changed, unchanged, and deleted source files.\n * @param root - Project root directory containing the sources/ folder.\n * @param prevState - The previously persisted WikiState to compare against.\n * @returns Array of SourceChange entries describing each file's status.\n */\nexport async function detectChanges(\n root: string,\n prevState: WikiState,\n): Promise<SourceChange[]> {\n const sourcesPath = path.join(root, SOURCES_DIR);\n const currentFiles = await listSourceFiles(sourcesPath);\n const changes: SourceChange[] = [];\n\n for (const file of currentFiles) {\n const status = await classifyFile(root, file, prevState);\n changes.push({ file, status });\n }\n\n const deletedChanges = findDeletedFiles(currentFiles, prevState);\n changes.push(...deletedChanges);\n\n return changes;\n}\n\n/**\n * List all markdown files in the sources directory.\n * @param sourcesPath - Absolute path to the sources/ directory.\n * @returns Array of filenames (not full paths).\n */\nasync function listSourceFiles(sourcesPath: string): Promise<string[]> {\n try {\n const entries = await readdir(sourcesPath);\n return entries.filter((f) => f.endsWith(\".md\"));\n } catch {\n return [];\n }\n}\n\n/**\n * Classify a single source file as new, changed, or unchanged.\n * @param root - Project root directory.\n * @param file - Filename within sources/.\n * @param prevState - Previous compilation state.\n * @returns The change status for this file.\n */\nasync function classifyFile(\n root: string,\n file: string,\n prevState: WikiState,\n): Promise<SourceChange[\"status\"]> {\n const filePath = path.join(root, SOURCES_DIR, file);\n const hash = await hashFile(filePath);\n const prev = prevState.sources[file];\n\n if (!prev) return \"new\";\n if (prev.hash !== hash) return \"changed\";\n return \"unchanged\";\n}\n\n/**\n * Find source files present in previous state but missing from disk.\n * @param currentFiles - Files currently on disk.\n * @param prevState - Previous compilation state.\n * @returns Array of SourceChange entries for deleted files.\n */\nfunction findDeletedFiles(\n currentFiles: string[],\n prevState: WikiState,\n): SourceChange[] {\n const currentSet = new Set(currentFiles);\n return Object.keys(prevState.sources)\n .filter((file) => !currentSet.has(file))\n .map((file) => ({ file, status: \"deleted\" as const }));\n}\n","/**\n * OpenAI LLM provider implementation.\n *\n * Wraps the openai npm package to implement the LLMProvider interface.\n * Translates Anthropic-style tool schemas (input_schema) to OpenAI format (parameters).\n */\n\nimport OpenAI from \"openai\";\nimport type { LLMProvider, LLMMessage, LLMTool } from \"../utils/provider.js\";\nimport { EMBEDDING_MODELS, OPENAI_DEFAULT_TIMEOUT_MS } from \"../utils/constants.js\";\n\n/** Construction options for an OpenAI-compatible provider. */\ninterface OpenAIProviderOptions {\n baseURL?: string;\n apiKey?: string;\n embeddingsBaseURL?: string;\n embeddingModel?: string;\n /**\n * Per-request timeout in milliseconds. Defaults to 10 minutes for cloud\n * OpenAI (matches the SDK default). Long compile-time completions on\n * slower local models can exceed this — see {@link OllamaProvider} which\n * raises the default and reads LLMWIKI_REQUEST_TIMEOUT_MS / OLLAMA_TIMEOUT_MS.\n */\n timeoutMs?: number;\n}\n\n/**\n * Read an integer-millisecond timeout from an env var. Returns undefined when\n * the env var is unset, empty, non-numeric, zero, or negative — so the caller\n * silently falls back to the next source in its resolution chain (env-var\n * typos like `OLLAMA_TIMEOUT_MS=30m` are not surfaced to the user).\n */\nexport function readTimeoutEnv(name: string): number | undefined {\n const raw = process.env[name]?.trim();\n if (!raw) return undefined;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\n/** Resolve the OpenAI client timeout from LLMWIKI_REQUEST_TIMEOUT_MS, if set. */\nfunction resolveOpenAITimeoutMs(): number | undefined {\n return readTimeoutEnv(\"LLMWIKI_REQUEST_TIMEOUT_MS\");\n}\n\n/** Translate an Anthropic-style LLMTool to an OpenAI ChatCompletionTool. */\nexport function translateToolToOpenAI(\n tool: LLMTool,\n): OpenAI.ChatCompletionTool {\n return {\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input_schema,\n },\n };\n}\n\n/** OpenAI-backed LLM provider. */\nexport class OpenAIProvider implements LLMProvider {\n protected readonly client: OpenAI;\n protected readonly embeddingsClient: OpenAI;\n protected readonly model: string;\n protected readonly configuredEmbeddingModel?: string;\n\n constructor(model: string, options: OpenAIProviderOptions = {}) {\n this.model = model;\n this.configuredEmbeddingModel = options.embeddingModel;\n // The OpenAI SDK validates OPENAI_API_KEY at construction time.\n // Pass the key explicitly so the provider controls when validation happens.\n const resolvedKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? \"\";\n const timeout = options.timeoutMs ?? resolveOpenAITimeoutMs() ?? OPENAI_DEFAULT_TIMEOUT_MS;\n this.client = new OpenAI({\n apiKey: resolvedKey,\n baseURL: options.baseURL ?? null,\n timeout,\n });\n this.embeddingsClient = options.embeddingsBaseURL\n ? new OpenAI({ apiKey: resolvedKey, baseURL: options.embeddingsBaseURL, timeout })\n : this.client;\n }\n\n /** Send a single non-streaming completion request. */\n async complete(system: string, messages: LLMMessage[], maxTokens: number): Promise<string> {\n const response = await this.client.chat.completions.create({\n model: this.model,\n max_tokens: maxTokens,\n messages: [{ role: \"system\", content: system }, ...messages],\n });\n\n return response.choices[0]?.message?.content ?? \"\";\n }\n\n /** Stream a completion, invoking onToken for each text chunk. */\n async stream(\n system: string,\n messages: LLMMessage[],\n maxTokens: number,\n onToken?: (text: string) => void,\n ): Promise<string> {\n const stream = await this.client.chat.completions.create({\n model: this.model,\n max_tokens: maxTokens,\n messages: [{ role: \"system\", content: system }, ...messages],\n stream: true,\n });\n\n let fullText = \"\";\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta?.content;\n if (delta) {\n fullText += delta;\n onToken?.(delta);\n }\n }\n\n return fullText;\n }\n\n /** Call the model with tool definitions and return the parsed tool input as JSON. */\n async toolCall(\n system: string,\n messages: LLMMessage[],\n tools: LLMTool[],\n maxTokens: number,\n ): Promise<string> {\n const openaiTools = tools.map(translateToolToOpenAI);\n\n const response = await this.client.chat.completions.create({\n model: this.model,\n max_tokens: maxTokens,\n messages: [{ role: \"system\", content: system }, ...messages],\n tools: openaiTools,\n tool_choice: \"required\",\n });\n\n const toolCalls = response.choices[0]?.message?.tool_calls;\n if (toolCalls && toolCalls.length > 0) {\n return toolCalls[0].function.arguments;\n }\n\n return response.choices[0]?.message?.content ?? \"\";\n }\n\n /**\n * Produce a single embedding vector via the OpenAI embeddings API.\n * Subclasses (e.g. Ollama) override embeddingModel() to pick a different model.\n */\n async embed(text: string): Promise<number[]> {\n const response = await this.embeddingsClient.embeddings.create({\n model: this.embeddingModel(),\n input: text,\n });\n\n const vector = response.data[0]?.embedding;\n if (!Array.isArray(vector)) {\n throw new Error(\"OpenAI embeddings response did not include a vector.\");\n }\n return vector;\n }\n\n /** Default embedding model for this provider. Subclasses may override. */\n protected embeddingModel(): string {\n return this.configuredEmbeddingModel ?? EMBEDDING_MODELS.openai;\n }\n}\n","/**\n * Ollama LLM provider implementation.\n *\n * Extends OpenAIProvider since Ollama exposes an OpenAI-compatible API.\n * Overrides only the constructor to set baseURL and disable API key auth.\n */\n\nimport { OpenAIProvider, readTimeoutEnv } from \"./openai.js\";\nimport { EMBEDDING_MODELS, OLLAMA_DEFAULT_TIMEOUT_MS } from \"../utils/constants.js\";\n\n/** Construction options for an Ollama-compatible provider. */\ninterface OllamaProviderOptions {\n baseURL: string;\n embeddingsBaseURL?: string;\n embeddingModel?: string;\n /**\n * Per-request timeout in milliseconds. Defaults to 30 minutes for Ollama\n * because local models on modest hardware can take much longer than the\n * cloud-OpenAI default of 10. Override with OLLAMA_TIMEOUT_MS or the\n * provider-agnostic LLMWIKI_REQUEST_TIMEOUT_MS env var.\n */\n timeoutMs?: number;\n}\n\n/** Resolve the Ollama timeout: explicit option → OLLAMA_TIMEOUT_MS → LLMWIKI_REQUEST_TIMEOUT_MS → default. */\nfunction resolveOllamaTimeoutMs(explicit?: number): number {\n return (\n explicit ??\n readTimeoutEnv(\"OLLAMA_TIMEOUT_MS\") ??\n readTimeoutEnv(\"LLMWIKI_REQUEST_TIMEOUT_MS\") ??\n OLLAMA_DEFAULT_TIMEOUT_MS\n );\n}\n\n/** Ollama-backed LLM provider using the OpenAI-compatible endpoint. */\nexport class OllamaProvider extends OpenAIProvider {\n constructor(model: string, options: OllamaProviderOptions) {\n super(model, {\n baseURL: options.baseURL,\n apiKey: \"ollama\",\n embeddingsBaseURL: options.embeddingsBaseURL,\n embeddingModel: options.embeddingModel,\n timeoutMs: resolveOllamaTimeoutMs(options.timeoutMs),\n });\n }\n\n /** Ollama ships a dedicated embedding model (nomic-embed-text). */\n protected override embeddingModel(): string {\n return this.configuredEmbeddingModel ?? EMBEDDING_MODELS.ollama;\n }\n}\n","/**\n * MiniMax LLM provider implementation.\n *\n * Extends OpenAIProvider since MiniMax exposes an OpenAI-compatible API.\n * Overrides only the constructor to set MiniMax's base URL and API key.\n */\n\nimport { OpenAIProvider } from \"./openai.js\";\n\n/** MiniMax API base URL. */\nconst MINIMAX_BASE_URL = \"https://api.minimax.io/v1\";\n\n/** MiniMax-backed LLM provider using the OpenAI-compatible endpoint. */\nexport class MiniMaxProvider extends OpenAIProvider {\n constructor(model: string, apiKey: string) {\n super(model, { baseURL: MINIMAX_BASE_URL, apiKey });\n }\n}\n","/**\n * GitHub Copilot LLM provider implementation.\n *\n * Uses the GitHub Copilot API (https://api.githubcopilot.com), which exposes\n * an OpenAI-compatible chat endpoint. Requires a GitHub OAuth token with\n * Copilot access — use `gh auth token` to obtain one. Classic PATs are NOT\n * supported by this endpoint.\n *\n * Note: GitHub Copilot does not expose an embeddings API. Calling embed() will\n * throw with a helpful message. For workflows that require semantic search\n * (query with chunked retrieval), use the openai provider with OPENAI_API_KEY.\n */\n\nimport { OpenAIProvider } from \"./openai.js\";\nimport { COPILOT_BASE_URL } from \"../utils/constants.js\";\n\n/** GitHub Copilot-backed LLM provider using the OpenAI-compatible endpoint. */\nexport class CopilotProvider extends OpenAIProvider {\n constructor(model: string, apiKey: string) {\n super(model, { baseURL: COPILOT_BASE_URL, apiKey });\n }\n\n /**\n * GitHub Copilot has no native embeddings API.\n * Throws an informative error directing the user to an alternative.\n */\n override async embed(_text: string): Promise<number[]> {\n throw new Error(\n \"GitHub Copilot does not support embeddings.\\n\" +\n \" For semantic search (llmwiki query), switch to the OpenAI provider:\\n\" +\n \" export LLMWIKI_PROVIDER=openai\\n\" +\n \" export OPENAI_API_KEY=sk-...\",\n );\n }\n}\n","/**\n * LLM provider abstraction layer.\n *\n * Defines the LLMProvider interface and a factory function that reads\n * LLMWIKI_PROVIDER and LLMWIKI_MODEL env vars to instantiate the\n * appropriate backend (Anthropic, OpenAI, Ollama, or MiniMax).\n */\n\nimport { DEFAULT_PROVIDER, PROVIDER_MODELS, OLLAMA_DEFAULT_HOST } from \"./constants.js\";\nimport { AnthropicProvider } from \"../providers/anthropic.js\";\nimport { OpenAIProvider } from \"../providers/openai.js\";\nimport { OllamaProvider } from \"../providers/ollama.js\";\nimport { MiniMaxProvider } from \"../providers/minimax.js\";\nimport { CopilotProvider } from \"../providers/copilot.js\";\nimport {\n resolveAnthropicAuthFromEnv,\n resolveAnthropicBaseURLFromEnv,\n resolveAnthropicModelFromEnv,\n} from \"./claude-settings.js\";\n\n/** A single message in an LLM conversation. */\nexport interface LLMMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\n/** A tool definition in Anthropic-style format (used as the canonical shape). */\nexport interface LLMTool {\n name: string;\n description: string;\n input_schema: Record<string, unknown>;\n}\n\n/** Provider-agnostic interface for LLM backends. */\nexport interface LLMProvider {\n complete(system: string, messages: LLMMessage[], maxTokens: number): Promise<string>;\n stream(\n system: string,\n messages: LLMMessage[],\n maxTokens: number,\n onToken?: (text: string) => void,\n ): Promise<string>;\n toolCall(\n system: string,\n messages: LLMMessage[],\n tools: LLMTool[],\n maxTokens: number,\n ): Promise<string>;\n /** Return a single embedding vector for the given text. */\n embed(text: string): Promise<number[]>;\n}\n\nconst SUPPORTED_PROVIDERS: ReadonlySet<string> = new Set([\"anthropic\", \"openai\", \"ollama\", \"minimax\", \"copilot\"]);\n\n/**\n * Factory that returns the appropriate LLMProvider based on env vars.\n * Reads LLMWIKI_PROVIDER (default \"anthropic\") and LLMWIKI_MODEL\n * (defaults per provider from PROVIDER_MODELS).\n *\n * Direct process.env access is acceptable here as this is a system boundary.\n */\nexport function getProvider(): LLMProvider {\n const providerName = getProviderName();\n\n switch (providerName) {\n case \"anthropic\":\n return getAnthropicProvider();\n case \"openai\":\n return new OpenAIProvider(getModelForProvider(\"openai\"), {\n baseURL: readOptionalEnv(\"OPENAI_BASE_URL\"),\n embeddingsBaseURL: readOptionalEnv(\"OPENAI_EMBEDDINGS_BASE_URL\"),\n embeddingModel: readOptionalEnv(\"LLMWIKI_EMBEDDING_MODEL\"),\n });\n case \"ollama\":\n return new OllamaProvider(getModelForProvider(\"ollama\"), {\n baseURL: readOptionalEnv(\"OLLAMA_HOST\") ?? OLLAMA_DEFAULT_HOST,\n embeddingsBaseURL: readOptionalEnv(\"OLLAMA_EMBEDDINGS_HOST\"),\n embeddingModel: readOptionalEnv(\"LLMWIKI_EMBEDDING_MODEL\"),\n });\n case \"minimax\":\n return getMiniMaxProvider();\n case \"copilot\":\n return getCopilotProvider();\n default:\n throw new Error(`Unhandled provider: ${providerName}`);\n }\n}\n\nfunction readOptionalEnv(name: string): string | undefined {\n const value = process.env[name]?.trim();\n return value ? value : undefined;\n}\n\nfunction getModelForProvider(providerName: \"openai\" | \"ollama\" | \"minimax\" | \"copilot\"): string {\n return process.env.LLMWIKI_MODEL ?? PROVIDER_MODELS[providerName];\n}\n\nfunction getMiniMaxProvider(): MiniMaxProvider {\n const apiKey = process.env.MINIMAX_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"MiniMax provider requires MINIMAX_API_KEY environment variable.\\n\" +\n ' Set it with: export MINIMAX_API_KEY=your_key',\n );\n }\n return new MiniMaxProvider(getModelForProvider(\"minimax\"), apiKey);\n}\n\nfunction getCopilotProvider(): CopilotProvider {\n const apiKey = process.env.GITHUB_TOKEN;\n if (!apiKey) {\n throw new Error(\n \"GitHub Copilot provider requires GITHUB_TOKEN environment variable.\\n\" +\n \" Run: gh auth refresh --scopes copilot\\n\" +\n \" Then set it with: export GITHUB_TOKEN=$(gh auth token)\\n\" +\n \" The token must belong to a GitHub account with an active Copilot subscription.\",\n );\n }\n return new CopilotProvider(getModelForProvider(\"copilot\"), apiKey);\n}\n\nfunction getAnthropicProvider(): AnthropicProvider {\n const model = resolveAnthropicModelFromEnv() ?? PROVIDER_MODELS.anthropic;\n const baseURL = resolveAnthropicBaseURLFromEnv();\n const auth = resolveAnthropicAuthFromEnv();\n\n return new AnthropicProvider(model, {\n baseURL,\n ...auth,\n });\n}\n\nfunction getProviderName(): string {\n const providerName = process.env.LLMWIKI_PROVIDER ?? DEFAULT_PROVIDER;\n if (!SUPPORTED_PROVIDERS.has(providerName)) {\n throw new Error(\n `Unknown provider \"${providerName}\". Supported: ${[...SUPPORTED_PROVIDERS].join(\", \")}`,\n );\n }\n return providerName;\n}\n\n/** Expose the resolved provider name for callers that need model lookup. */\nexport function getActiveProviderName(): string {\n return getProviderName();\n}\n","/**\n * Shared LLM helper with provider abstraction.\n *\n * Provides callClaude() for backward compatibility — delegates to the\n * active LLMProvider while preserving retry logic with exponential backoff.\n * The provider is selected via LLMWIKI_PROVIDER env var (see provider.ts).\n */\n\nimport { RETRY_COUNT, RETRY_BASE_MS, RETRY_MULTIPLIER } from \"./constants.js\";\nimport { getProvider } from \"./provider.js\";\nimport type { LLMMessage, LLMTool } from \"./provider.js\";\n\n/** Sleep for a given number of milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\ninterface CallClaudeOptions {\n system: string;\n messages: LLMMessage[];\n tools?: LLMTool[];\n maxTokens?: number;\n stream?: boolean;\n onToken?: (text: string) => void;\n}\n\n/**\n * Call the active LLM provider with retry logic.\n * Supports streaming, tool-use, and basic completion modes.\n * Preserves the original callClaude interface for backward compatibility.\n */\nexport async function callClaude(options: CallClaudeOptions): Promise<string> {\n const { system, messages, tools, maxTokens = 4096, stream = false, onToken } = options;\n const provider = getProvider();\n\n for (let attempt = 0; attempt <= RETRY_COUNT; attempt++) {\n try {\n if (stream) {\n return await provider.stream(system, messages, maxTokens, onToken);\n }\n\n if (tools && tools.length > 0) {\n return await provider.toolCall(system, messages, tools, maxTokens);\n }\n\n return await provider.complete(system, messages, maxTokens);\n } catch (error) {\n if (attempt === RETRY_COUNT) throw error;\n\n const delayMs = RETRY_BASE_MS * Math.pow(RETRY_MULTIPLIER, attempt);\n const errMsg = error instanceof Error ? error.message : String(error);\n console.warn(`⚠ API call failed (attempt ${attempt + 1}/${RETRY_COUNT + 1}): ${errMsg}`);\n console.warn(` Retrying in ${delayMs / 1000}s...`);\n await sleep(delayMs);\n }\n }\n\n throw new Error(\"Unreachable\");\n}\n","/**\n * PID-based lock file for preventing concurrent compilation.\n *\n * Fresh acquisition uses O_CREAT | O_EXCL (the 'wx' flag) for atomic lock\n * creation — the kernel guarantees only one process can create the file.\n *\n * Stale lock reclamation uses a two-lock protocol:\n * 1. Acquire a reclamation lock (.llmwiki/lock.reclaim) via 'wx' to serialize\n * all processes attempting to reclaim the same stale main lock.\n * 2. Re-verify the main lock is still stale (another reclaimer may have\n * already fixed it).\n * 3. unlink + tryCreateLock('wx') on the main lock — safe because we hold\n * exclusive reclamation access.\n * 4. Release the reclamation lock in a finally block.\n *\n * The reclamation lock itself can become stale if a process crashes during\n * the brief reclamation window. When that happens, acquireReclaimLock only\n * cleans up the stale file — it does NOT retry acquisition in the same call.\n * This eliminates the unlink-then-create race that would allow two processes\n * to both hold the reclaim lock. The outer retry loop in acquireLock handles\n * convergence: first pass cleans up the stale reclaim lock, second pass\n * acquires it cleanly via 'wx'.\n */\n\nimport { open, readFile, unlink, mkdir } from \"fs/promises\";\nimport path from \"path\";\nimport { LLMWIKI_DIR, LOCK_FILE } from \"./constants.js\";\nimport * as output from \"./output.js\";\n\nconst RECLAIM_SUFFIX = \".reclaim\";\nconst MAX_ACQUIRE_ATTEMPTS = 2;\n\n/** Check whether a process with the given PID is still running. */\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Acquire the compilation lock. Returns true if acquired, false if busy.\n *\n * Retries up to MAX_ACQUIRE_ATTEMPTS times to handle the case where the\n * first attempt cleans up a stale reclamation lock but cannot acquire it\n * in the same call (to avoid the double-winner race).\n */\nexport async function acquireLock(root: string): Promise<boolean> {\n const lockPath = path.join(root, LOCK_FILE);\n await mkdir(path.join(root, LLMWIKI_DIR), { recursive: true });\n\n for (let attempt = 0; attempt < MAX_ACQUIRE_ATTEMPTS; attempt++) {\n // Try atomic create — fails if file already exists\n const created = await tryCreateLock(lockPath);\n if (created) return true;\n\n // Lock exists. Check if the holding process is dead.\n const stale = await isLockStale(lockPath);\n if (!stale) {\n output.status(\"!\", output.warn(\"Another compilation is running.\"));\n return false;\n }\n\n // Stale lock — serialize reclamation via a second lock.\n const reclaimed = await reclaimStaleLock(root, lockPath);\n if (reclaimed) return true;\n\n // Reclamation failed (e.g. cleaned up stale reclaim lock). Retry.\n }\n\n output.status(\"!\", output.warn(\"Could not acquire lock after retrying.\"));\n return false;\n}\n\n/**\n * Reclaim a stale main lock using a serialized two-lock protocol.\n *\n * Acquires .llmwiki/lock.reclaim (via 'wx') so that only one process performs\n * the unlink + recreate sequence at a time. Re-verifies staleness under\n * the reclamation lock in case another process already fixed it.\n * @param root - Project root directory.\n * @param lockPath - Absolute path to the main lock file.\n */\nasync function reclaimStaleLock(root: string, lockPath: string): Promise<boolean> {\n const reclaimPath = lockPath + RECLAIM_SUFFIX;\n\n const gotReclaimLock = await acquireReclaimLock(reclaimPath);\n if (!gotReclaimLock) return false;\n\n try {\n // Re-verify under exclusive reclamation access.\n // Another reclaimer may have already fixed the main lock.\n if (!(await isLockStale(lockPath))) {\n return false;\n }\n\n // Still stale. Safe to reclaim — we're the only reclaimer.\n try { await unlink(lockPath); } catch { /* already gone */ }\n\n const acquired = await tryCreateLock(lockPath);\n if (acquired) {\n output.status(\"i\", output.dim(\"Reclaimed stale lock from dead process.\"));\n }\n return acquired;\n } finally {\n try { await unlink(reclaimPath); } catch { /* cleanup best-effort */ }\n }\n}\n\n/**\n * Acquire the reclamation lock. Uses 'wx' for atomic creation.\n *\n * If the reclaim lock is stale (holder crashed during reclamation), this\n * function ONLY cleans up the stale file and returns false. It does NOT\n * retry acquisition in the same call. This is the key safety property:\n * unlink and create never happen in the same call, so two processes that\n * both see a stale reclaim lock will both clean up (harmless — second\n * unlink gets ENOENT) and both return false. Neither holds the reclaim\n * lock, so neither proceeds to touch the main lock. The outer retry loop\n * in acquireLock converges on the next attempt via a clean 'wx'.\n * @param reclaimPath - Absolute path to the reclamation lock file.\n */\nasync function acquireReclaimLock(reclaimPath: string): Promise<boolean> {\n if (await tryCreateLock(reclaimPath)) return true;\n\n // Reclaim lock exists. If its holder is alive, back off.\n if (!(await isLockStale(reclaimPath))) return false;\n\n // Stale reclaim lock — clean it up but do NOT retry in this call.\n // Retrying here would reintroduce the unlink+create race.\n try { await unlink(reclaimPath); } catch { /* already gone */ }\n return false;\n}\n\n/**\n * Atomically create the lock file with our PID.\n * Returns true if we created it, false if it already exists.\n */\nasync function tryCreateLock(lockPath: string): Promise<boolean> {\n try {\n const fd = await open(lockPath, \"wx\");\n await fd.writeFile(String(process.pid), \"utf-8\");\n await fd.close();\n return true;\n } catch (err: unknown) {\n if (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"EEXIST\") {\n return false;\n }\n throw err;\n }\n}\n\n/** Check if an existing lock is stale (holding process is dead). */\nasync function isLockStale(lockPath: string): Promise<boolean> {\n try {\n const content = await readFile(lockPath, \"utf-8\");\n const pid = parseInt(content.trim(), 10);\n if (isNaN(pid)) return true;\n return !isProcessAlive(pid);\n } catch {\n return true;\n }\n}\n\n/** Release the compilation lock. Safe to call even if lock doesn't exist. */\nexport async function releaseLock(root: string): Promise<void> {\n const lockPath = path.join(root, LOCK_FILE);\n try {\n await unlink(lockPath);\n } catch {\n // Lock already removed or never existed\n }\n}\n","/**\n * Output-language configuration for LLM-generated wiki content (issue #37).\n *\n * Resolves the user's chosen target language for compile and query\n * prompts. The CLI's `--lang <code>` flag and the `LLMWIKI_OUTPUT_LANG`\n * environment variable both write into the same env slot, so prompt\n * builders only need to read one source of truth.\n *\n * When unset, the resolver returns null — preserving the historical\n * behaviour where the LLM follows its own default (typically the\n * source-document language, often English).\n */\n\nconst LANG_ENV_VAR = \"LLMWIKI_OUTPUT_LANG\";\n\n/**\n * Read the configured output language. Returns null when the user has\n * not opted into a specific target language.\n */\nexport function getOutputLanguage(): string | null {\n const raw = process.env[LANG_ENV_VAR];\n if (!raw) return null;\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\n/**\n * Build the language-directive line to inject into a system prompt.\n * Returns an empty string when no language is configured, which lets\n * callers concatenate unconditionally without producing an extra blank\n * line in the default case.\n */\nexport function languageDirective(): string {\n const lang = getOutputLanguage();\n if (!lang) return \"\";\n return `Write the output in ${lang}.`;\n}\n","/**\n * LLM prompt templates and tool schemas for the compilation pipeline.\n * Contains the Anthropic tool definition for concept extraction,\n * prompt builders for both extraction and page generation phases,\n * and a parser for the structured tool output.\n */\n\nimport type {\n ContradictionRef,\n ExtractedConcept,\n ProvenanceState,\n} from \"../utils/types.js\";\nimport type { PageKindRule, SeedPage } from \"../schema/index.js\";\nimport { languageDirective } from \"../utils/output-language.js\";\n\n/**\n * Build a list of optional prompt lines, omitting empty entries so the\n * default-case prompt is byte-identical to the previous version. Used by\n * the prompt builders to splice in the output-language directive only\n * when the user opted in.\n */\nfunction withLangLine(...lines: string[]): string[] {\n const lang = languageDirective();\n return lang ? [...lines, lang] : lines;\n}\n\n/** Allowed provenance state strings emitted by the LLM tool schema. */\nconst PROVENANCE_STATE_VALUES: ProvenanceState[] = [\n \"extracted\",\n \"merged\",\n \"inferred\",\n \"ambiguous\",\n];\n\n/**\n * Anthropic Tool definition for extracting knowledge concepts from a source.\n * Used with callClaude's tool_use mode to get structured concept data.\n */\nexport const CONCEPT_EXTRACTION_TOOL = {\n name: \"extract_concepts\",\n description: \"Extract knowledge concepts from a source document\",\n input_schema: {\n type: \"object\" as const,\n properties: {\n concepts: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n concept: {\n type: \"string\",\n description: \"Human-readable concept title\",\n },\n summary: {\n type: \"string\",\n description: \"One-line description\",\n },\n is_new: {\n type: \"boolean\",\n description: \"True if this is a new concept not in existing wiki\",\n },\n tags: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"2-4 categorical tags for organizing this concept (e.g., 'machine-learning', 'optimization')\",\n },\n confidence: {\n type: \"number\",\n description:\n \"Confidence in this concept on a 0..1 scale (1 = directly stated, 0 = highly speculative).\",\n },\n provenance_state: {\n type: \"string\",\n enum: PROVENANCE_STATE_VALUES,\n description:\n \"How this concept was produced: 'extracted' (direct from source), 'merged' (synthesised across sources), 'inferred' (model deduction), or 'ambiguous' (sources disagree).\",\n },\n contradicted_by: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n slug: { type: \"string\", description: \"Slug of the contradicting concept.\" },\n reason: { type: \"string\", description: \"Brief reason for the contradiction.\" },\n },\n required: [\"slug\"],\n },\n description: \"Slugs of other concepts whose evidence contradicts this one.\",\n },\n },\n required: [\"concept\", \"summary\", \"is_new\"],\n },\n },\n },\n required: [\"concepts\"],\n },\n};\n\n/**\n * Build the system prompt for the concept extraction phase.\n * Instructs the LLM to analyze a source document and identify distinct concepts.\n * @param sourceContent - The full text of the source document.\n * @param existingIndex - The current wiki index.md contents (may be empty).\n * @returns System prompt string for the extraction call.\n */\nexport function buildExtractionPrompt(\n sourceContent: string,\n existingIndex: string,\n): string {\n const indexSection = existingIndex\n ? `\\n\\nHere is the existing wiki index — avoid duplicating concepts already covered:\\n\\n${existingIndex}`\n : \"\\n\\nNo existing wiki pages yet.\";\n\n return [\n ...withLangLine(\n \"You are a knowledge extraction engine. Analyze the following source document\",\n \"and identify 3-8 distinct, meaningful concepts worth documenting as wiki pages.\",\n \"Each concept should be a standalone topic that someone might look up.\",\n \"Focus on key ideas, techniques, patterns, or entities — not trivial details.\",\n \"Use the extract_concepts tool to return your findings.\",\n ),\n \"\",\n \"For every concept, emit provenance metadata so downstream tools can reason\",\n \"about reliability:\",\n \" - confidence: 0..1 — how certain you are the source supports this concept.\",\n \" - provenance_state: 'extracted' if directly stated, 'merged' if synthesised\",\n \" from multiple parts of the source, 'inferred' if reasoned from context,\",\n \" or 'ambiguous' if the source is contradictory or unclear.\",\n \" - contradicted_by: slugs of other concepts (in this batch or the index)\",\n \" whose evidence conflicts with this one.\",\n indexSection,\n \"\\n\\n--- SOURCE DOCUMENT ---\\n\\n\",\n sourceContent,\n ].join(\"\\n\");\n}\n\n/**\n * Build the system prompt for wiki page generation.\n * Instructs the LLM to write a complete wiki page for a single concept.\n * @param concept - The concept title to write about.\n * @param sourceContent - The source material to draw from.\n * @param existingPage - The current page content if updating (empty for new pages).\n * @param relatedPages - Concatenated content of related wiki pages for context.\n * @returns System prompt string for the page generation call.\n */\nexport function buildPagePrompt(\n concept: string,\n sourceContent: string,\n existingPage: string,\n relatedPages: string,\n): string {\n const existingSection = existingPage\n ? `\\n\\nExisting page to update:\\n\\n${existingPage}`\n : \"\";\n\n const relatedSection = relatedPages\n ? `\\n\\nRelated wiki pages for cross-referencing:\\n\\n${relatedPages}`\n : \"\";\n\n return [\n ...withLangLine(\n `You are a wiki author. Write a clear, well-structured markdown page about \"${concept}\".`,\n \"Draw facts only from the provided source material.\",\n \"Include a ## Sources section at the end listing the source document.\",\n \"Suggest [[wikilinks]] to related concepts where appropriate.\",\n \"Write in a neutral, informative tone. Be concise but thorough.\",\n ),\n \"\",\n \"Source attribution: at the end of each prose paragraph, append a citation\",\n \"marker showing which source file(s) the paragraph drew from.\",\n \"Format: ^[filename.md] for single-source, ^[source-a.md, source-b.md] for multi-source.\",\n \"When a single sentence makes a specific factual claim and you can identify the\",\n \"exact line range it came from, you may use the claim-level form\",\n \"^[filename.md:START-END] (or ^[filename.md#LSTART-LEND]) at the end of that\",\n \"sentence — START and END are 1-indexed line numbers in the source file.\",\n \"Paragraph-level citations remain the default; only switch to claim-level form\",\n \"when it materially improves verifiability and the line range is unambiguous.\",\n \"Place citations only at the end of prose paragraphs or sentences — not on\",\n \"headings, list items, or code blocks.\",\n \"Source filenames are visible as `--- SOURCE: filename.md ---` headers in the content below.\",\n \"\",\n \"If a paragraph is your inference rather than a direct extraction, leave it\",\n \"uncited — downstream lint rules will count uncited paragraphs as 'inferred'\",\n \"so lint can surface excess-inferred-paragraphs warnings on review.\",\n existingSection,\n relatedSection,\n \"\\n\\n--- SOURCE MATERIAL ---\\n\\n\",\n sourceContent,\n ].join(\"\\n\");\n}\n\n/** Raw concept shape as it arrives from the tool JSON. */\ninterface RawConcept {\n concept: unknown;\n summary: unknown;\n is_new: unknown;\n tags?: unknown;\n confidence?: unknown;\n provenance_state?: unknown;\n contradicted_by?: unknown;\n}\n\n/** True if the raw concept has the required string/boolean fields. */\nfunction isValidRawConcept(c: RawConcept): boolean {\n return (\n typeof c.concept === \"string\" &&\n typeof c.summary === \"string\" &&\n typeof c.is_new === \"boolean\" &&\n (c.tags === undefined || Array.isArray(c.tags))\n );\n}\n\n/** Coerce raw contradiction entries from the tool into typed refs. */\nfunction coerceContradictedBy(raw: unknown): ContradictionRef[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const refs: ContradictionRef[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== \"object\") continue;\n const obj = entry as { slug?: unknown; reason?: unknown };\n if (typeof obj.slug !== \"string\" || obj.slug.trim().length === 0) continue;\n const ref: ContradictionRef = { slug: obj.slug.trim() };\n if (typeof obj.reason === \"string\") ref.reason = obj.reason;\n refs.push(ref);\n }\n return refs.length > 0 ? refs : undefined;\n}\n\n/** Map a validated raw concept into an ExtractedConcept. */\nfunction mapRawConcept(c: RawConcept): ExtractedConcept {\n const provenance = typeof c.provenance_state === \"string\" &&\n PROVENANCE_STATE_VALUES.includes(c.provenance_state as ProvenanceState)\n ? (c.provenance_state as ProvenanceState)\n : undefined;\n return {\n concept: c.concept as string,\n summary: c.summary as string,\n is_new: c.is_new as boolean,\n tags: Array.isArray(c.tags) ? (c.tags as string[]) : undefined,\n confidence: typeof c.confidence === \"number\" ? c.confidence : undefined,\n provenanceState: provenance,\n contradictedBy: coerceContradictedBy(c.contradicted_by),\n };\n}\n\n/**\n * Build a system prompt for generating a seed page (overview / comparison /\n * entity) declared in the project's schema config. Seed pages weave together\n * material from related concept pages rather than from raw source files.\n * @param seed - Seed page definition pulled from the schema.\n * @param rule - Per-kind rule (used for the description and link minimum).\n * @param relatedPagesContent - Concatenated content of related concept pages.\n * @returns System prompt string for the page generation call.\n */\nexport function buildSeedPagePrompt(\n seed: SeedPage,\n rule: PageKindRule,\n relatedPagesContent: string,\n): string {\n const minLinks = rule.minWikilinks;\n const linkExpectation = minLinks > 0\n ? `Include at least ${minLinks} [[wikilinks]] to related pages.`\n : \"Use [[wikilinks]] when referencing other pages.\";\n return [\n ...withLangLine(\n `You are a wiki author. Write a ${seed.kind} page titled \"${seed.title}\".`,\n `Page-kind guidance: ${rule.description}`,\n `Summary line for context: ${seed.summary}`,\n \"Draw facts only from the related wiki pages provided below.\",\n linkExpectation,\n \"Write in a neutral, informative tone. Be concise but thorough.\",\n ),\n \"\\n\\n--- RELATED PAGES ---\\n\\n\",\n relatedPagesContent,\n ].join(\"\\n\");\n}\n\n/**\n * Parse the JSON tool output from concept extraction into typed objects.\n * @param toolOutput - Raw JSON string returned from the extract_concepts tool.\n * @returns Array of ExtractedConcept objects.\n */\nexport function parseConcepts(toolOutput: string): ExtractedConcept[] {\n try {\n const parsed = JSON.parse(toolOutput);\n const concepts: RawConcept[] = parsed.concepts ?? [];\n return concepts.filter(isValidRawConcept).map(mapRawConcept);\n } catch {\n return [];\n }\n}\n","/**\n * Type definitions for the wiki schema layer.\n *\n * The schema layer turns llmwiki from a flat compiler pipeline into a shaped\n * knowledge system. It declares the kinds of pages a project supports\n * (`concept`, `entity`, `comparison`, `overview`) and the cross-link\n * expectations that lint and review enforce per kind.\n *\n * Types live in their own module so that compile, lint, CLI, and tests can\n * depend on the schema vocabulary without pulling in YAML/JSON loaders.\n */\n\n/** All page kinds the schema layer recognises. */\nexport type PageKind = \"concept\" | \"entity\" | \"comparison\" | \"overview\";\n\n/** All recognised page kinds, exported for validation and CLI display. */\nexport const PAGE_KINDS: readonly PageKind[] = [\n \"concept\",\n \"entity\",\n \"comparison\",\n \"overview\",\n] as const;\n\n/** Per-kind policy: minimum cross-links and a human description used in prompts. */\nexport interface PageKindRule {\n /** Minimum number of [[wikilinks]] a page of this kind should contain. */\n minWikilinks: number;\n /** Short human-readable description; surfaced in prompts and review output. */\n description: string;\n}\n\n/** Optional declarative seed for non-concept pages the compiler can generate. */\nexport interface SeedPage {\n /** Display title; also used to derive the page slug. */\n title: string;\n /** Page kind — must be one of `PAGE_KINDS`. */\n kind: PageKind;\n /** One-line summary written into frontmatter. */\n summary: string;\n /**\n * For `overview` and `comparison` kinds, the slugs the page should weave\n * together. The compiler passes these to the LLM as the source material.\n */\n relatedSlugs?: string[];\n}\n\n/** Resolved schema config the rest of the compiler reads. */\nexport interface SchemaConfig {\n /** Schema format version. Currently always `1`. */\n version: 1;\n /** Kind assigned to pages that don't declare a kind in frontmatter. */\n defaultKind: PageKind;\n /** Per-kind rules keyed by `PageKind`. */\n kinds: Record<PageKind, PageKindRule>;\n /** Optional seed pages the compiler should materialise on each run. */\n seedPages: SeedPage[];\n /** Path the schema was loaded from, or `null` when defaults are used. */\n loadedFrom: string | null;\n}\n\n/** Raw schema file contents — every field is optional so partial files work. */\nexport interface PartialSchemaFile {\n version?: number;\n defaultKind?: string;\n kinds?: Partial<Record<string, Partial<PageKindRule>>>;\n seedPages?: Array<Partial<SeedPage>>;\n}\n","/**\n * Default schema constants.\n *\n * Projects without a schema file fall back to these defaults so the compiler\n * keeps working on day one. Every existing wiki — created before the schema\n * layer existed — is treated as a wiki of `concept` pages with no\n * cross-link minimums, preserving backward compatibility.\n */\n\nimport type { PageKind, PageKindRule, SchemaConfig } from \"./types.js\";\n\n/** Minimum cross-links per kind, chosen to match each kind's purpose. */\nconst DEFAULT_MIN_LINKS: Record<PageKind, number> = {\n concept: 0,\n entity: 1,\n comparison: 2,\n overview: 3,\n};\n\n/** Human-readable descriptions used in prompts and review output. */\nconst DEFAULT_DESCRIPTIONS: Record<PageKind, string> = {\n concept: \"A standalone idea, technique, or pattern worth documenting.\",\n entity: \"A specific thing — a person, product, organization, or named artifact.\",\n comparison: \"A side-by-side analysis weighing two or more concepts or entities.\",\n overview: \"A top-down map page that situates several concepts within a domain.\",\n};\n\n/** Build the default per-kind rule table. */\nfunction buildDefaultKindRules(): Record<PageKind, PageKindRule> {\n return {\n concept: { minWikilinks: DEFAULT_MIN_LINKS.concept, description: DEFAULT_DESCRIPTIONS.concept },\n entity: { minWikilinks: DEFAULT_MIN_LINKS.entity, description: DEFAULT_DESCRIPTIONS.entity },\n comparison: {\n minWikilinks: DEFAULT_MIN_LINKS.comparison,\n description: DEFAULT_DESCRIPTIONS.comparison,\n },\n overview: {\n minWikilinks: DEFAULT_MIN_LINKS.overview,\n description: DEFAULT_DESCRIPTIONS.overview,\n },\n };\n}\n\n/** The schema returned when no schema file exists. */\nexport function buildDefaultSchema(): SchemaConfig {\n return {\n version: 1,\n defaultKind: \"concept\",\n kinds: buildDefaultKindRules(),\n seedPages: [],\n loadedFrom: null,\n };\n}\n","/**\n * Schema config loader.\n *\n * Discovers a project's schema file from a fixed list of candidate paths,\n * parses it (JSON or YAML), and merges it onto the default schema. Missing\n * files are not an error — the compiler falls back to defaults so existing\n * projects continue to work without any migration.\n */\n\nimport { existsSync } from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport yaml from \"js-yaml\";\nimport type {\n PageKind,\n PageKindRule,\n PartialSchemaFile,\n SchemaConfig,\n SeedPage,\n} from \"./types.js\";\nimport { PAGE_KINDS } from \"./types.js\";\nimport { buildDefaultSchema } from \"./defaults.js\";\n\n/** Candidate schema file paths searched in priority order. */\nconst SCHEMA_CANDIDATE_PATHS = [\n \".llmwiki/schema.json\",\n \".llmwiki/schema.yaml\",\n \".llmwiki/schema.yml\",\n \"wiki/.schema.yaml\",\n \"wiki/.schema.yml\",\n];\n\n/** Find the first existing schema candidate path under `root`, or null. */\nfunction findSchemaPath(root: string): string | null {\n for (const candidate of SCHEMA_CANDIDATE_PATHS) {\n const absolute = path.join(root, candidate);\n if (existsSync(absolute)) return absolute;\n }\n return null;\n}\n\n/** Decide whether to parse the file as JSON or YAML based on its extension. */\nfunction parseSchemaFile(filePath: string, content: string): PartialSchemaFile {\n const isJson = filePath.endsWith(\".json\");\n const parsed = isJson ? JSON.parse(content) : yaml.load(content);\n if (parsed && typeof parsed === \"object\") return parsed as PartialSchemaFile;\n return {};\n}\n\n/** Type-guard checking whether a string is one of the supported page kinds. */\nfunction isPageKind(value: unknown): value is PageKind {\n return typeof value === \"string\" && (PAGE_KINDS as readonly string[]).includes(value);\n}\n\n/** Merge a single per-kind rule from the file onto the default rule. */\nfunction mergeKindRule(\n defaults: PageKindRule,\n override: Partial<PageKindRule> | undefined,\n): PageKindRule {\n if (!override) return defaults;\n const minWikilinks = typeof override.minWikilinks === \"number\"\n ? override.minWikilinks\n : defaults.minWikilinks;\n const description = typeof override.description === \"string\"\n ? override.description\n : defaults.description;\n return { minWikilinks, description };\n}\n\n/** Merge per-kind rule overrides onto the default rule table. */\nfunction mergeKinds(\n defaults: Record<PageKind, PageKindRule>,\n overrides: PartialSchemaFile[\"kinds\"],\n): Record<PageKind, PageKindRule> {\n const merged = { ...defaults };\n if (!overrides) return merged;\n\n for (const kind of PAGE_KINDS) {\n merged[kind] = mergeKindRule(defaults[kind], overrides[kind]);\n }\n return merged;\n}\n\n/** Validate and coerce a single seed page entry. Returns null when invalid. */\nfunction normalizeSeedPage(entry: Partial<SeedPage>): SeedPage | null {\n if (typeof entry.title !== \"string\" || entry.title.trim() === \"\") return null;\n if (!isPageKind(entry.kind)) return null;\n const summary = typeof entry.summary === \"string\" ? entry.summary : \"\";\n const relatedSlugs = Array.isArray(entry.relatedSlugs)\n ? entry.relatedSlugs.filter((slug): slug is string => typeof slug === \"string\")\n : undefined;\n return { title: entry.title, kind: entry.kind, summary, relatedSlugs };\n}\n\n/** Coerce raw seed page entries into validated SeedPage objects. */\nfunction normalizeSeedPages(entries: PartialSchemaFile[\"seedPages\"]): SeedPage[] {\n if (!Array.isArray(entries)) return [];\n return entries\n .map(normalizeSeedPage)\n .filter((entry): entry is SeedPage => entry !== null);\n}\n\n/** Apply a parsed partial-schema onto the defaults, returning the resolved config. */\nfunction applyOverrides(\n defaults: SchemaConfig,\n overrides: PartialSchemaFile,\n loadedFrom: string,\n): SchemaConfig {\n const defaultKind = isPageKind(overrides.defaultKind)\n ? overrides.defaultKind\n : defaults.defaultKind;\n return {\n version: 1,\n defaultKind,\n kinds: mergeKinds(defaults.kinds, overrides.kinds),\n seedPages: normalizeSeedPages(overrides.seedPages),\n loadedFrom,\n };\n}\n\n/**\n * Load the schema for `root`, falling back to defaults when no file is present.\n * Throws on parse failure so the user sees a clear error rather than a silent\n * default — silent fallback would mask real config bugs.\n * @param root - Project root directory.\n * @returns Resolved schema config.\n */\nexport async function loadSchema(root: string): Promise<SchemaConfig> {\n const defaults = buildDefaultSchema();\n const schemaPath = findSchemaPath(root);\n if (!schemaPath) return defaults;\n\n const raw = await readFile(schemaPath, \"utf-8\");\n const parsed = parseSchemaFile(schemaPath, raw);\n return applyOverrides(defaults, parsed, schemaPath);\n}\n\n/** Expose candidate paths so the CLI `schema init` command can pick one. */\nexport function defaultSchemaInitPath(root: string): string {\n return path.join(root, SCHEMA_CANDIDATE_PATHS[0]);\n}\n","/**\n * Schema helper utilities shared by compile, lint, and CLI.\n *\n * Kept separate from `loader.ts` so callers that just need to interpret a\n * page's kind or count its wikilinks don't pull the YAML/JSON parser into\n * their dependency graph.\n */\n\nimport yaml from \"js-yaml\";\nimport type { PageKind, SchemaConfig } from \"./types.js\";\nimport { PAGE_KINDS } from \"./types.js\";\n\n/** Pattern matching [[Wikilink Title]] references in markdown content. */\nconst WIKILINK_PATTERN = /\\[\\[([^\\]]+)\\]\\]/g;\n\n/**\n * Resolve a page's kind from its raw frontmatter value, falling back to the\n * schema default when no explicit kind is set or the value is invalid.\n * @param rawKind - Raw `kind` value pulled from frontmatter (untyped).\n * @param schema - Resolved schema config.\n * @returns The resolved page kind.\n */\nexport function resolvePageKind(rawKind: unknown, schema: SchemaConfig): PageKind {\n if (typeof rawKind === \"string\" && (PAGE_KINDS as readonly string[]).includes(rawKind)) {\n return rawKind as PageKind;\n }\n return schema.defaultKind;\n}\n\n/**\n * Count the [[wikilinks]] in a page body.\n * Pure function so the linter can apply per-kind minimums without re-parsing.\n * @param body - Markdown body text.\n * @returns Number of wikilink references found.\n */\nexport function countWikilinks(body: string): number {\n const matches = body.match(WIKILINK_PATTERN);\n return matches ? matches.length : 0;\n}\n\n/**\n * Serialise a schema config to YAML for `llmwiki schema init` to write to disk.\n * The `loadedFrom` field is omitted because it's a runtime-only annotation.\n * @param schema - Resolved schema config.\n * @returns YAML string suitable for writing to a schema file.\n */\nexport function serializeSchemaToYaml(schema: SchemaConfig): string {\n const serializable = {\n version: schema.version,\n defaultKind: schema.defaultKind,\n kinds: schema.kinds,\n seedPages: schema.seedPages,\n };\n return yaml.dump(serializable, { lineWidth: -1, quotingType: '\"' });\n}\n","/**\n * Semantic dependency tracking for cross-source concept sharing.\n *\n * When multiple source files contribute to the same concept, a change in one\n * source should trigger recompilation of that concept using content from ALL\n * contributing sources. This module builds a reverse index from concepts back\n * to their source files, then identifies which unchanged sources are affected\n * by changes to other sources that share concepts with them.\n *\n * Without this, if sources A and B both produce concept X and source A changes,\n * concept X would be regenerated using only source A's content — losing source\n * B's contribution entirely.\n */\n\nimport { readState, updateSourceState, writeState } from \"../utils/state.js\";\nimport { slugify } from \"../utils/markdown.js\";\nimport * as output from \"../utils/output.js\";\nimport type { WikiState, SourceChange, ExtractedConcept } from \"../utils/types.js\";\n\nexport interface ExtractionResult {\n sourceFile: string;\n sourcePath: string;\n sourceContent: string;\n concepts: ExtractedConcept[];\n}\n\n/**\n * Build a reverse map from concept slugs to the source files that produced them.\n * @param sources - The sources record from WikiState.\n * @returns Map where keys are concept slugs and values are arrays of source filenames.\n */\nfunction buildConceptToSourcesMap(\n sources: WikiState[\"sources\"],\n): Map<string, string[]> {\n const conceptMap = new Map<string, string[]>();\n\n for (const [sourceFile, entry] of Object.entries(sources)) {\n for (const slug of entry.concepts) {\n const existing = conceptMap.get(slug);\n if (existing) {\n existing.push(sourceFile);\n } else {\n conceptMap.set(slug, [sourceFile]);\n }\n }\n }\n\n return conceptMap;\n}\n\n/** Extract filenames from changes matching a given status. */\nfunction filesByStatus(\n changes: SourceChange[],\n ...statuses: SourceChange[\"status\"][]\n): Set<string> {\n const statusSet = new Set(statuses);\n return new Set(\n changes.filter((c) => statusSet.has(c.status)).map((c) => c.file),\n );\n}\n\n/**\n * Collect co-contributors for a source's concepts, skipping files in the\n * exclusion sets. Mutates `out` by adding newly discovered contributors.\n */\nfunction collectSharedContributors(\n sourceFile: string,\n state: WikiState,\n conceptMap: Map<string, string[]>,\n excludeSets: Set<string>[],\n out: Set<string>,\n): void {\n const sourceEntry = state.sources[sourceFile];\n if (!sourceEntry) return;\n\n for (const slug of sourceEntry.concepts) {\n const contributors = conceptMap.get(slug);\n if (!contributors || contributors.length < 2) continue;\n\n for (const contributor of contributors) {\n const isExcluded = excludeSets.some((s) => s.has(contributor));\n if (!isExcluded) out.add(contributor);\n }\n }\n}\n\n/**\n * Identify unchanged sources that need recompilation because they share\n * concepts with directly changed sources. This enables correct cross-source\n * concept regeneration — ensuring shared concepts are rebuilt with content\n * from ALL contributing sources.\n *\n * Deleted sources are intentionally excluded: recompiling a concept-mate of\n * a deleted source would regenerate the page from fewer sources, losing\n * content. Shared concepts from deleted sources are preserved as-is by\n * markOrphaned (which skips shared concepts).\n *\n * @param state - The current persisted WikiState.\n * @param directChanges - Changes detected by hash comparison.\n * @returns Filenames of indirectly affected sources not already in the changed list.\n */\nexport function findAffectedSources(\n state: WikiState,\n directChanges: SourceChange[],\n): string[] {\n const changedFiles = filesByStatus(directChanges, \"new\", \"changed\");\n const deletedFiles = filesByStatus(directChanges, \"deleted\");\n const conceptMap = buildConceptToSourcesMap(state.sources);\n const affected = new Set<string>();\n\n for (const changedFile of changedFiles) {\n collectSharedContributors(\n changedFile, state, conceptMap,\n [changedFiles, deletedFiles, affected],\n affected,\n );\n }\n\n return Array.from(affected);\n}\n\n/**\n * Find concept slugs that must NOT be regenerated during this compile batch.\n * A slug is \"frozen\" when it was shared between a deleted source and at least\n * one surviving source. Regenerating it would overwrite the existing page\n * (which has combined content from all prior contributors) with content from\n * only the surviving sources, silently losing the deleted source's contribution.\n * @param state - Current persisted state.\n * @param changes - All detected source changes in this batch.\n * @returns Set of concept slugs that compileSource should skip.\n */\nexport function findFrozenSlugs(\n state: WikiState,\n changes: SourceChange[],\n): Set<string> {\n // Start with persisted frozen slugs from prior batches.\n const frozen = new Set<string>(state.frozenSlugs ?? []);\n\n // Add new frozen slugs from deletions in this batch.\n const deletedFiles = changes\n .filter((c) => c.status === \"deleted\")\n .map((c) => c.file);\n\n const conceptMap = buildConceptToSourcesMap(state.sources);\n\n for (const file of deletedFiles) {\n const entry = state.sources[file];\n if (!entry) continue;\n\n for (const slug of entry.concepts) {\n const contributors = conceptMap.get(slug);\n if (contributors && contributors.length > 1) {\n frozen.add(slug);\n }\n }\n }\n\n return frozen;\n}\n\n/**\n * Unfreeze slugs that were successfully regenerated by all their current\n * contributors, then persist the remaining frozen set to state.\n * A slug is safe to unfreeze when every source that claims it in state\n * was compiled in this batch and successfully extracted it.\n */\nexport async function persistFrozenSlugs(\n root: string,\n frozenSlugs: Set<string>,\n successfulExtractions: ExtractionResult[],\n): Promise<void> {\n const currentState = await readState(root);\n const conceptMap = buildConceptToSourcesMap(currentState.sources);\n\n // Concepts successfully extracted in this batch, keyed by slug.\n const extractedBy = new Set<string>();\n for (const result of successfulExtractions) {\n if (result.concepts.length === 0) continue;\n for (const c of result.concepts) {\n extractedBy.add(slugify(c.concept));\n }\n }\n const compiledFiles = new Set(\n successfulExtractions\n .filter((r) => r.concepts.length > 0)\n .map((r) => r.sourceFile),\n );\n\n const remaining = new Set<string>();\n for (const slug of frozenSlugs) {\n const owners = conceptMap.get(slug) ?? [];\n // Unfreeze only if ALL current owners were compiled and extracted it.\n const allOwnersCompiled = owners.length > 0\n && owners.every((f) => compiledFiles.has(f))\n && extractedBy.has(slug);\n\n if (!allOwnersCompiled) remaining.add(slug);\n }\n\n const stateToSave = { ...currentState, frozenSlugs: Array.from(remaining) };\n await writeState(root, stateToSave);\n}\n\n/**\n * Collect concept slugs from extractions that were not in the source's\n * previous concept list — these are \"newly gained\" concepts that\n * findAffectedSources could not have matched pre-extraction.\n */\nfunction collectFreshSlugs(\n extractions: ExtractionResult[],\n state: WikiState,\n): Set<string> {\n const freshSlugs = new Set<string>();\n\n for (const result of extractions) {\n const oldConcepts = new Set(state.sources[result.sourceFile]?.concepts ?? []);\n for (const c of result.concepts) {\n const slug = slugify(c.concept);\n if (!oldConcepts.has(slug)) freshSlugs.add(slug);\n }\n }\n\n return freshSlugs;\n}\n\n/**\n * Find unchanged sources that own any of the given slugs, excluding files\n * present in the provided exclusion sets.\n */\nfunction findSlugOwners(\n slugs: Set<string>,\n conceptMap: Map<string, string[]>,\n excludeSets: Set<string>[],\n): string[] {\n const affected = new Set<string>();\n\n for (const slug of slugs) {\n const owners = conceptMap.get(slug);\n if (!owners) continue;\n for (const owner of owners) {\n const isExcluded = excludeSets.some((s) => s.has(owner));\n if (!isExcluded) affected.add(owner);\n }\n }\n\n return Array.from(affected);\n}\n\n/**\n * Post-extraction check for compiled sources whose freshly extracted concepts\n * overlap with unchanged sources not already in the batch. Covers two cases\n * that findAffectedSources (pre-extraction) cannot detect:\n * 1. New sources have no state entry, so their concepts are unknown.\n * 2. Changed sources may gain concepts they didn't previously have.\n * @param extractions - Results from Phase 1 extraction.\n * @param state - Current persisted state.\n * @param allChanges - Full changes array including deleted/unchanged entries.\n * @returns Filenames of unchanged sources that share concepts with compiled sources.\n */\nexport function findLateAffectedSources(\n extractions: ExtractionResult[],\n state: WikiState,\n allChanges: SourceChange[],\n): string[] {\n const compilingFiles = filesByStatus(allChanges, \"new\", \"changed\");\n const deletedFiles = filesByStatus(allChanges, \"deleted\");\n const conceptMap = buildConceptToSourcesMap(state.sources);\n const freshSlugs = collectFreshSlugs(extractions, state);\n\n return findSlugOwners(freshSlugs, conceptMap, [compilingFiles, deletedFiles]);\n}\n\n/**\n * Find concept slugs from a source that are also produced by other sources.\n * Used by markOrphaned to skip orphaning shared concepts when a source is\n * deleted — preserving combined content from prior compilations.\n * @param sourceFile - The source being checked.\n * @param state - Current persisted state.\n * @returns Set of slugs that have at least one other contributing source.\n */\nexport function findSharedConcepts(\n sourceFile: string,\n state: WikiState,\n): Set<string> {\n const shared = new Set<string>();\n const sourceEntry = state.sources[sourceFile];\n if (!sourceEntry) return shared;\n\n const conceptMap = buildConceptToSourcesMap(state.sources);\n\n for (const slug of sourceEntry.concepts) {\n const contributors = conceptMap.get(slug);\n if (contributors && contributors.length > 1) {\n shared.add(slug);\n }\n }\n\n return shared;\n}\n\n/**\n * Freeze concepts from failed extractions and persist their state with a\n * blank hash so they retry on the next compile. Preserves old concept lists\n * to keep dependency tracking intact.\n */\nexport async function freezeFailedExtractions(\n root: string,\n results: ExtractionResult[],\n frozenSlugs: Set<string>,\n): Promise<void> {\n for (const result of results) {\n if (result.concepts.length > 0) continue;\n\n output.status(\"!\", output.warn(`${result.sourceFile}: no concepts — will retry.`));\n const currentState = await readState(root);\n const oldConcepts = currentState.sources[result.sourceFile]?.concepts ?? [];\n for (const slug of oldConcepts) frozenSlugs.add(slug);\n\n await updateSourceState(root, result.sourceFile, {\n hash: \"\",\n concepts: oldConcepts,\n compiledAt: new Date().toISOString(),\n });\n }\n}\n","/**\n * Orphan management for deleted source files.\n *\n * When a source is deleted, its exclusively-owned concept pages are marked\n * orphaned (orphaned: true in frontmatter). Shared concepts are preserved\n * to avoid losing combined content from prior compilations.\n *\n * After compilation, frozen slugs (shared concepts that lost a contributor)\n * are checked against the updated state. Any that lost ALL owners are\n * orphaned as a cleanup pass.\n */\n\nimport path from \"path\";\nimport { readState, removeSourceState } from \"../utils/state.js\";\nimport {\n atomicWrite,\n safeReadFile,\n parseFrontmatter,\n} from \"../utils/markdown.js\";\nimport { findSharedConcepts } from \"./deps.js\";\nimport * as output from \"../utils/output.js\";\nimport { CONCEPTS_DIR } from \"../utils/constants.js\";\n\n/**\n * Mark wiki pages as orphaned when their source is deleted.\n * Only orphans concepts exclusively owned by the deleted source.\n * Shared concepts (contributed to by other live sources) are preserved\n * as-is to avoid losing combined content from prior compilations.\n */\nexport async function markOrphaned(\n root: string,\n sourceFile: string,\n state: Awaited<ReturnType<typeof readState>>,\n): Promise<void> {\n const sourceEntry = state.sources[sourceFile];\n if (!sourceEntry) return;\n\n const sharedSlugs = findSharedConcepts(sourceFile, state);\n\n for (const slug of sourceEntry.concepts) {\n if (sharedSlugs.has(slug)) {\n output.status(\"i\", output.dim(`Kept: ${slug}.md (shared with other sources)`));\n continue;\n }\n\n await orphanPage(root, slug, \"source deleted\");\n }\n\n await removeSourceState(root, sourceFile);\n}\n\n/**\n * Check frozen slugs against the updated state after compilation.\n * If no source still claims a frozen slug, orphan its page so it doesn't\n * linger as an untracked stale file.\n */\nexport async function orphanUnownedFrozenPages(\n root: string,\n frozenSlugs: Set<string>,\n): Promise<void> {\n const currentState = await readState(root);\n const ownedSlugs = new Set<string>();\n for (const entry of Object.values(currentState.sources)) {\n for (const slug of entry.concepts) ownedSlugs.add(slug);\n }\n\n for (const slug of frozenSlugs) {\n if (ownedSlugs.has(slug)) continue;\n await orphanPage(root, slug, \"no remaining sources\");\n }\n}\n\n/**\n * Mark a single concept page as orphaned if it exists and isn't already marked.\n * @param root - Project root directory.\n * @param slug - Concept slug to orphan.\n * @param reason - Human-readable reason for the log message.\n */\nasync function orphanPage(root: string, slug: string, reason: string): Promise<void> {\n const pagePath = path.join(root, CONCEPTS_DIR, `${slug}.md`);\n const content = await safeReadFile(pagePath);\n if (!content) return;\n\n const { meta } = parseFrontmatter(content);\n if (meta.orphaned === true) return;\n\n const updated = content.replace(\"---\\n\", \"---\\norphaned: true\\n\");\n await atomicWrite(pagePath, updated);\n output.status(\"⚠\", output.warn(`Orphaned: ${slug}.md (${reason})`));\n}\n","/**\n * Interlink resolution for wiki pages.\n *\n * Rule-based (not LLM-based) pass that scans wiki pages for concept title\n * mentions and wraps them in [[slug|Title]] wikilinks. The piped alias form\n * keeps Obsidian link resolution stable when a page's filename (slug) differs\n * from its display title.\n *\n * Complexity: O(changed * total) per incremental compile.\n * Full recompile degrades to O(total^2).\n */\n\nimport { readdir, readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { existsSync } from \"fs\";\nimport { atomicWrite, parseFrontmatter } from \"../utils/markdown.js\";\nimport { CONCEPTS_DIR } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\n\ninterface PageInfo {\n slug: string;\n title: string;\n filePath: string;\n}\n\n/** Build an index of all wiki page titles from the concepts directory. */\nasync function buildTitleIndex(root: string): Promise<PageInfo[]> {\n const conceptsDir = path.join(root, CONCEPTS_DIR);\n if (!existsSync(conceptsDir)) return [];\n\n const files = await readdir(conceptsDir);\n const pages: PageInfo[] = [];\n\n for (const file of files) {\n if (!file.endsWith(\".md\")) continue;\n\n const filePath = path.join(conceptsDir, file);\n const content = await readFile(filePath, \"utf-8\");\n const { meta } = parseFrontmatter(content);\n\n if (meta.title && typeof meta.title === \"string\" && !meta.orphaned) {\n pages.push({\n slug: file.replace(/\\.md$/, \"\"),\n title: meta.title,\n filePath,\n });\n }\n }\n\n return pages;\n}\n\n/** Check if a position is inside an existing [[wikilink]]. */\nfunction isInsideWikilink(text: string, position: number): boolean {\n const before = text.lastIndexOf(\"[[\", position);\n const after = text.indexOf(\"]]\", position);\n if (before === -1 || after === -1) return false;\n\n const closeBefore = text.indexOf(\"]]\", before);\n return closeBefore >= position;\n}\n\n/** Check if a position is inside a ^[...] citation marker. */\nfunction isInsideCitation(text: string, position: number): boolean {\n const before = text.lastIndexOf(\"^[\", position);\n const after = text.indexOf(\"]\", position);\n if (before === -1 || after === -1) return false;\n\n const closeBefore = text.indexOf(\"]\", before);\n return closeBefore >= position;\n}\n\n/** Check if a match is at a word boundary. */\nfunction isWordBoundary(text: string, start: number, end: number): boolean {\n const before = start === 0 || /[\\s,.:;!?()\\[\\]{}/\"']/.test(text[start - 1]);\n const after = end >= text.length || /[\\s,.:;!?()\\[\\]{}/\"']/.test(text[end]);\n return before && after;\n}\n\n/** Find all regex matches for a title in the text, returned as position spans. */\nfunction findTitleMatches(text: string, title: string): { start: number; end: number }[] {\n const escaped = title.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regex = new RegExp(escaped, \"gi\");\n const matches: { start: number; end: number }[] = [];\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n matches.push({ start: match.index, end: match.index + match[0].length });\n }\n\n return matches;\n}\n\n/** Determine whether a match position is eligible for wikilink insertion. */\nfunction isLinkablePosition(text: string, start: number, end: number): boolean {\n if (isInsideWikilink(text, start)) return false;\n if (isInsideCitation(text, start)) return false;\n return isWordBoundary(text, start, end);\n}\n\n/**\n * Add [[wikilinks]] to a page's body for any title mentions.\n * Skips already-linked text and non-word-boundary matches.\n */\nfunction addWikilinks(body: string, titles: PageInfo[], selfTitle: string): string {\n let result = body;\n const selfLower = selfTitle.toLowerCase();\n\n for (const page of titles) {\n if (page.title.toLowerCase() === selfLower) continue;\n\n const matches = findTitleMatches(result, page.title);\n\n // Process matches in reverse to preserve positions\n for (const m of matches.reverse()) {\n if (!isLinkablePosition(result, m.start, m.end)) continue;\n result = result.slice(0, m.start) + `[[${page.slug}|${page.title}]]` + result.slice(m.end);\n }\n }\n\n return result;\n}\n\n/**\n * Run interlink resolution on changed and affected pages.\n *\n * Two passes:\n * 1. Outbound: changed pages get [[wikilinks]] for any title they mention.\n * 2. Inbound: ALL pages get scanned for mentions of newly created titles.\n * This ensures existing pages link to new concepts without a full recompile.\n *\n * Complexity: O(changed * total) for outbound, O(newTitles * total) for inbound.\n */\nexport async function resolveLinks(\n root: string,\n changedSlugs: string[],\n newSlugs: string[],\n): Promise<number> {\n const titleIndex = await buildTitleIndex(root);\n if (titleIndex.length === 0) return 0;\n\n let linkCount = 0;\n\n // Pass 1: outbound links on changed pages\n linkCount += await resolveOutboundLinks(titleIndex, changedSlugs);\n\n // Pass 2: inbound links on all pages for new titles\n linkCount += await resolveInboundLinks(titleIndex, newSlugs);\n\n if (linkCount > 0) {\n output.status(\"🔗\", output.dim(`Resolved links in ${linkCount} page(s)`));\n }\n\n return linkCount;\n}\n\n/** Add outbound [[wikilinks]] to changed pages for any title they mention. */\nasync function resolveOutboundLinks(\n titleIndex: PageInfo[],\n changedSlugs: string[],\n): Promise<number> {\n let count = 0;\n\n for (const page of titleIndex) {\n if (!changedSlugs.includes(page.slug)) continue;\n const didLink = await linkPage(page, titleIndex);\n if (didLink) count++;\n }\n\n return count;\n}\n\n/** Scan ALL pages for mentions of newly created concept titles. */\nasync function resolveInboundLinks(\n titleIndex: PageInfo[],\n newSlugs: string[],\n): Promise<number> {\n if (newSlugs.length === 0) return 0;\n\n const newTitles = titleIndex.filter((p) => newSlugs.includes(p.slug));\n if (newTitles.length === 0) return 0;\n\n let count = 0;\n\n for (const page of titleIndex) {\n // Skip pages that were already processed in outbound pass\n if (newSlugs.includes(page.slug)) continue;\n\n const content = await readFile(page.filePath, \"utf-8\");\n const { body } = parseFrontmatter(content);\n const linked = addWikilinks(body, newTitles, page.title);\n\n if (linked !== body) {\n const newContent = content.replace(body, linked);\n await atomicWrite(page.filePath, newContent);\n count++;\n }\n }\n\n return count;\n}\n\n/** Add wikilinks to a single page, writing atomically if changed. */\nasync function linkPage(page: PageInfo, titleIndex: PageInfo[]): Promise<boolean> {\n const content = await readFile(page.filePath, \"utf-8\");\n const { body } = parseFrontmatter(content);\n const linked = addWikilinks(body, titleIndex, page.title);\n\n if (linked === body) return false;\n\n const newContent = content.replace(body, linked);\n await atomicWrite(page.filePath, newContent);\n return true;\n}\n","/**\n * Wiki index generator.\n *\n * Scans all concept pages in wiki/concepts/, extracts frontmatter metadata,\n * and produces wiki/index.md with a sorted list of all concepts and their\n * summaries. Used after each compilation pass.\n */\n\nimport { readdir } from \"fs/promises\";\nimport path from \"path\";\nimport { atomicWrite, safeReadFile, parseFrontmatter } from \"../utils/markdown.js\";\nimport { CONCEPTS_DIR, QUERIES_DIR, INDEX_FILE } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\nimport type { PageSummary } from \"../utils/types.js\";\n\n/**\n * Generate the wiki/index.md listing all concept pages with summaries.\n * @param root - Project root directory.\n */\nexport async function generateIndex(root: string): Promise<void> {\n output.status(\"*\", output.info(\"Generating index...\"));\n\n const conceptsPath = path.join(root, CONCEPTS_DIR);\n const queriesPath = path.join(root, QUERIES_DIR);\n const concepts = await collectPageSummaries(conceptsPath);\n const queries = await collectPageSummaries(queriesPath);\n\n concepts.sort((a, b) => a.title.localeCompare(b.title));\n queries.sort((a, b) => a.title.localeCompare(b.title));\n\n const indexContent = buildIndexContent(concepts, queries);\n const indexPath = path.join(root, INDEX_FILE);\n await atomicWrite(indexPath, indexContent);\n\n const total = concepts.length + queries.length;\n output.status(\"+\", output.success(`Index updated with ${total} pages.`));\n}\n\n/** A scanned page paired with its parsed frontmatter. */\ninterface ScannedPage {\n slug: string;\n meta: Record<string, unknown>;\n}\n\n/**\n * Scan a wiki directory and return every .md page paired with its parsed\n * frontmatter. Read-only utility shared by index generation and the MCP\n * server's status tool.\n * @param dirPath - Absolute path to a wiki page directory.\n * @returns Array of {slug, meta} entries — empty when the directory is missing.\n */\nexport async function scanWikiPages(dirPath: string): Promise<ScannedPage[]> {\n let files: string[];\n try {\n files = await readdir(dirPath);\n } catch {\n return [];\n }\n\n const scanned: ScannedPage[] = [];\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const content = await safeReadFile(path.join(dirPath, file));\n const { meta } = parseFrontmatter(content);\n scanned.push({ slug: file.replace(/\\.md$/, \"\"), meta });\n }\n return scanned;\n}\n\n/**\n * Project a wiki directory into PageSummary entries (excludes orphaned and\n * untitled pages). Built on top of scanWikiPages so the MCP server can share\n * the underlying scan logic without re-reading the directory.\n * @param conceptsPath - Absolute path to wiki/concepts/.\n * @returns Array of page summary objects.\n */\nexport async function collectPageSummaries(\n conceptsPath: string,\n): Promise<PageSummary[]> {\n const scanned = await scanWikiPages(conceptsPath);\n return scanned\n .filter(({ meta }) => meta.title && typeof meta.title === \"string\" && !meta.orphaned)\n .map(({ slug, meta }) => ({\n title: meta.title as string,\n slug,\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n }));\n}\n\n/** Strip [[wikilink]] brackets from text, leaving the inner text intact. */\nfunction stripWikilinks(text: string): string {\n return text.replace(/\\[\\[([^\\]]+)\\]\\]/g, \"$1\");\n}\n\n/**\n * Build the index.md markdown content from page summaries.\n * @param pages - Sorted array of page summaries.\n * @returns Full index.md content string.\n */\nfunction buildIndexContent(concepts: PageSummary[], queries: PageSummary[]): string {\n const lines = [\"# Knowledge Wiki\", \"\", \"## Concepts\", \"\"];\n\n for (const page of concepts) {\n lines.push(`- **[[${page.slug}|${page.title}]]** — ${stripWikilinks(page.summary)}`);\n }\n\n if (queries.length > 0) {\n lines.push(\"\", \"## Saved Queries\", \"\");\n for (const page of queries) {\n lines.push(`- **[[${page.slug}|${page.title}]]** — ${stripWikilinks(page.summary)}`);\n }\n }\n\n const total = concepts.length + queries.length;\n lines.push(\"\");\n lines.push(`_${total} pages | Generated ${new Date().toISOString()}_`);\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n","/**\n * Per-concept prompt-budget enforcement (issue #39).\n *\n * When the same concept is extracted from many overlapping sources, the\n * page-generation prompt would otherwise concatenate every full source\n * — linear in source count — and reliably blow past the LLM provider's\n * context window. This module clips each contributing source's slice to\n * a fair share of a configurable total budget and emits a single warning\n * when truncation kicks in.\n *\n * The fix is deliberately defensive (proportional truncation) rather than\n * smart (semantic ranking / summarisation). It prevents crashes while a\n * deeper retrieval-driven solution is designed.\n */\n\nimport * as output from \"../utils/output.js\";\nimport {\n DEFAULT_PROMPT_BUDGET_CHARS,\n PROMPT_BUDGET_ENV_VAR,\n} from \"../utils/constants.js\";\n\n/** Marker appended to a source slice when it was truncated to fit the budget. */\nconst TRUNCATION_MARKER = \"\\n\\n[…truncated for prompt budget — see #39…]\";\n\n/** A single source's contribution to the combined per-concept content. */\nexport interface SourceSlice {\n /** Source filename (e.g. \"ml-paper.md\") shown as a section header in the prompt. */\n file: string;\n /** Raw extracted source content, before any budgeting. */\n content: string;\n}\n\n/**\n * Resolve the active prompt-budget character cap. Reads the\n * `LLMWIKI_PROMPT_BUDGET_CHARS` env var when present and parseable; falls\n * back to `DEFAULT_PROMPT_BUDGET_CHARS`. Invalid values (non-numeric or\n * <= 0) are ignored so a typo can't accidentally truncate every prompt\n * to nothing.\n */\nexport function resolvePromptBudgetChars(): number {\n const raw = process.env[PROMPT_BUDGET_ENV_VAR];\n if (!raw) return DEFAULT_PROMPT_BUDGET_CHARS;\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_PROMPT_BUDGET_CHARS;\n return parsed;\n}\n\n/**\n * Combine per-source slices into the single content blob the LLM prompt\n * receives, applying a fair-share budget when the raw total would exceed\n * the configured ceiling. When no truncation is needed the output is\n * byte-identical to the previous unbudgeted concatenation, so existing\n * compile output is unchanged for typical workloads.\n *\n * @param concept - Human-readable concept title (used in the warning only).\n * @param slices - One entry per contributing source, in arrival order.\n * @returns The combined content string suitable for buildPagePrompt.\n */\nexport function buildBudgetedCombinedContent(\n concept: string,\n slices: SourceSlice[],\n): string {\n const budget = resolvePromptBudgetChars();\n const totalRaw = slices.reduce((sum, s) => sum + s.content.length, 0);\n\n if (totalRaw <= budget) {\n return formatSlices(slices);\n }\n\n const perSource = Math.max(1, Math.floor(budget / slices.length));\n warnTruncation(concept, totalRaw, slices.length, perSource, budget);\n\n const trimmed = slices.map((s) =>\n s.content.length > perSource\n ? { ...s, content: s.content.slice(0, perSource) + TRUNCATION_MARKER }\n : s,\n );\n return formatSlices(trimmed);\n}\n\n/** Render the slice list using the same `--- SOURCE: ---` headers the LLM is taught to read. */\nfunction formatSlices(slices: SourceSlice[]): string {\n return slices\n .map((s) => `--- SOURCE: ${s.file} ---\\n\\n${s.content}`)\n .join(\"\\n\\n\");\n}\n\n/** Emit a single, actionable warning when the budget kicks in for a concept. */\nfunction warnTruncation(\n concept: string,\n totalRaw: number,\n sourceCount: number,\n perSource: number,\n budget: number,\n): void {\n output.status(\n \"!\",\n output.warn(\n `Combined source content for \"${concept}\" (${totalRaw.toLocaleString()} chars across ` +\n `${sourceCount} sources) exceeds the ${budget.toLocaleString()}-char prompt budget; ` +\n `truncating each source to ~${perSource.toLocaleString()} chars. ` +\n `Raise via ${PROMPT_BUDGET_ENV_VAR} when running against larger-context models.`,\n ),\n );\n}\n","/**\n * Obsidian integration helpers for the llmwiki knowledge compiler.\n *\n * Provides two capabilities:\n * 1. Enriching wiki page frontmatter with tags and aliases for better\n * Obsidian graph navigation and search.\n * 2. Generating a Map of Content (MOC) page that groups concept pages\n * by tag for easy browsing.\n */\n\nimport { readdir } from \"fs/promises\";\nimport path from \"path\";\nimport { slugify, atomicWrite, safeReadFile, parseFrontmatter } from \"../utils/markdown.js\";\nimport { CONCEPTS_DIR, MOC_FILE } from \"../utils/constants.js\";\n\n/** Minimum word count to generate an abbreviation alias. */\nconst ABBREVIATION_MIN_WORDS = 3;\n\n/** Conjunctions that trigger a word-swap alias. */\nconst SWAP_CONJUNCTIONS = [\" and \", \" or \"];\n\n/**\n * Enrich a frontmatter object with Obsidian-specific tags and aliases.\n * Mutates the frontmatter object in place.\n * @param frontmatter - The frontmatter object to enrich.\n * @param conceptTitle - The human-readable concept title.\n * @param tags - Tags from extraction (may be empty).\n */\nexport function addObsidianMeta(\n frontmatter: Record<string, unknown>,\n conceptTitle: string,\n tags: string[],\n): void {\n frontmatter.tags = tags;\n frontmatter.aliases = generateAliases(conceptTitle);\n}\n\n/**\n * Generate deterministic aliases from a concept title.\n * Produces up to three alias variants:\n * - Slug form (e.g., \"gradient-descent\")\n * - Word-swap around conjunctions (e.g., \"Optimization and Gradient Descent\")\n * - Abbreviation from first letters for 3+ word titles (e.g., \"RAG\")\n * @param title - The concept title to derive aliases from.\n * @returns Array of aliases that differ from the original title.\n */\nfunction generateAliases(title: string): string[] {\n const aliases: string[] = [];\n const slug = slugify(title);\n\n if (slug !== title) {\n aliases.push(slug);\n }\n\n const swapAlias = generateSwapAlias(title);\n if (swapAlias) {\n aliases.push(swapAlias);\n }\n\n const abbreviation = generateAbbreviation(title);\n if (abbreviation) {\n aliases.push(abbreviation);\n }\n\n return aliases;\n}\n\n/**\n * Generate a word-swap alias by reversing parts around a conjunction.\n * E.g., \"Gradient Descent and Optimization\" becomes \"Optimization and Gradient Descent\".\n * @param title - The concept title.\n * @returns The swapped alias, or null if no conjunction found.\n */\nfunction generateSwapAlias(title: string): string | null {\n for (const conjunction of SWAP_CONJUNCTIONS) {\n const index = title.toLowerCase().indexOf(conjunction);\n if (index === -1) continue;\n\n const before = title.slice(0, index);\n const after = title.slice(index + conjunction.length);\n const originalConjunction = title.slice(index, index + conjunction.length);\n return `${after}${originalConjunction}${before}`;\n }\n return null;\n}\n\n/**\n * Generate an abbreviation from first letters of each word for titles with 3+ words.\n * E.g., \"Retrieval Augmented Generation\" becomes \"RAG\".\n * @param title - The concept title.\n * @returns The abbreviation, or null if title has fewer than 3 words.\n */\nfunction generateAbbreviation(title: string): string | null {\n const words = title.split(/\\s+/);\n if (words.length < ABBREVIATION_MIN_WORDS) return null;\n\n const abbreviation = words.map((w) => w[0].toUpperCase()).join(\"\");\n if (abbreviation === title) return null;\n\n return abbreviation;\n}\n\n/**\n * Generate a Map of Content (MOC) page grouping concept pages by tag.\n * Reads all concept pages, extracts their tags from frontmatter, and writes\n * a structured MOC.md with sections per tag and an Uncategorized section.\n * @param root - Project root directory.\n */\nexport async function generateMOC(root: string): Promise<void> {\n const conceptsPath = path.join(root, CONCEPTS_DIR);\n const pages = await loadConceptPages(conceptsPath);\n\n const tagGroups = groupPagesByTag(pages);\n const content = buildMOCContent(tagGroups);\n\n await atomicWrite(path.join(root, MOC_FILE), content);\n}\n\n/** Minimal page info needed for MOC generation. */\ninterface PageInfo {\n slug: string;\n title: string;\n tags: string[];\n}\n\n/**\n * Load all concept pages and extract their title and tags.\n * @param conceptsPath - Absolute path to the concepts directory.\n * @returns Array of page info objects.\n */\nasync function loadConceptPages(conceptsPath: string): Promise<PageInfo[]> {\n let files: string[];\n try {\n files = await readdir(conceptsPath);\n } catch {\n return [];\n }\n\n const pages: PageInfo[] = [];\n for (const file of files) {\n if (!file.endsWith(\".md\")) continue;\n\n const content = await safeReadFile(path.join(conceptsPath, file));\n if (!content) continue;\n\n const { meta } = parseFrontmatter(content);\n if (meta.orphaned) continue;\n\n const slug = file.replace(/\\.md$/, \"\");\n const title = typeof meta.title === \"string\" ? meta.title : slug;\n const tags = Array.isArray(meta.tags) ? (meta.tags as string[]) : [];\n pages.push({ slug, title, tags });\n }\n\n return pages;\n}\n\n/**\n * Group pages by their tags into a map. Pages with no tags go under \"Uncategorized\".\n * @param pages - Array of page info objects.\n * @returns Map of tag name to array of page titles.\n */\nfunction groupPagesByTag(pages: PageInfo[]): Map<string, PageInfo[]> {\n const groups = new Map<string, PageInfo[]>();\n\n for (const page of pages) {\n if (page.tags.length === 0) {\n appendToGroup(groups, \"Uncategorized\", page);\n continue;\n }\n\n for (const tag of page.tags) {\n appendToGroup(groups, tag, page);\n }\n }\n\n return groups;\n}\n\n/** Append a page to a group, creating the group if needed. */\nfunction appendToGroup(groups: Map<string, PageInfo[]>, key: string, page: PageInfo): void {\n const existing = groups.get(key);\n if (existing) {\n existing.push(page);\n } else {\n groups.set(key, [page]);\n }\n}\n\n/**\n * Build the MOC markdown content from grouped pages.\n * @param tagGroups - Map of tag name to array of page titles.\n * @returns Complete MOC markdown string.\n */\nfunction buildMOCContent(tagGroups: Map<string, PageInfo[]>): string {\n const lines: string[] = [\"# Map of Content\", \"\"];\n\n const sortedTags = [...tagGroups.keys()].sort((a, b) => {\n // \"Uncategorized\" always goes last\n if (a === \"Uncategorized\") return 1;\n if (b === \"Uncategorized\") return -1;\n return a.localeCompare(b);\n });\n\n for (const tag of sortedTags) {\n const pages = tagGroups.get(tag) ?? [];\n lines.push(`## ${tag}`, \"\");\n for (const page of pages.sort((a, b) => a.title.localeCompare(b.title))) {\n lines.push(`- [[${page.slug}|${page.title}]]`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Embedding-based semantic search utilities.\n *\n * Maintains a persistent store of page and chunk embeddings in\n * .llmwiki/embeddings.json and provides cosine-similarity retrieval so the\n * query command can narrow hundreds of pages down to a small top-K before\n * calling the selection LLM.\n *\n * The store is additive: successful embedding calls update entries; failures\n * degrade gracefully (caller falls back to full-index selection).\n *\n * The store has two on-disk versions:\n * - v1: page-level entries only (legacy; still readable).\n * - v2: page-level entries plus optional chunk-level entries that enable\n * paragraph-precision retrieval, content-hash-aware incremental updates,\n * and reranking before final page selection.\n */\n\nimport { readFile, readdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { getProvider, getActiveProviderName } from \"./provider.js\";\nimport { atomicWrite, safeReadFile, parseFrontmatter } from \"./markdown.js\";\nimport {\n CONCEPTS_DIR,\n QUERIES_DIR,\n EMBEDDINGS_FILE,\n EMBEDDING_TOP_K,\n EMBEDDING_MODELS,\n} from \"./constants.js\";\nimport { hashChunkText, splitIntoChunks } from \"./retrieval.js\";\nimport * as output from \"./output.js\";\n\n/** Current store version; bumped from 1 → 2 when chunk entries were added. */\nconst STORE_VERSION = 2 as const;\n\n/** A single embedded page record. */\nexport interface EmbeddingEntry {\n slug: string;\n title: string;\n summary: string;\n vector: number[];\n updatedAt: string;\n}\n\n/** A single embedded chunk drawn from a page body. */\nexport interface ChunkEmbeddingEntry {\n slug: string;\n title: string;\n chunkIndex: number;\n contentHash: string;\n text: string;\n vector: number[];\n updatedAt: string;\n}\n\n/** Root shape of .llmwiki/embeddings.json. */\nexport interface EmbeddingStore {\n version: 1 | 2;\n model: string;\n dimensions: number;\n entries: EmbeddingEntry[];\n /** Optional in v2 stores; absent in v1 stores. */\n chunks?: ChunkEmbeddingEntry[];\n}\n\n/** A retrievable page record on disk (concepts/ or queries/). */\ninterface PageRecord {\n slug: string;\n title: string;\n summary: string;\n body: string;\n}\n\n/**\n * Cosine similarity between two equal-length vectors.\n * Returns 0 when either vector has zero magnitude (safer than NaN for ranking).\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length || a.length === 0) return 0;\n\n let dot = 0;\n let magA = 0;\n let magB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n magA += a[i] * a[i];\n magB += b[i] * b[i];\n }\n\n if (magA === 0 || magB === 0) return 0;\n return dot / (Math.sqrt(magA) * Math.sqrt(magB));\n}\n\n/** Return the top-K entries most similar to the query vector, sorted descending. */\nexport function findTopK(\n queryVec: number[],\n store: EmbeddingStore,\n k: number,\n): EmbeddingEntry[] {\n const scored = store.entries.map((entry) => ({\n entry,\n score: cosineSimilarity(queryVec, entry.vector),\n }));\n scored.sort((left, right) => right.score - left.score);\n return scored.slice(0, k).map((item) => item.entry);\n}\n\n/** Score and sort chunk entries by cosine similarity, returning the top-K. */\nexport function findTopKChunks(\n queryVec: number[],\n chunks: ChunkEmbeddingEntry[],\n k: number,\n): Array<{ chunk: ChunkEmbeddingEntry; score: number }> {\n const scored = chunks.map((chunk) => ({\n chunk,\n score: cosineSimilarity(queryVec, chunk.vector),\n }));\n scored.sort((left, right) => right.score - left.score);\n return scored.slice(0, k);\n}\n\n/** Read .llmwiki/embeddings.json, returning null if it does not exist. */\nexport async function readEmbeddingStore(root: string): Promise<EmbeddingStore | null> {\n const filePath = path.join(root, EMBEDDINGS_FILE);\n if (!existsSync(filePath)) return null;\n const raw = await readFile(filePath, \"utf-8\");\n return JSON.parse(raw) as EmbeddingStore;\n}\n\n/** Atomically persist the embedding store. */\nexport async function writeEmbeddingStore(root: string, store: EmbeddingStore): Promise<void> {\n const filePath = path.join(root, EMBEDDINGS_FILE);\n await atomicWrite(filePath, JSON.stringify(store, null, 2));\n}\n\n/**\n * Embed the question, look up top-K matches, and return lightweight page records.\n * Returns [] when no store exists so callers can transparently fall back.\n */\nexport async function findRelevantPages(\n root: string,\n question: string,\n): Promise<Array<{ slug: string; title: string; summary: string }>> {\n const store = await loadActiveStore(root, (s) => s.entries.length > 0);\n if (!store) return [];\n\n const queryVec = await getProvider().embed(question);\n return findTopK(queryVec, store, EMBEDDING_TOP_K).map((entry) => ({\n slug: entry.slug,\n title: entry.title,\n summary: entry.summary,\n }));\n}\n\n/**\n * Look up top-K chunks similar to the question. Returns [] when no chunk-level\n * store exists so callers can fall back to page-level retrieval.\n */\nexport async function findRelevantChunks(\n root: string,\n question: string,\n k: number,\n): Promise<Array<{ chunk: ChunkEmbeddingEntry; score: number }>> {\n const store = await loadActiveStore(root, (s) => Boolean(s.chunks && s.chunks.length > 0));\n if (!store) return [];\n const queryVec = await getProvider().embed(question);\n return findTopKChunks(queryVec, store.chunks ?? [], k);\n}\n\n/**\n * Read the embedding store, returning null when it is missing, empty (per the\n * caller's predicate), or built with a stale model. Centralises the \"is this\n * store usable for semantic lookup right now?\" check.\n */\nasync function loadActiveStore(\n root: string,\n hasContent: (store: EmbeddingStore) => boolean,\n): Promise<EmbeddingStore | null> {\n const store = await readEmbeddingStore(root);\n if (!store || !hasContent(store)) return null;\n const activeModel = resolveEmbeddingModel();\n if (store.model !== activeModel) {\n warnStaleEmbeddingStore(store.model, activeModel);\n return null;\n }\n return store;\n}\n\n/** Scan concepts/ and queries/ directories, returning retrievable pages. */\nasync function collectPageRecords(root: string): Promise<PageRecord[]> {\n const records: PageRecord[] = [];\n for (const dir of [CONCEPTS_DIR, QUERIES_DIR]) {\n const absDir = path.join(root, dir);\n let files: string[];\n try {\n files = await readdir(absDir);\n } catch {\n continue;\n }\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const record = await readPageRecord(absDir, file);\n if (record) records.push(record);\n }\n }\n return records;\n}\n\n/** Parse a single page file into a PageRecord, skipping orphans/untitled pages. */\nasync function readPageRecord(absDir: string, file: string): Promise<PageRecord | null> {\n const content = await safeReadFile(path.join(absDir, file));\n const { meta, body } = parseFrontmatter(content);\n if (meta.orphaned || typeof meta.title !== \"string\") return null;\n return {\n slug: file.replace(/\\.md$/, \"\"),\n title: meta.title,\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n body,\n };\n}\n\n/** Build the text that represents a page in the embedding space. */\nfunction buildEmbeddingText(record: PageRecord): string {\n return record.summary\n ? `${record.title}\\n\\n${record.summary}`\n : record.title;\n}\n\n/**\n * Embed every page in `records` whose slug appears in `slugsToEmbed`,\n * returning the new entries. Failures bubble up to the caller.\n */\nasync function embedPages(\n records: PageRecord[],\n slugsToEmbed: Set<string>,\n): Promise<EmbeddingEntry[]> {\n const provider = getProvider();\n const now = new Date().toISOString();\n const fresh: EmbeddingEntry[] = [];\n\n for (const record of records) {\n if (!slugsToEmbed.has(record.slug)) continue;\n const vector = await provider.embed(buildEmbeddingText(record));\n fresh.push({\n slug: record.slug,\n title: record.title,\n summary: record.summary,\n vector,\n updatedAt: now,\n });\n }\n return fresh;\n}\n\n/** Tracks which (stored, active) model pairs have already been warned about. */\nconst warnedStaleModels = new Set<string>();\n\n/** Warn once per (stored, active) model pair so queries stay quiet on repeat runs. */\nfunction warnStaleEmbeddingStore(storedModel: string, activeModel: string): void {\n const key = `${storedModel}→${activeModel}`;\n if (warnedStaleModels.has(key)) return;\n warnedStaleModels.add(key);\n output.status(\n \"!\",\n output.warn(\n `Embedding store was built with \"${storedModel}\" but active embedding model is \"${activeModel}\". ` +\n `Falling back to full-index selection. Run 'llmwiki compile' to rebuild embeddings.`,\n ),\n );\n}\n\n/** Test-only hook: clear the warned-pair cache so each test sees a fresh warning. */\nexport function resetStaleEmbeddingWarnings(): void {\n warnedStaleModels.clear();\n}\n\n/** Choose the active embedding model name, defaulting to anthropic's voyage model. */\nexport function resolveEmbeddingModel(): string {\n const providerName = getActiveProviderName();\n const configuredModel = process.env.LLMWIKI_EMBEDDING_MODEL?.trim();\n if (configuredModel && (providerName === \"openai\" || providerName === \"ollama\")) {\n return configuredModel;\n }\n return EMBEDDING_MODELS[providerName] ?? EMBEDDING_MODELS.anthropic;\n}\n\n/** Merge fresh embeddings into an existing store, dropping slugs not in liveSlugs. */\nfunction mergeEntries(\n existing: EmbeddingEntry[],\n fresh: EmbeddingEntry[],\n liveSlugs: Set<string>,\n): EmbeddingEntry[] {\n const bySlug = new Map<string, EmbeddingEntry>();\n for (const entry of existing) {\n if (liveSlugs.has(entry.slug)) bySlug.set(entry.slug, entry);\n }\n for (const entry of fresh) {\n bySlug.set(entry.slug, entry);\n }\n return Array.from(bySlug.values());\n}\n\n/**\n * Refresh chunk embeddings for the given pages, reusing existing chunk vectors\n * whose contentHash still matches. Pages absent from `records` are pruned.\n */\nasync function refreshChunkEmbeddings(\n records: PageRecord[],\n existing: ChunkEmbeddingEntry[],\n forceAll: boolean,\n): Promise<ChunkEmbeddingEntry[]> {\n const liveSlugs = new Set(records.map((r) => r.slug));\n const existingByKey = indexChunksByKey(existing.filter((c) => liveSlugs.has(c.slug)));\n const now = new Date().toISOString();\n const fresh: ChunkEmbeddingEntry[] = [];\n\n for (const record of records) {\n const pageChunks = await embedRecordChunks(record, existingByKey, forceAll, now);\n fresh.push(...pageChunks);\n }\n return fresh;\n}\n\n/**\n * Embed (or reuse) every chunk for a single page, in order. Reused chunks have\n * their `title` refreshed so a renamed page propagates to the chunk metadata.\n */\nasync function embedRecordChunks(\n record: PageRecord,\n existingByKey: Map<string, ChunkEmbeddingEntry>,\n forceAll: boolean,\n now: string,\n): Promise<ChunkEmbeddingEntry[]> {\n const provider = getProvider();\n const chunkTexts = splitIntoChunks(record.body);\n const out: ChunkEmbeddingEntry[] = [];\n\n for (let i = 0; i < chunkTexts.length; i++) {\n const text = chunkTexts[i];\n const contentHash = hashChunkText(text);\n const reused = pickReusableChunk(existingByKey, record.slug, i, contentHash, forceAll);\n if (reused) {\n out.push({ ...reused, title: record.title });\n continue;\n }\n const vector = await provider.embed(text);\n out.push({\n slug: record.slug, title: record.title, chunkIndex: i,\n contentHash, text, vector, updatedAt: now,\n });\n }\n return out;\n}\n\n/** Index existing chunks by `${slug}#${chunkIndex}` for O(1) reuse lookup. */\nfunction indexChunksByKey(chunks: ChunkEmbeddingEntry[]): Map<string, ChunkEmbeddingEntry> {\n const byKey = new Map<string, ChunkEmbeddingEntry>();\n for (const chunk of chunks) byKey.set(chunkKey(chunk.slug, chunk.chunkIndex), chunk);\n return byKey;\n}\n\n/** Compose the index key for a chunk lookup. */\nfunction chunkKey(slug: string, chunkIndex: number): string {\n return `${slug}#${chunkIndex}`;\n}\n\n/** Return the existing chunk vector when its hash still matches and reuse is allowed. */\nfunction pickReusableChunk(\n byKey: Map<string, ChunkEmbeddingEntry>,\n slug: string,\n chunkIndex: number,\n contentHash: string,\n forceAll: boolean,\n): ChunkEmbeddingEntry | null {\n if (forceAll) return null;\n const existing = byKey.get(chunkKey(slug, chunkIndex));\n if (!existing) return null;\n return existing.contentHash === contentHash ? existing : null;\n}\n\n/**\n * Re-embed the given changed slugs and prune any entries whose pages no longer\n * exist on disk. Changed slugs not present as live pages are silently skipped.\n */\nexport async function updateEmbeddings(root: string, changedSlugs: string[]): Promise<void> {\n const records = await collectPageRecords(root);\n const liveSlugs = new Set(records.map((r) => r.slug));\n const embeddingModel = resolveEmbeddingModel();\n const existingStore = await readEmbeddingStore(root);\n const modelChanged = Boolean(existingStore && existingStore.model !== embeddingModel);\n const toEmbed = new Set(changedSlugs.filter((slug) => liveSlugs.has(slug)));\n const previousEntries = modelChanged ? [] : existingStore?.entries ?? [];\n const previousChunks = modelChanged ? [] : existingStore?.chunks ?? [];\n\n // Cold start: embed every page so the store is immediately useful.\n // Also treat an empty on-disk store as a cold start so that a project\n // with no ingested pages yet (or a wiped store) gets populated the next\n // time `compile` runs without needing an explicit slug change.\n const isEmptyStore = isStoreEmpty(existingStore);\n if (!existingStore || modelChanged || (isEmptyStore && liveSlugs.size > 0)) {\n for (const record of records) toEmbed.add(record.slug);\n }\n\n if (!shouldRunEmbedding(modelChanged, toEmbed, previousEntries, previousChunks, liveSlugs)) {\n return;\n }\n\n const freshEntries = await embedPages(records, toEmbed);\n const mergedEntries = mergeEntries(previousEntries, freshEntries, liveSlugs);\n const mergedChunks = await refreshChunkEmbeddings(records, previousChunks, modelChanged);\n\n await persistRefreshedStore(root, embeddingModel, mergedEntries, mergedChunks);\n}\n\n/** Persist a freshly merged store and emit a friendly status line. */\nasync function persistRefreshedStore(\n root: string,\n embeddingModel: string,\n entries: EmbeddingEntry[],\n chunks: ChunkEmbeddingEntry[],\n): Promise<void> {\n const dimensions = entries[0]?.vector.length ?? chunks[0]?.vector.length ?? 0;\n const store: EmbeddingStore = {\n version: STORE_VERSION,\n model: embeddingModel,\n dimensions,\n entries,\n chunks,\n };\n await writeEmbeddingStore(root, store);\n output.status(\n \"*\",\n output.dim(`Embeddings updated (${entries.length} pages, ${chunks.length} chunks).`),\n );\n}\n\n/** Return true when a store exists on disk but has neither page nor chunk entries. */\nfunction isStoreEmpty(store: EmbeddingStore | null): boolean {\n if (!store) return false;\n return store.entries.length === 0 && (!store.chunks || store.chunks.length === 0);\n}\n\n/** Decide whether updateEmbeddings has work to do beyond a no-op. */\nfunction shouldRunEmbedding(\n modelChanged: boolean,\n toEmbed: Set<string>,\n previousEntries: EmbeddingEntry[],\n previousChunks: ChunkEmbeddingEntry[],\n liveSlugs: Set<string>,\n): boolean {\n if (modelChanged) return true;\n if (toEmbed.size > 0) return true;\n if (!previousEntries.every((e) => liveSlugs.has(e.slug))) return true;\n if (!previousChunks.every((c) => liveSlugs.has(c.slug))) return true;\n // Cold-start case where we have entries but no chunks yet.\n if (previousEntries.length > 0 && previousChunks.length === 0 && liveSlugs.size > 0) return true;\n return false;\n}\n","/**\n * Chunked retrieval helpers: text splitting, content hashing, and BM25 reranking.\n *\n * The query pipeline relies on these utilities to:\n * 1. Split a wiki page into paragraph-aligned chunks for embedding.\n * 2. Detect unchanged chunks via a stable content hash so embedding refreshes\n * can skip work.\n * 3. Rerank a candidate set with a lightweight BM25 score over chunk text,\n * improving precision over pure cosine similarity for keyword-heavy\n * questions.\n *\n * No network calls happen here — these are deterministic CPU-side helpers\n * that are easy to unit test and safe to invoke from any code path.\n */\n\nimport { createHash } from \"crypto\";\nimport {\n CHUNK_MAX_CHARS,\n CHUNK_MIN_CHARS,\n CHUNK_TARGET_CHARS,\n} from \"./constants.js\";\n\n/** Stable content hash used to detect chunk-level changes between runs. */\nexport function hashChunkText(text: string): string {\n return createHash(\"sha256\").update(text, \"utf8\").digest(\"hex\").slice(0, 16);\n}\n\n/**\n * Split a page body into paragraph-aligned chunks bounded by CHUNK_TARGET_CHARS.\n * Trailing fragments smaller than CHUNK_MIN_CHARS are merged into the previous\n * chunk so we never emit a tiny dangling piece. Paragraphs longer than\n * CHUNK_MAX_CHARS are sentence-split before being added.\n *\n * @param body - Raw page body (frontmatter already stripped).\n * @returns Ordered chunk strings; empty array when body has no usable text.\n */\nexport function splitIntoChunks(body: string): string[] {\n const paragraphs = extractParagraphs(body);\n if (paragraphs.length === 0) return [];\n\n const chunks: string[] = [];\n let buffer = \"\";\n\n for (const paragraph of paragraphs) {\n for (const piece of splitOversizedParagraph(paragraph)) {\n buffer = appendParagraph(buffer, piece, chunks);\n }\n }\n\n if (buffer.length > 0) chunks.push(buffer);\n return mergeTrailingFragment(chunks);\n}\n\n/** Append a paragraph to the buffer, flushing when the target size is exceeded. */\nfunction appendParagraph(buffer: string, paragraph: string, chunks: string[]): string {\n const candidate = buffer ? `${buffer}\\n\\n${paragraph}` : paragraph;\n if (candidate.length <= CHUNK_TARGET_CHARS) return candidate;\n\n if (buffer.length > 0) {\n chunks.push(buffer);\n return paragraph;\n }\n // Single paragraph already exceeds target — emit it as a standalone chunk.\n chunks.push(candidate);\n return \"\";\n}\n\n/**\n * Merge a too-small trailing chunk back into its predecessor for cleaner\n * ranking. We only merge when the combined size would still respect\n * CHUNK_MAX_CHARS — otherwise the tiny tail stays standalone.\n */\nfunction mergeTrailingFragment(chunks: string[]): string[] {\n if (chunks.length < 2) return chunks;\n const last = chunks[chunks.length - 1];\n if (last.length >= CHUNK_MIN_CHARS) return chunks;\n const previous = chunks[chunks.length - 2];\n // +2 covers the \"\\n\\n\" separator length we insert between paragraphs.\n if (previous.length + last.length + 2 > CHUNK_MAX_CHARS) return chunks;\n const merged = chunks.slice(0, -2);\n merged.push(`${previous}\\n\\n${last}`);\n return merged;\n}\n\n/** Strip whitespace-only paragraphs from a markdown body. */\nfunction extractParagraphs(body: string): string[] {\n return body\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter((p) => p.length > 0);\n}\n\n/**\n * Sentence-split a paragraph that exceeds CHUNK_MAX_CHARS so the resulting\n * pieces still respect the upper bound. A single sentence longer than the\n * cap is hard-cut at CHUNK_MAX_CHARS — preferable to dropping content.\n */\nfunction splitOversizedParagraph(paragraph: string): string[] {\n if (paragraph.length <= CHUNK_MAX_CHARS) return [paragraph];\n\n const sentences = paragraph.split(/(?<=[.!?])\\s+/);\n const pieces: string[] = [];\n let buffer = \"\";\n\n for (const sentence of sentences) {\n if ((buffer + \" \" + sentence).length > CHUNK_MAX_CHARS && buffer.length > 0) {\n pieces.push(buffer.trim());\n buffer = sentence;\n } else {\n buffer = buffer ? `${buffer} ${sentence}` : sentence;\n }\n }\n\n if (buffer.length > 0) pieces.push(buffer.trim());\n return pieces.flatMap(hardCut);\n}\n\n/** Hard-cut a string longer than CHUNK_MAX_CHARS into fixed-size pieces. */\nfunction hardCut(text: string): string[] {\n if (text.length <= CHUNK_MAX_CHARS) return [text];\n const pieces: string[] = [];\n for (let start = 0; start < text.length; start += CHUNK_MAX_CHARS) {\n pieces.push(text.slice(start, start + CHUNK_MAX_CHARS));\n }\n return pieces;\n}\n\n/** A scored candidate that the BM25 reranker accepts. */\ninterface RankableCandidate {\n text: string;\n /** Initial similarity score; preserved for debug output and tie-breaking. */\n baseScore: number;\n}\n\n/** Result of a BM25 rerank: original candidate plus the rerank score. */\ninterface RankedCandidate<T extends RankableCandidate> {\n candidate: T;\n score: number;\n}\n\n/**\n * Rerank candidates with BM25 over their `text` field given a free-text query.\n * BM25 is a deterministic keyword-overlap metric that complements semantic\n * similarity well: it boosts chunks that literally mention the query terms.\n *\n * @param query - Natural-language query.\n * @param candidates - Items to rerank; their `baseScore` is used as a tiebreaker.\n * @returns Sorted descending by combined score.\n */\nexport function rerankWithBm25<T extends RankableCandidate>(\n query: string,\n candidates: T[],\n): Array<RankedCandidate<T>> {\n if (candidates.length === 0) return [];\n const queryTerms = tokenize(query);\n if (queryTerms.length === 0) {\n return candidates.map((candidate) => ({ candidate, score: candidate.baseScore }));\n }\n\n const docs = candidates.map((c) => tokenize(c.text));\n const stats = buildCorpusStats(docs);\n return rankByBm25Score(candidates, docs, queryTerms, stats);\n}\n\n/** Rank candidates by combined BM25 + base semantic score. */\nfunction rankByBm25Score<T extends RankableCandidate>(\n candidates: T[],\n docs: string[][],\n queryTerms: string[],\n stats: CorpusStats,\n): Array<RankedCandidate<T>> {\n const scored = candidates.map((candidate, index) => {\n const lexical = bm25Score(queryTerms, docs[index], stats);\n return { candidate, score: lexical + candidate.baseScore * BASE_SCORE_WEIGHT };\n });\n scored.sort((a, b) => b.score - a.score);\n return scored;\n}\n\n/** Tokenise a string into lowercase alphanumeric tokens for BM25. */\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n}\n\ninterface CorpusStats {\n /** Document frequency per term: how many docs contain the term. */\n docFreq: Map<string, number>;\n /** Average document length across the corpus. */\n avgDocLen: number;\n /** Total document count. */\n totalDocs: number;\n}\n\n/** Precompute BM25 corpus statistics from the tokenised candidate set. */\nfunction buildCorpusStats(docs: string[][]): CorpusStats {\n const docFreq = new Map<string, number>();\n let totalLen = 0;\n for (const tokens of docs) {\n totalLen += tokens.length;\n const unique = new Set(tokens);\n for (const term of unique) docFreq.set(term, (docFreq.get(term) ?? 0) + 1);\n }\n const totalDocs = docs.length;\n const avgDocLen = totalDocs > 0 ? totalLen / totalDocs : 0;\n return { docFreq, avgDocLen, totalDocs };\n}\n\n/** BM25 saturation parameter; higher = slower term-frequency saturation. */\nconst BM25_K1 = 1.5;\n/** BM25 length normalisation strength; 0 disables, 1 is full normalisation. */\nconst BM25_B = 0.75;\n/** How much weight the original semantic score retains in the rerank tie-break. */\nconst BASE_SCORE_WEIGHT = 0.5;\n\n/** Compute BM25 score for one document against a tokenised query. */\nfunction bm25Score(queryTerms: string[], docTokens: string[], stats: CorpusStats): number {\n if (docTokens.length === 0 || stats.totalDocs === 0) return 0;\n const termFreq = countTerms(docTokens);\n const lengthRatio = docTokens.length / (stats.avgDocLen || 1);\n\n let total = 0;\n for (const term of queryTerms) {\n const tf = termFreq.get(term) ?? 0;\n if (tf === 0) continue;\n const idf = idfWeight(stats.docFreq.get(term) ?? 0, stats.totalDocs);\n const numerator = tf * (BM25_K1 + 1);\n const denominator = tf + BM25_K1 * (1 - BM25_B + BM25_B * lengthRatio);\n total += idf * (numerator / denominator);\n }\n return total;\n}\n\n/** BM25 inverse-document-frequency component (Robertson-Spärck Jones form). */\nfunction idfWeight(docFrequency: number, totalDocs: number): number {\n const numerator = totalDocs - docFrequency + 0.5;\n const denominator = docFrequency + 0.5;\n // +1 inside log keeps idf non-negative even when a term appears in every doc.\n return Math.log(1 + numerator / denominator);\n}\n\n/** Count token occurrences in a single document. */\nfunction countTerms(tokens: string[]): Map<string, number> {\n const counts = new Map<string, number>();\n for (const token of tokens) counts.set(token, (counts.get(token) ?? 0) + 1);\n return counts;\n}\n","/**\n * Lint rules for wiki quality checks.\n *\n * Each rule is a function that takes a project root path and returns\n * an array of LintResult diagnostics. Rules perform pure static analysis\n * with no LLM calls — they inspect frontmatter, wikilinks, citations,\n * and file structure to find potential issues.\n */\n\nimport { readdir, readFile } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport {\n isMalformedCitationEntry,\n parseFrontmatter,\n parseProvenanceMetadata,\n safeReadFile,\n slugify,\n} from \"../utils/markdown.js\";\nimport {\n CONCEPTS_DIR,\n LOW_CONFIDENCE_THRESHOLD,\n MAX_INFERRED_PARAGRAPHS_WITHOUT_CITATIONS,\n QUERIES_DIR,\n SOURCES_DIR,\n} from \"../utils/constants.js\";\nimport type { LintResult } from \"./types.js\";\nimport {\n countWikilinks,\n resolvePageKind,\n type SchemaConfig,\n} from \"../schema/index.js\";\n\n/** Minimum body length (in characters) for a page to be considered non-empty. */\nconst MIN_BODY_LENGTH = 50;\n\n/** Pattern matching [[Wikilink Title]] references in markdown content. */\nconst WIKILINK_PATTERN = /\\[\\[([^\\]]+)\\]\\]/g;\n\n/** Pattern matching ^[filename.md] citation markers in markdown content. */\nconst CITATION_PATTERN = /\\^\\[([^\\]]+)\\]/g;\n\n/** Match result with its line number and captured group. */\ninterface LineMatch {\n captured: string;\n line: number;\n}\n\n/**\n * Scan all lines of a page's content and return regex matches with line numbers.\n * Shared by rules that need to locate patterns within page bodies.\n */\nfunction findMatchesInContent(content: string, pattern: RegExp): LineMatch[] {\n const results: LineMatch[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const matches = lines[i].matchAll(pattern);\n for (const match of matches) {\n results.push({ captured: match[1], line: i + 1 });\n }\n }\n return results;\n}\n\n/**\n * Read all .md files from a directory, returning their paths and parsed content.\n * Returns an empty array if the directory does not exist.\n */\nasync function readMarkdownFiles(\n dirPath: string,\n): Promise<Array<{ filePath: string; content: string }>> {\n if (!existsSync(dirPath)) return [];\n\n const entries = await readdir(dirPath);\n const mdFiles = entries.filter((f) => f.endsWith(\".md\"));\n\n const results = await Promise.all(\n mdFiles.map(async (fileName) => {\n const filePath = path.join(dirPath, fileName);\n const content = await readFile(filePath, \"utf-8\");\n return { filePath, content };\n }),\n );\n\n return results;\n}\n\n/**\n * Collect all wiki pages from both concepts/ and queries/ directories.\n */\nasync function collectAllPages(\n root: string,\n): Promise<Array<{ filePath: string; content: string }>> {\n const conceptPages = await readMarkdownFiles(path.join(root, CONCEPTS_DIR));\n const queryPages = await readMarkdownFiles(path.join(root, QUERIES_DIR));\n return [...conceptPages, ...queryPages];\n}\n\n/**\n * Build a set of slugs for all existing wiki pages.\n * Used to verify that wikilink targets actually exist.\n */\nfunction buildPageSlugSet(\n pages: Array<{ filePath: string }>,\n): Set<string> {\n const slugs = new Set<string>();\n for (const page of pages) {\n const baseName = path.basename(page.filePath, \".md\");\n slugs.add(baseName.toLowerCase());\n }\n return slugs;\n}\n\n/** Find [[Title]] wikilinks that don't match any existing wiki page. */\nexport async function checkBrokenWikilinks(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const existingSlugs = buildPageSlugSet(pages);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n for (const { captured, line } of findMatchesInContent(page.content, WIKILINK_PATTERN)) {\n const linkSlug = slugify(captured);\n if (!existingSlugs.has(linkSlug)) {\n results.push({\n rule: \"broken-wikilink\",\n severity: \"error\",\n file: page.filePath,\n message: `Broken wikilink [[${captured}]] — no matching page found`,\n line,\n });\n }\n }\n }\n\n return results;\n}\n\n/** Find pages with `orphaned: true` in their frontmatter. */\nexport async function checkOrphanedPages(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n if (meta.orphaned === true) {\n results.push({\n rule: \"orphaned-page\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page is marked as orphaned`,\n });\n }\n }\n\n return results;\n}\n\n/** Find pages with empty or missing `summary` in frontmatter. */\nexport async function checkMissingSummaries(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n const summary = meta.summary;\n const isMissing = !summary || (typeof summary === \"string\" && summary.trim() === \"\");\n\n if (isMissing) {\n results.push({\n rule: \"missing-summary\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page has no summary in frontmatter`,\n });\n }\n }\n\n return results;\n}\n\n/** Find multiple pages whose titles match case-insensitively. */\nexport async function checkDuplicateConcepts(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const titleMap = new Map<string, string[]>();\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n const title = typeof meta.title === \"string\" ? meta.title : \"\";\n if (!title) continue;\n\n const normalizedTitle = title.toLowerCase().trim();\n const existing = titleMap.get(normalizedTitle) ?? [];\n existing.push(page.filePath);\n titleMap.set(normalizedTitle, existing);\n }\n\n const results: LintResult[] = [];\n for (const [title, files] of titleMap) {\n if (files.length <= 1) continue;\n for (const file of files) {\n results.push({\n rule: \"duplicate-concept\",\n severity: \"error\",\n file,\n message: `Duplicate title \"${title}\" — also in ${files.filter((f) => f !== file).join(\", \")}`,\n });\n }\n }\n\n return results;\n}\n\n/** Find pages with frontmatter but very short or empty body content. */\nexport async function checkEmptyPages(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta, body } = parseFrontmatter(page.content);\n const hasTitle = typeof meta.title === \"string\" && meta.title.trim() !== \"\";\n const isBodyEmpty = body.trim().length < MIN_BODY_LENGTH;\n\n if (hasTitle && isBodyEmpty) {\n results.push({\n rule: \"empty-page\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page body is empty or too short (< ${MIN_BODY_LENGTH} chars)`,\n });\n }\n }\n\n return results;\n}\n\n/** Strip an optional `:start-end` or `#Lstart-Lend` span suffix from a citation entry. */\nfunction stripSpanSuffix(entry: string): string {\n const colonIdx = entry.indexOf(\":\");\n const hashIdx = entry.indexOf(\"#\");\n const cuts = [colonIdx, hashIdx].filter((i) => i >= 0);\n if (cuts.length === 0) return entry;\n return entry.slice(0, Math.min(...cuts));\n}\n\n/**\n * Flag pages whose frontmatter declares confidence below the threshold.\n * Pages without a confidence field are silently skipped to preserve\n * backward-compatibility with pre-existing wikis.\n */\nexport async function checkLowConfidencePages(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n const { confidence } = parseProvenanceMetadata(meta);\n if (confidence === undefined || confidence >= LOW_CONFIDENCE_THRESHOLD) continue;\n results.push({\n rule: \"low-confidence\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page confidence ${confidence.toFixed(2)} is below ${LOW_CONFIDENCE_THRESHOLD}`,\n });\n }\n\n return results;\n}\n\n/** Flag pages whose frontmatter records contradictions with other pages. */\nexport async function checkContradictedPages(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n const { contradictedBy } = parseProvenanceMetadata(meta);\n if (!contradictedBy || contradictedBy.length === 0) continue;\n const slugs = contradictedBy.map((r) => r.slug).join(\", \");\n results.push({\n rule: \"contradicted-page\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page contradicts: ${slugs}`,\n });\n }\n\n return results;\n}\n\n/**\n * Flag pages with too many inferred paragraphs unsupported by direct\n * citations. Always derived from the rendered page body — the body is\n * the single source of truth, no metadata field is consulted. Earlier\n * versions trusted an LLM-estimated `inferredParagraphs` frontmatter\n * field, but that estimate was made before the page even existed and\n * routinely disagreed with what the model actually produced. Counting\n * uncited prose paragraphs in the rendered body matches what a\n * reviewer would see and survives hand-edits.\n */\nexport async function checkInferredWithoutCitations(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { body } = parseFrontmatter(page.content);\n const inferred = countUncitedProseParagraphs(body);\n if (inferred <= MAX_INFERRED_PARAGRAPHS_WITHOUT_CITATIONS) continue;\n results.push({\n rule: \"excess-inferred-paragraphs\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page has ${inferred} inferred paragraphs without citations (max ${MAX_INFERRED_PARAGRAPHS_WITHOUT_CITATIONS})`,\n });\n }\n\n return results;\n}\n\n/**\n * Match a paragraph that looks like prose (not a heading, list, or code\n * block). Uses the Unicode `Letter` property so non-ASCII pages\n * generated via `--lang Chinese`, `--lang Japanese`, etc. (#46) are\n * still detected — the previous `[A-Za-z]` form silently dropped CJK,\n * Cyrillic, Greek, and Arabic prose, leaving\n * `excess-inferred-paragraphs` blind on those pages.\n */\nconst PROSE_PARAGRAPH_LEAD = /^\\p{L}/u;\n\n/** Count prose paragraphs in a body that lack a ^[citation] marker. */\nfunction countUncitedProseParagraphs(body: string): number {\n const paragraphs = body.split(/\\n\\s*\\n/);\n let count = 0;\n for (const block of paragraphs) {\n const trimmed = block.trim();\n if (trimmed.length === 0) continue;\n if (!PROSE_PARAGRAPH_LEAD.test(trimmed)) continue;\n if (CITATION_PATTERN.test(trimmed)) {\n CITATION_PATTERN.lastIndex = 0;\n continue;\n }\n CITATION_PATTERN.lastIndex = 0;\n count += 1;\n }\n return count;\n}\n\n/** Regex matching the `:start-end` span suffix on a citation entry. */\nconst COLON_SPAN_PATTERN = /^[^:#]+:(\\d+)(?:-(\\d+))?$/;\n\n/** Regex matching the `#Lstart-Lend` span suffix on a citation entry. */\nconst HASH_SPAN_PATTERN = /^[^:#]+#L(\\d+)(?:-L(\\d+))?$/;\n\n/** Parsed line range from a citation entry, or null if no range is present. */\ninterface ParsedLineRange {\n start: number;\n end: number;\n}\n\n/**\n * Enforce per-kind cross-link minimums declared in the schema.\n * For each page, resolve its kind, look up the rule, and warn when the page\n * body has fewer wikilinks than the rule requires. Pages with kind `concept`\n * and a minimum of 0 (the default) generate no diagnostics, so existing\n * projects without a schema file see no behaviour change.\n *\n * Implementation delegates to {@link checkPageCrossLinks} per page so the\n * actual rule logic lives in exactly one place — the on-disk walker just\n * fans the per-page helper across `collectAllPages`.\n * @param root - Project root directory.\n * @param schema - Resolved schema config.\n */\nexport async function checkSchemaCrossLinks(\n root: string,\n schema: SchemaConfig,\n): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n for (const page of pages) {\n results.push(...checkPageCrossLinks(page.content, page.filePath, schema));\n }\n return results;\n}\n\n/**\n * Check cross-link minimums for a single page given as a raw content string.\n *\n * Unlike `checkSchemaCrossLinks`, this function operates on content already in\n * memory without reading from disk. Used by the review pipeline to attach\n * schema violations to a candidate at write time so `review show` can surface\n * them before the reviewer approves the page.\n *\n * The `filePath` parameter is embedded verbatim in each `LintResult.file` so\n * callers control how the candidate is identified in diagnostic output.\n *\n * @param content - Full page content including frontmatter.\n * @param filePath - Logical file path to embed in diagnostics (may be virtual).\n * @param schema - Resolved schema config.\n * @returns Lint results for this single page, empty when no violations found.\n */\nexport function checkPageCrossLinks(\n content: string,\n filePath: string,\n schema: SchemaConfig,\n): LintResult[] {\n const { meta, body } = parseFrontmatter(content);\n const kind = resolvePageKind(meta.kind, schema);\n const rule = schema.kinds[kind];\n if (rule.minWikilinks <= 0) return [];\n\n const linkCount = countWikilinks(body);\n if (linkCount >= rule.minWikilinks) return [];\n\n return [\n {\n rule: \"schema-cross-link-minimum\",\n severity: \"warning\",\n file: filePath,\n message:\n `Page kind \"${kind}\" requires at least ${rule.minWikilinks} ` +\n `[[wikilinks]] but only ${linkCount} found.`,\n },\n ];\n}\n\n/** Extract the line range from a citation entry string, or return null if there is none. */\nfunction parseLineRange(entry: string): ParsedLineRange | null {\n const colonMatch = COLON_SPAN_PATTERN.exec(entry);\n if (colonMatch) {\n const start = Number(colonMatch[1]);\n const end = colonMatch[2] !== undefined ? Number(colonMatch[2]) : start;\n return { start, end };\n }\n const hashMatch = HASH_SPAN_PATTERN.exec(entry);\n if (hashMatch) {\n const start = Number(hashMatch[1]);\n const end = hashMatch[2] !== undefined ? Number(hashMatch[2]) : start;\n return { start, end };\n }\n return null;\n}\n\n/** Count the number of lines in a file's text content. */\nfunction countLines(content: string): number {\n if (content.length === 0) return 0;\n return content.split(\"\\n\").length;\n}\n\n/**\n * Find ^[filename.md] citations referencing source files that don't exist, and\n * flag claim-level spans whose line ranges exceed the source file's actual length.\n * Handles both single-source ^[file.md] and multi-source ^[a.md, b.md] forms,\n * plus the claim-level extension `^[file.md:42-58]` / `^[file.md#L42-L58]`.\n * Line counts are cached per source file to avoid redundant reads.\n */\nexport async function checkBrokenCitations(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const sourcesDir = path.join(root, SOURCES_DIR);\n const results: LintResult[] = [];\n const lineCountCache = new Map<string, number>();\n\n for (const page of pages) {\n const pageFindings = await checkPageBrokenCitations(\n page.content,\n page.filePath,\n sourcesDir,\n lineCountCache,\n );\n results.push(...pageFindings);\n }\n\n return results;\n}\n\n/**\n * Pure-body variant of {@link checkBrokenCitations} that inspects a single\n * page's content against an in-memory or on-disk sources directory. Used\n * by the on-disk lint walker above, and by the in-memory candidate-lint\n * path so `compile --review` surfaces broken-source-file and out-of-bounds\n * span findings before a reviewer approves the candidate.\n *\n * @param content - Full page markdown including frontmatter.\n * @param filePath - Logical path embedded in diagnostics (may be virtual).\n * @param sourcesDir - Absolute path to the project's sources/ directory.\n * @param lineCountCache - Optional cross-page cache; provide one when\n * linting many pages so source file line counts aren't re-read.\n */\nexport async function checkPageBrokenCitations(\n content: string,\n filePath: string,\n sourcesDir: string,\n lineCountCache: Map<string, number> = new Map(),\n): Promise<LintResult[]> {\n const results: LintResult[] = [];\n for (const { captured, line } of findMatchesInContent(content, CITATION_PATTERN)) {\n await collectBrokenForMarker(captured, line, filePath, sourcesDir, lineCountCache, results);\n }\n return results;\n}\n\n/** Append broken-citation diagnostics for every entry inside a single ^[...] marker. */\nasync function collectBrokenForMarker(\n captured: string,\n line: number,\n pageFile: string,\n sourcesDir: string,\n lineCountCache: Map<string, number>,\n out: LintResult[],\n): Promise<void> {\n for (const part of captured.split(\",\")) {\n const trimmed = part.trim();\n if (trimmed.length === 0) continue;\n const filename = stripSpanSuffix(trimmed);\n const citedPath = path.join(sourcesDir, filename);\n if (!existsSync(citedPath)) {\n out.push({\n rule: \"broken-citation\",\n severity: \"error\",\n file: pageFile,\n message: `Broken citation ^[${filename}] — source file not found`,\n line,\n });\n continue;\n }\n const range = parseLineRange(trimmed);\n if (range === null) continue;\n const lineCount = await resolveLineCount(citedPath, filename, lineCountCache);\n if (range.end <= lineCount) continue;\n out.push({\n rule: \"broken-citation\",\n severity: \"error\",\n file: pageFile,\n message: `Claim-level span ^[${trimmed}] is out of bounds (source has only ${lineCount} lines)`,\n line,\n });\n }\n}\n\n/** Return the line count for a source file, reading and caching if necessary. */\nasync function resolveLineCount(\n citedPath: string,\n filename: string,\n cache: Map<string, number>,\n): Promise<number> {\n const cached = cache.get(filename);\n if (cached !== undefined) return cached;\n const content = await safeReadFile(citedPath);\n const lineCount = countLines(content);\n cache.set(filename, lineCount);\n return lineCount;\n}\n\n/**\n * Find ^[...] markers whose entries do not parse against the documented\n * paragraph or claim-level grammar (e.g. `^[file.md:abc]` or `^[file.md#X]`).\n * Detects malformed claim-level citations without breaking the paragraph form.\n */\nexport async function checkMalformedClaimCitations(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n for (const page of pages) {\n results.push(...checkPageMalformedCitations(page.content, page.filePath));\n }\n return results;\n}\n\n/**\n * Pure-body variant of {@link checkMalformedClaimCitations} that inspects\n * a single page's content. Used by both the on-disk lint walker above and\n * the in-memory candidate-lint path so `compile --review` surfaces\n * malformed claim citations before a reviewer approves the candidate.\n */\nexport function checkPageMalformedCitations(content: string, filePath: string): LintResult[] {\n const results: LintResult[] = [];\n for (const { captured, line } of findMatchesInContent(content, CITATION_PATTERN)) {\n for (const part of captured.split(\",\")) {\n if (!isMalformedCitationEntry(part)) continue;\n results.push({\n rule: \"malformed-claim-citation\",\n severity: \"error\",\n file: filePath,\n message: `Malformed claim citation ^[${captured}] — expected file.md, file.md:N-N, or file.md#LN-LN`,\n line,\n });\n }\n }\n return results;\n}\n","/**\n * Wiki page rendering for the llmwiki compile pipeline.\n *\n * Encapsulates the single-page generation step: gather related pages, call\n * the LLM, build frontmatter, and produce the final markdown blob. Splitting\n * this away from the orchestrator (`compiler/index.ts`) keeps the orchestrator\n * focused on phase sequencing and lets the review-candidate code path reuse\n * the exact same renderer used for direct writes.\n */\n\nimport { readdir } from \"fs/promises\";\nimport path from \"path\";\nimport {\n buildFrontmatter,\n parseFrontmatter,\n safeReadFile,\n} from \"../utils/markdown.js\";\nimport { callClaude } from \"../utils/llm.js\";\nimport { buildPagePrompt } from \"./prompts.js\";\nimport { addObsidianMeta } from \"./obsidian.js\";\nimport { addProvenanceMeta, reportContradictionWarnings } from \"./provenance.js\";\nimport { CONCEPTS_DIR } from \"../utils/constants.js\";\nimport type { SchemaConfig } from \"../schema/index.js\";\nimport type { ExtractedConcept } from \"../utils/types.js\";\n\n/** Maximum number of existing concept pages to include as cross-reference context. */\nconst RELATED_PAGE_CONTEXT_LIMIT = 5;\n\n/** A merged-concept input from the orchestrator (multiple sources merged into one). */\ninterface RenderableConcept {\n slug: string;\n concept: ExtractedConcept;\n sourceFiles: string[];\n combinedContent: string;\n}\n\n/**\n * Render a wiki page (frontmatter + body) for a merged concept by calling\n * the LLM with cross-referencing context from existing concept pages.\n * @param root - Project root directory.\n * @param entry - The merged concept to render.\n * @param schema - Resolved schema config, used to stamp `kind` on frontmatter.\n * @returns Full markdown content (frontmatter + body, trailing newline).\n */\nexport async function renderMergedPageContent(\n root: string,\n entry: RenderableConcept,\n schema: SchemaConfig,\n): Promise<string> {\n const pagePath = path.join(root, CONCEPTS_DIR, `${entry.slug}.md`);\n const existingPage = await safeReadFile(pagePath);\n const relatedPages = await loadRelatedPages(root, entry.slug);\n\n const system = buildPagePrompt(\n entry.concept.concept,\n entry.combinedContent,\n existingPage,\n relatedPages,\n );\n\n const pageBody = await callClaude({\n system,\n messages: [\n { role: \"user\", content: `Write the wiki page for \"${entry.concept.concept}\".` },\n ],\n });\n\n const frontmatter = buildMergedFrontmatter(entry, existingPage, schema);\n reportContradictionWarnings(entry.concept.concept, entry.concept);\n return `${frontmatter}\\n\\n${pageBody}\\n`;\n}\n\n/**\n * Construct the frontmatter block for a merged concept, preserving createdAt\n * and stamping the `kind` field from the schema's default kind.\n */\nfunction buildMergedFrontmatter(\n entry: RenderableConcept,\n existingPage: string,\n schema: SchemaConfig,\n): string {\n const now = new Date().toISOString();\n const existing = existingPage ? parseFrontmatter(existingPage) : null;\n const createdAt = (existing?.meta.createdAt && typeof existing.meta.createdAt === \"string\")\n ? existing.meta.createdAt\n : now;\n const frontmatterFields: Record<string, unknown> = {\n title: entry.concept.concept,\n summary: entry.concept.summary,\n sources: entry.sourceFiles,\n kind: schema.defaultKind,\n createdAt,\n updatedAt: now,\n };\n addObsidianMeta(frontmatterFields, entry.concept.concept, entry.concept.tags ?? []);\n addProvenanceMeta(frontmatterFields, entry.concept);\n return buildFrontmatter(frontmatterFields);\n}\n\n/**\n * Load related wiki pages to provide cross-referencing context.\n * Returns concatenated content of up to RELATED_PAGE_CONTEXT_LIMIT pages.\n * @param root - Project root directory.\n * @param excludeSlug - Slug of the current page to exclude.\n * @returns Concatenated related page contents (empty when concepts dir is missing).\n */\nasync function loadRelatedPages(root: string, excludeSlug: string): Promise<string> {\n const conceptsPath = path.join(root, CONCEPTS_DIR);\n let files: string[];\n\n try {\n files = await readdir(conceptsPath);\n } catch {\n return \"\";\n }\n\n const related = files\n .filter((f) => f.endsWith(\".md\") && f !== `${excludeSlug}.md`)\n .slice(0, RELATED_PAGE_CONTEXT_LIMIT);\n\n const contents: string[] = [];\n for (const f of related) {\n const content = await safeReadFile(path.join(conceptsPath, f));\n if (!content) continue;\n const { meta } = parseFrontmatter(content);\n if (meta.orphaned) continue;\n contents.push(content);\n }\n\n return contents.join(\"\\n\\n---\\n\\n\");\n}\n","/**\n * Helpers for surfacing provenance metadata during compilation.\n *\n * Keeps the compile orchestrator small by isolating the logic that copies\n * confidence/contradiction signals from extracted concepts onto wiki page\n * frontmatter and emits compile-time warnings when contradictions are\n * reported.\n */\n\nimport * as output from \"../utils/output.js\";\nimport type { ExtractedConcept } from \"../utils/types.js\";\n\n/**\n * Copy provenance metadata fields from an extracted concept onto the\n * frontmatter record, omitting fields the LLM did not provide so existing\n * pages without these fields stay clean.\n * @param fields - Mutable frontmatter record being assembled for a page.\n * @param concept - Source concept whose provenance metadata to apply.\n */\nexport function addProvenanceMeta(\n fields: Record<string, unknown>,\n concept: ExtractedConcept,\n): void {\n if (typeof concept.confidence === \"number\") {\n fields.confidence = concept.confidence;\n }\n if (concept.provenanceState) {\n fields.provenanceState = concept.provenanceState;\n }\n if (concept.contradictedBy && concept.contradictedBy.length > 0) {\n fields.contradictedBy = concept.contradictedBy;\n }\n}\n\n/**\n * Print a compile-time warning when a concept reports contradictions with\n * other pages. Returns silently when there is nothing to report.\n * @param conceptTitle - Human-readable title of the concept being compiled.\n * @param concept - The extracted concept whose contradictions to surface.\n */\nexport function reportContradictionWarnings(\n conceptTitle: string,\n concept: ExtractedConcept,\n): void {\n const refs = concept.contradictedBy;\n if (!refs || refs.length === 0) return;\n const slugs = refs.map((r) => r.slug).join(\", \");\n output.status(\n \"!\",\n output.warn(`Contradiction reported on \"${conceptTitle}\" — conflicts with: ${slugs}`),\n );\n}\n","/**\n * Commander action for `llmwiki query <question>`.\n * Two-step LLM-powered wiki query that first selects relevant pages from the\n * wiki index, then streams an answer grounded in those pages. Optionally saves\n * the response as a new page in wiki/queries/.\n *\n * Step 1 - Page Selection: Reads wiki/index.md and asks Claude (via tool_use)\n * to pick the most relevant concept pages for the question.\n *\n * Step 2 - Answer Generation: Loads the selected pages in full and streams\n * a cited answer to the terminal.\n */\n\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { callClaude } from \"../utils/llm.js\";\nimport type { LLMTool } from \"../utils/provider.js\";\nimport { atomicWrite, safeReadFile, slugify, buildFrontmatter, parseFrontmatter } from \"../utils/markdown.js\";\nimport { languageDirective } from \"../utils/output-language.js\";\nimport { generateIndex } from \"../compiler/indexgen.js\";\nimport * as output from \"../utils/output.js\";\nimport {\n QUERY_PAGE_LIMIT,\n INDEX_FILE,\n CONCEPTS_DIR,\n QUERIES_DIR,\n CHUNK_TOP_K,\n CHUNK_RERANK_KEEP,\n} from \"../utils/constants.js\";\nimport {\n findRelevantPages,\n findRelevantChunks,\n updateEmbeddings,\n type ChunkEmbeddingEntry,\n} from \"../utils/embeddings.js\";\nimport { rerankWithBm25 } from \"../utils/retrieval.js\";\nimport type { ChunkCitation, QueryResult, RetrievalDebug } from \"../utils/types.js\";\n\n/** Directories to search when loading selected pages, in priority order. */\nconst PAGE_DIRS = [CONCEPTS_DIR, QUERIES_DIR];\n\n/** Tool schema for page selection (provider-agnostic). */\nconst PAGE_SELECTION_TOOL: LLMTool = {\n name: \"select_pages\",\n description: \"Select the most relevant wiki pages to answer a question\",\n input_schema: {\n type: \"object\" as const,\n properties: {\n pages: {\n type: \"array\",\n items: {\n type: \"string\",\n description: \"Slug of a relevant wiki page (e.g. 'llm-knowledge-bases')\",\n },\n maxItems: QUERY_PAGE_LIMIT,\n },\n reasoning: {\n type: \"string\",\n description: \"Brief explanation of why these pages were selected\",\n },\n },\n required: [\"pages\", \"reasoning\"],\n },\n};\n\ninterface PageSelectionResult {\n pages: string[];\n reasoning: string;\n}\n\n/**\n * Select the most relevant wiki pages for a question using Claude tool_use.\n * @param question - The user's natural language question.\n * @param indexContent - The full text of wiki/index.md.\n * @returns Parsed page slugs and reasoning from Claude.\n */\nexport async function selectPages(\n question: string,\n indexContent: string,\n): Promise<PageSelectionResult> {\n const systemPrompt =\n \"You are a knowledge base assistant. Given a question and a wiki index, select the most relevant pages.\";\n\n const userMessage = `Question: ${question}\\n\\nWiki Index:\\n${indexContent}`;\n\n const rawResult = await callClaude({\n system: systemPrompt,\n messages: [{ role: \"user\", content: userMessage }],\n tools: [PAGE_SELECTION_TOOL],\n });\n\n try {\n const parsed = JSON.parse(rawResult);\n return {\n pages: Array.isArray(parsed.pages) ? parsed.pages.filter((p: unknown) => typeof p === \"string\") : [],\n reasoning: typeof parsed.reasoning === \"string\" ? parsed.reasoning : \"No reasoning provided\",\n };\n } catch {\n return { pages: [], reasoning: \"Failed to parse page selection response\" };\n }\n}\n\n/** Render a list of candidate pages in the same bullet format selectPages() consumes. */\nfunction buildFilteredIndex(\n candidates: Array<{ slug: string; title: string; summary: string }>,\n): string {\n return candidates\n .map((entry) => `- **${entry.slug}**: ${entry.title} — ${entry.summary}`)\n .join(\"\\n\");\n}\n\ninterface SelectedPages {\n pages: string[];\n rawPages: string[];\n reasoning: string;\n /** Chunk citations driving the selection — empty when chunk store is absent. */\n chunks: ChunkCitation[];\n /** Debug snapshot of the retrieval pipeline (only populated in debug mode). */\n debug?: RetrievalDebug;\n}\n\n/**\n * Pick relevant pages using a chunk-aware embedding pre-filter when available,\n * falling back to page-level embeddings, then to sending the full wiki index.\n */\nasync function selectRelevantPages(\n root: string,\n question: string,\n debug: boolean,\n): Promise<SelectedPages> {\n const chunkSelection = await trySelectViaChunks(root, question, debug);\n if (chunkSelection) return chunkSelection;\n\n const candidates = await tryFindRelevantPages(root, question);\n\n if (candidates.length > 0) {\n const filteredIndex = buildFilteredIndex(candidates);\n const { pages: rawPages, reasoning } = await selectPages(question, filteredIndex);\n // Tool output holds slugs directly in the semantic path — no slugify needed.\n return { pages: rawPages, rawPages, reasoning, chunks: [] };\n }\n\n const indexContent = await safeReadFile(path.join(root, INDEX_FILE));\n const { pages: rawPages, reasoning } = await selectPages(question, indexContent);\n return { pages: rawPages.map((p) => slugify(p)), rawPages, reasoning, chunks: [] };\n}\n\n/**\n * Attempt chunk-level retrieval + reranking. Returns null when no chunk store\n * is available (caller falls back to page-level retrieval transparently).\n */\nasync function trySelectViaChunks(\n root: string,\n question: string,\n debug: boolean,\n): Promise<SelectedPages | null> {\n const ranked = await tryFindRelevantChunks(root, question);\n if (ranked.length === 0) return null;\n\n const reranked = rerankWithBm25(\n question,\n ranked.map(({ chunk, score }) => ({ text: chunk.text, baseScore: score, chunk })),\n );\n const kept = reranked.slice(0, CHUNK_RERANK_KEEP);\n const reorderingHappened = wasReordered(ranked, kept.map((k) => k.candidate.chunk));\n const chunkCitations = toChunkCitations(kept);\n const pageSlugs = collapseToPages(chunkCitations, QUERY_PAGE_LIMIT);\n const reasoning = buildChunkReasoning(chunkCitations, pageSlugs);\n\n return {\n pages: pageSlugs,\n rawPages: pageSlugs,\n reasoning,\n chunks: chunkCitations,\n debug: debug ? buildDebug(chunkCitations, pageSlugs, reorderingHappened) : undefined,\n };\n}\n\n/** Detect whether reranking actually changed the chunk order. */\nfunction wasReordered(\n before: Array<{ chunk: ChunkEmbeddingEntry }>,\n after: ChunkEmbeddingEntry[],\n): boolean {\n const limit = Math.min(before.length, after.length);\n for (let i = 0; i < limit; i++) {\n if (before[i].chunk !== after[i]) return true;\n }\n return false;\n}\n\ninterface RankedChunk {\n candidate: { chunk: ChunkEmbeddingEntry };\n score: number;\n}\n\n/** Convert reranked candidates into citation records consumed downstream. */\nfunction toChunkCitations(ranked: RankedChunk[]): ChunkCitation[] {\n return ranked.map(({ candidate, score }) => ({\n slug: candidate.chunk.slug,\n title: candidate.chunk.title,\n chunkIndex: candidate.chunk.chunkIndex,\n score,\n text: candidate.chunk.text,\n }));\n}\n\n/** Collapse chunk citations down to a deduplicated list of parent page slugs. */\nfunction collapseToPages(chunks: ChunkCitation[], limit: number): string[] {\n const slugs: string[] = [];\n const seen = new Set<string>();\n for (const chunk of chunks) {\n if (seen.has(chunk.slug)) continue;\n seen.add(chunk.slug);\n slugs.push(chunk.slug);\n if (slugs.length >= limit) break;\n }\n return slugs;\n}\n\n/** Human-readable reasoning trail for the chunk-driven selection. */\nfunction buildChunkReasoning(chunks: ChunkCitation[], pages: string[]): string {\n const top = chunks.slice(0, pages.length);\n const summary = top.map((c) => `${c.slug}#${c.chunkIndex} (${c.score.toFixed(3)})`).join(\", \");\n return `Selected ${pages.length} page(s) from ${chunks.length} reranked chunks: ${summary}`;\n}\n\n/** Snapshot used by debug mode — pure data, no side-effects. */\nfunction buildDebug(\n chunks: ChunkCitation[],\n pageSlugs: string[],\n reranked: boolean,\n): RetrievalDebug {\n const bestPerPage = new Map<string, number>();\n for (const c of chunks) {\n const prev = bestPerPage.get(c.slug);\n if (prev === undefined || c.score > prev) bestPerPage.set(c.slug, c.score);\n }\n return {\n pages: pageSlugs.map((slug) => ({ slug, score: bestPerPage.get(slug) ?? 0 })),\n chunks,\n usedChunks: true,\n reranked,\n };\n}\n\n/** Chunk-level candidate lookup that never throws. */\nasync function tryFindRelevantChunks(\n root: string,\n question: string,\n): Promise<Array<{ chunk: ChunkEmbeddingEntry; score: number }>> {\n try {\n return await findRelevantChunks(root, question, CHUNK_TOP_K);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.dim(`Chunk pre-filter unavailable (${message}); falling back.`));\n return [];\n }\n}\n\n/** Embedding-based candidate lookup that never throws. */\nasync function tryFindRelevantPages(\n root: string,\n question: string,\n): Promise<Array<{ slug: string; title: string; summary: string }>> {\n try {\n return await findRelevantPages(root, question);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.dim(`Semantic pre-filter unavailable (${message}); using full index.`));\n return [];\n }\n}\n\n/**\n * Load the full content of each selected wiki page.\n * Skips pages that don't exist and warns the user.\n * @param root - Absolute path to the project root directory.\n * @param slugs - Array of page slugs to load from wiki/concepts/.\n * @returns Combined page contents with slug headers for context.\n */\nexport async function loadSelectedPages(root: string, slugs: string[]): Promise<string> {\n const sections: string[] = [];\n\n for (const slug of slugs) {\n let content = \"\";\n for (const dir of PAGE_DIRS) {\n const candidate = await safeReadFile(path.join(root, dir, `${slug}.md`));\n if (!candidate) continue;\n const { meta } = parseFrontmatter(candidate);\n if (meta.orphaned) continue;\n content = candidate;\n break;\n }\n\n if (!content) {\n output.status(\"?\", output.warn(`Page not found: ${slug}.md — skipping`));\n continue;\n }\n\n sections.push(`--- Page: ${slug} ---\\n${content}`);\n }\n\n return sections.join(\"\\n\\n\");\n}\n\n/** Base system prompt body. The output-language directive is appended at call time. */\nconst ANSWER_SYSTEM_PROMPT_BASE =\n \"You are a knowledge assistant. Answer the question using ONLY the wiki content provided. \" +\n \"Cite specific pages using [[Page Title]] wikilinks. \" +\n \"If the wiki doesn't contain enough information, say so.\";\n\n/**\n * Build the answer-generation system prompt, appending the configured\n * output-language directive when present (issue #37).\n */\nfunction buildAnswerSystemPrompt(): string {\n const lang = languageDirective();\n return lang ? `${ANSWER_SYSTEM_PROMPT_BASE} ${lang}` : ANSWER_SYSTEM_PROMPT_BASE;\n}\n\n/**\n * Call the LLM with the loaded wiki pages as grounding context. When chunk\n * citations are available, they are attached as a \"Most relevant excerpts\"\n * section so the model can prioritise the precise paragraphs that drove\n * page selection.\n */\nasync function callAnswerLLM(\n question: string,\n pagesContent: string,\n chunks: ChunkCitation[],\n onToken?: (text: string) => void,\n): Promise<string> {\n const provenance = chunks.length > 0 ? buildChunkProvenance(chunks) : \"\";\n const userMessage =\n `Question: ${question}\\n\\nRelevant wiki pages:\\n${pagesContent}${provenance}`;\n return callClaude({\n system: buildAnswerSystemPrompt(),\n messages: [{ role: \"user\", content: userMessage }],\n stream: Boolean(onToken),\n onToken,\n });\n}\n\n/** Render the top chunk excerpts as a labelled section appended to the prompt. */\nfunction buildChunkProvenance(chunks: ChunkCitation[]): string {\n const sections = chunks.map(\n (chunk) => `--- ${chunk.slug} (chunk ${chunk.chunkIndex}) ---\\n${chunk.text}`,\n );\n return `\\n\\nMost relevant excerpts (from chunk-level retrieval):\\n${sections.join(\"\\n\\n\")}`;\n}\n\n/**\n * Generate a one-line summary from the answer for use in the wiki index.\n * Takes the first sentence (up to 120 chars) so the page-selection LLM\n * has retrieval signal beyond just the title.\n * @param answer - The full answer text.\n * @returns A short summary string.\n */\nexport function summarizeAnswer(answer: string): string {\n const firstLine = answer.trim().split(/\\n/)[0] ?? \"\";\n const firstSentence = firstLine.split(/(?<=[.!?])\\s/)[0] ?? firstLine;\n return firstSentence.slice(0, 120);\n}\n\n/**\n * Save a query answer as a wiki page in the queries/ directory,\n * then regenerate the wiki index so the answer is immediately retrievable.\n * @param root - Absolute path to the project root directory.\n * @param question - The original question used as the page title.\n * @param answer - The generated answer body.\n */\nasync function saveQueryPage(root: string, question: string, answer: string): Promise<string> {\n const slug = slugify(question);\n const filePath = path.join(root, QUERIES_DIR, `${slug}.md`);\n\n const frontmatter = buildFrontmatter({\n title: question,\n summary: summarizeAnswer(answer),\n type: \"query\",\n createdAt: new Date().toISOString(),\n });\n\n const document = `${frontmatter}\\n\\n${answer}\\n`;\n await atomicWrite(filePath, document);\n\n output.status(\n \"+\",\n output.success(`Saved query → ${output.source(filePath)}`),\n );\n\n // Regenerate the index so the saved query is immediately discoverable\n // by the next query's page-selection step.\n await generateIndex(root);\n\n // Index the new query so semantic search retrieves it on the next question.\n // Non-critical: embedding failures (e.g. missing VOYAGE_API_KEY) don't block save.\n try {\n await updateEmbeddings(root, [slug]);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.warn(`Skipped embeddings update: ${message}`));\n }\n\n return slug;\n}\n\n/** Options for generateAnswer — programmatic-friendly. */\ninterface GenerateAnswerOptions {\n /** Persist the answer as a wiki query page when set. */\n save?: boolean;\n /** Per-token callback for streaming. Omit for non-streaming usage. */\n onToken?: (text: string) => void;\n /** Callback fired once page selection completes — lets CLIs print reasoning before streaming. */\n onPageSelection?: (pages: string[], reasoning: string) => void;\n /** Capture chunk-level provenance + scoring detail in the result. */\n debug?: boolean;\n}\n\n/**\n * Run the two-step page-selection + answer-generation pipeline and return\n * a structured QueryResult. This is the programmatic entry point used by\n * the MCP server and any non-CLI consumer.\n *\n * @param root - Absolute path to the project root directory.\n * @param question - The natural language question to answer.\n * @param options - Streaming + save behaviour controls.\n * @returns Answer text, selected slugs, reasoning, and saved slug if applicable.\n */\nexport async function generateAnswer(\n root: string,\n question: string,\n options: GenerateAnswerOptions = {},\n): Promise<QueryResult> {\n if (!existsSync(path.join(root, INDEX_FILE))) {\n throw new Error(\"Wiki index not found. Run `llmwiki compile` first.\");\n }\n\n const selection = await selectRelevantPages(root, question, Boolean(options.debug));\n options.onPageSelection?.(selection.pages, selection.reasoning);\n\n const pagesContent = await loadSelectedPages(root, selection.pages);\n\n if (!pagesContent) {\n return buildEmptyResult(selection);\n }\n\n const answer = await callAnswerLLM(question, pagesContent, selection.chunks, options.onToken);\n const saved = options.save ? await saveQueryPage(root, question, answer) : undefined;\n\n return {\n answer,\n selectedPages: selection.pages,\n reasoning: selection.reasoning,\n saved,\n debug: selection.debug,\n };\n}\n\n/** Build the empty-pages result while preserving any debug/chunk context. */\nfunction buildEmptyResult(selection: SelectedPages): QueryResult {\n return {\n answer: \"\",\n selectedPages: selection.pages,\n reasoning: selection.reasoning,\n debug: selection.debug,\n };\n}\n\n/**\n * Run a two-step LLM-powered query against the knowledge wiki.\n * @param root - Absolute path to the project root directory.\n * @param question - The natural language question to answer.\n * @param options - Command options (e.g. --save to persist the answer).\n */\nexport default async function queryCommand(\n root: string,\n question: string,\n options: { save?: boolean; debug?: boolean },\n): Promise<void> {\n if (!existsSync(path.join(root, INDEX_FILE))) {\n output.status(\"!\", output.error(\"Wiki index not found. Run `llmwiki compile` first.\"));\n return;\n }\n\n output.header(\"Selecting relevant pages\");\n\n const result = await generateAnswer(root, question, {\n save: options.save,\n debug: options.debug,\n onToken: (text) => process.stdout.write(text),\n onPageSelection: (pages, reasoning) => {\n output.status(\"i\", output.dim(`Reasoning: ${reasoning}`));\n output.status(\"*\", output.info(`Selected ${pages.length} page(s): ${pages.join(\", \")}`));\n output.header(\"Generating answer\");\n },\n });\n\n // Newline after streamed answer so subsequent terminal output formats cleanly.\n process.stdout.write(\"\\n\");\n\n if (result.debug) printDebugSnapshot(result.debug);\n\n if (!result.answer) {\n output.status(\"!\", output.error(\"No matching pages found. Try refining your question.\"));\n return;\n }\n\n if (result.saved) {\n output.status(\"→\", output.dim(\"Saved. Future queries will use this answer as context.\"));\n } else {\n output.status(\"→\", output.dim(\"Tip: use --save to add this answer to your wiki\"));\n }\n}\n\n/** Render the retrieval debug snapshot to the terminal for human inspection. */\nfunction printDebugSnapshot(debug: RetrievalDebug): void {\n output.header(\"Retrieval debug\");\n output.status(\n \"i\",\n output.dim(\n `Source: ${debug.usedChunks ? \"chunk-level\" : \"page-level\"}; ` +\n `reranked: ${debug.reranked ? \"yes\" : \"no\"}`,\n ),\n );\n for (const page of debug.pages) {\n output.status(\"•\", `${page.slug} (best chunk score ${page.score.toFixed(3)})`);\n }\n for (const chunk of debug.chunks) {\n const preview = chunk.text.slice(0, DEBUG_CHUNK_PREVIEW_CHARS).replace(/\\s+/g, \" \").trim();\n output.status(\n \"·\",\n output.dim(`${chunk.slug}#${chunk.chunkIndex} score=${chunk.score.toFixed(3)} :: ${preview}…`),\n );\n }\n}\n\n/** Maximum chunk preview length printed in --debug output. */\nconst DEBUG_CHUNK_PREVIEW_CHARS = 120;\n","/**\n * Commander action for `llmwiki watch`.\n *\n * Monitors sources/ for file changes via chokidar and triggers incremental\n * recompilation automatically. Uses a debounce to batch rapid changes into\n * a single compile pass. Respects the .llmwiki/lock file — queues changes\n * if a compile is already running.\n */\n\nimport { watch as chokidarWatch } from \"chokidar\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { compile } from \"../compiler/index.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\n\nconst DEBOUNCE_MS = 500;\n\n/**\n * Start watching sources/ for changes and auto-recompile.\n * Runs until the process is killed (Ctrl+C).\n */\nexport default async function watchCommand(): Promise<void> {\n const sourcesPath = path.resolve(SOURCES_DIR);\n\n if (!existsSync(sourcesPath)) {\n output.status(\n \"!\",\n output.warn('No sources/ directory found. Run `llmwiki ingest <url>` first.'),\n );\n return;\n }\n\n output.header(\"llmwiki watch\");\n output.status(\"👁\", output.info(`Watching ${sourcesPath} for changes...`));\n output.status(\"i\", output.dim(\"Press Ctrl+C to stop.\\n\"));\n\n let compiling = false;\n let pendingRecompile = false;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const triggerCompile = async () => {\n if (compiling) {\n pendingRecompile = true;\n return;\n }\n\n compiling = true;\n try {\n await compile(process.cwd());\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.error(`Compile failed: ${msg}`));\n }\n\n compiling = false;\n\n // If changes arrived during compilation, recompile\n if (pendingRecompile) {\n pendingRecompile = false;\n await triggerCompile();\n }\n };\n\n const scheduleCompile = (eventPath: string, event: string) => {\n output.status(\n \"~\",\n output.dim(`${event}: ${path.basename(eventPath)}`),\n );\n\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(triggerCompile, DEBOUNCE_MS);\n };\n\n const watcher = chokidarWatch(sourcesPath, {\n ignoreInitial: true,\n awaitWriteFinish: { stabilityThreshold: 200 },\n });\n\n watcher\n .on(\"add\", (p) => scheduleCompile(p, \"added\"))\n .on(\"change\", (p) => scheduleCompile(p, \"changed\"))\n .on(\"unlink\", (p) => scheduleCompile(p, \"deleted\"));\n\n // Keep process alive\n await new Promise<void>(() => {});\n}\n","/**\n * Wiki linter orchestrator.\n *\n * Imports all lint rules, runs them concurrently, and aggregates\n * results into a summary with error/warning/info counts.\n * This is the main entry point for programmatic lint access.\n */\n\nimport type { LintResult, LintRule, LintSummary, SchemaAwareLintRule } from \"./types.js\";\nimport {\n checkBrokenWikilinks,\n checkOrphanedPages,\n checkMissingSummaries,\n checkDuplicateConcepts,\n checkEmptyPages,\n checkBrokenCitations,\n checkMalformedClaimCitations,\n checkLowConfidencePages,\n checkContradictedPages,\n checkInferredWithoutCitations,\n checkSchemaCrossLinks,\n} from \"./rules.js\";\nimport { loadSchema } from \"../schema/index.js\";\n\n/** Rule-only lint checks that don't depend on the schema layer. */\nconst RULES_WITHOUT_SCHEMA: LintRule[] = [\n checkBrokenWikilinks,\n checkOrphanedPages,\n checkMissingSummaries,\n checkDuplicateConcepts,\n checkEmptyPages,\n checkBrokenCitations,\n checkMalformedClaimCitations,\n checkLowConfidencePages,\n checkContradictedPages,\n checkInferredWithoutCitations,\n];\n\n/** Lint rules that need the resolved schema to know per-kind expectations. */\nconst RULES_WITH_SCHEMA: SchemaAwareLintRule[] = [checkSchemaCrossLinks];\n\n/**\n * Count occurrences of a specific severity level in the results.\n */\nfunction countBySeverity(\n results: LintResult[],\n severity: LintResult[\"severity\"],\n): number {\n return results.filter((r) => r.severity === severity).length;\n}\n\n/**\n * Run all lint rules concurrently against the wiki at the given root.\n * Loads the project schema (or defaults) so schema-aware rules can enforce\n * per-kind cross-link minimums alongside structural checks.\n * @param root - Absolute path to the project root directory.\n * @returns A summary containing all diagnostics and severity counts.\n */\nexport async function lint(root: string): Promise<LintSummary> {\n const schema = await loadSchema(root);\n const [plainResults, schemaResults] = await Promise.all([\n Promise.all(RULES_WITHOUT_SCHEMA.map((rule) => rule(root))),\n Promise.all(RULES_WITH_SCHEMA.map((rule) => rule(root, schema))),\n ]);\n\n const results = [...plainResults.flat(), ...schemaResults.flat()];\n\n return {\n errors: countBySeverity(results, \"error\"),\n warnings: countBySeverity(results, \"warning\"),\n info: countBySeverity(results, \"info\"),\n results,\n };\n}\n","/**\n * Commander action for `llmwiki lint`.\n *\n * Runs rule-based quality checks against the wiki without any LLM calls.\n * Prints colored diagnostics grouped by severity and exits with code 1\n * if any errors are found.\n */\n\nimport { lint } from \"../linter/index.js\";\nimport { writeLintCache } from \"../linter/cache.js\";\nimport * as output from \"../utils/output.js\";\nimport type { LintResult } from \"../linter/types.js\";\nimport { loadSchema } from \"../schema/index.js\";\n\n/** Map severity levels to output formatting functions. */\nconst SEVERITY_FORMATTERS: Record<LintResult[\"severity\"], (text: string) => string> = {\n error: output.error,\n warning: output.warn,\n info: output.info,\n};\n\n/** Map severity levels to display icons. */\nconst SEVERITY_ICONS: Record<LintResult[\"severity\"], string> = {\n error: \"x\",\n warning: \"!\",\n info: \"i\",\n};\n\n/** Print a single lint result with colored output. */\nfunction printResult(result: LintResult): void {\n const formatter = SEVERITY_FORMATTERS[result.severity];\n const icon = SEVERITY_ICONS[result.severity];\n const location = result.line ? `${result.file}:${result.line}` : result.file;\n output.status(icon, `${formatter(result.severity)} ${output.dim(location)} ${result.message}`);\n}\n\n/**\n * Run the lint command: execute all rules and print results.\n * Exits with code 1 if any errors are found.\n */\nexport default async function lintCommand(): Promise<void> {\n output.header(\"Linting wiki\");\n\n const schema = await loadSchema(process.cwd());\n const schemaSource = schema.loadedFrom ?? \"defaults (no schema file)\";\n output.status(\"i\", output.dim(`Schema: ${schemaSource}`));\n\n const summary = await lint(process.cwd());\n\n for (const result of summary.results) {\n printResult(result);\n }\n\n console.log();\n const summaryLine = [\n output.error(`${summary.errors} error(s)`),\n output.warn(`${summary.warnings} warning(s)`),\n output.info(`${summary.info} info`),\n ].join(\", \");\n output.status(\"*\", summaryLine);\n\n await writeLintCache(process.cwd(), summary);\n\n if (summary.errors > 0) {\n process.exit(1);\n }\n}\n","/**\n * Commander action for `llmwiki export [--target <name>]`.\n *\n * Transforms existing wiki content into portable export artifacts and writes\n * them into dist/exports/ (relative to the project root). Supports six formats:\n *\n * llms-txt — concise index per llmstxt.org spec → llms.txt\n * llms-full-txt — full content export → llms-full.txt\n * json — pages + metadata as JSON → wiki.json\n * json-ld — Schema.org JSON-LD graph → wiki.jsonld\n * graphml — directed link graph as XML → wiki.graphml\n * marp — Marp slide deck → wiki.md\n *\n * No LLM calls are made — export is a pure transformation of wiki content.\n */\n\nimport path from \"path\";\nimport { createRequire } from \"module\";\nimport { atomicWrite } from \"../utils/markdown.js\";\nimport * as output from \"../utils/output.js\";\nimport { collectExportPages } from \"../export/collect.js\";\nimport { buildLlmsTxt, buildLlmsFullTxt } from \"../export/llms-txt.js\";\nimport { buildJsonExport } from \"../export/json-export.js\";\nimport { buildJsonLd } from \"../export/json-ld.js\";\nimport { buildGraphml } from \"../export/graphml.js\";\nimport { buildMarp } from \"../export/marp.js\";\nimport { EXPORT_TARGETS, MARP_SOURCES } from \"../export/types.js\";\nimport type { ExportPage, ExportTarget, MarpSource } from \"../export/types.js\";\n\nconst require = createRequire(import.meta.url);\n\n/** Output paths relative to dist/exports/ within the project root. */\nconst EXPORT_DIR = \"dist/exports\";\n\n/** Map each target to its output filename. */\nconst TARGET_FILENAMES: Record<ExportTarget, string> = {\n \"llms-txt\": \"llms.txt\",\n \"llms-full-txt\": \"llms-full.txt\",\n json: \"wiki.json\",\n \"json-ld\": \"wiki.jsonld\",\n graphml: \"wiki.graphml\",\n marp: \"wiki.md\",\n};\n\n/** Options accepted by exportCommand and its programmatic entry point. */\nexport interface ExportOptions {\n /** Limit export to a single target. When absent all targets are produced. */\n target?: string;\n /**\n * For the marp target: which page kinds to include.\n * Accepts \"concepts\", \"queries\", or \"all\" (default when absent).\n */\n source?: string;\n}\n\n/** Result returned by runExport for testing and MCP consumers. */\nexport interface ExportResult {\n /** Absolute paths of files that were written. */\n written: string[];\n /** Number of pages included in each export. */\n pageCount: number;\n}\n\n/** Resolve the human-readable project title from package.json, defaulting gracefully. */\nfunction resolveProjectTitle(root: string): string {\n try {\n const pkg = require(path.join(root, \"package.json\")) as { name?: string };\n return typeof pkg.name === \"string\" ? pkg.name : \"Knowledge Wiki\";\n } catch {\n return \"Knowledge Wiki\";\n }\n}\n\n/** Return true when the given string is a valid ExportTarget. */\nfunction isValidTarget(value: string): value is ExportTarget {\n return (EXPORT_TARGETS as readonly string[]).includes(value);\n}\n\n/** Return true when the given string is a valid MarpSource. */\nfunction isValidMarpSource(value: string): value is MarpSource {\n return (MARP_SOURCES as readonly string[]).includes(value);\n}\n\n/** Resolve and validate the marp source filter. Throws for unknown values. */\nfunction resolveMarpSource(rawSource: string | undefined): MarpSource {\n if (!rawSource) return \"all\";\n if (!isValidMarpSource(rawSource)) {\n throw new Error(\n `Unknown --source value \"${rawSource}\". Valid values: ${MARP_SOURCES.join(\", \")}`,\n );\n }\n return rawSource;\n}\n\n/** Build the content string for a single target. */\nfunction buildContent(\n target: ExportTarget,\n pages: ReturnType<typeof collectExportPages> extends Promise<infer T> ? T : never,\n projectTitle: string,\n marpSource: MarpSource,\n): string {\n switch (target) {\n case \"llms-txt\":\n return buildLlmsTxt(pages, projectTitle);\n case \"llms-full-txt\":\n return buildLlmsFullTxt(pages, projectTitle);\n case \"json\":\n return buildJsonExport(pages);\n case \"json-ld\":\n return buildJsonLd(pages);\n case \"graphml\":\n return buildGraphml(pages);\n case \"marp\":\n return buildMarp(pages, projectTitle, marpSource);\n }\n}\n\n/**\n * Compute the page count to report in the CLI summary. When marp is the\n * only target and --source narrows the deck, report the filtered count so\n * the summary doesn't overstate what was exported. Multi-target runs keep\n * the collected total because non-marp targets always include every page.\n */\nfunction computeReportedPageCount(\n pages: ExportPage[],\n targets: ExportTarget[],\n marpSource: MarpSource,\n): number {\n const onlyMarpTarget = targets.length === 1 && targets[0] === \"marp\";\n if (onlyMarpTarget && marpSource !== \"all\") {\n return pages.filter((p) => p.pageDirectory === marpSource).length;\n }\n return pages.length;\n}\n\n/**\n * Programmatic entry point for the export pipeline.\n * @param root - Absolute path to the project root directory.\n * @param options - Export options (optional target filter).\n * @returns Paths written and page count.\n */\nexport async function runExport(root: string, options: ExportOptions = {}): Promise<ExportResult> {\n const pages = await collectExportPages(root);\n const projectTitle = resolveProjectTitle(root);\n\n const targets = resolveTargets(options.target);\n const marpSource = resolveMarpSource(options.source);\n const written: string[] = [];\n\n for (const target of targets) {\n const content = buildContent(target, pages, projectTitle, marpSource);\n const outPath = path.join(root, EXPORT_DIR, TARGET_FILENAMES[target]);\n await atomicWrite(outPath, content);\n written.push(outPath);\n output.status(\"+\", output.success(`Exported ${target} → ${output.source(outPath)}`));\n }\n\n return { written, pageCount: computeReportedPageCount(pages, targets, marpSource) };\n}\n\n/**\n * Resolve the list of targets to run.\n * When a specific target is given it is validated; an error is thrown for unknown values.\n * Defaults to all targets.\n */\nfunction resolveTargets(rawTarget: string | undefined): ExportTarget[] {\n if (!rawTarget) return [...EXPORT_TARGETS];\n\n if (!isValidTarget(rawTarget)) {\n throw new Error(\n `Unknown export target \"${rawTarget}\". Valid targets: ${EXPORT_TARGETS.join(\", \")}`,\n );\n }\n\n return [rawTarget];\n}\n\n/**\n * CLI action for `llmwiki export`.\n * @param root - Project root directory (defaults to cwd).\n * @param options - Commander-parsed options.\n */\nexport default async function exportCommand(\n root: string,\n options: ExportOptions,\n): Promise<void> {\n output.header(\"Exporting wiki\");\n const { written, pageCount } = await runExport(root, options);\n output.status(\n \"✓\",\n output.success(`Done — ${pageCount} pages exported to ${written.length} file(s).`),\n );\n}\n","/**\n * Wiki page collector for the export subsystem.\n *\n * Thin wrapper over `src/wiki/collect.ts::collectRawWikiPages()` that\n * applies export-specific filters (drop orphaned and untitled pages) and\n * decorates each surviving record with the export-facing fields (summary,\n * sources, tags, timestamps, link slugs). The wikilink extraction regex\n * and slug-normalization helper live in `src/wiki/collect.ts` so both\n * export and viewer callers share one source.\n */\n\nimport { collectRawWikiPages, extractWikilinkSlugs } from \"../wiki/collect.js\";\nimport type { RawWikiPage } from \"../wiki/collect.js\";\nimport type { ExportPage } from \"./types.js\";\n\nexport { extractWikilinkSlugs };\n\n/**\n * Normalize a kept page into the shape every export writer consumes.\n * Caller is responsible for filtering out records that fail the export\n * gate (orphaned, untitled, unreadable).\n */\nfunction toExportPage(raw: RawWikiPage): ExportPage {\n const meta = raw.frontmatter;\n return {\n title: raw.title as string,\n slug: raw.slug,\n pageDirectory: raw.pageDirectory,\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n sources: Array.isArray(meta.sources)\n ? (meta.sources as unknown[]).filter((s): s is string => typeof s === \"string\")\n : [],\n tags: Array.isArray(meta.tags)\n ? (meta.tags as unknown[]).filter((t): t is string => typeof t === \"string\")\n : [],\n createdAt: typeof meta.createdAt === \"string\" ? meta.createdAt : new Date().toISOString(),\n updatedAt: typeof meta.updatedAt === \"string\" ? meta.updatedAt : new Date().toISOString(),\n links: extractWikilinkSlugs(raw.body),\n body: raw.body,\n };\n}\n\n/**\n * Collect all exportable wiki pages from `wiki/concepts/` and `wiki/queries/`.\n * Drops orphaned and untitled records — those are diagnosed by the viewer,\n * not exported. Returns the surviving pages sorted by title.\n */\nexport async function collectExportPages(root: string): Promise<ExportPage[]> {\n const raw = await collectRawWikiPages(root);\n const kept = raw.filter((page) => page.parseStatus.hasTitle && !page.parseStatus.orphaned);\n const pages = kept.map(toExportPage);\n pages.sort((a, b) => a.title.localeCompare(b.title));\n return pages;\n}\n","/**\n * llms.txt export format writer.\n *\n * Produces a machine-readable index per the llmstxt.org spec:\n * - H1 project title\n * - Optional blockquote description\n * - H2-delimited sections per page directory (## Concepts, ## Saved Queries)\n * - Bullet entries: [Title](path): summary | tags | sources | timestamps\n *\n * The companion llms-full.txt format appends the full body of every page\n * so a model can read the entire wiki in one file.\n *\n * Reference: https://llmstxt.org\n */\n\nimport type { ExportPage } from \"./types.js\";\n\n/**\n * Build the wiki-relative path for a page based on its source directory.\n * Concepts live in wiki/concepts/, queries in wiki/queries/.\n */\nfunction pageRelativePath(page: ExportPage): string {\n return `wiki/${page.pageDirectory}/${page.slug}.md`;\n}\n\n/**\n * Build the inline note clause for a page entry.\n * Follows the colon after the markdown link per spec.\n */\nfunction buildEntryNote(page: ExportPage): string {\n const parts: string[] = [];\n if (page.summary) parts.push(page.summary);\n if (page.tags.length > 0) parts.push(`tags: ${page.tags.join(\", \")}`);\n if (page.sources.length > 0) parts.push(`sources: ${page.sources.join(\", \")}`);\n parts.push(`created: ${page.createdAt}`);\n parts.push(`updated: ${page.updatedAt}`);\n return parts.join(\" | \");\n}\n\n/** Format a single page as a spec-compliant bullet entry. */\nfunction formatPageEntry(page: ExportPage): string {\n const note = buildEntryNote(page);\n return `- [${page.title}](${pageRelativePath(page)}): ${note}`;\n}\n\n/** Build entries for a filtered subset of pages under an H2 section. */\nfunction buildSection(heading: string, pages: ExportPage[]): string[] {\n if (pages.length === 0) return [];\n return [`## ${heading}`, \"\", ...pages.map(formatPageEntry), \"\"];\n}\n\n/**\n * Build the concise llms.txt index content per the llmstxt.org spec.\n * Pages are split into Concepts and Saved Queries sections (H2 delimited).\n * @param pages - Sorted array of export pages.\n * @param projectTitle - Human-readable wiki title shown as the H1.\n * @returns Full llms.txt string.\n */\nexport function buildLlmsTxt(pages: ExportPage[], projectTitle: string): string {\n const concepts = pages.filter((p) => p.pageDirectory === \"concepts\");\n const queries = pages.filter((p) => p.pageDirectory === \"queries\");\n\n const lines: string[] = [\n `# ${projectTitle}`,\n \"\",\n `> ${pages.length} pages — exported ${new Date().toISOString()}`,\n \"\",\n ...buildSection(\"Concepts\", concepts),\n ...buildSection(\"Saved Queries\", queries),\n ];\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Build the full llms-full.txt content (index + full page bodies).\n * Each page is separated by a horizontal rule and includes its metadata block.\n * @param pages - Sorted array of export pages.\n * @param projectTitle - Human-readable wiki title shown as the H1.\n * @returns Full llms-full.txt string.\n */\nexport function buildLlmsFullTxt(pages: ExportPage[], projectTitle: string): string {\n const sections: string[] = [buildLlmsTxt(pages, projectTitle)];\n\n for (const page of pages) {\n const tags = page.tags.length > 0 ? `\\nTags: ${page.tags.join(\", \")}` : \"\";\n const sources = page.sources.length > 0 ? `\\nSources: ${page.sources.join(\", \")}` : \"\";\n const header = [\n \"---\",\n `## ${page.title}`,\n `> ${page.summary}${tags}${sources}`,\n `Created: ${page.createdAt} | Updated: ${page.updatedAt}`,\n \"\",\n ].join(\"\\n\");\n sections.push(`${header}\\n${page.body.trim()}\\n`);\n }\n\n return sections.join(\"\\n\");\n}\n","/**\n * JSON export format writer.\n *\n * Produces a structured JSON document containing all wiki pages and their\n * metadata. The schema is intentionally simple and human-readable so it can\n * be consumed directly by scripts, agents, or downstream pipelines without\n * additional transformation.\n *\n * Schema:\n * { exportedAt, pageCount, pages: ExportPage[] }\n */\n\nimport type { ExportPage } from \"./types.js\";\n\n/** Top-level shape of the JSON export file. */\ninterface JsonExportDocument {\n exportedAt: string;\n pageCount: number;\n pages: ExportPage[];\n}\n\n/**\n * Build the JSON export document from a list of export pages.\n * @param pages - Sorted array of export pages.\n * @returns Pretty-printed JSON string.\n */\nexport function buildJsonExport(pages: ExportPage[]): string {\n const doc: JsonExportDocument = {\n exportedAt: new Date().toISOString(),\n pageCount: pages.length,\n pages,\n };\n return JSON.stringify(doc, null, 2);\n}\n","/**\n * JSON-LD graph export format writer.\n *\n * Produces a JSON-LD document using schema.org vocabulary so the wiki graph\n * can be consumed by linked-data tooling, knowledge graph platforms, or any\n * agent that understands the Schema.org ontology.\n *\n * Each wiki page is represented as a schema:Article node. Links derived from\n * [[wikilinks]] are expressed as schema:mentions relationships between nodes.\n */\n\nimport type { ExportPage } from \"./types.js\";\n\n/** Base URL used for page IRIs when no external URL is configured. */\nconst LOCAL_BASE = \"urn:llmwiki:\";\n\n/** Build the IRI for a page slug. */\nfunction pageIri(slug: string): string {\n return `${LOCAL_BASE}${slug}`;\n}\n\n/** Serialise one ExportPage as a JSON-LD Article node. */\nfunction pageToJsonLd(page: ExportPage): Record<string, unknown> {\n const node: Record<string, unknown> = {\n \"@id\": pageIri(page.slug),\n \"@type\": \"Article\",\n name: page.title,\n description: page.summary,\n dateCreated: page.createdAt,\n dateModified: page.updatedAt,\n };\n\n if (page.tags.length > 0) {\n node[\"keywords\"] = page.tags;\n }\n\n // schema.org/isBasedOn is the standard property for citing source material.\n if (page.sources.length > 0) {\n node[\"isBasedOn\"] = page.sources;\n }\n\n if (page.links.length > 0) {\n node[\"mentions\"] = page.links.map((slug) => ({ \"@id\": pageIri(slug) }));\n }\n\n return node;\n}\n\n/**\n * Build the JSON-LD graph document from a list of export pages.\n * @param pages - Sorted array of export pages.\n * @returns Pretty-printed JSON-LD string.\n */\nexport function buildJsonLd(pages: ExportPage[]): string {\n const doc = {\n \"@context\": \"https://schema.org\",\n \"@graph\": pages.map(pageToJsonLd),\n };\n return JSON.stringify(doc, null, 2);\n}\n","/**\n * GraphML export format writer.\n *\n * Produces a GraphML XML document representing the wiki link graph.\n * Each page becomes a node; each [[wikilink]] between pages becomes a\n * directed edge. Node attributes carry page metadata (title, summary, tags).\n *\n * GraphML is the standard XML format for graph exchange and is supported by\n * Gephi, yEd, NetworkX, and many other graph tools.\n */\n\nimport type { ExportPage } from \"./types.js\";\n\n/** XML special characters that must be escaped in attribute values and text. */\nconst XML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\n/** Escape a string for safe inclusion in XML. */\nfunction escapeXml(value: string): string {\n return value.replace(/[&<>\"']/g, (ch) => XML_ESCAPES[ch] ?? ch);\n}\n\n/** GraphML attribute key definitions. */\nconst KEY_DEFS = [\n '<key id=\"title\" for=\"node\" attr.name=\"title\" attr.type=\"string\"/>',\n '<key id=\"summary\" for=\"node\" attr.name=\"summary\" attr.type=\"string\"/>',\n '<key id=\"tags\" for=\"node\" attr.name=\"tags\" attr.type=\"string\"/>',\n '<key id=\"sources\" for=\"node\" attr.name=\"sources\" attr.type=\"string\"/>',\n '<key id=\"createdAt\" for=\"node\" attr.name=\"createdAt\" attr.type=\"string\"/>',\n '<key id=\"updatedAt\" for=\"node\" attr.name=\"updatedAt\" attr.type=\"string\"/>',\n].join(\"\\n \");\n\n/** Serialise one ExportPage as a GraphML <node> element. */\nfunction pageToNode(page: ExportPage): string {\n const tags = page.tags.join(\", \");\n const sources = page.sources.join(\", \");\n return [\n ` <node id=\"${escapeXml(page.slug)}\">`,\n ` <data key=\"title\">${escapeXml(page.title)}</data>`,\n ` <data key=\"summary\">${escapeXml(page.summary)}</data>`,\n ` <data key=\"tags\">${escapeXml(tags)}</data>`,\n ` <data key=\"sources\">${escapeXml(sources)}</data>`,\n ` <data key=\"createdAt\">${escapeXml(page.createdAt)}</data>`,\n ` <data key=\"updatedAt\">${escapeXml(page.updatedAt)}</data>`,\n ` </node>`,\n ].join(\"\\n\");\n}\n\n/** Build all <edge> elements for a single source page's outgoing links. */\nfunction pageToEdges(page: ExportPage, knownSlugs: Set<string>): string[] {\n return page.links\n .filter((slug) => knownSlugs.has(slug))\n .map(\n (slug) =>\n ` <edge source=\"${escapeXml(page.slug)}\" target=\"${escapeXml(slug)}\"/>`,\n );\n}\n\n/**\n * Build the GraphML document from a list of export pages.\n * Only edges whose target slug exists in the page set are included so the\n * graph contains no dangling references.\n * @param pages - Sorted array of export pages.\n * @returns GraphML XML string.\n */\nexport function buildGraphml(pages: ExportPage[]): string {\n const knownSlugs = new Set(pages.map((p) => p.slug));\n const nodes = pages.map(pageToNode).join(\"\\n\");\n const edges = pages.flatMap((p) => pageToEdges(p, knownSlugs)).join(\"\\n\");\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<graphml xmlns=\"http://graphml.graphdrawing.org/graphml\"',\n ' xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"',\n ' xsi:schemaLocation=\"http://graphml.graphdrawing.org/graphml',\n ' http://graphml.graphdrawing.org/graphml/1.0/graphml.xsd\">',\n ` ${KEY_DEFS}`,\n ' <graph id=\"wiki\" edgedefault=\"directed\">',\n nodes,\n edges,\n \" </graph>\",\n \"</graphml>\",\n \"\",\n ].join(\"\\n\");\n}\n","/**\n * Marp slide export format writer.\n *\n * Produces a single Markdown file with Marp frontmatter that can be rendered\n * as a slide deck by the Marp CLI or VS Code Marp extension. Each wiki page\n * becomes one slide showing the title, summary, tags, sources, timestamps,\n * and an excerpt of the body (first paragraph, up to a readable limit).\n *\n * The caller may pre-filter pages by source directory (\"concepts\" |\n * \"queries\" | \"all\") using the --source option on the CLI.\n *\n * Reference: https://marp.app/\n */\n\nimport type { ExportPage, MarpSource } from \"./types.js\";\n\n/** Maximum characters of body text to include per slide. */\nconst SLIDE_BODY_MAX_CHARS = 300;\n\n/** Extract the first prose paragraph from a markdown body. */\nfunction extractFirstParagraph(body: string): string {\n const trimmed = body.trim();\n // Take the first non-empty block separated by a blank line.\n const firstBlock = trimmed.split(/\\n\\s*\\n/)[0] ?? \"\";\n // Strip markdown headings and list markers so slides read cleanly.\n const stripped = firstBlock\n .replace(/^#{1,6}\\s+/gm, \"\")\n .replace(/^[-*+]\\s+/gm, \"\")\n .trim();\n if (stripped.length <= SLIDE_BODY_MAX_CHARS) return stripped;\n return `${stripped.slice(0, SLIDE_BODY_MAX_CHARS)}…`;\n}\n\n/** Build the speaker-notes block for a slide containing metadata. */\nfunction buildSpeakerNotes(page: ExportPage): string {\n const parts: string[] = [`created: ${page.createdAt}`, `updated: ${page.updatedAt}`];\n if (page.sources.length > 0) parts.push(`sources: ${page.sources.join(\", \")}`);\n return `<!-- ${parts.join(\" | \")} -->`;\n}\n\n/** Render one ExportPage as a Marp slide. */\nfunction pageToSlide(page: ExportPage): string {\n const tagLine = page.tags.length > 0 ? `\\n_Tags: ${page.tags.join(\", \")}_` : \"\";\n const excerpt = extractFirstParagraph(page.body);\n const notes = buildSpeakerNotes(page);\n return [\n `## ${page.title}`,\n \"\",\n `> ${page.summary}${tagLine}`,\n \"\",\n excerpt,\n \"\",\n notes,\n ].join(\"\\n\");\n}\n\n/**\n * Filter pages by the requested marp source directory.\n * \"all\" returns the full list unchanged.\n */\nfunction filterBySource(pages: ExportPage[], source: MarpSource): ExportPage[] {\n if (source === \"all\") return pages;\n return pages.filter((p) => p.pageDirectory === source);\n}\n\n/**\n * Build the Marp slide deck content from a list of export pages.\n * @param pages - Array of all export pages.\n * @param projectTitle - Shown on the title slide.\n * @param source - Which page directories to include (default \"all\").\n * @returns Full Marp markdown string.\n */\nexport function buildMarp(\n pages: ExportPage[],\n projectTitle: string,\n source: MarpSource = \"all\",\n): string {\n const filtered = filterBySource(pages, source);\n\n const frontmatter = [\n \"---\",\n \"marp: true\",\n \"theme: default\",\n \"paginate: true\",\n `title: \"${projectTitle}\"`,\n \"---\",\n ].join(\"\\n\");\n\n const titleSlide = [\n \"\",\n `# ${projectTitle}`,\n \"\",\n `${filtered.length} pages | ${new Date().toISOString()}`,\n ].join(\"\\n\");\n\n const slides = filtered.map((p) => `---\\n\\n${pageToSlide(p)}`);\n\n return [frontmatter, titleSlide, ...slides, \"\"].join(\"\\n\\n\");\n}\n","/**\n * Shared types for the llmwiki export subsystem.\n *\n * ExportPage is the normalised in-memory representation of a wiki page used\n * by every export format. It is derived from the page's YAML frontmatter plus\n * the wikilink graph extracted from the body.\n */\n\n/**\n * Which wiki/ subdirectory a page lives in.\n *\n * Intentionally distinct from the schema layer's `PageKind`\n * (concept/entity/comparison/overview) — this is a filesystem location, not\n * a semantic typology. Renaming avoids field collision when JSON export and\n * schema metadata are consumed by the same downstream tooling.\n */\nexport type PageDirectory = \"concepts\" | \"queries\";\n\n/** A fully-resolved wiki page ready for export serialisation. */\nexport interface ExportPage {\n /** Human-readable page title (from frontmatter). */\n title: string;\n /** Filesystem slug (filename without .md). */\n slug: string;\n /** Whether this page came from wiki/concepts or wiki/queries. */\n pageDirectory: PageDirectory;\n /** One-line page summary (from frontmatter). */\n summary: string;\n /** Source filenames cited in the page body. */\n sources: string[];\n /** Taxonomy tags (from frontmatter). */\n tags: string[];\n /** ISO-8601 creation timestamp. */\n createdAt: string;\n /** ISO-8601 last-updated timestamp. */\n updatedAt: string;\n /** Slugs of other pages this page links to via [[wikilinks]]. */\n links: string[];\n /** Full markdown body (without frontmatter). */\n body: string;\n}\n\n/**\n * Source filter for marp export: which page kinds to include.\n * \"all\" includes both concepts and queries (the default).\n */\nexport type MarpSource = \"concepts\" | \"queries\" | \"all\";\n\n/** All recognised marp source values — used for validation. */\nexport const MARP_SOURCES: readonly MarpSource[] = [\"concepts\", \"queries\", \"all\"];\n\n/** Supported export target identifiers. */\nexport type ExportTarget =\n | \"llms-txt\"\n | \"llms-full-txt\"\n | \"json\"\n | \"json-ld\"\n | \"graphml\"\n | \"marp\";\n\n/** All recognised export target names — used for validation. */\nexport const EXPORT_TARGETS: readonly ExportTarget[] = [\n \"llms-txt\",\n \"llms-full-txt\",\n \"json\",\n \"json-ld\",\n \"graphml\",\n \"marp\",\n];\n","/**\n * Commander actions for `llmwiki schema` subcommands.\n *\n * Exposes two operations:\n * - `schema init` writes a starter schema file seeded with sensible defaults\n * so users can customise page kinds and cross-link minimums without\n * hand-rolling the format.\n * - `schema show` prints the resolved schema a project would use, including\n * which file (if any) it was loaded from — helpful for debugging.\n */\n\nimport { existsSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport path from \"path\";\nimport * as output from \"../utils/output.js\";\nimport {\n buildDefaultSchema,\n defaultSchemaInitPath,\n loadSchema,\n serializeSchemaToYaml,\n} from \"../schema/index.js\";\n\n/**\n * Write a starter schema file to `.llmwiki/schema.json` under the project root.\n * Refuses to overwrite an existing file so `schema init` is safe to re-run.\n */\nexport async function schemaInitCommand(): Promise<void> {\n const root = process.cwd();\n const defaults = buildDefaultSchema();\n const targetPath = defaultSchemaInitPath(root);\n\n if (existsSync(targetPath)) {\n output.status(\"!\", output.warn(`Schema file already exists at ${targetPath}`));\n return;\n }\n\n await mkdir(path.dirname(targetPath), { recursive: true });\n const serializable = {\n version: defaults.version,\n defaultKind: defaults.defaultKind,\n kinds: defaults.kinds,\n seedPages: defaults.seedPages,\n };\n await writeFile(targetPath, `${JSON.stringify(serializable, null, 2)}\\n`, \"utf-8\");\n output.status(\"+\", output.success(`Wrote schema to ${targetPath}`));\n}\n\n/**\n * Print the resolved schema for the current project, showing defaults and\n * whichever file (if any) supplied overrides.\n */\nexport async function schemaShowCommand(): Promise<void> {\n const schema = await loadSchema(process.cwd());\n const loadedFrom = schema.loadedFrom ?? \"(defaults — no schema file found)\";\n output.header(`Schema (${loadedFrom})`);\n console.log(serializeSchemaToYaml(schema));\n}\n","/**\n * Commander action for `llmwiki review list`.\n *\n * Prints every pending review candidate (id, slug, sources, generated time)\n * so reviewers can pick one to inspect with `llmwiki review show <id>`.\n */\n\nimport { listCandidates } from \"../compiler/candidates.js\";\nimport * as output from \"../utils/output.js\";\n\n/** List every pending candidate from .llmwiki/candidates/. */\nexport default async function reviewListCommand(): Promise<void> {\n output.header(\"Pending review candidates\");\n\n const candidates = await listCandidates(process.cwd());\n if (candidates.length === 0) {\n output.status(\"✓\", output.success(\"No pending candidates.\"));\n return;\n }\n\n for (const candidate of candidates) {\n const sources = candidate.sources.join(\", \");\n const meta = output.dim(`${candidate.generatedAt} | sources: ${sources}`);\n output.status(\"?\", `${output.info(candidate.id)} → ${candidate.slug} ${meta}`);\n }\n\n output.status(\n \"→\",\n output.dim(`Use \\`llmwiki review show <id>\\` to inspect a candidate.`),\n );\n}\n","/**\n * Commander action for `llmwiki review show <id>`.\n *\n * Prints a single candidate's metadata header followed by its full body so\n * reviewers can read the proposed page before approving or rejecting.\n */\n\nimport { loadCandidateOrFail } from \"../compiler/candidates.js\";\nimport * as output from \"../utils/output.js\";\n\n/** Print a single candidate's full content to stdout. */\nexport default async function reviewShowCommand(id: string): Promise<void> {\n const candidate = await loadCandidateOrFail(process.cwd(), id);\n if (!candidate) return;\n\n output.header(`Candidate ${candidate.id}`);\n output.status(\"i\", output.dim(`title: ${candidate.title}`));\n output.status(\"i\", output.dim(`slug: ${candidate.slug}`));\n output.status(\"i\", output.dim(`summary: ${candidate.summary}`));\n output.status(\"i\", output.dim(`sources: ${candidate.sources.join(\", \")}`));\n output.status(\"i\", output.dim(`generated: ${candidate.generatedAt}`));\n\n console.log();\n console.log(candidate.body);\n\n if (candidate.schemaViolations && candidate.schemaViolations.length > 0) {\n console.log();\n output.header(\"Schema violations\");\n for (const v of candidate.schemaViolations) {\n output.status(\"!\", output.warn(`[${v.severity}] ${v.message}`));\n }\n }\n\n if (candidate.provenanceViolations && candidate.provenanceViolations.length > 0) {\n console.log();\n output.header(\"Provenance violations\");\n for (const v of candidate.provenanceViolations) {\n output.status(\"!\", output.warn(`[${v.severity}] ${v.message}`));\n }\n }\n}\n","/**\n * Commander action for `llmwiki review approve <id>`.\n *\n * Promotes a pending candidate into the live wiki: writes the page body to\n * wiki/concepts/<slug>.md, refreshes the index/MOC, updates embeddings, and\n * removes the candidate file. Approval never re-invokes the LLM — the body\n * stored in the candidate is written verbatim.\n *\n * All mutations are performed under `.llmwiki/lock` to prevent races with a\n * concurrent compile or sibling approve/reject. The candidate is re-read under\n * the lock (TOCTOU guard) — if it disappears between the fast-fail check and\n * lock acquisition (e.g. a concurrent reject ran first), the approval aborts\n * cleanly rather than writing a page from a stale in-memory snapshot.\n */\n\nimport path from \"path\";\nimport {\n atomicWrite,\n validateWikiPage,\n} from \"../utils/markdown.js\";\nimport {\n deleteCandidate,\n listCandidates,\n} from \"../compiler/candidates.js\";\nimport { generateIndex } from \"../compiler/indexgen.js\";\nimport { generateMOC } from \"../compiler/obsidian.js\";\nimport { resolveLinks } from \"../compiler/resolver.js\";\nimport { updateEmbeddings } from \"../utils/embeddings.js\";\nimport { updateSourceState } from \"../utils/state.js\";\nimport { CONCEPTS_DIR } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\nimport type { ReviewCandidate } from \"../utils/types.js\";\nimport { runReviewUnderLock, readCandidateUnderLock } from \"./review-helpers.js\";\n\n/** Approve a pending candidate by promoting its body into wiki/concepts/. */\nexport default async function reviewApproveCommand(id: string): Promise<void> {\n await runReviewUnderLock(id, approveUnderLock);\n}\n\n/**\n * Perform all wiki mutations for an approval while holding the lock.\n *\n * Re-reads the candidate under the lock so that a concurrent reject that ran\n * between the pre-lock fast-fail and lock acquisition is detected. Aborts with\n * exit code 1 if the candidate has disappeared or fails page validation.\n */\nasync function approveUnderLock(root: string, id: string): Promise<void> {\n const candidate = await readCandidateUnderLock(root, id);\n if (!candidate) return;\n\n if (!validateWikiPage(candidate.body)) {\n output.status(\"!\", output.error(`Candidate ${id} failed page validation; not approved.`));\n process.exitCode = 1;\n return;\n }\n\n const pagePath = path.join(root, CONCEPTS_DIR, `${candidate.slug}.md`);\n await atomicWrite(pagePath, candidate.body);\n output.status(\"+\", output.success(`Approved → ${output.source(pagePath)}`));\n\n await persistCandidateSourceStates(root, candidate);\n await refreshWikiAfterApproval(root, candidate.slug);\n await deleteCandidate(root, id);\n output.status(\"✓\", output.dim(`Candidate ${id} cleared.`));\n}\n\n/**\n * Flush the source-state snapshot stored on the candidate into\n * `.llmwiki/state.json` so the contributing source files are marked\n * compiled. Without this, approved candidates would re-appear on the next\n * `compile` run because the source still looks \"new\" or \"changed\" to the\n * change detector.\n *\n * When a single source produced multiple candidates (e.g. an extraction\n * yielded several concepts), persisting state on the first approval would\n * mark the source as fully compiled and silently strand the remaining\n * pending candidates — the next `compile --review` would skip the source\n * entirely. To avoid that, we only persist a source's state when no OTHER\n * pending candidate still references that source filename.\n */\nasync function persistCandidateSourceStates(\n root: string,\n candidate: ReviewCandidate,\n): Promise<void> {\n const states = candidate.sourceStates;\n if (!states) return;\n const otherSources = await collectOtherCandidateSources(root, candidate.id);\n for (const [sourceFile, entry] of Object.entries(states)) {\n if (otherSources.has(sourceFile)) continue;\n await updateSourceState(root, sourceFile, entry);\n }\n}\n\n/**\n * Build the set of source filenames referenced by every pending candidate\n * other than the one currently being approved. Used to defer source-state\n * persistence until the LAST candidate from a given source is reviewed.\n */\nasync function collectOtherCandidateSources(\n root: string,\n approvingId: string,\n): Promise<Set<string>> {\n const pending = await listCandidates(root);\n const sources = new Set<string>();\n for (const candidate of pending) {\n if (candidate.id === approvingId) continue;\n for (const source of candidate.sources) sources.add(source);\n }\n return sources;\n}\n\n/** Refresh interlinks, index, MOC, and embeddings after writing a candidate. */\nasync function refreshWikiAfterApproval(root: string, slug: string): Promise<void> {\n await resolveLinks(root, [slug], [slug]);\n await generateIndex(root);\n await generateMOC(root);\n await safelyUpdateEmbeddings(root, [slug]);\n}\n\n/**\n * Refresh the embeddings store without failing approval.\n * Mirrors the compiler's tolerance: missing API keys / transient provider\n * failures should warn, not abort the approval flow.\n */\nasync function safelyUpdateEmbeddings(root: string, slugs: string[]): Promise<void> {\n try {\n await updateEmbeddings(root, slugs);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.warn(`Skipped embeddings update: ${message}`));\n }\n}\n","/**\n * Shared helpers for review subcommands (approve and reject).\n *\n * Both commands follow the same pattern:\n * 1. Fast-fail: read the candidate before locking (cheap early exit for bad ids).\n * 2. Acquire lock: serialize against concurrent compile / approve / reject.\n * 3. Under-lock re-read: authoritative TOCTOU guard — abort if the candidate\n * was removed between steps 1 and 2 (e.g. a concurrent reject ran first).\n * 4. Run the mutation.\n * 5. Release lock.\n *\n * Extracting this pattern avoids duplicating the acquire/release boilerplate\n * in both approve and reject.\n */\n\nimport {\n loadCandidateOrFail,\n loadCandidateUnderLockOrFail,\n} from \"../compiler/candidates.js\";\nimport { acquireLock, releaseLock } from \"../utils/lock.js\";\nimport * as output from \"../utils/output.js\";\n\n/** Re-export for use by the under-lock mutation functions in approve/reject. */\nexport { loadCandidateUnderLockOrFail as readCandidateUnderLock };\n\n/**\n * Run a review mutation under the `.llmwiki/lock`.\n *\n * Performs the pre-lock fast-fail, acquires the lock, then delegates to the\n * provided `underLock` callback. The lock is released in a `finally` block.\n *\n * @param id - Candidate id to review.\n * @param underLock - Async mutation to run while holding the lock.\n */\nexport async function runReviewUnderLock(\n id: string,\n underLock: (root: string, id: string) => Promise<void>,\n): Promise<void> {\n const root = process.cwd();\n\n // Fast-fail: surface a clear error for obviously missing ids.\n // The authoritative check happens under the lock via loadCandidateUnderLockOrFail.\n const preCheck = await loadCandidateOrFail(root, id);\n if (!preCheck) return;\n\n const locked = await acquireLock(root);\n if (!locked) {\n output.status(\"!\", output.error(\"Could not acquire lock. Try again later.\"));\n process.exitCode = 1;\n return;\n }\n\n try {\n await underLock(root, id);\n } finally {\n await releaseLock(root);\n }\n}\n","/**\n * Commander action for `llmwiki review reject <id>`.\n *\n * Removes a candidate from the pending area without touching `wiki/`.\n * Rejected candidates are moved into .llmwiki/candidates/archive/ so they\n * remain auditable but never appear in `llmwiki review list` again.\n *\n * The archive mutation is performed under `.llmwiki/lock` to serialize\n * concurrent approve/reject and approve-vs-compile operations, matching\n * the lock discipline used by compile and approve.\n *\n * The candidate is re-read under the lock (TOCTOU guard) — if it disappears\n * between the pre-lock fast-fail and lock acquisition, the rejection aborts\n * cleanly rather than silently succeeding on a stale handle.\n */\n\nimport { archiveCandidate } from \"../compiler/candidates.js\";\nimport * as output from \"../utils/output.js\";\nimport { runReviewUnderLock, readCandidateUnderLock } from \"./review-helpers.js\";\n\n/** Reject a pending candidate by archiving its JSON record. */\nexport default async function reviewRejectCommand(id: string): Promise<void> {\n await runReviewUnderLock(id, rejectUnderLock);\n}\n\n/**\n * Perform the archive mutation while holding the lock.\n *\n * Re-reads the candidate under the lock so that a concurrent approve that ran\n * between the pre-lock fast-fail and lock acquisition is detected. Aborts with\n * exit code 1 if the candidate has disappeared.\n */\nasync function rejectUnderLock(root: string, id: string): Promise<void> {\n const candidate = await readCandidateUnderLock(root, id);\n if (!candidate) return;\n\n await archiveCandidate(root, id);\n output.status(\n \"-\",\n output.warn(`Rejected candidate ${id} (${candidate.slug}) — archived, wiki unchanged.`),\n );\n}\n","/**\n * MCP (Model Context Protocol) server entry point for llmwiki.\n *\n * Exposes llmwiki's automated pipelines (ingest, compile, query, search,\n * lint, read, status) as MCP tools so AI agents can drive the compiler\n * without scraping CLI output. Read-only wiki views are exposed as\n * MCP resources for direct context injection.\n *\n * Transport: stdio. The server reads JSON-RPC messages on stdin and\n * writes responses on stdout, which is the standard surface area for\n * Claude Desktop, Cursor, and other MCP-aware clients.\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { registerWikiTools } from \"./tools.js\";\nimport { registerWikiResources } from \"./resources.js\";\n\ninterface ServerOptions {\n /** Project root directory the server operates on. */\n root: string;\n /** Server version surfaced to MCP clients in the initialize handshake. */\n version: string;\n}\n\n/**\n * Start the MCP server bound to stdio transport.\n * Resolves once the transport closes (typically when the parent process exits).\n *\n * @param options - Root directory and server version (the CLI passes its own\n * version so the server doesn't need to read package.json).\n */\nexport async function startMCPServer(options: ServerOptions): Promise<void> {\n const { root, version } = options;\n const server = new McpServer({ name: \"llmwiki\", version }, {\n instructions:\n \"llmwiki is a knowledge compiler. Use ingest_source to add raw sources, \" +\n \"compile_wiki to run the LLM pipeline, query_wiki for grounded answers, \" +\n \"and search_pages to retrieve relevant pages. read_page, lint_wiki, and \" +\n \"wiki_status work without an API key.\",\n });\n\n registerWikiTools(server, root);\n registerWikiResources(server, root);\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n","/**\n * MCP tool registrations for llmwiki.\n *\n * Each tool wraps an existing pipeline function (ingest, compile, query,\n * search, read, lint, status) and converts its structured result into\n * an MCP CallToolResult. Tools that need an LLM provider validate the\n * provider lazily — the server itself starts without credentials so\n * read-only tools always work.\n */\n\nimport path from \"path\";\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { ingestSource } from \"../commands/ingest.js\";\nimport { compileAndReport } from \"../compiler/index.js\";\nimport { generateAnswer, selectPages } from \"../commands/query.js\";\nimport { lint } from \"../linter/index.js\";\nimport { collectPageSummaries, scanWikiPages } from \"../compiler/indexgen.js\";\nimport { detectChanges } from \"../compiler/hasher.js\";\nimport { countCandidates } from \"../compiler/candidates.js\";\nimport { readState } from \"../utils/state.js\";\nimport { safeReadFile, parseFrontmatter } from \"../utils/markdown.js\";\nimport { findRelevantChunks, findRelevantPages } from \"../utils/embeddings.js\";\nimport {\n CONCEPTS_DIR,\n INDEX_FILE,\n QUERIES_DIR,\n CHUNK_TOP_K,\n} from \"../utils/constants.js\";\nimport { ensureProviderAvailable } from \"./provider-check.js\";\n\n/** Directories searched (in priority order) when resolving a page slug. */\nconst PAGE_DIRS = [CONCEPTS_DIR, QUERIES_DIR];\n\n/** Shape returned by search_pages for each matching page. */\ninterface PageRecord {\n slug: string;\n title: string;\n summary: string;\n body: string;\n}\n\n/**\n * Wrap an arbitrary JSON value as the standard MCP CallToolResult.\n * MCP requires content blocks even for structured payloads, so we mirror\n * the JSON in a text block for clients that don't read structuredContent.\n */\nfunction jsonResult(payload: unknown): {\n content: Array<{ type: \"text\"; text: string }>;\n structuredContent: { result: unknown };\n} {\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(payload, null, 2) }],\n structuredContent: { result: payload },\n };\n}\n\n/** Register all 7 wiki tools on the given MCP server instance. */\nexport function registerWikiTools(server: McpServer, root: string): void {\n registerIngestTool(server, root);\n registerCompileTool(server, root);\n registerQueryTool(server, root);\n registerSearchTool(server, root);\n registerReadTool(server, root);\n registerLintTool(server, root);\n registerStatusTool(server, root);\n}\n\nfunction registerIngestTool(server: McpServer, root: string): void {\n server.registerTool(\n \"ingest_source\",\n {\n title: \"Ingest Source\",\n description:\n \"Fetch a URL or copy a local file into sources/. Returns the saved filename, \" +\n \"character count, and whether content was truncated to fit the size limit.\",\n inputSchema: {\n source: z\n .string()\n .describe(\"URL (http/https) or absolute path to a .md/.txt file\"),\n },\n },\n async ({ source }) => {\n const previousCwd = process.cwd();\n try {\n process.chdir(root);\n const result = await ingestSource(source);\n return jsonResult(result);\n } finally {\n process.chdir(previousCwd);\n }\n },\n );\n}\n\nfunction registerCompileTool(server: McpServer, root: string): void {\n server.registerTool(\n \"compile_wiki\",\n {\n title: \"Compile Wiki\",\n description:\n \"Run the incremental compile pipeline: extract concepts from new/changed \" +\n \"sources, generate wiki pages, resolve interlinks, and rebuild the index. \" +\n \"Requires an LLM provider with credentials.\",\n inputSchema: {},\n },\n async () => {\n ensureProviderAvailable();\n const result = await compileAndReport(root);\n return jsonResult(result);\n },\n );\n}\n\nfunction registerQueryTool(server: McpServer, root: string): void {\n server.registerTool(\n \"query_wiki\",\n {\n title: \"Query Wiki\",\n description:\n \"Ask a natural-language question. Selects relevant pages with the LLM, \" +\n \"loads them, and returns a grounded answer with citations. Set save=true \" +\n \"to persist the answer as a wiki page. Set debug=true to include the \" +\n \"selected chunks and their scores. Requires an LLM provider.\",\n inputSchema: {\n question: z.string().describe(\"The natural-language question to answer.\"),\n save: z\n .boolean()\n .optional()\n .describe(\"Persist the answer as a wiki/queries/ page when true.\"),\n debug: z\n .boolean()\n .optional()\n .describe(\"Include retrieval debug info (selected chunks/pages + scores).\"),\n },\n },\n async ({ question, save, debug }) => {\n ensureProviderAvailable();\n const result = await generateAnswer(root, question, { save, debug });\n return jsonResult(result);\n },\n );\n}\n\nfunction registerSearchTool(server: McpServer, root: string): void {\n server.registerTool(\n \"search_pages\",\n {\n title: \"Search Pages\",\n description:\n \"Select pages relevant to a question and return their full content. \" +\n \"Uses semantic embeddings when available, falling back to LLM-based \" +\n \"selection over the wiki index. Requires an LLM provider.\",\n inputSchema: {\n question: z.string().describe(\"The query used to rank pages.\"),\n },\n },\n async ({ question }) => {\n ensureProviderAvailable();\n const slugs = await pickSearchSlugs(root, question);\n const records = await loadPageRecords(root, slugs);\n return jsonResult({ pages: records });\n },\n );\n}\n\n/**\n * Resolve search candidates. Tries chunk-level retrieval first (highest\n * precision), then falls back to page-level embeddings, then to LLM-driven\n * selection over the wiki index.\n */\nasync function pickSearchSlugs(root: string, question: string): Promise<string[]> {\n try {\n const chunks = await findRelevantChunks(root, question, CHUNK_TOP_K);\n if (chunks.length > 0) return dedupePreservingOrder(chunks.map((c) => c.chunk.slug));\n } catch {\n // Chunk store unavailable — fall through to page-level embeddings.\n }\n\n try {\n const candidates = await findRelevantPages(root, question);\n if (candidates.length > 0) return candidates.map((c) => c.slug);\n } catch {\n // Embeddings unavailable — fall through to index-based selection.\n }\n\n const indexContent = await safeReadFile(path.join(root, INDEX_FILE));\n const { pages } = await selectPages(question, indexContent);\n return pages;\n}\n\n/** Deduplicate slugs while preserving the first-seen ordering. */\nfunction dedupePreservingOrder(slugs: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const slug of slugs) {\n if (seen.has(slug)) continue;\n seen.add(slug);\n out.push(slug);\n }\n return out;\n}\n\nfunction registerReadTool(server: McpServer, root: string): void {\n server.registerTool(\n \"read_page\",\n {\n title: \"Read Page\",\n description:\n \"Read a single wiki page by slug. Searches concepts/ first, then queries/. \" +\n \"Returns the parsed frontmatter and body. No LLM call required.\",\n inputSchema: {\n slug: z.string().describe(\"Page slug, without .md extension.\"),\n },\n },\n async ({ slug }) => {\n const page = await readPage(root, slug);\n if (!page) {\n throw new Error(`Page not found: ${slug}`);\n }\n return jsonResult(page);\n },\n );\n}\n\nfunction registerLintTool(server: McpServer, root: string): void {\n server.registerTool(\n \"lint_wiki\",\n {\n title: \"Lint Wiki\",\n description:\n \"Run rule-based quality checks (broken wikilinks, orphans, duplicates, \" +\n \"empty pages, broken citations). Returns structured diagnostics. No LLM call.\",\n inputSchema: {},\n },\n async () => {\n const summary = await lint(root);\n return jsonResult(summary);\n },\n );\n}\n\nfunction registerStatusTool(server: McpServer, root: string): void {\n server.registerTool(\n \"wiki_status\",\n {\n title: \"Wiki Status\",\n description:\n \"Summarize the wiki: page count, source count, last compile time, \" +\n \"orphaned pages, and pending source changes. Read-only — never \" +\n \"modifies the workspace.\",\n inputSchema: {},\n },\n async () => jsonResult(await collectStatus(root)),\n );\n}\n\n/** Read-only status snapshot used by the wiki_status tool. */\nasync function collectStatus(root: string): Promise<WikiStatus> {\n const concepts = await collectPageSummaries(path.join(root, CONCEPTS_DIR));\n const queries = await collectPageSummaries(path.join(root, QUERIES_DIR));\n const state = await readState(root);\n const changes = await detectChanges(root, state);\n const orphans = await findOrphanedSlugs(root);\n const pendingCandidates = await countCandidates(root);\n const compileTimes = Object.values(state.sources).map((s) => s.compiledAt);\n const lastCompile = compileTimes.length > 0\n ? compileTimes.sort().slice(-1)[0]\n : null;\n\n return {\n pages: { concepts: concepts.length, queries: queries.length, total: concepts.length + queries.length },\n sources: Object.keys(state.sources).length,\n lastCompiledAt: lastCompile,\n orphanedPages: orphans,\n pendingCandidates,\n pendingChanges: changes\n .filter((c) => c.status !== \"unchanged\")\n .map((c) => ({ file: c.file, status: c.status })),\n };\n}\n\ninterface WikiStatus {\n pages: { concepts: number; queries: number; total: number };\n sources: number;\n lastCompiledAt: string | null;\n orphanedPages: string[];\n /** Number of compile candidates awaiting human review. */\n pendingCandidates: number;\n pendingChanges: Array<{ file: string; status: string }>;\n}\n\n/** Find concept slugs whose pages are flagged as orphaned. */\nasync function findOrphanedSlugs(root: string): Promise<string[]> {\n const scanned = await scanWikiPages(path.join(root, CONCEPTS_DIR));\n return scanned.filter(({ meta }) => meta.orphaned).map(({ slug }) => slug);\n}\n\n/** Load full content for a list of slugs, skipping missing/orphaned pages. */\nasync function loadPageRecords(root: string, slugs: string[]): Promise<PageRecord[]> {\n const records: PageRecord[] = [];\n for (const slug of slugs) {\n const page = await readPage(root, slug);\n if (page) records.push(page);\n }\n return records;\n}\n\n/**\n * Locate a page by slug across the priority-ordered page directories,\n * skipping orphaned entries to match the query pipeline's behaviour.\n */\nexport async function readPage(root: string, slug: string): Promise<PageRecord | null> {\n for (const dir of PAGE_DIRS) {\n const content = await safeReadFile(path.join(root, dir, `${slug}.md`));\n if (!content) continue;\n\n const { meta, body } = parseFrontmatter(content);\n if (meta.orphaned) continue;\n\n return {\n slug,\n title: typeof meta.title === \"string\" ? meta.title : slug,\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n body: body.trim(),\n };\n }\n return null;\n}\n\n","/**\n * Per-tool provider validation for the MCP server.\n *\n * The MCP server starts without any API key check so read-only tools\n * (read_page, lint_wiki, wiki_status) and the ingest tool always work.\n * Tools that need an LLM call (compile, query, search) invoke this guard\n * to surface a clean error if credentials are missing.\n */\n\nimport { DEFAULT_PROVIDER } from \"../utils/constants.js\";\nimport { resolveAnthropicAuthFromEnv } from \"../utils/claude-settings.js\";\n\n/** Map of provider name to the env var that satisfies it. Null = no key needed. */\nconst PROVIDER_KEY_VARS: Record<string, string | null> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n ollama: null,\n minimax: \"MINIMAX_API_KEY\",\n copilot: \"GITHUB_TOKEN\",\n};\n\n/**\n * Throw if the active LLM provider is missing credentials.\n * Anthropic accepts either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN\n * (resolved through the Claude Code settings fallback chain).\n */\nexport function ensureProviderAvailable(): void {\n const provider = process.env.LLMWIKI_PROVIDER ?? DEFAULT_PROVIDER;\n\n if (provider === \"anthropic\") {\n const auth = resolveAnthropicAuthFromEnv();\n if (!auth.apiKey && !auth.authToken) {\n throw new Error(\n 'Anthropic credentials are required for the \"anthropic\" provider. ' +\n \"Set ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN.\",\n );\n }\n return;\n }\n\n const keyVar = PROVIDER_KEY_VARS[provider];\n if (keyVar === undefined) {\n throw new Error(\n `Unknown provider \"${provider}\". Supported: ${Object.keys(PROVIDER_KEY_VARS).join(\", \")}`,\n );\n }\n\n if (keyVar && !process.env[keyVar]) {\n throw new Error(\n `${keyVar} environment variable is required for the \"${provider}\" provider.`,\n );\n }\n}\n","/**\n * MCP resource registrations for llmwiki.\n *\n * Resources expose read-only views of the wiki under the llmwiki:// URI\n * scheme. Hosts can attach these as context without invoking a tool —\n * useful for letting agents browse the wiki passively.\n */\n\nimport path from \"path\";\nimport { readdir } from \"fs/promises\";\nimport { McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n CONCEPTS_DIR,\n INDEX_FILE,\n QUERIES_DIR,\n SOURCES_DIR,\n STATE_FILE,\n} from \"../utils/constants.js\";\nimport { safeReadFile, parseFrontmatter } from \"../utils/markdown.js\";\nimport { readState } from \"../utils/state.js\";\n\n/** Standard JSON content block for an MCP resource read result. */\nfunction jsonContent(uri: URL, payload: unknown): {\n uri: string;\n mimeType: string;\n text: string;\n} {\n return {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n };\n}\n\n/** Standard markdown content block for an MCP resource read result. */\nfunction markdownContent(uri: URL, text: string): {\n uri: string;\n mimeType: string;\n text: string;\n} {\n return {\n uri: uri.href,\n mimeType: \"text/markdown\",\n text,\n };\n}\n\n/** Register all 5 read-only wiki resources on the given MCP server. */\nexport function registerWikiResources(server: McpServer, root: string): void {\n registerIndexResource(server, root);\n registerSourcesResource(server, root);\n registerStateResource(server, root);\n registerConceptResource(server, root);\n registerQueryResource(server, root);\n}\n\nfunction registerIndexResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-index\",\n \"llmwiki://index\",\n {\n title: \"Wiki Index\",\n description: \"Full content of wiki/index.md (auto-generated table of contents).\",\n mimeType: \"text/markdown\",\n },\n async (uri) => {\n const content = await safeReadFile(path.join(root, INDEX_FILE));\n return { contents: [markdownContent(uri, content)] };\n },\n );\n}\n\nfunction registerSourcesResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-sources\",\n \"llmwiki://sources\",\n {\n title: \"Wiki Sources\",\n description: \"List of ingested source files with frontmatter metadata.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [jsonContent(uri, await listSources(root))],\n }),\n );\n}\n\nfunction registerStateResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-state\",\n \"llmwiki://state\",\n {\n title: \"Compilation State\",\n description: \"Per-source hashes, concepts, and last compile times from .llmwiki/state.json.\",\n mimeType: \"application/json\",\n },\n async (uri) => {\n const state = await readState(root);\n return { contents: [jsonContent(uri, state)] };\n },\n );\n}\n\nfunction registerConceptResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-concept\",\n new ResourceTemplate(\"llmwiki://concept/{slug}\", {\n list: async () => listPagesUnder(root, CONCEPTS_DIR, \"concept\"),\n }),\n {\n title: \"Wiki Concept\",\n description: \"A single concept page from wiki/concepts/ — frontmatter plus body.\",\n mimeType: \"application/json\",\n },\n async (uri, { slug }) => ({\n contents: [jsonContent(uri, await loadPageWithMeta(root, CONCEPTS_DIR, String(slug)))],\n }),\n );\n}\n\nfunction registerQueryResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-query\",\n new ResourceTemplate(\"llmwiki://query/{slug}\", {\n list: async () => listPagesUnder(root, QUERIES_DIR, \"query\"),\n }),\n {\n title: \"Wiki Query\",\n description: \"A single saved query page from wiki/queries/ — frontmatter plus body.\",\n mimeType: \"application/json\",\n },\n async (uri, { slug }) => ({\n contents: [jsonContent(uri, await loadPageWithMeta(root, QUERIES_DIR, String(slug)))],\n }),\n );\n}\n\n/** Source listing: filename, frontmatter (truncation, source URL, etc.). */\nasync function listSources(root: string): Promise<Array<Record<string, unknown>>> {\n const sourcesPath = path.join(root, SOURCES_DIR);\n let files: string[];\n try {\n files = await readdir(sourcesPath);\n } catch {\n return [];\n }\n\n const records: Array<Record<string, unknown>> = [];\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const content = await safeReadFile(path.join(sourcesPath, file));\n const { meta } = parseFrontmatter(content);\n records.push({ filename: file, ...meta });\n }\n return records;\n}\n\n/** Read a single page and return a structured payload (slug, meta, body). */\nasync function loadPageWithMeta(\n root: string,\n dir: string,\n slug: string,\n): Promise<{ slug: string; meta: Record<string, unknown>; body: string }> {\n const filePath = path.join(root, dir, `${slug}.md`);\n const content = await safeReadFile(filePath);\n if (!content) {\n throw new Error(`Page not found: ${dir}/${slug}.md`);\n }\n\n const { meta, body } = parseFrontmatter(content);\n return { slug, meta, body: body.trim() };\n}\n\n/** Build a resource list payload by enumerating .md files in a wiki directory. */\nasync function listPagesUnder(\n root: string,\n dir: string,\n scheme: \"concept\" | \"query\",\n): Promise<{ resources: Array<{ uri: string; name: string }> }> {\n const pagesPath = path.join(root, dir);\n let files: string[];\n try {\n files = await readdir(pagesPath);\n } catch {\n return { resources: [] };\n }\n\n const resources = files\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => {\n const slug = f.replace(/\\.md$/, \"\");\n return { uri: `llmwiki://${scheme}/${slug}`, name: slug };\n });\n\n return { resources };\n}\n"],"mappings":"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","names":["createRequire","path","readFile","mkdir","readFile","writeFile","path","source","candidatePath","path","readFile","mkdir","writeFile","readFile","path","path","path","readFile","readFile","info","readFile","readFile","path","Anthropic","path","Anthropic","path","readFile","readFile","path","source","path","readFile","source","TIMESTAMP_PATTERN","readFile","path","path","readFile","path","path","readFile","readFile","path","path","readFile","readFile","path","path","readFile","path","info","mkdir","readFile","path","mkdir","path","readFile","lint","readFile","path","readFile","path","path","path","path","readFile","status","readdir","readFile","realpath","path","realpath","path","readFile","readdir","path","CHAR_OPEN_BRACKET","buildParser","path","status","readdir","readFile","realpath","path","readdir","rename","writeFile","mkdir","path","path","readdir","mkdir","rename","writeFile","readFile","writeFile","rename","mkdir","existsSync","path","path","existsSync","readFile","mkdir","writeFile","rename","path","realpath","readdir","readFile","existsSync","readFile","path","path","createHash","readFile","readdir","path","readFile","createHash","path","listSourceFiles","status","readdir","path","error","readFile","unlink","mkdir","path","path","mkdir","unlink","readFile","existsSync","readFile","path","yaml","path","existsSync","yaml","readFile","yaml","yaml","path","path","readdir","readFile","path","existsSync","path","existsSync","readdir","readFile","readdir","path","path","readdir","readdir","path","path","readdir","readFile","readdir","existsSync","path","createHash","createHash","path","existsSync","readFile","readdir","readdir","readFile","existsSync","path","WIKILINK_PATTERN","existsSync","readdir","path","readFile","readdir","path","path","readdir","path","readFile","error","existsSync","existsSync","path","rawPages","reasoning","path","existsSync","existsSync","path","path","existsSync","path","header","source","require","path","existsSync","mkdir","writeFile","path","existsSync","mkdir","path","writeFile","path","path","source","safelyUpdateEmbeddings","McpServer","path","PAGE_DIRS","source","path","path","readdir","path","readdir","version","McpServer","require","createRequire","source","PROVIDER_KEY_VARS"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/ingest.ts","../src/utils/markdown.ts","../src/utils/source-writer.ts","../src/utils/constants.ts","../src/utils/output.ts","../src/ingest/web.ts","../src/ingest/file.ts","../src/ingest/shared.ts","../src/ingest/pdf.ts","../src/ingest/image.ts","../src/providers/anthropic.ts","../src/utils/claude-settings.ts","../src/ingest/transcript.ts","../src/commands/ingest-session.ts","../src/adapters/claude.ts","../src/adapters/utils.ts","../src/adapters/codex.ts","../src/adapters/cursor.ts","../src/adapters/registry.ts","../src/commands/view.ts","../src/viewer/server.ts","../src/linter/cache.ts","../src/viewer/health.ts","../src/viewer/graph.ts","../src/viewer/shell.ts","../src/viewer/static-assets.ts","../src/viewer/path-safety.ts","../src/viewer/render.ts","../src/wiki/collect.ts","../src/viewer/collect.ts","../src/viewer/markdown-it-helpers.ts","../src/viewer/wikilink-rule.ts","../src/viewer/citation-rule.ts","../src/viewer/search.ts","../src/viewer/snapshot.ts","../src/compiler/candidates.ts","../src/utils/state.ts","../src/commands/compile.ts","../src/compiler/index.ts","../src/compiler/source-state.ts","../src/compiler/hasher.ts","../src/providers/openai.ts","../src/providers/ollama.ts","../src/providers/minimax.ts","../src/providers/copilot.ts","../src/utils/provider.ts","../src/utils/llm.ts","../src/utils/lock.ts","../src/utils/output-language.ts","../src/compiler/prompts.ts","../src/schema/types.ts","../src/schema/defaults.ts","../src/schema/loader.ts","../src/schema/helpers.ts","../src/compiler/deps.ts","../src/compiler/orphan.ts","../src/compiler/resolver.ts","../src/compiler/indexgen.ts","../src/compiler/prompt-budget.ts","../src/compiler/obsidian.ts","../src/utils/embeddings.ts","../src/utils/retrieval.ts","../src/linter/rules.ts","../src/compiler/page-renderer.ts","../src/compiler/provenance.ts","../src/commands/query.ts","../src/commands/watch.ts","../src/linter/index.ts","../src/commands/lint.ts","../src/eval/health.ts","../src/eval/citation-coverage.ts","../src/eval/source-path.ts","../src/eval/citation-support.ts","../src/eval/stats.ts","../src/eval/delta.ts","../src/eval/thresholds.ts","../src/eval/report.ts","../src/eval/cache.ts","../src/commands/eval.ts","../src/commands/export.ts","../src/export/collect.ts","../src/export/llms-txt.ts","../src/export/json-export.ts","../src/export/json-ld.ts","../src/export/graphml.ts","../src/export/marp.ts","../src/export/types.ts","../src/commands/schema.ts","../src/commands/review-list.ts","../src/commands/review-show.ts","../src/commands/review-approve.ts","../src/commands/review-helpers.ts","../src/commands/review-reject.ts","../src/project/state.ts","../src/project/recommendations.ts","../src/commands/next.ts","../src/commands/quickstart.ts","../src/utils/provider-guard.ts","../src/commands/context.ts","../src/context/provenance.ts","../src/context/ranking.ts","../src/context/retrieval.ts","../src/context/graph.ts","../src/context/budget.ts","../src/context/types.ts","../src/context/build.ts","../src/mcp/server.ts","../src/mcp/tools.ts","../src/mcp/resources.ts"],"sourcesContent":["/**\n * CLI entry point for llmwiki — the knowledge compiler.\n *\n * Registers all commands (ingest, compile, query, watch, lint) via Commander.\n * Validates the correct API key for the selected LLM provider.\n * Designed for `npx llmwiki` or global install via `npm install -g llm-wiki-compiler`.\n */\n\nimport \"dotenv/config\";\nimport { createRequire } from \"module\";\nimport { Command } from \"commander\";\nimport ingestCommand from \"./commands/ingest.js\";\nimport ingestSessionCommand from \"./commands/ingest-session.js\";\nimport viewCommand from \"./commands/view.js\";\nimport compileCommand from \"./commands/compile.js\";\nimport queryCommand from \"./commands/query.js\";\nimport watchCommand from \"./commands/watch.js\";\nimport lintCommand from \"./commands/lint.js\";\nimport evalCommand, {\n evalCacheClearCommand,\n evalCacheShowCommand,\n evalReportCommand,\n evalHistoryCommand,\n evalJudgementsCommand,\n} from \"./commands/eval.js\";\nimport exportCommand from \"./commands/export.js\";\nimport { schemaInitCommand, schemaShowCommand } from \"./commands/schema.js\";\nimport reviewListCommand from \"./commands/review-list.js\";\nimport reviewShowCommand from \"./commands/review-show.js\";\nimport reviewApproveCommand from \"./commands/review-approve.js\";\nimport reviewRejectCommand from \"./commands/review-reject.js\";\nimport nextCommand from \"./commands/next.js\";\nimport quickstartCommand, { type QuickstartOptions } from \"./commands/quickstart.js\";\nimport contextCommand, { type ContextCommandOptions } from \"./commands/context.js\";\nimport { startMCPServer } from \"./mcp/server.js\";\nimport { applyLanguageOption } from \"./utils/output-language.js\";\nimport { ensureProviderAvailable } from \"./utils/provider-guard.js\";\n\nconst require = createRequire(import.meta.url);\nconst { version } = require(\"../package.json\") as { version: string };\n\nconst program = new Command();\n\nprogram\n .name(\"llmwiki\")\n .description(\"The knowledge compiler — raw sources in, interlinked wiki out\")\n .version(version);\n\nprogram\n .command(\"ingest <source>\")\n .description(\"Ingest a URL or local file into sources/\")\n .action(async (source: string) => {\n try {\n await ingestCommand(source);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"ingest-session <path>\")\n .description(\"Ingest a coding-agent session export (Claude, Codex, Cursor) into sources/\")\n .action(async (targetPath: string) => {\n try {\n await ingestSessionCommand(targetPath);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"view\")\n .description(\"Start a local read-only web viewer for the current wiki project\")\n .option(\"--port <port>\", \"Port to bind (default 0 — OS-assigned)\")\n .option(\"--host <host>\", \"Host to bind (requires --allow-lan; default 127.0.0.1)\")\n .option(\"--allow-lan\", \"Bind beyond loopback (requires --host); off by default for privacy\")\n .option(\"--open\", \"Open the viewer in the default browser after startup\")\n .action(async (options: { port?: string; host?: string; allowLan?: boolean; open?: boolean }) => {\n try {\n await viewCommand(options);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"compile\")\n .description(\"Compile sources/ into an interlinked wiki\")\n .option(\n \"--review\",\n \"Write generated pages as review candidates under .llmwiki/candidates/ instead of mutating wiki/. Orphan-marking for deleted sources is deferred until the next non-review compile.\",\n )\n .option(\n \"--lang <code>\",\n \"Target language for generated wiki content (e.g. \\\"Chinese\\\", \\\"ja\\\", \\\"zh-CN\\\"). Equivalent to setting LLMWIKI_OUTPUT_LANG.\",\n )\n .action(async (options: { review?: boolean; lang?: string }) => {\n try {\n applyLanguageOption(options.lang);\n requireProvider();\n await compileCommand({ review: options.review });\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nconst reviewCommand = program\n .command(\"review\")\n .description(\"Inspect and act on pending compile review candidates\");\n\nreviewCommand\n .command(\"list\")\n .description(\"List pending review candidates\")\n .action(async () => {\n try {\n await reviewListCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nreviewCommand\n .command(\"show <id>\")\n .description(\"Print a single candidate's metadata and body\")\n .action(async (id: string) => {\n try {\n await reviewShowCommand(id);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nreviewCommand\n .command(\"approve <id>\")\n .description(\"Approve a candidate and promote it into wiki/concepts/\")\n .action(async (id: string) => {\n try {\n await reviewApproveCommand(id);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nreviewCommand\n .command(\"reject <id>\")\n .description(\"Reject a candidate and archive it without touching wiki/\")\n .action(async (id: string) => {\n try {\n await reviewRejectCommand(id);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"query <question>\")\n .description(\"Ask a question against the wiki\")\n .option(\"--save\", \"Save the answer as a wiki page\")\n .option(\"--debug\", \"Print which pages and chunks were selected and their scores\")\n .option(\n \"--lang <code>\",\n \"Target language for the answer (e.g. \\\"Chinese\\\", \\\"ja\\\", \\\"zh-CN\\\"). Equivalent to setting LLMWIKI_OUTPUT_LANG.\",\n )\n .action(\n async (\n question: string,\n options: { save?: boolean; debug?: boolean; lang?: string },\n ) => {\n try {\n applyLanguageOption(options.lang);\n requireProvider();\n await queryCommand(process.cwd(), question, options);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n },\n );\n\nprogram\n .command(\"watch\")\n .description(\"Watch sources/ and auto-recompile on changes\")\n .action(async () => {\n try {\n requireProvider();\n await watchCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"lint\")\n .description(\"Run rule-based quality checks against the wiki\")\n .action(async () => {\n try {\n await lintCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nconst evalCmd = program\n .command(\"eval\")\n .description(\"Evaluate wiki quality (health, citation coverage, LLM judge)\")\n .option(\"--suite <level>\", \"fast (deterministic) or full (+ LLM judge)\", \"fast\")\n .option(\"--out <format>\", \"terminal or json\", \"terminal\")\n .option(\"--sample <n>\", \"number of citations to judge in full suite\", \"20\")\n .action(async (opts) => {\n try {\n await evalCommand(opts);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nconst evalCacheCmd = evalCmd\n .command(\"cache\")\n .description(\"Manage the citation judgement cache\");\n\nevalCacheCmd\n .command(\"clear\")\n .description(\"Delete the cache so all judgements re-run on the next full eval\")\n .action(async () => {\n try { await evalCacheClearCommand(); }\n catch (err) { console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`); process.exit(1); }\n });\n\nevalCacheCmd\n .command(\"show\")\n .description(\"Print a score distribution summary of cached citation judgements\")\n .action(async () => {\n try { await evalCacheShowCommand(); }\n catch (err) { console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`); process.exit(1); }\n });\n\nevalCmd\n .command(\"report\")\n .description(\"Re-display the most recent eval report without running a new eval\")\n .option(\"--out <format>\", \"terminal or json\", \"terminal\")\n .action(async (opts) => {\n try { await evalReportCommand(opts); }\n catch (err) { console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`); process.exit(1); }\n });\n\nevalCmd\n .command(\"history\")\n .description(\"Show a trend table of past eval runs\")\n .option(\"--n <count>\", \"number of runs to show\", \"10\")\n .option(\"--out <format>\", \"terminal or json\", \"terminal\")\n .action(async (opts) => {\n try { await evalHistoryCommand(opts); }\n catch (err) { console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`); process.exit(1); }\n });\n\nevalCmd\n .command(\"judgements\")\n .description(\"Browse cached citation judgements\")\n .option(\"--score <0|1|2>\", \"filter by support score (0=unsupported, 1=partial, 2=full)\")\n .option(\"--page <slug>\", \"filter by wiki page slug\")\n .option(\"--n <count>\", \"limit number of judgements shown\")\n .option(\"--out <format>\", \"terminal or json\", \"terminal\")\n .action(async (opts) => {\n try { await evalJudgementsCommand(opts); }\n catch (err) { console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`); process.exit(1); }\n });\n\nconst schemaCmd = program\n .command(\"schema\")\n .description(\"Inspect or initialize the project's wiki schema config\");\n\nschemaCmd\n .command(\"init\")\n .description(\"Write a starter schema file to .llmwiki/schema.json\")\n .action(async () => {\n try {\n await schemaInitCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nschemaCmd\n .command(\"show\")\n .description(\"Print the resolved schema for this project\")\n .action(async () => {\n try {\n await schemaShowCommand();\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"export\")\n .description(\"Export wiki content to portable formats (llms.txt, JSON, GraphML, Marp, …)\")\n .option(\"--target <name>\", \"Limit export to a single target format\")\n .option(\n \"--source <kind>\",\n \"For marp target: which pages to include — concepts, queries, or all (default: all)\",\n )\n .action(async (options: { target?: string; source?: string }) => {\n try {\n await exportCommand(process.cwd(), options);\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\nprogram\n .command(\"next\")\n .description(\"Show the recommended next action for this llmwiki project (read-only)\")\n .option(\"--json\", \"Emit a stable JSON envelope for agent consumption\")\n .action(async (options: { json?: boolean }) =>\n runExitCodeCommand(() => nextCommand({ json: options.json })),\n );\n\nprogram\n .command(\"context <prompt>\")\n .description(\n \"Build an agent-ready evidence pack for <prompt> from the compiled wiki \" +\n \"(read-only; provider credentials optional — semantic retrieval is used \" +\n \"when available and falls back to lexical otherwise)\",\n )\n .option(\"--budget <tokens>\", \"Approximate output token budget (default 8000)\")\n .option(\"--format <format>\", \"Output format: json | markdown (default markdown)\")\n .option(\"--json\", \"Emit the stable v1 JSON envelope (overrides --format)\")\n .option(\"--depth <n>\", \"Graph neighborhood depth, default 1, max 2; 0 disables expansion\")\n .option(\"--top-pages <n>\", \"Max primary pages (default 5, max 20)\")\n .option(\"--top-chunks <n>\", \"Max semantic chunks (default 8, max 50)\")\n .option(\"--omit-root\", \"Emit project.root as null for privacy\")\n .option(\"--no-neighbors\", \"Suppress graph expansion (keeps neighbors/gaps as empty arrays)\")\n .option(\n \"--include-sources\",\n \"Populate primary[].sourceWindows from claim-level citation spans (max 20 windows, 30 lines each)\",\n )\n .action(async (prompt: string, options: ContextCommandOptions) =>\n runExitCodeCommand(() => contextCommand(prompt, options)),\n );\n\nprogram\n .command(\"quickstart <source>\")\n .description(\n \"Ingest a source and compile it into a wiki in one step. Recommends the next action when finished.\",\n )\n .option(\"--review\", \"Generate review candidates instead of mutating wiki/\")\n .option(\"--no-open\", \"Skip the viewer handoff after a successful compile\")\n .option(\n \"--provider <name>\",\n \"Override LLMWIKI_PROVIDER for this run only (e.g. anthropic, openai, ollama)\",\n )\n .option(\n \"--lang <code>\",\n \"Target language for generated wiki content (e.g. \\\"Chinese\\\", \\\"ja\\\", \\\"zh-CN\\\"). Equivalent to setting LLMWIKI_OUTPUT_LANG.\",\n )\n .option(\"--json\", \"Emit the quickstart JSON envelope instead of human output (implies --no-open)\")\n .action(async (source: string, options: QuickstartOptions) =>\n runExitCodeCommand(() => quickstartCommand(source, options)),\n );\n\nprogram\n .command(\"serve\")\n .description(\"Start an MCP server exposing wiki tools and resources over stdio\")\n .option(\"--root <dir>\", \"Project root directory\", process.cwd())\n .action(async (options: { root: string }) => {\n try {\n // Per-tool credential checks happen inside the MCP layer so read-only\n // tools and ingest still work without an API key.\n await startMCPServer({ root: options.root, version });\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n });\n\n/**\n * Run the shared provider guard but match the legacy CLI error path:\n * print the error in red and exit 1 instead of letting the throw\n * surface as a stack trace. Programmatic callers (quickstart, MCP) use\n * `ensureProviderAvailable` directly so they can convert the throw into\n * a structured envelope.\n */\nfunction requireProvider(): void {\n try {\n ensureProviderAvailable();\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n console.error(`\\x1b[31mError:\\x1b[0m ${message}`);\n process.exit(1);\n }\n}\n\n/**\n * Wrap a command implementation that returns an exit code with the\n * shared CLI exit semantics: assign process.exitCode for non-zero\n * returns (so stdout can drain before the event loop exits) and\n * print a red-formatted error then process.exit(1) on throws.\n *\n * Centralised so command actions stay one-liners and fallow does not\n * flag the try/catch+exitCode skeleton as duplicated across siblings.\n */\nasync function runExitCodeCommand(work: () => Promise<number>): Promise<void> {\n try {\n const code = await work();\n if (code !== 0) process.exitCode = code;\n } catch (err) {\n console.error(`\\x1b[31mError:\\x1b[0m ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n}\n\nprogram.parse();\n","/**\n * Commander action for `llmwiki ingest <source>`.\n *\n * Detects the source type (URL, image, PDF, transcript, or generic file),\n * delegates to the appropriate ingestion module, and saves the result as a\n * markdown file with YAML frontmatter in the sources/ directory.\n *\n * Source type is persisted in frontmatter under the `sourceType` key for\n * downstream tooling and human readers.\n */\n\nimport path from \"path\";\nimport { readFile } from \"fs/promises\";\nimport { buildFrontmatter } from \"../utils/markdown.js\";\nimport { saveSource } from \"../utils/source-writer.js\";\nimport { MAX_SOURCE_CHARS, MIN_SOURCE_CHARS, SOURCES_DIR, IMAGE_EXTENSIONS, TRANSCRIPT_EXTENSIONS } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\nimport ingestWeb from \"../ingest/web.js\";\nimport ingestFile from \"../ingest/file.js\";\nimport ingestPdf from \"../ingest/pdf.js\";\nimport ingestImage from \"../ingest/image.js\";\nimport ingestTranscript, { isYoutubeUrl } from \"../ingest/transcript.js\";\nimport type { IngestResult, SourceType } from \"../utils/types.js\";\n\n/** Check whether a source string looks like a URL. */\nfunction isUrl(source: string): boolean {\n return source.startsWith(\"http://\") || source.startsWith(\"https://\");\n}\n\n/** Number of bytes to peek at when sniffing .txt content for transcript signals. */\nconst TXT_SNIFF_BYTES = 2048;\n\n/**\n * Regex for a speaker-tag line: captures the speaker name before the colon.\n * Allows names up to ~40 chars with letters, spaces, dots, apostrophes, hyphens.\n * The `gm` flags let us find ALL occurrences in the sample.\n */\nconst SPEAKER_TAG_PATTERN = /^([A-Z][a-zA-Z .'-]{0,40}):\\s/gm;\n\n/**\n * Regex for a bare timestamp at the start of a line (allowing leading\n * whitespace): \"H:MM\", \"HH:MM\", or \"HH:MM:SS\". Anchored to line starts so\n * incidental times in prose (e.g. \"the meeting at 3:00 was productive\")\n * don't trip the transcript heuristic.\n */\nconst TIMESTAMP_PATTERN = /^\\s*\\d{1,2}:\\d{2}(:\\d{2})?/;\n\n/** Minimum number of timestamp-like matches to treat a file as a transcript. */\nconst MIN_TIMESTAMP_MATCHES = 3;\n\n/**\n * Minimum number of times a single speaker name must appear to signal dialogue\n * (rules out one-off section headers like \"Summary:\" that appear only once).\n */\nconst MIN_SPEAKER_REPEAT_COUNT = 2;\n\n/**\n * Minimum number of distinct speaker names required alongside the repeat\n * condition (rules out single-speaker monologues).\n */\nconst MIN_DISTINCT_SPEAKERS = 2;\n\n/**\n * Count how many times each speaker name appears in the collected tag matches.\n * Returns a Map from name → occurrence count.\n */\nfunction countSpeakerOccurrences(sample: string): Map<string, number> {\n const counts = new Map<string, number>();\n // Reset lastIndex since SPEAKER_TAG_PATTERN has the `g` flag.\n SPEAKER_TAG_PATTERN.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = SPEAKER_TAG_PATTERN.exec(sample)) !== null) {\n const name = match[1].trim();\n counts.set(name, (counts.get(name) ?? 0) + 1);\n }\n return counts;\n}\n\n/**\n * Decide whether speaker-tag occurrences in a sample look like dialogue.\n *\n * A file passes when both of the following are true:\n * - At least {@link MIN_DISTINCT_SPEAKERS} distinct speaker names appear.\n * - At least one name appears {@link MIN_SPEAKER_REPEAT_COUNT}+ times,\n * indicating back-and-forth turns rather than a list of section headers\n * (e.g. \"Summary: …\", \"Details: …\") where every label is unique.\n */\nfunction hasSpeakerDialoguePattern(sample: string): boolean {\n const counts = countSpeakerOccurrences(sample);\n\n const distinctSpeakers = counts.size;\n const hasEnoughSpeakers = distinctSpeakers >= MIN_DISTINCT_SPEAKERS;\n\n const hasRepeatedSpeaker = [...counts.values()].some(\n (n) => n >= MIN_SPEAKER_REPEAT_COUNT,\n );\n\n return hasEnoughSpeakers && hasRepeatedSpeaker;\n}\n\n/**\n * Peek at the first {@link TXT_SNIFF_BYTES} of a plain-text file and decide\n * whether it looks like a conversation transcript.\n *\n * Heuristic: at least one of the following must be true in the sampled content:\n *\n * 1. **Speaker-tag dialogue pattern** — lines of the form \"Name: …\" where:\n * - At least {@link MIN_DISTINCT_SPEAKERS} distinct names appear, AND\n * - At least one name appears {@link MIN_SPEAKER_REPEAT_COUNT}+ times.\n * This rejects lone section headers (\"Summary: …\") and lists of unique\n * labels (\"Summary:\", \"Details:\", \"Notes:\") that have no repetition, while\n * accepting real back-and-forth dialogue (\"Alice: …\\nBob: …\\nAlice: …\").\n *\n * 2. **Timestamp density** — three or more bare timestamp patterns (e.g.\n * \"01:23\" / \"1:23:45\"), the signature of time-coded scripts or subtitles.\n *\n * When neither signal fires the caller routes the file as a generic text file.\n *\n * @param filePath - Absolute or relative path to the .txt file.\n * @returns `true` when transcript signals are detected, `false` otherwise.\n */\nasync function looksLikeTxtTranscript(filePath: string): Promise<boolean> {\n const raw = await readFile(filePath, \"utf-8\");\n const sample = raw.slice(0, TXT_SNIFF_BYTES);\n\n if (hasSpeakerDialoguePattern(sample)) return true;\n\n const timestampMatches = sample.match(new RegExp(TIMESTAMP_PATTERN.source, \"gm\"));\n return (timestampMatches?.length ?? 0) >= MIN_TIMESTAMP_MATCHES;\n}\n\n/** Truncate result including whether truncation occurred and original length. */\ninterface TruncateResult {\n content: string;\n truncated: boolean;\n originalChars: number;\n}\n\n/** Truncate content if it exceeds the character limit, logging a warning. */\nexport function enforceCharLimit(content: string): TruncateResult {\n if (content.length <= MAX_SOURCE_CHARS) {\n return { content, truncated: false, originalChars: content.length };\n }\n\n output.status(\n \"!\",\n output.warn(\n `Content truncated from ${content.length.toLocaleString()} to ${MAX_SOURCE_CHARS.toLocaleString()} characters.`\n )\n );\n return {\n content: content.slice(0, MAX_SOURCE_CHARS),\n truncated: true,\n originalChars: content.length,\n };\n}\n\n/** Reject empty content and warn when content is trivially short. */\nfunction enforceMinContent(content: string): void {\n const length = content.trim().length;\n\n if (length === 0) {\n throw new Error(\n \"No readable content could be extracted from the source.\"\n );\n }\n\n if (length < MIN_SOURCE_CHARS) {\n output.status(\n \"!\",\n output.warn(\n `Content seems very short (${length} chars, minimum recommended is ${MIN_SOURCE_CHARS}).`\n )\n );\n }\n}\n\n/**\n * Determine the source type for a given source string.\n *\n * For `.txt` files, content-sniffing is used instead of a pure extension check.\n * The file's first {@link TXT_SNIFF_BYTES} bytes are inspected for transcript\n * signals (speaker-tag lines or repeated timestamps). Only when both heuristics\n * fail is the file routed to the generic `file` adapter. `.vtt` and `.srt` are\n * always treated as transcripts regardless of content.\n *\n * @param source - A URL, local file path, or image path.\n * @returns The detected SourceType.\n */\nexport async function detectSourceType(source: string): Promise<SourceType> {\n if (!isUrl(source)) {\n const ext = path.extname(source).toLowerCase();\n if (ext === \".pdf\") return \"pdf\";\n if (IMAGE_EXTENSIONS.has(ext)) return \"image\";\n if (TRANSCRIPT_EXTENSIONS.has(ext)) return \"transcript\";\n if (ext === \".txt\") {\n const isTranscript = await looksLikeTxtTranscript(source);\n return isTranscript ? \"transcript\" : \"file\";\n }\n return \"file\";\n }\n\n if (isYoutubeUrl(source)) return \"transcript\";\n return \"web\";\n}\n\n/** Build the full markdown document with frontmatter. */\nexport function buildDocument(\n title: string,\n source: string,\n result: TruncateResult,\n sourceType?: SourceType,\n): string {\n const meta: Record<string, unknown> = {\n title,\n source,\n ingestedAt: new Date().toISOString(),\n };\n if (sourceType !== undefined) {\n meta.sourceType = sourceType;\n }\n if (result.truncated) {\n meta.truncated = true;\n meta.originalChars = result.originalChars;\n }\n const frontmatter = buildFrontmatter(meta);\n\n return `${frontmatter}\\n\\n${result.content}\\n`;\n}\n\n/** Fetch content from the appropriate ingestion module based on source type. */\nasync function fetchContent(\n source: string,\n sourceType: SourceType,\n): Promise<{ title: string; content: string }> {\n switch (sourceType) {\n case \"web\":\n return ingestWeb(source);\n case \"pdf\":\n return ingestPdf(source);\n case \"image\":\n return ingestImage(source);\n case \"transcript\":\n return ingestTranscript(source);\n case \"file\":\n return ingestFile(source);\n }\n}\n\n/**\n * Programmatic ingest entry point. Identical fetch + write logic to the CLI\n * command but returns a structured IngestResult instead of writing to stdout.\n * Used by the MCP server's ingest_source tool.\n *\n * @param source - A URL (http/https), YouTube URL, local file, PDF, or image path.\n * @returns Saved filename, character count, truncation flag, source URI, and detected source type.\n */\nexport async function ingestSource(source: string): Promise<IngestResult> {\n const sourceType = await detectSourceType(source);\n output.status(\"*\", output.info(`Ingesting [${sourceType}]: ${source}`));\n\n const { title, content } = await fetchContent(source, sourceType);\n\n const result = enforceCharLimit(content);\n enforceMinContent(result.content);\n const document = buildDocument(title, source, result, sourceType);\n const savedPath = await saveSource(title, document, source);\n\n return {\n filename: path.basename(savedPath),\n charCount: result.content.length,\n truncated: result.truncated,\n source,\n sourceType,\n };\n}\n\n/**\n * Ingest a source and save it to the sources/ directory.\n * @param source - A URL (http/https), YouTube URL, local file, PDF, or image path.\n */\nexport default async function ingest(source: string): Promise<void> {\n const result = await ingestSource(source);\n const savedPath = path.join(SOURCES_DIR, result.filename);\n\n output.status(\n \"+\",\n output.success(`Saved ${output.bold(result.filename)} → ${output.source(savedPath)}`)\n );\n output.status(\"→\", output.dim(\"Next: llmwiki compile\"));\n}\n","/**\n * Markdown parsing and manipulation helpers.\n * Handles YAML frontmatter extraction, slugification, and atomic file writes\n * for wiki pages.\n */\n\nimport { writeFile, rename, readFile, mkdir } from \"fs/promises\";\nimport path from \"path\";\nimport yaml from \"js-yaml\";\nimport type {\n ClaimCitation,\n ContradictionRef,\n ProvenanceMetadata,\n ProvenanceState,\n SourceSpan,\n} from \"./types.js\";\n\n/** Regex matching `^[...]` citation markers (paragraph or claim-level). */\nconst CITATION_MARKER_PATTERN = /\\^\\[([^\\]]+)\\]/g;\n\n/** Regex matching the optional `:start-end` or `#Lstart-Lend` span suffix on a citation entry. */\nconst SPAN_SUFFIX_PATTERN = /^(?<file>[^:#]+)(?:(?::(?<colonStart>\\d+)(?:[,-]\\s*(?<colonEnd>\\d+))?)|(?:#L(?<hashStart>\\d+)(?:-L(?<hashEnd>\\d+))?))?$/;\n\n/**\n * Regex matching a colon-form entry with two or more comma-separated line numbers,\n * e.g. `source.md:1, 12` or `source.md:3,7,42`. Captured `lines` is the raw\n * digit-and-comma string; each token expands into its own single-line SourceSpan.\n */\nconst COLON_MULTILINE_PATTERN = /^(?<file>[^:#]+):(?<lines>\\d+(?:,\\s*\\d+)+)$/;\n\n/** The minimum valid line number in a source span (lines are 1-indexed). */\nconst MIN_LINE_NUMBER = 1;\n\n/** The set of valid provenance state strings, used to reject unknown values. */\nconst VALID_PROVENANCE_STATES: ReadonlySet<ProvenanceState> = new Set([\n \"extracted\",\n \"merged\",\n \"inferred\",\n \"ambiguous\",\n]);\n\n/**\n * Convert a human-readable concept title to a filename slug.\n *\n * Unicode-aware: keeps letters and numbers from any script (Latin, CJK,\n * Cyrillic, Greek, Arabic, etc.). Strips punctuation, emoji, and other\n * symbols. The previous implementation used `\\w` without the `u` flag,\n * which only matches `[A-Za-z0-9_]` — that silently dropped CJK titles\n * to the empty string and caused the bug fixed in #35.\n *\n * Returns an empty string when the title contains no letters or numbers\n * at all (callers that write files should detect this and fail loudly\n * instead of writing a dotfile).\n */\nexport function slugify(title: string): string {\n return title\n .toLowerCase()\n .replace(/['']/g, \"\")\n .replace(/[^\\p{L}\\p{N}\\s-]/gu, \"\")\n .replace(/\\s+/g, \"-\")\n .replace(/-+/g, \"-\")\n .replace(/^-|-$/g, \"\");\n}\n\n/** Build YAML frontmatter string from key-value pairs. */\nexport function buildFrontmatter(fields: Record<string, unknown>): string {\n const dumped = yaml.dump(fields, { lineWidth: -1, quotingType: '\"' }).trimEnd();\n return `---\\n${dumped}\\n---`;\n}\n\n/** Parse YAML frontmatter from a markdown string. Returns { meta, body }. */\nexport function parseFrontmatter(content: string): {\n meta: Record<string, unknown>;\n body: string;\n} {\n const { meta, body } = parseFrontmatterStatus(content);\n return { meta, body };\n}\n\n/**\n * Like `parseFrontmatter` but also reports whether a frontmatter block was\n * present and whether the YAML inside it parsed cleanly. Callers that need\n * to distinguish \"no frontmatter block\" from \"malformed YAML\" (e.g. the\n * viewer collector, which surfaces these as different warnings) use this\n * variant; the plain `parseFrontmatter` stays a thin wrapper so existing\n * callers are unaffected.\n */\nexport function parseFrontmatterStatus(content: string): {\n meta: Record<string, unknown>;\n body: string;\n hasFrontmatterBlock: boolean;\n malformedFrontmatter: boolean;\n} {\n const match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n if (!match) {\n return { meta: {}, body: content, hasFrontmatterBlock: false, malformedFrontmatter: false };\n }\n\n let meta: Record<string, unknown> = {};\n let malformedFrontmatter = false;\n try {\n const parsed = yaml.load(match[1]);\n if (parsed && typeof parsed === \"object\") {\n meta = parsed as Record<string, unknown>;\n } else if (parsed !== null && parsed !== undefined) {\n // YAML parsed to a scalar/array — frontmatter must be a mapping.\n malformedFrontmatter = true;\n }\n } catch {\n malformedFrontmatter = true;\n }\n return { meta, body: match[2], hasFrontmatterBlock: true, malformedFrontmatter };\n}\n\n/** Atomically write a file (write to .tmp, then rename). */\nexport async function atomicWrite(filePath: string, content: string): Promise<void> {\n await mkdir(path.dirname(filePath), { recursive: true });\n const tmpPath = filePath + \".tmp\";\n await writeFile(tmpPath, content, \"utf-8\");\n await rename(tmpPath, filePath);\n}\n\n/**\n * Extract all source filenames from ^[filename.md] citation markers in a page body.\n * Handles paragraph form (`^[source.md]`), multi-source (`^[a.md, b.md]`), and the\n * claim-level extension that pins a line range (`^[source.md:42-58]` or\n * `^[source.md#L42-L58]`). Only the filename component is returned — span data is\n * discarded so existing callers continue to receive a flat filename list.\n * @param body - The markdown body text to parse.\n * @returns Array of unique source filenames.\n */\nexport function extractCitations(body: string): string[] {\n const filenames = new Set<string>();\n for (const citation of extractClaimCitations(body)) {\n for (const span of citation.spans) {\n if (span.file.length > 0) filenames.add(span.file);\n }\n }\n return [...filenames];\n}\n\n/**\n * Extract claim-level citations from a markdown body. Each `^[...]` marker\n * becomes one `ClaimCitation`; comma-separated entries inside a single marker\n * become multiple spans on that citation. Entries that fail to parse against\n * the span grammar are returned as bare-file spans so callers can still tell\n * the marker was present (the linter inspects `raw` to flag malformed forms).\n * @param body - The markdown body text to parse.\n * @returns Array of ClaimCitation objects in document order.\n */\nexport function extractClaimCitations(body: string): ClaimCitation[] {\n const citations: ClaimCitation[] = [];\n let match: RegExpExecArray | null;\n CITATION_MARKER_PATTERN.lastIndex = 0;\n while ((match = CITATION_MARKER_PATTERN.exec(body)) !== null) {\n const raw = match[1];\n const spans = parseCitationEntries(raw);\n if (spans.length > 0) citations.push({ raw, spans });\n }\n return citations;\n}\n\n/**\n * Split a raw citation marker interior (the content between `^[` and `]`) into\n * individual source-entry strings, without separating comma-separated line\n * numbers like the `12` in `source.md:1, 12`.\n *\n * The rule: split on every comma EXCEPT those followed by a purely-digit token\n * (which must be a line-number continuation). This correctly handles\n * digit-leading filenames such as `2024-notes.md`, `99problems.md`, and `1.md`.\n */\nexport function splitCitationMarker(inner: string): string[] {\n return inner.split(/,(?!\\s*\\d+\\s*(?:,|$))/);\n}\n\n/**\n * Parse the inside of `^[...]` into one or more SourceSpan entries.\n * Delegates splitting to {@link splitCitationMarker} so `source.md:1, 12`\n * stays as one entry while digit-leading filenames like `2024-notes.md` are\n * correctly recognised as separate entries.\n */\nfunction parseCitationEntries(inner: string): SourceSpan[] {\n const spans: SourceSpan[] = [];\n for (const part of splitCitationMarker(inner)) {\n const trimmed = part.trim();\n if (trimmed.length === 0) continue;\n spans.push(...parseSpanEntries(trimmed));\n }\n return spans;\n}\n\n/**\n * Dispatch a single trimmed citation entry to the right span parser.\n * Comma-separated line numbers (e.g. `file.md:1, 12`) expand into one\n * SourceSpan per line; everything else delegates to parseSpanEntry.\n */\nfunction parseSpanEntries(entry: string): SourceSpan[] {\n const multi = COLON_MULTILINE_PATTERN.exec(entry);\n if (multi?.groups) return parseCommaLines(multi.groups.file, multi.groups.lines);\n const single = parseSpanEntry(entry);\n return single !== undefined ? [single] : [];\n}\n\n/** Expand a comma-separated line-number string into individual single-line spans. */\nfunction parseCommaLines(file: string, linesStr: string): SourceSpan[] {\n const spans: SourceSpan[] = [];\n for (const token of linesStr.split(/,\\s*/)) {\n const lineNum = Number(token);\n if (isValidLineRange(lineNum, lineNum)) {\n spans.push({ file, lines: { start: lineNum, end: lineNum } });\n }\n }\n return spans;\n}\n\n/**\n * Parse a single citation entry (`file.md` / `file.md:1-3` / `file.md#L1-L3`).\n * Returns undefined when the parsed line range is semantically invalid (line\n * numbers must be >= 1 and end must be >= start).\n */\nfunction parseSpanEntry(entry: string): SourceSpan | undefined {\n const match = SPAN_SUFFIX_PATTERN.exec(entry);\n if (!match || !match.groups) {\n return { file: entry };\n }\n const { file, colonStart, colonEnd, hashStart, hashEnd } = match.groups;\n const start = colonStart ?? hashStart;\n const end = colonEnd ?? hashEnd;\n if (start === undefined) return { file };\n const startLine = Number(start);\n const endLine = end === undefined ? startLine : Number(end);\n if (!isValidLineRange(startLine, endLine)) return undefined;\n return { file, lines: { start: startLine, end: endLine } };\n}\n\n/** Returns true when both lines are >= 1 and end is not before start. */\nfunction isValidLineRange(start: number, end: number): boolean {\n return start >= MIN_LINE_NUMBER && end >= start;\n}\n\n/**\n * Detect whether a citation entry is malformed: bracket text that contains\n * `:` or `#` characters but does not match the documented span grammar, or\n * contains a semantically invalid line range (line 0 or end before start).\n * Used by the linter to flag broken claim-level provenance markers.\n */\nexport function isMalformedCitationEntry(entry: string): boolean {\n const trimmed = entry.trim();\n if (trimmed.length === 0) return true;\n if (!trimmed.includes(\":\") && !trimmed.includes(\"#\")) return false;\n const match = SPAN_SUFFIX_PATTERN.exec(trimmed);\n if (!match || !match.groups) return true;\n const { colonStart, colonEnd, hashStart, hashEnd } = match.groups;\n const start = colonStart ?? hashStart;\n const end = colonEnd ?? hashEnd;\n if (start === undefined) return false;\n const startLine = Number(start);\n const endLine = end === undefined ? startLine : Number(end);\n return !isValidLineRange(startLine, endLine);\n}\n\n/**\n * Inspect provenance for a page body, grouping every parsed span by source file.\n * Useful for tooling that wants to render a \"this page draws from\" panel without\n * worrying about how the markers were formatted in source. Each filename maps to\n * a deduplicated list of `{start, end}` line ranges (paragraph-only citations\n * appear as the empty array, signalling \"no specific span\").\n */\nexport function inspectProvenance(body: string): Map<string, Array<{ start: number; end: number }>> {\n const grouped = new Map<string, Array<{ start: number; end: number }>>();\n for (const citation of extractClaimCitations(body)) {\n for (const span of citation.spans) {\n const ranges = grouped.get(span.file) ?? [];\n if (span.lines && !rangeAlreadyTracked(ranges, span.lines)) {\n ranges.push(span.lines);\n }\n grouped.set(span.file, ranges);\n }\n }\n return grouped;\n}\n\n/** Has this start/end pair already been recorded for a file? */\nfunction rangeAlreadyTracked(\n ranges: Array<{ start: number; end: number }>,\n candidate: { start: number; end: number },\n): boolean {\n return ranges.some((r) => r.start === candidate.start && r.end === candidate.end);\n}\n\n/** Read a file, returning empty string if it doesn't exist. */\nexport async function safeReadFile(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf-8\");\n } catch {\n return \"\";\n }\n}\n\n/** Parse a numeric confidence value, clamping to 0..1 and rejecting non-numbers. */\nfunction parseConfidence(raw: unknown): number | undefined {\n if (typeof raw !== \"number\" || !Number.isFinite(raw)) return undefined;\n if (raw < 0) return 0;\n if (raw > 1) return 1;\n return raw;\n}\n\n/** Parse a provenance state string, returning undefined for unknown values. */\nfunction parseProvenanceState(raw: unknown): ProvenanceState | undefined {\n if (typeof raw !== \"string\") return undefined;\n return VALID_PROVENANCE_STATES.has(raw as ProvenanceState)\n ? (raw as ProvenanceState)\n : undefined;\n}\n\n/** Coerce a single contradiction entry to a ContradictionRef, or null if invalid. */\nfunction coerceContradictionEntry(entry: unknown): ContradictionRef | null {\n if (typeof entry === \"string\" && entry.trim().length > 0) {\n return { slug: entry.trim() };\n }\n if (entry && typeof entry === \"object\" && \"slug\" in entry) {\n const obj = entry as { slug: unknown; reason?: unknown };\n if (typeof obj.slug !== \"string\" || obj.slug.trim().length === 0) return null;\n const ref: ContradictionRef = { slug: obj.slug.trim() };\n if (typeof obj.reason === \"string\") ref.reason = obj.reason;\n return ref;\n }\n return null;\n}\n\n/** Parse a contradictedBy array, accepting strings or objects with slug. */\nfunction parseContradictedBy(raw: unknown): ContradictionRef[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const refs = raw\n .map(coerceContradictionEntry)\n .filter((ref): ref is ContradictionRef => ref !== null);\n return refs.length > 0 ? refs : undefined;\n}\n\n/**\n * Extract provenance metadata fields from a parsed frontmatter record.\n * Defensively handles missing or malformed values so existing pages without\n * the new fields continue to parse correctly.\n *\n * Note: legacy pages may also carry an `inferredParagraphs` frontmatter\n * field from earlier compiles. It is intentionally not parsed here —\n * the lint rule derives the count from the rendered body instead, so\n * the cached field is ignored.\n * @param meta - Raw frontmatter object as returned by parseFrontmatter.\n * @returns Typed provenance metadata with only the fields that were present.\n */\nexport function parseProvenanceMetadata(\n meta: Record<string, unknown>,\n): ProvenanceMetadata {\n return {\n confidence: parseConfidence(meta.confidence),\n provenanceState: parseProvenanceState(meta.provenanceState),\n contradictedBy: parseContradictedBy(meta.contradictedBy),\n };\n}\n\n/**\n * Validate that a wiki page has non-empty content and valid frontmatter.\n * Returns true if the page is valid.\n */\nexport function validateWikiPage(content: string): boolean {\n if (!content || content.trim().length === 0) return false;\n\n const { meta, body } = parseFrontmatter(content);\n if (!meta.title) return false;\n if (body.trim().length === 0) return false;\n\n return true;\n}\n","/**\n * Shared writer for the `sources/` directory.\n *\n * Centralises the slug → filename resolution every ingest path needs, so\n * `ingest`, `ingest-session`, and any future ingester get the same\n * protections:\n *\n * - Empty-slug guard (#35): titles that strip to \"\" (e.g. pure emoji)\n * fail with an actionable error instead of writing `sources/.md`.\n * - Stable basename-collision suffix (#36): two distinct sources that\n * slugify to the same name coexist as `name.md` and\n * `name-<8-hex-of-source>.md`; re-ingesting the same source still\n * overwrites in place because the existing file's frontmatter\n * `source` field is consulted before suffixing.\n */\n\nimport { mkdir, readFile, writeFile } from \"fs/promises\";\nimport path from \"path\";\nimport { createHash } from \"crypto\";\nimport { parseFrontmatter, slugify } from \"./markdown.js\";\nimport { SOURCES_DIR } from \"./constants.js\";\n\n/** Length of the hex hash suffix appended to disambiguate basename collisions. */\nconst COLLISION_HASH_LEN = 8;\n\n/**\n * Compute a short, stable hex hash of a source identifier. Stability\n * matters — re-ingesting the same source must always produce the same\n * hash so existing files are overwritten cleanly rather than\n * accumulating duplicates.\n */\nfunction shortHashOfSource(source: string): string {\n return createHash(\"sha256\").update(source).digest(\"hex\").slice(0, COLLISION_HASH_LEN);\n}\n\n/**\n * Resolve the destination filename for a slug + source identity:\n *\n * - When `${slug}.md` does not exist, return `${slug}.md`.\n * - When it exists and its frontmatter `source` matches the incoming\n * source, return `${slug}.md` so re-ingest stays idempotent.\n * - Otherwise return `${slug}-<hash>.md` so two distinct sources that\n * share a basename coexist instead of one silently overwriting the\n * other.\n */\nasync function resolveCollisionFreeFilename(slug: string, source: string): Promise<string> {\n const candidate = `${slug}.md`;\n const candidatePath = path.join(SOURCES_DIR, candidate);\n let existing: string;\n try {\n existing = await readFile(candidatePath, \"utf-8\");\n } catch (err) {\n const e = err as { code?: string };\n if (e.code === \"ENOENT\") return candidate;\n throw err;\n }\n const { meta } = parseFrontmatter(existing);\n if (typeof meta.source === \"string\" && meta.source === source) {\n return candidate;\n }\n return `${slug}-${shortHashOfSource(source)}.md`;\n}\n\n/**\n * Write a markdown document into `sources/` under a slug derived from\n * the title, applying the empty-slug guard and basename-collision\n * disambiguation. Returns the resolved destination path.\n *\n * @param title - Human-readable title used to derive the filename.\n * @param document - Full markdown content (frontmatter + body) to write.\n * @param source - Source identity (URL, file path, etc.) used both for\n * collision disambiguation and idempotency on re-ingest.\n */\nexport async function saveSource(\n title: string,\n document: string,\n source: string,\n): Promise<string> {\n const slug = slugify(title);\n // Defense in depth — even with the Unicode-aware slugifier (#35), a\n // title made entirely of punctuation/emoji/symbols still slugifies to\n // \"\". Without this guard the file would land at sources/.md.\n if (!slug) {\n throw new Error(\n `Could not derive a filename from title \"${title}\". ` +\n `The title contains no letter or number characters. ` +\n `Rename the source file to one with at least one letter or digit.`,\n );\n }\n await mkdir(SOURCES_DIR, { recursive: true });\n const filename = await resolveCollisionFreeFilename(slug, source);\n const destPath = path.join(SOURCES_DIR, filename);\n await writeFile(destPath, document, \"utf-8\");\n return destPath;\n}\n","/**\n * Shared constants for the llmwiki knowledge compiler.\n * Centralized config values to avoid magic numbers scattered across the codebase.\n */\n\n/** Maximum source file size in characters before truncation. */\nexport const MAX_SOURCE_CHARS = 100_000;\n\n/** Minimum source content length to ingest without a warning. */\nexport const MIN_SOURCE_CHARS = 50;\n\n/**\n * Default character budget for the combined source content sent to the LLM\n * during page generation for a single concept (issue #39).\n *\n * Caps the per-prompt content at ~200,000 chars (~50k tokens). When two or\n * more sources contribute to the same concept and their combined raw size\n * exceeds this budget, each source's slice is proportionally truncated so\n * the prompt fits the model's context window. Without this cap, popular\n * concepts that appear in many overlapping documents reliably blow past\n * the LLM provider's context limit and the compile crashes.\n *\n * Override via the LLMWIKI_PROMPT_BUDGET_CHARS env var when running against\n * larger-context (raise) or smaller-context (lower) models.\n */\nexport const DEFAULT_PROMPT_BUDGET_CHARS = 200_000;\n\n/** Env var that overrides DEFAULT_PROMPT_BUDGET_CHARS at runtime. */\nexport const PROMPT_BUDGET_ENV_VAR = \"LLMWIKI_PROMPT_BUDGET_CHARS\";\n\n/** Number of most relevant wiki pages to load for query context. */\nexport const QUERY_PAGE_LIMIT = 5;\n\n/** Maximum concurrent API calls during page generation. */\nexport const COMPILE_CONCURRENCY = 5;\n\n/** API retry configuration. */\nexport const RETRY_COUNT = 3;\nexport const RETRY_BASE_MS = 1000;\nexport const RETRY_MULTIPLIER = 4;\n\n/** Default provider when LLMWIKI_PROVIDER is not set. */\nexport const DEFAULT_PROVIDER = \"anthropic\";\n\n/** Default model per provider. */\nexport const PROVIDER_MODELS: Record<string, string> = {\n anthropic: \"claude-sonnet-4-20250514\",\n openai: \"gpt-4o\",\n ollama: \"llama3.1\",\n minimax: \"MiniMax-M2.7\",\n copilot: \"gpt-4o\",\n};\n\n/** Default Ollama API base URL. */\nexport const OLLAMA_DEFAULT_HOST = \"http://localhost:11434/v1\";\n\n/** GitHub Copilot API base URL (OpenAI-compatible, requires OAuth token). */\nexport const COPILOT_BASE_URL = \"https://api.githubcopilot.com\";\n\n/**\n * Default request timeout for cloud OpenAI-compatible providers (10 minutes).\n * Matches the OpenAI SDK's own default; called out here so it's explicit.\n */\nexport const OPENAI_DEFAULT_TIMEOUT_MS = 10 * 60 * 1000;\n\n/**\n * Default request timeout for Ollama (30 minutes). Local models on modest\n * hardware can take well over the cloud-provider default for a single\n * compile-time completion. Configurable via LLMWIKI_REQUEST_TIMEOUT_MS or\n * OLLAMA_TIMEOUT_MS env vars.\n */\nexport const OLLAMA_DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;\n\n/** Directory names relative to the project root. */\nexport const SOURCES_DIR = \"sources\";\nexport const CONCEPTS_DIR = \"wiki/concepts\";\nexport const QUERIES_DIR = \"wiki/queries\";\nexport const LLMWIKI_DIR = \".llmwiki\";\nexport const STATE_FILE = \".llmwiki/state.json\";\nexport const LOCK_FILE = \".llmwiki/lock\";\nexport const INDEX_FILE = \"wiki/index.md\";\nexport const MOC_FILE = \"wiki/MOC.md\";\nexport const EMBEDDINGS_FILE = \".llmwiki/embeddings.json\";\nexport const LAST_LINT_FILE = \".llmwiki/last-lint.json\";\n\n/** Supported image file extensions for vision-based ingest. */\nexport const IMAGE_EXTENSIONS = new Set([\".jpg\", \".jpeg\", \".png\", \".gif\", \".webp\"]);\n\n/** Supported transcript file extensions (content-sniff .txt separately). */\nexport const TRANSCRIPT_EXTENSIONS = new Set([\".vtt\", \".srt\"]);\n\n/** Max tokens for image-description completions. */\nexport const IMAGE_DESCRIBE_MAX_TOKENS = 2048;\n\n/** Pending review candidates awaiting approval/rejection. */\nexport const CANDIDATES_DIR = \".llmwiki/candidates\";\n\n/** Rejected review candidates archived for audit (not deleted). */\nexport const CANDIDATES_ARCHIVE_DIR = \".llmwiki/candidates/archive\";\n\n/** Number of most similar pages to return from embedding-based pre-filter. */\nexport const EMBEDDING_TOP_K = 15;\n\n/** Number of chunk candidates to retain after the semantic-similarity step. */\nexport const CHUNK_TOP_K = 30;\n\n/** Number of chunk candidates to keep after reranking. */\nexport const CHUNK_RERANK_KEEP = 12;\n\n/** Target chunk size in characters; chunks try to land near this length. */\nexport const CHUNK_TARGET_CHARS = 800;\n\n/** Hard upper bound on a single chunk's character length. */\nexport const CHUNK_MAX_CHARS = 1_400;\n\n/** Minimum standalone chunk size; smaller trailing fragments are merged back. */\nexport const CHUNK_MIN_CHARS = 200;\n\n/** Provenance metadata thresholds used by lint rules. */\nexport const LOW_CONFIDENCE_THRESHOLD = 0.5;\nexport const MAX_INFERRED_PARAGRAPHS_WITHOUT_CITATIONS = 2;\n\n/** Embedding model to use per provider. */\nexport const EMBEDDING_MODELS: Record<string, string> = {\n anthropic: \"voyage-3-lite\",\n openai: \"text-embedding-3-small\",\n ollama: \"nomic-embed-text\",\n};\n","/**\n * ANSI colored terminal output helpers.\n * Provides consistent styling for compilation progress, status messages,\n * and streaming token display.\n */\n\nconst RESET = \"\\x1b[0m\";\nconst BOLD = \"\\x1b[1m\";\nconst DIM = \"\\x1b[2m\";\nconst GREEN = \"\\x1b[32m\";\nconst YELLOW = \"\\x1b[33m\";\nconst BLUE = \"\\x1b[34m\";\nconst MAGENTA = \"\\x1b[35m\";\nconst CYAN = \"\\x1b[36m\";\nconst RED = \"\\x1b[31m\";\n\nexport function bold(text: string): string {\n return `${BOLD}${text}${RESET}`;\n}\n\nexport function dim(text: string): string {\n return `${DIM}${text}${RESET}`;\n}\n\nexport function success(text: string): string {\n return `${GREEN}${text}${RESET}`;\n}\n\nexport function warn(text: string): string {\n return `${YELLOW}${text}${RESET}`;\n}\n\nexport function info(text: string): string {\n return `${BLUE}${text}${RESET}`;\n}\n\nexport function error(text: string): string {\n return `${RED}${text}${RESET}`;\n}\n\nexport function source(text: string): string {\n return `${CYAN}${text}${RESET}`;\n}\n\n/**\n * Process-wide quiet flag. Toggled by `quickstart --json` so the\n * structured envelope is the only thing on stdout — every status/header\n * call short-circuits while the flag is set.\n *\n * Default is false, preserving byte-for-byte behaviour for every other\n * command. Callers are responsible for restoring the flag in a `finally`\n * block if they need partial silence.\n */\nlet quietMode = false;\n\n/** Toggle the process-wide quiet flag. */\nexport function setQuiet(quiet: boolean): void {\n quietMode = quiet;\n}\n\n/** Print a status line with an icon. No-op while quiet mode is enabled. */\nexport function status(icon: string, message: string): void {\n if (quietMode) return;\n console.log(`${icon} ${message}`);\n}\n\n/** Print a section header. No-op while quiet mode is enabled. */\nexport function header(title: string): void {\n if (quietMode) return;\n console.log(`\\n${BOLD}${title}${RESET}`);\n console.log(dim(\"─\".repeat(Math.min(title.length + 4, 60))));\n}\n","/**\n * Web URL ingestion module.\n * Fetches a URL, extracts readable content using Mozilla Readability,\n * and converts the result to clean markdown via Turndown.\n *\n * Throws descriptive errors on network failures or when the page\n * cannot be parsed into readable content.\n */\n\nimport { JSDOM } from \"jsdom\";\nimport { Readability } from \"@mozilla/readability\";\nimport TurndownService from \"turndown\";\n\ninterface WebIngestResult {\n title: string;\n content: string;\n}\n\n/** Fetch a URL and return its readable content as markdown. */\nasync function fetchAndParse(url: string): Promise<Response> {\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(`Failed to fetch ${url}: HTTP ${response.status}`);\n }\n return response;\n}\n\n/** Extract readable content from raw HTML using Readability. */\nfunction extractReadableContent(html: string, url: string): { title: string; htmlContent: string } {\n const dom = new JSDOM(html, { url });\n const reader = new Readability(dom.window.document);\n const article = reader.parse();\n\n if (!article || !article.content) {\n throw new Error(`Could not extract readable content from ${url}`);\n }\n\n return {\n title: article.title || \"Untitled\",\n htmlContent: article.content,\n };\n}\n\n/** Convert HTML to clean markdown using Turndown. */\nfunction convertToMarkdown(html: string): string {\n const turndown = new TurndownService({ headingStyle: \"atx\" });\n return turndown.turndown(html);\n}\n\n/**\n * Ingest a web URL and return its content as markdown.\n * @param url - The URL to fetch and convert.\n * @returns An object with the extracted title and markdown content.\n * @throws On network failure or unparseable content.\n */\nexport default async function ingestWeb(url: string): Promise<WebIngestResult> {\n const response = await fetchAndParse(url);\n const html = await response.text();\n const { title, htmlContent } = extractReadableContent(html, url);\n const content = convertToMarkdown(htmlContent);\n\n return { title, content };\n}\n","/**\n * Local file ingestion module.\n * Reads .md and .txt files from the local filesystem and returns their\n * content as markdown. Markdown files are returned as-is; plain text files\n * are wrapped in a markdown code block. All other extensions are rejected.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { titleFromFilename, type IngestedSource } from \"./shared.js\";\n\n/** Plain-text file extensions handled directly by this module. */\nconst SUPPORTED_EXTENSIONS = new Set([\".md\", \".txt\"]);\n\n/** Wrap plain text content in a markdown fenced block. */\nfunction wrapPlainText(text: string): string {\n return `\\`\\`\\`\\n${text}\\n\\`\\`\\``;\n}\n\n/**\n * Ingest a local file and return its content as markdown.\n * @param filePath - Absolute or relative path to a .md or .txt file.\n * @returns An object with a title derived from the filename and the markdown content.\n * @throws On unsupported file type or read failure.\n */\nexport default async function ingestFile(filePath: string): Promise<IngestedSource> {\n const ext = path.extname(filePath).toLowerCase();\n\n if (!SUPPORTED_EXTENSIONS.has(ext)) {\n throw new Error(\n `Unsupported file type \"${ext}\". Only .md and .txt files are supported.`\n );\n }\n\n const raw = await readFile(filePath, \"utf-8\");\n const title = titleFromFilename(filePath);\n const content = ext === \".md\" ? raw : wrapPlainText(raw);\n\n return { title, content };\n}\n","/**\n * Shared helpers used by every ingest module.\n *\n * Centralizes the IngestedSource result shape and title-derivation logic so\n * each per-format ingester (file, pdf, image, transcript, web) doesn't\n * reimplement the same primitives.\n */\n\nimport path from \"path\";\n\n/** Common shape returned by every ingest module. */\nexport interface IngestedSource {\n title: string;\n content: string;\n}\n\n/**\n * Derive a human-readable title from a filename.\n *\n * Strips the extension and converts dashes/underscores to spaces so that\n * \"quarterly_report.pdf\" becomes \"quarterly report\".\n *\n * @param filePath - Path to a source file.\n * @returns Humanized title (lowercase preserved, no extension).\n */\nexport function titleFromFilename(filePath: string): string {\n const basename = path.basename(filePath, path.extname(filePath));\n return basename.replace(/[-_]+/g, \" \").trim();\n}\n","/**\n * PDF ingestion module.\n *\n * Reads a local PDF file using the pdf-parse v2 PDFParse class, extracts the\n * text content via getText() and the document metadata via getInfo(). The\n * title comes from the PDF's Info dictionary when present, falling back to\n * the filename. Pages are joined into a single markdown body.\n *\n * pdf-parse (and its transitive pdfjs-dist) is imported dynamically so the\n * cost of loading the PDF parser is only paid when a PDF is actually being\n * ingested — `node dist/cli.js --help` and every other non-PDF code path\n * stays lean.\n */\n\nimport { readFile } from \"fs/promises\";\nimport { titleFromFilename, type IngestedSource } from \"./shared.js\";\n\n/** Extract the title from PDF metadata or fall back to the filename. */\nexport function resolveTitle(filePath: string, info: unknown): string {\n if (info && typeof info === \"object\") {\n const titleField = (info as Record<string, unknown>)[\"Title\"];\n if (typeof titleField === \"string\" && titleField.trim().length > 0) {\n return titleField.trim();\n }\n }\n return titleFromFilename(filePath);\n}\n\n/**\n * Ingest a local PDF file and return its text content with the document title.\n *\n * pdf-parse is imported dynamically so this module's load cost stays minimal\n * for non-PDF code paths (the parser pulls in pdfjs-dist which is sizeable).\n *\n * @param filePath - Absolute or relative path to a .pdf file.\n * @returns An object with the document title and extracted text content.\n * @throws On read failure or unparseable PDF.\n */\nexport default async function ingestPdf(filePath: string): Promise<IngestedSource> {\n const { PDFParse } = await import(\"pdf-parse\");\n\n const buffer = await readFile(filePath);\n const parser = new PDFParse({ data: new Uint8Array(buffer) });\n\n try {\n // Sequential calls are required: pdfjs-dist's LoopbackPort.postMessage\n // uses structuredClone internally; concurrent calls cause a DataCloneError\n // when the port tries to transfer the same underlying state simultaneously.\n const textResult = await parser.getText();\n const infoResult = await parser.getInfo();\n\n const title = resolveTitle(filePath, infoResult.info);\n const content = textResult.text.trim();\n return { title, content };\n } finally {\n await parser.destroy();\n }\n}\n","/**\n * Image ingestion module using LLM vision capabilities.\n *\n * Reads a local image file, encodes it as base64, and sends it to the\n * configured LLM provider's vision endpoint for OCR-plus-description\n * extraction. Requires the active provider to support image content blocks\n * (currently: Anthropic).\n *\n * Throws a clear error when the provider does not support vision, rather\n * than falling back silently.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { buildAnthropicClientOptions } from \"../providers/anthropic.js\";\nimport { IMAGE_DESCRIBE_MAX_TOKENS } from \"../utils/constants.js\";\nimport { resolveAnthropicAuthFromEnv, resolveAnthropicBaseURLFromEnv, resolveAnthropicModelFromEnv } from \"../utils/claude-settings.js\";\nimport { PROVIDER_MODELS } from \"../utils/constants.js\";\nimport { titleFromFilename, type IngestedSource } from \"./shared.js\";\n\n/** Mime types supported by Anthropic vision. */\ntype AnthropicImageMediaType = \"image/jpeg\" | \"image/png\" | \"image/gif\" | \"image/webp\";\n\nconst EXTENSION_TO_MIME: Record<string, AnthropicImageMediaType> = {\n \".jpg\": \"image/jpeg\",\n \".jpeg\": \"image/jpeg\",\n \".png\": \"image/png\",\n \".gif\": \"image/gif\",\n \".webp\": \"image/webp\",\n};\n\n/** Return the MIME type for an image file, or throw on unknown extension. */\nfunction mimeTypeForExtension(ext: string): AnthropicImageMediaType {\n const mimeType = EXTENSION_TO_MIME[ext.toLowerCase()];\n if (!mimeType) {\n throw new Error(\n `Unsupported image extension \"${ext}\". Supported: ${Object.keys(EXTENSION_TO_MIME).join(\", \")}`,\n );\n }\n return mimeType;\n}\n\n/** Build an Anthropic client from the current environment config. */\nfunction buildClient(): Anthropic {\n const baseURL = resolveAnthropicBaseURLFromEnv();\n const auth = resolveAnthropicAuthFromEnv();\n return new Anthropic(buildAnthropicClientOptions({ baseURL, ...auth }));\n}\n\n/** Send an image to Anthropic vision and return the extracted description. */\nasync function describeImageWithVision(\n client: Anthropic,\n model: string,\n imageData: string,\n mimeType: AnthropicImageMediaType,\n): Promise<string> {\n const response = await client.messages.create({\n model,\n max_tokens: IMAGE_DESCRIBE_MAX_TOKENS,\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"image\",\n source: { type: \"base64\", media_type: mimeType, data: imageData },\n },\n {\n type: \"text\",\n text: \"Extract and transcribe all text visible in this image. Then provide a detailed description of any non-text visual content. Format your response as markdown.\",\n },\n ],\n },\n ],\n });\n\n const textBlock = response.content.find((block) => block.type === \"text\");\n return textBlock?.type === \"text\" ? textBlock.text : \"\";\n}\n\n/**\n * Ingest a local image file using LLM vision for OCR and description.\n *\n * Only Anthropic is supported for vision. The active provider must be\n * Anthropic; if not, a clear error is thrown rather than degrading silently.\n *\n * @param filePath - Absolute or relative path to an image file.\n * @returns An object with a title derived from the filename and the extracted content.\n * @throws When the provider does not support vision or on read/API failure.\n */\nexport default async function ingestImage(filePath: string): Promise<IngestedSource> {\n const providerName = process.env.LLMWIKI_PROVIDER ?? \"anthropic\";\n\n if (providerName !== \"anthropic\") {\n throw new Error(\n `Image ingest requires the Anthropic provider (vision). ` +\n `Current provider: \"${providerName}\". ` +\n `Set LLMWIKI_PROVIDER=anthropic and ANTHROPIC_API_KEY to use image ingest.`,\n );\n }\n\n const ext = path.extname(filePath).toLowerCase();\n const mimeType = mimeTypeForExtension(ext);\n const imageBuffer = await readFile(filePath);\n const imageData = imageBuffer.toString(\"base64\");\n\n const client = buildClient();\n const model = resolveAnthropicModelFromEnv() ?? PROVIDER_MODELS.anthropic;\n const content = await describeImageWithVision(client, model, imageData, mimeType);\n const title = titleFromFilename(filePath);\n\n return { title, content };\n}\n","/**\n * Anthropic LLM provider implementation.\n *\n * Wraps the @anthropic-ai/sdk to implement the LLMProvider interface.\n * Handles complete, streaming, and tool-use calls against Claude models.\n */\n\nimport Anthropic, { type ClientOptions } from \"@anthropic-ai/sdk\";\nimport type { LLMProvider, LLMMessage, LLMTool } from \"../utils/provider.js\";\nimport { EMBEDDING_MODELS } from \"../utils/constants.js\";\n\nconst VOYAGE_EMBEDDINGS_URL = \"https://api.voyageai.com/v1/embeddings\";\n\n/**\n * Builds the client options for the Anthropic SDK.\n *\n * Handles optional baseURL and filters out empty values so the SDK\n * can fall back to its internal defaults when not specified.\n */\ninterface AnthropicProviderOptions {\n apiKey?: string;\n authToken?: string;\n baseURL?: string;\n}\n\nexport function buildAnthropicClientOptions(\n options: AnthropicProviderOptions = {},\n): ClientOptions {\n const trimmedBaseURL = options.baseURL?.trim();\n const trimmedApiKey = options.apiKey?.trim();\n const trimmedAuthToken = options.authToken?.trim();\n\n const result: ClientOptions = {};\n\n if (trimmedApiKey) {\n result.apiKey = trimmedApiKey;\n }\n if (trimmedAuthToken) {\n result.authToken = trimmedAuthToken;\n }\n\n if (!trimmedBaseURL) {\n return result;\n }\n\n const normalizedBaseURL =\n trimmedBaseURL.endsWith(\"/\") && trimmedBaseURL.length > 1\n ? trimmedBaseURL.slice(0, -1)\n : trimmedBaseURL;\n\n result.baseURL = normalizedBaseURL;\n return result;\n}\n\n\n/** Anthropic-backed LLM provider using the official SDK. */\nexport class AnthropicProvider implements LLMProvider {\n private readonly client: Anthropic;\n private readonly model: string;\n\n constructor(model: string, options: AnthropicProviderOptions = {}) {\n this.model = model;\n this.client = new Anthropic(buildAnthropicClientOptions(options));\n }\n\n /** Send a single non-streaming completion request. */\n async complete(system: string, messages: LLMMessage[], maxTokens: number): Promise<string> {\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: maxTokens,\n system,\n messages,\n });\n\n const textBlock = response.content.find((block) => block.type === \"text\");\n return textBlock?.type === \"text\" ? textBlock.text : \"\";\n }\n\n /** Stream a completion, invoking onToken for each text chunk. */\n async stream(\n system: string,\n messages: LLMMessage[],\n maxTokens: number,\n onToken?: (text: string) => void,\n ): Promise<string> {\n const stream = this.client.messages.stream({\n model: this.model,\n max_tokens: maxTokens,\n system,\n messages,\n });\n\n let fullText = \"\";\n for await (const event of stream) {\n if (event.type === \"content_block_delta\" && event.delta.type === \"text_delta\") {\n fullText += event.delta.text;\n onToken?.(event.delta.text);\n }\n }\n\n return fullText;\n }\n\n /** Call Claude with tool definitions and return the parsed tool input as JSON. */\n async toolCall(\n system: string,\n messages: LLMMessage[],\n tools: LLMTool[],\n maxTokens: number,\n ): Promise<string> {\n const anthropicTools: Anthropic.Tool[] = tools.map((t) => ({\n name: t.name,\n description: t.description,\n input_schema: t.input_schema as Anthropic.Tool.InputSchema,\n }));\n\n const response = await this.client.messages.create({\n model: this.model,\n max_tokens: maxTokens,\n system,\n messages,\n tools: anthropicTools,\n tool_choice: { type: \"any\" },\n });\n\n const toolBlock = response.content.find((block) => block.type === \"tool_use\");\n if (toolBlock?.type === \"tool_use\") {\n return JSON.stringify(toolBlock.input);\n }\n\n const textBlock = response.content.find((block) => block.type === \"text\");\n return textBlock?.type === \"text\" ? textBlock.text : \"\";\n }\n\n /**\n * Produce a single embedding vector via the Voyage API.\n *\n * Anthropic does not ship a first-party embeddings endpoint, so we delegate\n * to Voyage (their recommended partner). Requires VOYAGE_API_KEY.\n */\n async embed(text: string): Promise<number[]> {\n const apiKey = process.env.VOYAGE_API_KEY?.trim();\n if (!apiKey) {\n throw new Error(\n \"VOYAGE_API_KEY is not set. Anthropic embeddings use Voyage — set VOYAGE_API_KEY to enable semantic search.\",\n );\n }\n\n const response = await fetch(VOYAGE_EMBEDDINGS_URL, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify({ input: text, model: EMBEDDING_MODELS.anthropic }),\n });\n\n if (!response.ok) {\n const detail = await response.text();\n throw new Error(`Voyage embeddings request failed (${response.status}): ${detail}`);\n }\n\n const json = (await response.json()) as { data?: Array<{ embedding?: number[] }> };\n const vector = json.data?.[0]?.embedding;\n if (!Array.isArray(vector)) {\n throw new Error(\"Voyage embeddings response did not include a vector.\");\n }\n return vector;\n }\n}\n","/**\n * Claude settings fallback helpers.\n *\n * Provides a narrow, read-only integration with `~/.claude/settings.json`.\n * We only read the `env` object and only extract Anthropic-related values that\n * llmwiki can safely consume. Explicit process env values remain higher priority.\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport path from \"node:path\";\n\nconst CLAUDE_SETTINGS_PATH_ENV = \"LLMWIKI_CLAUDE_SETTINGS_PATH\";\n\ninterface ClaudeSettingsEnv {\n ANTHROPIC_API_KEY?: string;\n ANTHROPIC_AUTH_TOKEN?: string;\n ANTHROPIC_BASE_URL?: string;\n ANTHROPIC_MODEL?: string;\n}\n\ninterface AnthropicAuthConfig {\n apiKey?: string;\n authToken?: string;\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === \"object\" && value !== null;\n}\n\nfunction normalize(value: unknown): string | undefined {\n if (typeof value !== \"string\") return undefined;\n const trimmed = value.trim();\n return trimmed.length > 0 ? trimmed : undefined;\n}\n\nfunction resolveClaudeSettingsPath(env: NodeJS.ProcessEnv): string {\n return env[CLAUDE_SETTINGS_PATH_ENV] ?? path.join(homedir(), \".claude\", \"settings.json\");\n}\n\nfunction readClaudeSettingsFile(settingsPath: string): string | undefined {\n try {\n return readFileSync(settingsPath, \"utf8\");\n } catch (err) {\n if (isRecord(err) && err.code === \"ENOENT\") {\n return undefined;\n }\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to read Claude settings at \"${settingsPath}\": ${message}`);\n }\n}\n\nexport function readClaudeSettingsEnv(env: NodeJS.ProcessEnv = process.env): ClaudeSettingsEnv | undefined {\n const settingsPath = resolveClaudeSettingsPath(env);\n const raw = readClaudeSettingsFile(settingsPath);\n if (!raw) return undefined;\n\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Failed to parse Claude settings at \"${settingsPath}\": ${message}`);\n }\n\n if (!isRecord(parsed) || !isRecord(parsed.env)) {\n return undefined;\n }\n\n const values: ClaudeSettingsEnv = {\n ANTHROPIC_API_KEY: normalize(parsed.env.ANTHROPIC_API_KEY),\n ANTHROPIC_AUTH_TOKEN: normalize(parsed.env.ANTHROPIC_AUTH_TOKEN),\n ANTHROPIC_BASE_URL: normalize(parsed.env.ANTHROPIC_BASE_URL),\n ANTHROPIC_MODEL: normalize(parsed.env.ANTHROPIC_MODEL),\n };\n\n if (!values.ANTHROPIC_API_KEY && !values.ANTHROPIC_AUTH_TOKEN && !values.ANTHROPIC_BASE_URL && !values.ANTHROPIC_MODEL) {\n return undefined;\n }\n return values;\n}\n\nfunction tryReadClaudeSettingsEnv(env: NodeJS.ProcessEnv): ClaudeSettingsEnv | undefined {\n try {\n return readClaudeSettingsEnv(env);\n } catch {\n return undefined;\n }\n}\n\nfunction validateAnthropicBaseURL(value: string): string {\n const normalized = value.trim();\n try {\n const parsed = new URL(normalized);\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(\"Must use http:// or https:// protocol.\");\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : \"Must be a valid http(s) URL.\";\n throw new Error(`Invalid ANTHROPIC_BASE_URL: \"${normalized}\". ${message}`);\n }\n return normalized;\n}\n\nexport function resolveAnthropicAuthFromEnv(env: NodeJS.ProcessEnv = process.env): AnthropicAuthConfig {\n const explicitApiKey = normalize(env.ANTHROPIC_API_KEY);\n if (explicitApiKey) return { apiKey: explicitApiKey };\n\n const explicitAuthToken = normalize(env.ANTHROPIC_AUTH_TOKEN);\n if (explicitAuthToken) return { authToken: explicitAuthToken };\n\n const fallback = readClaudeSettingsEnv(env);\n if (fallback?.ANTHROPIC_API_KEY) return { apiKey: fallback.ANTHROPIC_API_KEY };\n if (fallback?.ANTHROPIC_AUTH_TOKEN) return { authToken: fallback.ANTHROPIC_AUTH_TOKEN };\n return {};\n}\n\nexport function resolveAnthropicModelFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const explicitModel = env.LLMWIKI_MODEL;\n if (explicitModel !== undefined) return explicitModel;\n return tryReadClaudeSettingsEnv(env)?.ANTHROPIC_MODEL;\n}\n\nexport function resolveAnthropicBaseURLFromEnv(env: NodeJS.ProcessEnv = process.env): string | undefined {\n const explicitBaseURL = normalize(env.ANTHROPIC_BASE_URL);\n if (explicitBaseURL) return validateAnthropicBaseURL(explicitBaseURL);\n\n const fallbackBaseURL = tryReadClaudeSettingsEnv(env)?.ANTHROPIC_BASE_URL;\n if (!fallbackBaseURL) return undefined;\n return validateAnthropicBaseURL(fallbackBaseURL);\n}\n","/**\n * Transcript ingestion module.\n *\n * Handles three transcript source types:\n * 1. YouTube URLs — fetched via the youtube-transcript package.\n * 2. WebVTT (.vtt) — speaker/time markers preserved in output.\n * 3. SubRip (.srt) — speaker/time markers preserved in output.\n * 4. Plain-text (.txt) with speaker tags (e.g. \"Speaker: text\").\n *\n * Speaker and timing metadata are kept in the output so downstream\n * compilation can reference them. Content is returned as markdown.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { titleFromFilename, type IngestedSource } from \"./shared.js\";\n// youtube-transcript exports proper ESM via its package.json `exports` map.\n// Import from the package root; the \"import\" condition resolves to\n// ./dist/esm/index.js and includes bundled .d.ts types.\nimport { YoutubeTranscript } from \"youtube-transcript\";\n\n/** Pattern that identifies a YouTube URL. */\nconst YOUTUBE_URL_PATTERN = /^https?:\\/\\/(www\\.)?(youtube\\.com\\/watch|youtu\\.be\\/)/;\n\n/** Pattern for SRT sequence number lines (numeric-only). */\nconst SRT_SEQUENCE_PATTERN = /^\\d+$/;\n\n/** Pattern for SRT/VTT timestamp lines. */\nconst TIMESTAMP_PATTERN = /\\d{2}:\\d{2}[:.]\\d{2}/;\n\n/** Number of milliseconds in one minute. */\nconst MS_PER_MINUTE = 60_000;\n\n/** Number of milliseconds in one second. */\nconst MS_PER_SECOND = 1_000;\n\n/** Check whether a source string is a YouTube URL. */\nexport function isYoutubeUrl(source: string): boolean {\n return YOUTUBE_URL_PATTERN.test(source);\n}\n\n/** Extract the YouTube video ID from a URL. */\nfunction extractVideoId(url: string): string {\n const match = url.match(/(?:v=|youtu\\.be\\/)([^&?/]+)/);\n if (!match) {\n throw new Error(`Could not extract video ID from YouTube URL: ${url}`);\n }\n return match[1];\n}\n\n/** Format a millisecond offset as a \"MM:SS\" timestamp. */\nfunction formatOffset(offsetMs: number): string {\n const minutes = Math.floor(offsetMs / MS_PER_MINUTE);\n const seconds = Math.floor((offsetMs % MS_PER_MINUTE) / MS_PER_SECOND);\n return `${String(minutes).padStart(2, \"0\")}:${String(seconds).padStart(2, \"0\")}`;\n}\n\n/** Fetch and format a YouTube transcript as markdown. */\nasync function fetchYoutubeTranscript(url: string): Promise<IngestedSource> {\n const videoId = extractVideoId(url);\n const segments = await YoutubeTranscript.fetchTranscript(videoId);\n\n if (!segments || segments.length === 0) {\n throw new Error(`No transcript available for YouTube video: ${url}`);\n }\n\n const lines = segments.map((seg) => `[${formatOffset(seg.offset)}] ${seg.text}`);\n\n return {\n title: `YouTube Transcript ${videoId}`,\n content: lines.join(\"\\n\"),\n };\n}\n\n/** Decide whether a trimmed line is a VTT/SRT cue timestamp marker. */\nfunction isCueTimestamp(trimmed: string): boolean {\n return TIMESTAMP_PATTERN.test(trimmed) && trimmed.includes(\"-->\");\n}\n\n/** Parse a VTT file, preserving speaker cues and timestamps. */\nfunction parseVtt(raw: string, filePath: string): IngestedSource {\n const lines = raw.split(\"\\n\");\n const output: string[] = [];\n let inCue = false;\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === \"WEBVTT\" || trimmed === \"\") {\n inCue = false;\n continue;\n }\n if (isCueTimestamp(trimmed)) {\n output.push(`\\n**[${trimmed}]**`);\n inCue = true;\n continue;\n }\n if (inCue && trimmed.length > 0) {\n output.push(trimmed);\n }\n }\n\n return { title: titleFromFilename(filePath), content: output.join(\"\\n\").trim() };\n}\n\n/** Parse an SRT file, preserving speaker cues and timestamps. */\nfunction parseSrt(raw: string, filePath: string): IngestedSource {\n const lines = raw.split(\"\\n\");\n const output: string[] = [];\n\n for (const line of lines) {\n const trimmed = line.trim();\n if (trimmed === \"\" || SRT_SEQUENCE_PATTERN.test(trimmed)) {\n continue;\n }\n if (isCueTimestamp(trimmed)) {\n output.push(`\\n**[${trimmed}]**`);\n continue;\n }\n if (trimmed.length > 0) {\n output.push(trimmed);\n }\n }\n\n return { title: titleFromFilename(filePath), content: output.join(\"\\n\").trim() };\n}\n\n/** Parse a plain-text transcript, preserving speaker tags. */\nfunction parsePlainTranscript(raw: string, filePath: string): IngestedSource {\n // Plain .txt transcripts are returned as-is; speaker lines like \"Alice: ...\"\n // are naturally readable.\n return { title: titleFromFilename(filePath), content: raw.trim() };\n}\n\n/**\n * Ingest a transcript source: a YouTube URL or a local .vtt/.srt/.txt file.\n *\n * @param source - YouTube URL or path to a transcript file.\n * @returns Title and markdown-formatted content with speaker/time markers.\n * @throws On network failure, missing transcript, or unsupported file type.\n */\nexport default async function ingestTranscript(source: string): Promise<IngestedSource> {\n if (isYoutubeUrl(source)) {\n return fetchYoutubeTranscript(source);\n }\n\n const ext = path.extname(source).toLowerCase();\n const raw = await readFile(source, \"utf-8\");\n\n if (ext === \".vtt\") return parseVtt(raw, source);\n if (ext === \".srt\") return parseSrt(raw, source);\n if (ext === \".txt\") return parsePlainTranscript(raw, source);\n\n throw new Error(\n `Unsupported transcript file type \"${ext}\". Supported: .vtt, .srt, .txt`,\n );\n}\n","/**\n * Commander action for `llmwiki ingest-session <path>`.\n *\n * Accepts a single session export file or a directory of session files.\n * Each file is detected, parsed, and written to `sources/` as a markdown\n * document with YAML frontmatter recording the adapter name and timestamps.\n *\n * Supported formats (auto-detected): Claude, Codex, Cursor.\n */\n\nimport path from \"path\";\nimport { readdir, stat } from \"fs/promises\";\nimport { buildFrontmatter } from \"../utils/markdown.js\";\nimport { saveSource } from \"../utils/source-writer.js\";\nimport * as output from \"../utils/output.js\";\nimport { parseSessionFile, formatSessionAsMarkdown } from \"../adapters/registry.js\";\nimport type { NormalizedSession } from \"../adapters/types.js\";\n\n/** Result of ingesting a single session file. */\ninterface SessionIngestResult {\n filename: string;\n adapter: string;\n title: string;\n source: string;\n}\n\n/** Build the YAML frontmatter for a session source. */\nfunction buildSessionFrontmatter(session: NormalizedSession, sourcePath: string): string {\n const meta: Record<string, unknown> = {\n title: session.title,\n source: sourcePath,\n adapter: session.adapter,\n ingestedAt: new Date().toISOString(),\n };\n if (session.startedAt) meta.sessionStartedAt = session.startedAt;\n if (session.endedAt) meta.sessionEndedAt = session.endedAt;\n if (session.participantIdentity) meta.participant = session.participantIdentity;\n\n return buildFrontmatter(meta);\n}\n\n/**\n * Write a session as a markdown file under `sources/` using the shared\n * source writer — gets the empty-slug guard (#35) and the\n * basename-collision suffix (#36) for free, so two sessions with the\n * same title from different transcript files coexist instead of one\n * silently overwriting the other.\n */\nasync function saveSessionSource(session: NormalizedSession, sourcePath: string): Promise<string> {\n const frontmatter = buildSessionFrontmatter(session, sourcePath);\n const body = formatSessionAsMarkdown(session);\n const document = `${frontmatter}\\n\\n${body}\\n`;\n return saveSource(session.title, document, sourcePath);\n}\n\n/**\n * Ingest a single session file.\n * @throws When the file is not recognised or is malformed.\n */\nexport async function ingestSessionFile(filePath: string): Promise<SessionIngestResult> {\n output.status(\"*\", output.info(`Ingesting session: ${filePath}`));\n\n const session = await parseSessionFile(filePath);\n const savedPath = await saveSessionSource(session, filePath);\n\n output.status(\n \"+\",\n output.success(\n `Saved ${output.bold(path.basename(savedPath))} [${session.adapter}] → ${output.source(savedPath)}`\n )\n );\n\n return {\n filename: path.basename(savedPath),\n adapter: session.adapter,\n title: session.title,\n source: filePath,\n };\n}\n\n/** Collect all files directly inside a directory (non-recursive). */\nasync function listDirectoryFiles(dirPath: string): Promise<string[]> {\n const entries = await readdir(dirPath);\n const files: string[] = [];\n\n for (const entry of entries) {\n const full = path.join(dirPath, entry);\n const info = await stat(full);\n if (info.isFile()) files.push(full);\n }\n\n return files;\n}\n\n/**\n * Ingest all session files in a directory. Recognised files import; the\n * rest are skipped with a warning so a single bad file doesn't abort\n * the whole batch.\n *\n * Throws when ZERO files imported successfully, even if the directory\n * contained candidate files. A bulk run with nothing usable is a\n * failure mode the user needs to know about — exiting 0 with \"Imported\n * 0 session(s), skipped N\" was easy to miss in scripts.\n */\nasync function ingestDirectory(dirPath: string): Promise<void> {\n const files = await listDirectoryFiles(dirPath);\n\n if (files.length === 0) {\n throw new Error(`No files found in directory: ${dirPath}`);\n }\n\n output.status(\"*\", output.info(`Scanning ${files.length} file(s) in: ${dirPath}`));\n\n let imported = 0;\n let skipped = 0;\n\n for (const file of files) {\n try {\n await ingestSessionFile(file);\n imported++;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.warn(`Skipped ${path.basename(file)}: ${message}`));\n skipped++;\n }\n }\n\n if (imported === 0) {\n throw new Error(\n `No sessions imported from ${dirPath} (${skipped} file(s) skipped). ` +\n `Check that at least one file is in a supported session format.`,\n );\n }\n\n output.status(\n \"→\",\n output.dim(`Imported ${imported} session(s), skipped ${skipped}.`)\n );\n}\n\n/**\n * Entry point for `llmwiki ingest-session <pathOrDir>`.\n * Dispatches to single-file or directory import based on the target type.\n */\nexport default async function ingestSession(targetPath: string): Promise<void> {\n const info = await stat(targetPath).catch(() => {\n throw new Error(`Path not found: ${targetPath}`);\n });\n\n if (info.isDirectory()) {\n await ingestDirectory(targetPath);\n } else {\n await ingestSessionFile(targetPath);\n }\n\n output.status(\"→\", output.dim(\"Next: llmwiki compile\"));\n}\n","/**\n * Adapter for Claude Code session exports.\n *\n * Claude Code writes session transcripts as newline-delimited JSON (`.jsonl`)\n * under `~/.claude/projects/<project>/<session>.jsonl`. Each line is a JSON\n * object representing one event in the session.\n *\n * Supported event schemas (as observed in Claude Code v≥0.2):\n * - `{ type: \"user\", message: { role: \"user\", content: <string|array> } }`\n * - `{ type: \"assistant\", message: { role: \"assistant\", content: <string|array> } }`\n *\n * The adapter extracts the first user message as the session title and records\n * the timestamp range from `timestamp` fields when present.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport type { SessionAdapter, NormalizedSession, SessionTurn } from \"./types.js\";\nimport { resolveSessionTitle } from \"./utils.js\";\n\nconst CLAUDE_EXTENSION = \".jsonl\";\n\n/** Known marker strings in the first line that confirm Claude Code origin. */\nconst CLAUDE_TYPE_MARKERS = new Set([\"user\", \"assistant\", \"system\", \"tool_use\", \"tool_result\"]);\n\ninterface ClaudeContentBlock {\n type: string;\n text?: string;\n}\n\ninterface ClaudeMessage {\n role: \"user\" | \"assistant\";\n content: string | ClaudeContentBlock[];\n}\n\ninterface ClaudeEvent {\n type: string;\n message?: ClaudeMessage;\n timestamp?: string;\n}\n\n/** Extract plain text from a Claude content value (string or block array). */\nfunction extractText(content: string | ClaudeContentBlock[]): string {\n if (typeof content === \"string\") return content;\n return content\n .filter((b) => b.type === \"text\" && typeof b.text === \"string\")\n .map((b) => b.text as string)\n .join(\"\\n\");\n}\n\n/** Derive a title from the first non-empty user turn text. */\nfunction titleFromFirstUserMessage(turns: SessionTurn[]): string {\n const firstUser = turns.find((t) => t.role === \"user\" && t.content.trim().length > 0);\n return resolveSessionTitle(undefined, firstUser?.content, \"Claude Session\");\n}\n\n/** Parse a single JSONL line into a ClaudeEvent, returning null on failure. */\nfunction parseLine(line: string): ClaudeEvent | null {\n try {\n return JSON.parse(line) as ClaudeEvent;\n } catch {\n return null;\n }\n}\n\n/** Convert a ClaudeEvent into a SessionTurn, or null if not a conversation event. */\nfunction eventToTurn(event: ClaudeEvent): SessionTurn | null {\n if (!event.message || !event.message.role) return null;\n const role = event.message.role;\n if (role !== \"user\" && role !== \"assistant\") return null;\n\n const content = extractText(event.message.content);\n if (content.trim().length === 0) return null;\n\n return { role, content, timestamp: event.timestamp };\n}\n\nexport const claudeAdapter: SessionAdapter = {\n name: \"claude\",\n\n async detect(filePath: string): Promise<boolean> {\n if (path.extname(filePath).toLowerCase() !== CLAUDE_EXTENSION) return false;\n const raw = await readFile(filePath, \"utf-8\").catch(() => \"\");\n const firstLine = raw.split(\"\\n\")[0].trim();\n if (!firstLine.startsWith(\"{\")) return false;\n try {\n const obj = JSON.parse(firstLine) as ClaudeEvent;\n return typeof obj.type === \"string\" && CLAUDE_TYPE_MARKERS.has(obj.type);\n } catch {\n return false;\n }\n },\n\n async parse(filePath: string): Promise<NormalizedSession> {\n const raw = await readFile(filePath, \"utf-8\");\n const lines = raw.split(\"\\n\").filter((l) => l.trim().length > 0);\n\n if (lines.length === 0) {\n throw new Error(`Claude session file is empty: ${filePath}`);\n }\n\n const turns: SessionTurn[] = [];\n const timestamps: string[] = [];\n\n for (const [index, line] of lines.entries()) {\n const event = parseLine(line);\n if (event === null) {\n throw new Error(\n `Malformed JSON on line ${index + 1} of Claude session: ${filePath}`\n );\n }\n if (event.timestamp) timestamps.push(event.timestamp);\n const turn = eventToTurn(event);\n if (turn) turns.push(turn);\n }\n\n const title = titleFromFirstUserMessage(turns);\n\n return {\n title,\n adapter: \"claude\",\n startedAt: timestamps[0],\n endedAt: timestamps[timestamps.length - 1],\n participantIdentity: \"Claude Code\",\n turns,\n };\n },\n};\n","/**\n * Shared utilities for session adapters.\n *\n * These helpers are used by multiple adapters to avoid code duplication\n * for common operations like title truncation and JSON file parsing.\n */\n\n/** Maximum title characters before truncation with an ellipsis. */\nconst MAX_TITLE_CHARS = 80;\n\n/**\n * Truncate a string to `MAX_TITLE_CHARS` and append an ellipsis if needed.\n * @param text - The input string to truncate.\n */\nfunction truncateTitle(text: string): string {\n const trimmed = text.trim();\n return trimmed.length > MAX_TITLE_CHARS\n ? trimmed.slice(0, MAX_TITLE_CHARS).trimEnd() + \"…\"\n : trimmed;\n}\n\n/**\n * Derive a session title from an optional raw title or a fallback default.\n * Falls back to the first line of `firstUserContent` when `rawTitle` is absent,\n * and to `defaultTitle` when both are unavailable.\n *\n * @param rawTitle - Optional title from session metadata.\n * @param firstUserContent - Content of the first user turn (for fallback).\n * @param defaultTitle - Adapter-specific default (e.g. \"Claude Session\").\n */\nexport function resolveSessionTitle(\n rawTitle: string | undefined,\n firstUserContent: string | undefined,\n defaultTitle: string,\n): string {\n if (rawTitle && rawTitle.trim().length > 0) return truncateTitle(rawTitle);\n if (firstUserContent) {\n const firstLine = firstUserContent.split(\"\\n\")[0];\n if (firstLine.trim().length > 0) return truncateTitle(firstLine);\n }\n return defaultTitle;\n}\n\n/**\n * Parse JSON from `raw`, throwing an actionable error on failure.\n * @param raw - Raw JSON string.\n * @param filePath - Used in the error message to identify the file.\n */\nexport function parseJsonOrThrow(raw: string, filePath: string): unknown {\n try {\n return JSON.parse(raw);\n } catch {\n throw new Error(`Invalid JSON in session file: ${filePath}`);\n }\n}\n","/**\n * Adapter for OpenAI Codex / ChatGPT session exports.\n *\n * OpenAI's conversation export format (`conversations.json`) is an array of\n * conversation objects. Each conversation contains a `mapping` of nodes, where\n * each node holds a `message` with `author.role` and `content.parts`.\n *\n * Supported schema (as documented in OpenAI's data export):\n * ```json\n * [{ \"title\": \"...\", \"create_time\": 1234, \"update_time\": 5678,\n * \"mapping\": { \"<id>\": { \"message\": { \"author\": { \"role\": \"user\" },\n * \"content\": { \"parts\": [\"...\"] },\n * \"create_time\": 1234 } } } }]\n * ```\n *\n * When the file contains multiple conversations, only the first is parsed.\n * For bulk import, callers should split the array and pass each element\n * as a separate file (or use the directory bulk-import path).\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport type { SessionAdapter, NormalizedSession, SessionTurn } from \"./types.js\";\nimport { resolveSessionTitle, parseJsonOrThrow } from \"./utils.js\";\n\nconst CODEX_EXTENSION = \".json\";\n\ninterface CodexAuthor {\n role: string;\n}\n\ninterface CodexContent {\n parts: string[];\n}\n\ninterface CodexMessage {\n author: CodexAuthor;\n content: CodexContent;\n create_time?: number;\n}\n\ninterface CodexNode {\n message?: CodexMessage | null;\n}\n\ninterface CodexConversation {\n title?: string;\n create_time?: number;\n update_time?: number;\n mapping?: Record<string, CodexNode>;\n}\n\n/** Convert a Unix timestamp (seconds) to an ISO-8601 string. */\nfunction unixToIso(ts: number): string {\n return new Date(ts * 1000).toISOString();\n}\n\n/** Extract conversation turns from the mapping, sorted by create_time. */\nfunction extractTurns(mapping: Record<string, CodexNode>): SessionTurn[] {\n const turns: SessionTurn[] = [];\n for (const node of Object.values(mapping)) {\n const turn = nodeToTurn(node);\n if (turn) turns.push(turn);\n }\n turns.sort(compareTurnsByTimestamp);\n return turns;\n}\n\n/** Convert a single Codex mapping node into a normalized turn, or null to skip. */\nfunction nodeToTurn(node: CodexNode): SessionTurn | null {\n const msg = node.message;\n if (!msg) return null;\n const role = normalizeRole(msg.author?.role);\n if (!role) return null;\n const content = joinTrimmedParts(msg.content?.parts);\n if (content.length === 0) return null;\n return { role, content, timestamp: timestampFromUnix(msg.create_time) };\n}\n\n/** Narrow an arbitrary author role string to the supported user/assistant roles. */\nfunction normalizeRole(role: string | undefined): \"user\" | \"assistant\" | null {\n if (role === \"user\" || role === \"assistant\") return role;\n return null;\n}\n\n/** Join Codex content parts into a single trimmed string. */\nfunction joinTrimmedParts(parts: string[] | undefined): string {\n return (parts ?? []).join(\"\\n\").trim();\n}\n\n/** Convert an optional Unix timestamp to ISO-8601 or undefined. */\nfunction timestampFromUnix(ts: number | undefined): string | undefined {\n return ts != null ? unixToIso(ts) : undefined;\n}\n\n/** Sort comparator: chronological when both timestamps exist, otherwise stable. */\nfunction compareTurnsByTimestamp(a: SessionTurn, b: SessionTurn): number {\n if (!a.timestamp || !b.timestamp) return 0;\n return a.timestamp.localeCompare(b.timestamp);\n}\n\n/** Return true if `value` looks like a Codex conversation export array. */\nfunction isCodexExport(value: unknown): value is CodexConversation[] {\n return (\n Array.isArray(value) &&\n value.length > 0 &&\n typeof (value[0] as CodexConversation).mapping === \"object\"\n );\n}\n\nexport const codexAdapter: SessionAdapter = {\n name: \"codex\",\n\n async detect(filePath: string): Promise<boolean> {\n if (path.extname(filePath).toLowerCase() !== CODEX_EXTENSION) return false;\n const raw = await readFile(filePath, \"utf-8\").catch(() => \"\");\n if (raw.trimStart()[0] !== \"[\") return false;\n try {\n return isCodexExport(JSON.parse(raw));\n } catch {\n return false;\n }\n },\n\n async parse(filePath: string): Promise<NormalizedSession> {\n const raw = await readFile(filePath, \"utf-8\");\n const parsed = parseJsonOrThrow(raw, filePath);\n\n if (!isCodexExport(parsed)) {\n throw new Error(\n `Codex session file does not contain a conversation array: ${filePath}`\n );\n }\n\n const conv = parsed[0];\n const turns = extractTurns(conv.mapping ?? {});\n const firstUser = turns.find((t) => t.role === \"user\");\n\n return {\n title: resolveSessionTitle(conv.title, firstUser?.content, \"Codex Session\"),\n adapter: \"codex\",\n startedAt: conv.create_time != null ? unixToIso(conv.create_time) : undefined,\n endedAt: conv.update_time != null ? unixToIso(conv.update_time) : undefined,\n participantIdentity: \"OpenAI Codex\",\n turns,\n };\n },\n};\n","/**\n * Adapter for Cursor conversation history exports.\n *\n * Cursor AI can export conversation history as JSON files. The schema\n * reflects Cursor's internal chat format:\n * ```json\n * { \"tabs\": [{ \"title\": \"...\",\n * \"messages\": [{ \"role\": \"user\"|\"assistant\",\n * \"content\": \"...\",\n * \"timestamp\": \"...\" }] }] }\n * ```\n *\n * When the export contains multiple tabs, only the first tab is parsed.\n * A top-level `messages` array (without `tabs`) is also supported for\n * simpler single-conversation exports.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport type { SessionAdapter, NormalizedSession, SessionTurn } from \"./types.js\";\nimport { resolveSessionTitle, parseJsonOrThrow } from \"./utils.js\";\n\nconst CURSOR_EXTENSION = \".json\";\n\ninterface CursorMessage {\n role: string;\n content: string;\n timestamp?: string;\n}\n\ninterface CursorTab {\n title?: string;\n messages: CursorMessage[];\n}\n\ninterface CursorTabsExport {\n tabs: CursorTab[];\n}\n\ninterface CursorFlatExport {\n messages: CursorMessage[];\n title?: string;\n}\n\ntype CursorExport = CursorTabsExport | CursorFlatExport;\n\n/** Guard: does the value look like a Cursor tabs export? */\nfunction isTabsExport(value: unknown): value is CursorTabsExport {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"tabs\" in value &&\n Array.isArray((value as CursorTabsExport).tabs)\n );\n}\n\n/** Guard: does the value look like a Cursor flat messages export? */\nfunction isFlatExport(value: unknown): value is CursorFlatExport {\n return (\n typeof value === \"object\" &&\n value !== null &&\n \"messages\" in value &&\n Array.isArray((value as CursorFlatExport).messages)\n );\n}\n\n/** Extract the raw messages and optional title from a Cursor export. */\nfunction extractMessagesAndTitle(\n data: CursorExport\n): { messages: CursorMessage[]; title?: string } {\n if (isTabsExport(data)) {\n const tab = data.tabs[0];\n return { messages: tab?.messages ?? [], title: tab?.title };\n }\n return { messages: data.messages, title: data.title };\n}\n\n/** Convert raw Cursor messages to normalised SessionTurns. */\nfunction toTurns(messages: CursorMessage[]): SessionTurn[] {\n const turns: SessionTurn[] = [];\n for (const msg of messages) {\n const role = msg.role;\n if (role !== \"user\" && role !== \"assistant\") continue;\n const content = (msg.content ?? \"\").trim();\n if (content.length === 0) continue;\n turns.push({ role, content, timestamp: msg.timestamp });\n }\n return turns;\n}\n\nexport const cursorAdapter: SessionAdapter = {\n name: \"cursor\",\n\n async detect(filePath: string): Promise<boolean> {\n if (path.extname(filePath).toLowerCase() !== CURSOR_EXTENSION) return false;\n const raw = await readFile(filePath, \"utf-8\").catch(() => \"\");\n if (raw.trimStart()[0] !== \"{\") return false;\n try {\n const parsed: unknown = JSON.parse(raw);\n return isTabsExport(parsed) || isFlatExport(parsed);\n } catch {\n return false;\n }\n },\n\n async parse(filePath: string): Promise<NormalizedSession> {\n const raw = await readFile(filePath, \"utf-8\");\n const parsed = parseJsonOrThrow(raw, filePath);\n\n if (!isTabsExport(parsed) && !isFlatExport(parsed)) {\n throw new Error(\n `Cursor session file does not match a known Cursor export schema: ${filePath}`\n );\n }\n\n const { messages, title: rawTitle } = extractMessagesAndTitle(parsed as CursorExport);\n const turns = toTurns(messages);\n const firstUser = turns.find((t) => t.role === \"user\");\n\n const timestamps = turns\n .filter((t) => t.timestamp != null)\n .map((t) => t.timestamp as string);\n\n return {\n title: resolveSessionTitle(rawTitle, firstUser?.content, \"Cursor Session\"),\n adapter: \"cursor\",\n startedAt: timestamps[0],\n endedAt: timestamps[timestamps.length - 1],\n participantIdentity: \"Cursor AI\",\n turns,\n };\n },\n};\n","/**\n * Adapter registry and auto-detection for session files.\n *\n * `detectAdapter` probes a file against each registered adapter in priority\n * order and returns the first match. New adapters are added to `ADAPTERS`.\n */\n\nimport type { SessionAdapter, NormalizedSession } from \"./types.js\";\nimport { claudeAdapter } from \"./claude.js\";\nimport { codexAdapter } from \"./codex.js\";\nimport { cursorAdapter } from \"./cursor.js\";\n\n/** All registered session adapters, checked in order during detection. */\nexport const ADAPTERS: SessionAdapter[] = [claudeAdapter, codexAdapter, cursorAdapter];\n\n/**\n * Probe `filePath` against each adapter and return the first match.\n * Returns `null` when no adapter recognises the file.\n */\nexport async function detectAdapter(filePath: string): Promise<SessionAdapter | null> {\n for (const adapter of ADAPTERS) {\n if (await adapter.detect(filePath)) return adapter;\n }\n return null;\n}\n\n/**\n * Parse a session file using automatic adapter detection.\n *\n * After parsing, requires the session to contain at least one user or\n * assistant turn with non-empty content. Detection is intentionally\n * shape-based and lenient (file extension + first-line/JSON-shape match)\n * to avoid false-negatives on slightly-malformed-but-intelligible\n * exports — but a \"recognised-looking\" file with zero valid turns is\n * almost always a corrupted or empty export and should fail loudly,\n * not import as a content-free `sources/` page.\n *\n * @throws When no adapter recognises the file, the file is malformed,\n * or the parsed session has no usable turns.\n */\nexport async function parseSessionFile(filePath: string): Promise<NormalizedSession> {\n const adapter = await detectAdapter(filePath);\n if (!adapter) {\n throw new Error(\n `No session adapter recognised the file: ${filePath}\\n` +\n `Supported formats: ${ADAPTERS.map((a) => a.name).join(\", \")}`\n );\n }\n const session = await adapter.parse(filePath);\n assertSessionHasUsableTurns(session, filePath);\n return session;\n}\n\n/**\n * Reject sessions where every adapter-side filter dropped the input —\n * shape-based detection passed, but no usable user/assistant turn\n * survived. Throws with an actionable error rather than producing a\n * markdown file with \"No conversation turns found\".\n */\nfunction assertSessionHasUsableTurns(session: NormalizedSession, filePath: string): void {\n const hasUsableTurn = session.turns.some(\n (t) => (t.role === \"user\" || t.role === \"assistant\") && t.content.trim().length > 0,\n );\n if (!hasUsableTurn) {\n throw new Error(\n `${session.adapter} session has no usable turns: ${filePath}\\n` +\n `The file matches the ${session.adapter} export shape, but no user or ` +\n `assistant message with content was found. Re-export the session or ` +\n `delete the file if it is empty.`,\n );\n }\n}\n\n/**\n * Format a normalised session as a markdown document body.\n * Each turn is rendered as a level-3 heading plus the turn's content.\n *\n * Note: callers should obtain `session` via {@link parseSessionFile},\n * which enforces ≥1 usable turn. Direct construction with an empty\n * turns array would render as nothing — there is no fallback line\n * because the empty case should fail before reaching here.\n */\nexport function formatSessionAsMarkdown(session: NormalizedSession): string {\n const lines: string[] = [];\n\n for (const turn of session.turns) {\n const label = turn.role === \"user\" ? \"User\" : session.participantIdentity ?? \"Assistant\";\n const heading = turn.timestamp\n ? `### ${label} _(${turn.timestamp})_`\n : `### ${label}`;\n lines.push(heading);\n lines.push(\"\");\n lines.push(turn.content);\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\").trimEnd();\n}\n","/**\n * CLI handler for `llmwiki view` — the local read-only web viewer.\n *\n * Responsibilities:\n * - parse and validate the host/port/allow-lan symmetry (spec\n * §Non-Negotiable Security Requirements: non-loopback bind requires\n * BOTH `--allow-lan` AND `--host <host>`; either alone exits 1)\n * - build the frozen `ViewerSnapshot` once at startup\n * - start the HTTP server and emit a parseable readiness line so\n * test fixtures (and the user) know what URL to point a browser at\n * - register SIGINT / SIGTERM handlers for graceful shutdown\n */\n\nimport { spawn } from \"child_process\";\nimport { startViewerServer } from \"../viewer/server.js\";\nimport { buildViewerSnapshot } from \"../viewer/snapshot.js\";\n\nconst LOOPBACK_HOST = \"127.0.0.1\";\n\n/**\n * Bind hosts that listen on every interface. We reject these in v1\n * because the Host / Origin / Sec-Fetch-Site checks in the server are\n * built around a single canonical bind address; a wildcard bind would\n * mean any reachable interface's IP serves as a valid Host, which\n * defeats the DNS-rebind protection. A future PR can add a real\n * allowed-host model; for now, fail closed with a clear CLI error.\n */\nconst WILDCARD_HOSTS = new Set([\n \"0.0.0.0\",\n \"::\",\n \"0:0:0:0:0:0:0:0\",\n \"0000:0000:0000:0000:0000:0000:0000:0000\",\n \"*\",\n]);\n\n/** Parsed CLI options. */\ninterface ViewCommandOptions {\n port?: string | number;\n host?: string;\n allowLan?: boolean;\n open?: boolean;\n}\n\n/**\n * Run `llmwiki view`. Resolves once the server has bound; the returned\n * promise stays unresolved for the lifetime of the listening process so\n * Commander's exit semantics keep the event loop alive until the signal\n * handler closes the server.\n */\nexport default async function viewCommand(options: ViewCommandOptions): Promise<void> {\n const { host, port } = resolveBindConfig(options);\n const root = process.cwd();\n const snapshot = await buildViewerSnapshot(root);\n const handle = await startViewerServer(snapshot, { host, port });\n const url = buildReadyUrl(handle.host, handle.port);\n process.stdout.write(`Viewer ready at ${url}\\n`);\n if (options.open) openInBrowser(url);\n registerShutdown(handle.close);\n}\n\n/**\n * Fire-and-forget native-shell open of the viewer URL. Failures here are\n * intentionally swallowed: a broken browser launch must not prevent the\n * server from keeping the readiness it just announced.\n */\nfunction openInBrowser(url: string): void {\n const command =\n process.platform === \"darwin\" ? \"open\"\n : process.platform === \"win32\" ? \"cmd\"\n : \"xdg-open\";\n const args = process.platform === \"win32\" ? [\"/c\", \"start\", \"\", url] : [url];\n const child = spawn(command, args, { stdio: \"ignore\", detached: true });\n child.on(\"error\", () => undefined);\n child.unref();\n}\n\n/**\n * Apply the spec's host/port symmetry: `--allow-lan` and `--host`\n * together unlock non-loopback bind; either alone is a fatal error.\n */\nfunction resolveBindConfig(options: ViewCommandOptions): { host: string; port: number } {\n const hostFlag = typeof options.host === \"string\" && options.host.length > 0;\n const allowLan = options.allowLan === true;\n assertHostAllowLanSymmetry(hostFlag, allowLan);\n const host = hostFlag ? (options.host as string) : LOOPBACK_HOST;\n assertHostNotWildcard(host);\n return { host, port: parsePort(options.port) };\n}\n\n/** Reject `--host` and `--allow-lan` being supplied independently. */\nfunction assertHostAllowLanSymmetry(hostFlag: boolean, allowLan: boolean): void {\n if (hostFlag === allowLan) return;\n throw new Error(\n \"Privacy gate: --host and --allow-lan must be supplied together. \" +\n \"Use both to bind beyond loopback, or neither to keep the viewer on 127.0.0.1.\",\n );\n}\n\n/** Reject wildcard binds that would defeat DNS-rebind protection. */\nfunction assertHostNotWildcard(host: string): void {\n if (!WILDCARD_HOSTS.has(host)) return;\n throw new Error(\n `--host ${host} is not supported: wildcard binds defeat the viewer's DNS-rebind protection. ` +\n \"Use a specific interface IP (e.g. 192.168.1.10) instead.\",\n );\n}\n\n/**\n * Build the readiness-line URL. IPv6 literal hosts must be bracketed\n * (`http://[::1]:PORT/`) per RFC 3986 — bare `http://::1:PORT/` is a\n * malformed URL and won't open in a browser. Heuristic for \"literal\n * IPv6\": a colon in the host portion (domain names + IPv4 dotted\n * quads never contain `:`).\n */\nfunction buildReadyUrl(host: string, port: number): string {\n if (host.includes(\":\")) return `http://[${host}]:${port}`;\n return `http://${host}:${port}`;\n}\n\n/** Coerce the optional --port string into a non-negative integer. */\nfunction parsePort(raw: string | number | undefined): number {\n if (raw === undefined) return 0;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n if (!isValidPort(value)) {\n throw new Error(`Invalid --port value: ${raw}`);\n }\n return value;\n}\n\n/** True when `value` is an integer in the legal TCP port range [0, 65535]. */\nfunction isValidPort(value: number): boolean {\n return Number.isInteger(value) && value >= 0 && value <= 65535;\n}\n\n/** Install SIGINT/SIGTERM handlers that close the server gracefully. */\nfunction registerShutdown(close: () => Promise<void>): void {\n const shutdown = async (): Promise<void> => {\n try {\n await close();\n process.exit(0);\n } catch {\n process.exit(1);\n }\n };\n process.once(\"SIGINT\", () => void shutdown());\n process.once(\"SIGTERM\", () => void shutdown());\n}\n","/**\n * Local read-only HTTP server for the llmwiki viewer.\n *\n * Built on Node's `http` module (no framework). The spec's mandatory\n * security headers (CSP, CORP, nosniff, Referrer-Policy) and the\n * Host / Origin / Sec-Fetch-Site rejection rules from\n * §Non-Negotiable Security Requirements apply to **every response**,\n * including 404s for unregistered paths and 403s for bad origin — see\n * `handleRequest` for the ordering rationale.\n *\n * The server reads from the frozen `ViewerSnapshot` for every request.\n * The single exception is `/api/health`, which calls `readLintCache`\n * per request — that's a documented cheap atomic-JSON contract, not a\n * filesystem rescan of the wiki.\n */\n\nimport http from \"http\";\nimport type { IncomingMessage, ServerResponse } from \"http\";\nimport { AddressInfo } from \"net\";\nimport { buildHealthResponse } from \"./health.js\";\nimport { resolvePageKind } from \"./graph.js\";\nimport { loadShellTemplate, substitutePageIndex } from \"./shell.js\";\nimport { ASSETS_DIR, handleAsset } from \"./static-assets.js\";\nimport { renderPageHtml } from \"./render.js\";\nimport { searchPages } from \"./search.js\";\nimport type { PageDirectory } from \"../export/types.js\";\nimport type { ViewerSnapshot, ViewerPage } from \"./types.js\";\nimport { assertSafeSlug, PathSafetyError } from \"./path-safety.js\";\n\nconst LOOPBACK_HOSTS = new Set([\"127.0.0.1\", \"::1\"]);\n\n/** Exact CSP string the spec mandates. Pinned here to keep the test contract obvious. */\nconst CONTENT_SECURITY_POLICY =\n \"default-src 'self'; script-src 'self'; style-src 'self'; \" +\n \"img-src 'self' data:; font-src 'self'; connect-src 'self'; \" +\n \"frame-ancestors 'none'; base-uri 'none'; object-src 'none'; form-action 'none'\";\n\n/** Configuration knobs accepted by `startViewerServer`. */\ninterface ViewerServerConfig {\n /** Listening host. `--allow-lan` callers set this to a non-loopback bind address. */\n host: string;\n /** Listening port. `0` lets the OS pick a free port. */\n port: number;\n}\n\n/** Handle returned by `startViewerServer`. */\ninterface ViewerServerHandle {\n /** Actual port the server bound to (resolves `port: 0`). */\n port: number;\n /** Actual host the server bound to. */\n host: string;\n /** Graceful shutdown — closes the listener and resolves when all sockets drain. */\n close(): Promise<void>;\n}\n\n/**\n * Bind the configured server to its host/port and resolve once `listen`\n * fires. Errors during bind (occupied port, invalid host) reject so the\n * CLI surfaces a clean failure instead of hanging. The internal config\n * the request handler uses is the actually-bound port — not the one the\n * caller passed in — so `--port 0` correctly accepts Host headers that\n * carry the OS-assigned port.\n */\nexport async function startViewerServer(\n snapshot: ViewerSnapshot,\n config: ViewerServerConfig,\n): Promise<ViewerServerHandle> {\n const boundConfig: ViewerServerConfig = { ...config };\n const server = http.createServer((req, res) => {\n handleRequest(req, res, snapshot, boundConfig).catch((err) => {\n // Per spec: never return raw thrown error text to the client.\n // The per-route handlers catch render/sanitize failures locally\n // and emit `render_failed`; reaching here means a genuinely\n // unexpected bug, so surface a generic envelope.\n void err;\n if (!res.headersSent) {\n writeJsonError(res, 500, \"internal_error\", \"Unexpected server error.\");\n }\n });\n });\n await new Promise<void>((resolve, reject) => {\n const onError = (err: Error): void => {\n server.off(\"listening\", onListening);\n reject(err);\n };\n const onListening = (): void => {\n server.off(\"error\", onError);\n resolve();\n };\n server.once(\"error\", onError);\n server.once(\"listening\", onListening);\n server.listen(config.port, config.host);\n });\n const address = server.address() as AddressInfo | null;\n if (!address) throw new Error(\"server bound but address is null\");\n boundConfig.port = address.port;\n return {\n host: config.host,\n port: address.port,\n close: () => new Promise<void>((resolve) => server.close(() => resolve())),\n };\n}\n\n/**\n * Dispatch a single request. The order matters:\n * 1. Set the mandatory security headers — every response carries them,\n * including 404s for unknown paths and 403s for bad Host/Origin.\n * 2. Validate Host / Origin / Sec-Fetch-Site. Hostile-origin requests\n * to unknown paths must still return 403, not a header-less 404.\n * 3. Dispatch to a registered route, or surface a JSON 404 envelope\n * for anything else.\n * That ordering closes the DNS-rebind / cross-site leakage gap the\n * naive \"404 first, then security\" flow would leave behind.\n */\nasync function handleRequest(\n req: IncomingMessage,\n res: ServerResponse,\n snapshot: ViewerSnapshot,\n config: ViewerServerConfig,\n): Promise<void> {\n applySecurityHeaders(res);\n if (!validateOriginHeaders(req, config)) {\n writeJsonError(res, 403, \"forbidden\", \"rejected by origin policy\");\n return;\n }\n const url = new URL(req.url ?? \"/\", buildOriginBase(config));\n if (!isRouteRegistered(req.method, url.pathname)) {\n writeJsonError(res, 404, \"not_found\", `${req.method ?? \"?\"} ${url.pathname}`);\n return;\n }\n await routeRegistered(req, res, url, snapshot, LOOPBACK_HOSTS.has(config.host));\n}\n\n/**\n * Dispatch the request to whichever registered handler owns this path.\n * `isLoopback` controls whether the rendered citation chips include\n * `absolutePath` / editor-link payloads — non-loopback binds suppress\n * both per spec §Support Rail.\n */\nasync function routeRegistered(\n req: IncomingMessage,\n res: ServerResponse,\n parsedUrl: URL,\n snapshot: ViewerSnapshot,\n isLoopback: boolean,\n): Promise<void> {\n if (parsedUrl.pathname === \"/\") return handleShell(res, snapshot);\n if (parsedUrl.pathname.startsWith(\"/assets/\")) return handleAsset(res, parsedUrl.pathname);\n if (parsedUrl.pathname === \"/api/pages\") return handleApiPages(res, snapshot);\n if (parsedUrl.pathname === \"/api/index\") return handleApiIndex(res, snapshot, isLoopback);\n if (parsedUrl.pathname === \"/api/health\") return handleApiHealth(res, snapshot);\n if (parsedUrl.pathname === \"/api/search\") return handleApiSearch(res, parsedUrl, snapshot);\n if (parsedUrl.pathname === \"/api/graph\") return handleApiGraph(res, snapshot);\n if (parsedUrl.pathname.startsWith(\"/api/page/\")) {\n return handleApiPage(res, parsedUrl.pathname, snapshot, isLoopback);\n }\n // Unreachable: `isRouteRegistered` is the gate, and every branch\n // there has a matching dispatch above. If it ever fires, the two\n // functions have drifted — fail loudly rather than silently 404.\n throw new Error(`route registration drift: no handler for ${parsedUrl.pathname}`);\n}\n\n/**\n * Exact-path registered routes for v1. Kept as a Set so additions are\n * just a string in one place and the membership test stays O(1).\n */\nconst REGISTERED_EXACT_PATHS: ReadonlySet<string> = new Set([\n \"/\",\n \"/api/pages\",\n \"/api/index\",\n \"/api/health\",\n \"/api/search\",\n \"/api/graph\",\n]);\n\n/** Prefix-based registered routes (assets and per-page API). */\nconst REGISTERED_PATH_PREFIXES: readonly string[] = [\"/assets/\", \"/api/page/\"];\n\n/** True when (method, path) is one of the v1 registered routes. */\nfunction isRouteRegistered(method: string | undefined, pathname: string): boolean {\n if (method !== \"GET\") return false;\n if (REGISTERED_EXACT_PATHS.has(pathname)) return true;\n return REGISTERED_PATH_PREFIXES.some((prefix) => pathname.startsWith(prefix));\n}\n\n/**\n * Stamp every response with the mandatory security headers. Called\n * first in `handleRequest` so unregistered-route 404s and bad-origin\n * 403s carry the same hardening as the v1 API responses.\n */\nfunction applySecurityHeaders(res: ServerResponse): void {\n res.setHeader(\"Content-Security-Policy\", CONTENT_SECURITY_POLICY);\n res.setHeader(\"Cross-Origin-Resource-Policy\", \"same-origin\");\n res.setHeader(\"X-Content-Type-Options\", \"nosniff\");\n res.setHeader(\"Referrer-Policy\", \"no-referrer\");\n}\n\n/**\n * Apply the Host / Origin / Sec-Fetch-Site rejection rules from\n * §Non-Negotiable Security Requirements. Returns false when a request\n * should be rejected with 403; the caller writes the error envelope.\n */\nfunction validateOriginHeaders(req: IncomingMessage, config: ViewerServerConfig): boolean {\n const host = req.headers.host;\n if (!host || !isAcceptableHost(host, config)) return false;\n const origin = req.headers.origin;\n if (typeof origin === \"string\" && origin.length > 0) {\n if (!isSameOrigin(origin, config)) return false;\n }\n const fetchSite = req.headers[\"sec-fetch-site\"];\n if (fetchSite === \"cross-site\") return false;\n return true;\n}\n\n/**\n * True when the incoming `Host` header matches the configured bind.\n * Handles IPv4 (`127.0.0.1:PORT`), IPv6 (`[::1]:PORT` — clients always\n * bracket the host portion when the Host header carries a literal IPv6\n * address per RFC 3986/7230), and the `localhost` alias on both\n * loopback families.\n */\nfunction isAcceptableHost(hostHeader: string, config: ViewerServerConfig): boolean {\n for (const acceptable of buildAcceptableHostHeaders(config)) {\n if (hostHeader === acceptable) return true;\n }\n return false;\n}\n\n/** Every Host header value we accept for the current bind. */\nfunction buildAcceptableHostHeaders(config: ViewerServerConfig): string[] {\n const formattedBind = formatHostHeader(config.host, config.port);\n const accepted = [formattedBind];\n if (config.host === \"127.0.0.1\" || config.host === \"::1\") {\n accepted.push(`localhost:${config.port}`);\n }\n return accepted;\n}\n\n/** True when the incoming `Origin` resolves to our own host:port. */\nfunction isSameOrigin(origin: string, config: ViewerServerConfig): boolean {\n try {\n const parsed = new URL(origin);\n const expectedHostname = normalizeHostnameForOrigin(config.host);\n const originHostname = normalizeHostnameForOrigin(parsed.hostname);\n return originHostname === expectedHostname && Number(parsed.port) === config.port;\n } catch {\n return false;\n }\n}\n\n/**\n * Format a Host header value for the given bind. IPv6 addresses must\n * be bracketed (`[::1]:54391`); IPv4 and named hosts go in bare. The\n * heuristic for \"literal IPv6\" is a colon in the host portion — domain\n * names and IPv4 dotted-quads never contain `:`.\n */\nfunction formatHostHeader(host: string, port: number): string {\n if (host.includes(\":\")) return `[${host}]:${port}`;\n return `${host}:${port}`;\n}\n\n/**\n * Build a URL base suitable for the `new URL(req.url, base)` resolver.\n * IPv6 literal hosts must be bracketed inside a URL — `http://::1:PORT/`\n * is malformed and `new URL` throws. The bracketed form is the only\n * legal way to express a literal IPv6 host in a URL.\n */\nfunction buildOriginBase(config: ViewerServerConfig): string {\n if (config.host.includes(\":\")) return `http://[${config.host}]:${config.port}`;\n return `http://${config.host}:${config.port}`;\n}\n\n/**\n * `URL.hostname` strips the brackets from a parsed IPv6 origin\n * (`new URL(\"http://[::1]/\").hostname === \"::1\"`), so compare against\n * the bare form. Lowercased for case-insensitive equality (RFC 3986\n * says the host is case-insensitive).\n */\nfunction normalizeHostnameForOrigin(host: string): string {\n let h = host.toLowerCase();\n if (h.startsWith(\"[\") && h.endsWith(\"]\")) h = h.slice(1, -1);\n return h;\n}\n\n/**\n * Serve the templated viewer shell. Reads `index.html` lazily through\n * `loadShellTemplate` (process-cached), substitutes the page-index JSON\n * blob, and returns the result with `Content-Type: text/html`. A missing\n * template surfaces as a 500 `shell_missing` so the rest of the routes\n * stay usable when the asset bundle is incomplete.\n */\nasync function handleShell(res: ServerResponse, snapshot: ViewerSnapshot): Promise<void> {\n const template = await loadShellTemplate(ASSETS_DIR);\n if (template === null) {\n writeJsonError(res, 500, \"shell_missing\", \"Viewer shell template not found on disk.\");\n return;\n }\n const body = substitutePageIndex(template, snapshot.pages);\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", \"text/html; charset=utf-8\");\n res.end(body);\n}\n\n/** `/api/pages` — full envelope with counts, recent pages, and page list. */\nfunction handleApiPages(res: ServerResponse, snapshot: ViewerSnapshot): void {\n writeJson(res, 200, {\n project: snapshot.project,\n counts: {\n concepts: snapshot.counts.concepts,\n queries: snapshot.counts.queries,\n sourceFiles: snapshot.counts.sourceFiles,\n pendingReviews: snapshot.counts.pendingReviews,\n },\n index: { available: snapshot.index.available, href: snapshot.index.href },\n recentPages: snapshot.recentPages,\n pages: snapshot.pages.map(pageListRow),\n updatedAt: snapshot.generatedAt,\n });\n}\n\n/** Per-page row shape returned in `/api/pages.pages`. */\nfunction pageListRow(page: ViewerPage): Record<string, unknown> {\n return {\n id: page.id,\n pageDirectory: page.pageDirectory,\n slug: page.slug,\n title: page.title,\n kind: resolvePageKind(page.frontmatter),\n summary: typeof page.frontmatter.summary === \"string\" ? page.frontmatter.summary : \"\",\n updatedAt:\n typeof page.frontmatter.updatedAt === \"string\" ? (page.frontmatter.updatedAt as string) : \"\",\n warnings: page.warnings,\n };\n}\n\n/** `/api/index` — rendered `wiki/index.md` with resolved outgoing links. */\nfunction handleApiIndex(\n res: ServerResponse,\n snapshot: ViewerSnapshot,\n isLoopback: boolean,\n): void {\n if (!snapshot.index.available) {\n writeJsonError(res, 404, \"index_unavailable\", \"wiki/index.md is not present.\");\n return;\n }\n const rendered = tryRenderBody(snapshot.index.body, snapshot, isLoopback);\n if (rendered === null) {\n writeRenderFailed(res);\n return;\n }\n writeJson(res, 200, {\n html: rendered.html,\n outgoingLinks: snapshot.index.outgoingLinks,\n generatedAt: snapshot.generatedAt,\n });\n}\n\n/** Serve the frozen graph adjacency data for the `#/graph` route. */\nfunction handleApiGraph(res: ServerResponse, snapshot: ViewerSnapshot): void {\n writeJson(res, 200, snapshot.graph);\n}\n\n/** `/api/health` — cheap status summary. */\nasync function handleApiHealth(res: ServerResponse, snapshot: ViewerSnapshot): Promise<void> {\n const health = await buildHealthResponse(snapshot);\n writeJson(res, 200, health);\n}\n\n/**\n * `/api/search?q=...` — substring search over the startup snapshot. The\n * query string is read directly from the parsed URL (Node's URL parser\n * has already percent-decoded it); `searchPages` does its own length\n * cap and tokenization. An empty or missing `q` returns an empty\n * results array, consistent with the no-tokens case.\n */\nfunction handleApiSearch(\n res: ServerResponse,\n parsedUrl: URL,\n snapshot: ViewerSnapshot,\n): void {\n const query = parsedUrl.searchParams.get(\"q\") ?? \"\";\n writeJson(res, 200, searchPages(snapshot, query));\n}\n\n/**\n * `/api/page/:directory/:slug` — single page payload with server-rendered\n * sanitized HTML. The `render_pending` Slice-2 placeholder is gone; any\n * remaining warnings come from the collector (missing/malformed\n * frontmatter, missing title).\n */\nfunction handleApiPage(\n res: ServerResponse,\n pathname: string,\n snapshot: ViewerSnapshot,\n isLoopback: boolean,\n): void {\n const segments = pathname.replace(/^\\/api\\/page\\//, \"\").split(\"/\");\n if (segments.length !== 2) {\n writeJsonError(res, 400, \"bad_request\", \"Expected /api/page/:directory/:slug\");\n return;\n }\n const [directorySegment, encodedSlug] = segments;\n const decodedSlug = safeDecodeSlug(directorySegment, encodedSlug);\n if (!decodedSlug) {\n writeJsonError(res, 400, \"bad_request\", \"Invalid directory or slug.\");\n return;\n }\n const page = snapshot.pages.find(\n (p) => p.pageDirectory === decodedSlug.directory && p.slug === decodedSlug.slug,\n );\n if (!page) {\n writeJsonError(res, 404, \"page_not_found\", `${decodedSlug.directory}/${decodedSlug.slug}`);\n return;\n }\n const rendered = tryRenderBody(page.body, snapshot, isLoopback);\n if (rendered === null) {\n writeRenderFailed(res);\n return;\n }\n writeJson(res, 200, pagePayload(page, snapshot, rendered.html));\n}\n\n/**\n * Decode the directory and slug segments together so a bad input on\n * either fails with a uniform 400. Resolves with `null` for any\n * structural rejection.\n */\nfunction safeDecodeSlug(\n directorySegment: string,\n encodedSlug: string,\n): { directory: PageDirectory; slug: string } | null {\n if (directorySegment !== \"concepts\" && directorySegment !== \"queries\") return null;\n let decoded: string;\n try {\n decoded = decodeURIComponent(encodedSlug);\n } catch {\n return null;\n }\n try {\n assertSafeSlug(decoded);\n } catch (err) {\n if (err instanceof PathSafetyError) return null;\n throw err;\n }\n return { directory: directorySegment, slug: decoded };\n}\n\n/** Build the JSON payload for `/api/page/:dir/:slug`. */\nfunction pagePayload(\n page: ViewerPage,\n snapshot: ViewerSnapshot,\n renderedHtml: string,\n): Record<string, unknown> {\n return {\n id: page.id,\n title: page.title,\n pageDirectory: page.pageDirectory,\n slug: page.slug,\n html: renderedHtml,\n citations: page.citations,\n outgoingLinks: page.outgoingLinks,\n frontmatter: page.frontmatter,\n warnings: page.warnings,\n updatedAt:\n typeof page.frontmatter.updatedAt === \"string\" ? (page.frontmatter.updatedAt as string) : \"\",\n createdAt:\n typeof page.frontmatter.createdAt === \"string\" ? (page.frontmatter.createdAt as string) : \"\",\n generatedAt: snapshot.generatedAt,\n };\n}\n\n/**\n * Wrap the renderer in a catch and return null on any thrown error.\n * Render or sanitize failures must emit the spec's `render_failed`\n * envelope rather than leak the raw thrown text — see `writeRenderFailed`.\n */\nfunction tryRenderBody(\n body: string,\n snapshot: ViewerSnapshot,\n isLoopback: boolean,\n): { html: string } | null {\n try {\n return renderPageHtml(body, snapshot, { isLoopback });\n } catch {\n return null;\n }\n}\n\n/** Write the spec's exact `render_failed` 500 envelope. */\nfunction writeRenderFailed(res: ServerResponse): void {\n writeJsonError(res, 500, \"render_failed\", \"Could not render page.\");\n}\n\n/** Write a JSON response body with the given status. */\nfunction writeJson(res: ServerResponse, status: number, body: unknown): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n res.end(JSON.stringify(body));\n}\n\n/** Standard `{ error: { code, message } }` envelope. */\nfunction writeJsonError(\n res: ServerResponse,\n status: number,\n code: string,\n message: string,\n): void {\n writeJson(res, status, { error: { code, message } });\n}\n","/**\n * Persistent cache of the most recent `llmwiki lint` run.\n *\n * Written by the lint command after a completed run, before any non-zero exit\n * for lint findings, so the cache always reflects the run the user just saw.\n * Crashed or partial runs leave the prior cache untouched.\n *\n * Consumers (e.g., the upcoming viewer's /api/health endpoint) read the cache\n * to surface lint counts without re-running lint per request. A missing or\n * malformed cache reads as null, which means \"lint has not been run yet.\"\n */\n\nimport { mkdir, readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { atomicWrite } from \"../utils/markdown.js\";\nimport { LLMWIKI_DIR, LAST_LINT_FILE } from \"../utils/constants.js\";\nimport type { LintSummary } from \"./types.js\";\n\n/** One persisted lint summary. Shape is part of the public viewer-cache contract. */\nexport interface LintCacheEntry {\n warnings: number;\n errors: number;\n /** ISO-8601 timestamp of the run that produced these counts. */\n at: string;\n}\n\n/**\n * The exact ISO-8601 shape `writeLintCache` produces and `readLintCache` accepts.\n * Exported so tests can assert against the same regex the validator enforces and\n * never drift from the documented contract.\n */\nexport const LINT_CACHE_TIMESTAMP_PATTERN = /^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}\\.\\d{3}Z$/;\n\n/**\n * Persist a lint summary to `.llmwiki/last-lint.json` after a completed run.\n * Creates the `.llmwiki/` directory if missing. Overwrites any prior entry so\n * the cache reflects the most recent run, including zero-issue runs.\n */\nexport async function writeLintCache(root: string, summary: LintSummary): Promise<void> {\n await mkdir(path.join(root, LLMWIKI_DIR), { recursive: true });\n const entry: LintCacheEntry = {\n warnings: summary.warnings,\n errors: summary.errors,\n at: new Date().toISOString(),\n };\n await atomicWrite(path.join(root, LAST_LINT_FILE), `${JSON.stringify(entry, null, 2)}\\n`);\n}\n\n/**\n * Read the cached lint summary, returning null for missing or malformed files.\n * Validation is strict: every field must have its expected type, otherwise the\n * cache is treated as absent so callers do not surface garbage counts.\n */\nexport async function readLintCache(root: string): Promise<LintCacheEntry | null> {\n let raw: string;\n try {\n raw = await readFile(path.join(root, LAST_LINT_FILE), \"utf-8\");\n } catch {\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!isValidEntry(parsed)) return null;\n return { warnings: parsed.warnings, errors: parsed.errors, at: parsed.at };\n}\n\n/** True for finite non-negative integers, including zero. NaN and Infinity fail Number.isInteger. */\nfunction isNonNegativeInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n\n/**\n * Strict type guard for the persisted cache entry.\n *\n * Counts must be finite non-negative integers (the writer only ever persists\n * `LintSummary` severity counts, which originate from a length on an array, so\n * anything else means the file was hand-edited or corrupted). The timestamp\n * must match the exact ISO-8601 shape the writer produces, otherwise downstream\n * consumers risk surfacing values like \"2026-01-01\" as full timestamps.\n */\nfunction isValidEntry(value: unknown): value is LintCacheEntry {\n if (typeof value !== \"object\" || value === null) return false;\n const candidate = value as Record<string, unknown>;\n return (\n isNonNegativeInteger(candidate.warnings) &&\n isNonNegativeInteger(candidate.errors) &&\n typeof candidate.at === \"string\" &&\n LINT_CACHE_TIMESTAMP_PATTERN.test(candidate.at)\n );\n}\n","/**\n * Build the `/api/health` response from the frozen startup snapshot.\n *\n * The only per-request filesystem read is `readLintCache(root)` — every\n * other count is captured at startup in `ViewerSnapshot`. The lint cache\n * is intentionally read per-request because PR #57 already designed it\n * as a tiny atomic JSON file the viewer's contract treats as a stable\n * surface (returns `null` when no lint run has completed yet).\n */\n\nimport { readLintCache } from \"../linter/cache.js\";\nimport type { LintCacheEntry } from \"../linter/cache.js\";\nimport type { ViewerSnapshot } from \"./types.js\";\n\n/**\n * Cheap health summary surfacing the same count fields MCP `wiki_status`\n * uses, plus the lint cache. Shapes diverge intentionally — MCP returns\n * an envelope with nested `pages`, the viewer returns a flat record for\n * the dashboard.\n */\ninterface ViewerHealthResponse {\n pendingReviews: number;\n sources: number;\n sourceFiles: number;\n concepts: number;\n queries: number;\n lint: LintCacheEntry | null;\n}\n\n/**\n * Assemble the health response. Reads `.llmwiki/last-lint.json` via\n * `readLintCache`; every other value is derived from the snapshot's\n * frozen counts.\n */\nexport async function buildHealthResponse(\n snapshot: ViewerSnapshot,\n): Promise<ViewerHealthResponse> {\n const lint = await readLintCache(snapshot.root);\n return {\n pendingReviews: snapshot.counts.pendingReviews,\n sources: snapshot.counts.compiledSources,\n sourceFiles: snapshot.counts.sourceFiles,\n concepts: snapshot.counts.concepts,\n queries: snapshot.counts.queries,\n lint,\n };\n}\n","/**\n * Graph data builder for the viewer's `#/graph` route.\n *\n * Converts the flat `ViewerPage[]` list from the startup snapshot into an\n * adjacency representation suitable for D3 force-directed rendering. Called\n * once by `buildViewerSnapshot` — the result is frozen in the snapshot and\n * served directly by `/api/graph` with no per-request computation.\n */\n\nimport type { ViewerPage } from \"./types.js\";\nimport type { GraphData, GraphEdge, GraphNode, PageId } from \"./types.js\";\n\nconst DEFAULT_KIND = \"concept\";\n\n/** Resolve the display kind for a page, defaulting to \"concept\" when absent or non-string. */\nexport function resolvePageKind(frontmatter: Record<string, unknown>): string {\n return typeof frontmatter.kind === \"string\" && frontmatter.kind.length > 0\n ? frontmatter.kind\n : DEFAULT_KIND;\n}\n\n/**\n * Build graph adjacency data from the page list. All outgoing links become\n * edges — including dangling ones whose target has no backing page. Dangling\n * targets appear as ghost nodes with `isDangling: true`. Real-node degree\n * counts only valid edges (dangling out-links do not inflate the source\n * node's out-degree).\n */\nexport function buildGraphData(pages: ViewerPage[]): GraphData {\n const pageIds = new Set<PageId>(pages.map((p) => p.id));\n const edges = buildEdges(pages);\n const ghostDisplayMap = buildGhostDisplayMap(pages);\n const inDegreeMap = buildInDegreeMap(edges);\n const realNodes = pages.map((p) => buildNode(p, pageIds, inDegreeMap));\n const ghostNodes = buildGhostNodes(edges, pageIds, inDegreeMap, ghostDisplayMap);\n return { nodes: [...realNodes, ...ghostNodes], edges };\n}\n\nfunction buildGhostDisplayMap(pages: ViewerPage[]): Map<PageId, string> {\n const map = new Map<PageId, string>();\n for (const page of pages) {\n for (const { slug, display } of page.danglingLinks ?? []) {\n const id = ghostId(slug);\n if (!map.has(id)) map.set(id, display);\n }\n }\n return map;\n}\n\nconst GHOST_DIRECTORY = \"concepts\";\n\n/** Synthesise a stable PageId for an unresolved slug so ghost nodes have a unique key. */\nfunction ghostId(slug: string): PageId {\n return `${GHOST_DIRECTORY}/${slug}` as PageId;\n}\n\nfunction buildEdges(pages: ViewerPage[]): GraphEdge[] {\n const edges: GraphEdge[] = [];\n for (const page of pages) {\n for (const target of page.outgoingLinks) {\n edges.push({ source: page.id, target });\n }\n for (const { slug } of page.danglingLinks ?? []) {\n edges.push({ source: page.id, target: ghostId(slug) });\n }\n }\n return edges;\n}\n\nfunction buildGhostNodes(\n edges: GraphEdge[],\n pageIds: Set<PageId>,\n inDegreeMap: Map<PageId, number>,\n displayMap: Map<PageId, string>,\n): GraphNode[] {\n const seen = new Set<PageId>();\n const ghosts: GraphNode[] = [];\n for (const { target } of edges) {\n if (pageIds.has(target) || seen.has(target)) continue;\n seen.add(target);\n const [directory, ...rest] = target.split(\"/\");\n const slug = rest.join(\"/\");\n ghosts.push({\n id: target,\n title: displayMap.get(target) ?? slug,\n slug,\n directory,\n kind: \"dangling\",\n degree: inDegreeMap.get(target) ?? 0,\n isDangling: true,\n });\n }\n return ghosts;\n}\n\nfunction buildInDegreeMap(edges: GraphEdge[]): Map<PageId, number> {\n const map = new Map<PageId, number>();\n for (const edge of edges) {\n map.set(edge.target, (map.get(edge.target) ?? 0) + 1);\n }\n return map;\n}\n\nfunction buildNode(\n page: ViewerPage,\n pageIds: Set<PageId>,\n inDegreeMap: Map<PageId, number>,\n): GraphNode {\n const outDegree = page.outgoingLinks.filter((t) => pageIds.has(t)).length;\n const inDegree = inDegreeMap.get(page.id) ?? 0;\n const kind = resolvePageKind(page.frontmatter);\n return {\n id: page.id,\n title: page.title,\n slug: page.slug,\n directory: page.pageDirectory,\n kind,\n degree: outDegree + inDegree,\n };\n}\n","/**\n * Shell-template loading, in-memory caching, and page-index substitution\n * for the viewer's `GET /` handler.\n *\n * The template lives at `dist/viewer/assets/index.html` (copied there by\n * `scripts/copy-viewer-assets.mjs`) and contains a literal `<!--PAGE_INDEX-->`\n * marker the server replaces per-request with a `<script type=\"application/json\"\n * id=\"page-index\">…</script>` blob carrying a trimmed page list. The JSON is\n * escaped so `</`, `<!`, and bare `<` cannot break out of the embedded script\n * tag — this is the spec's \"embed only as escaped JSON\" allowance, executed\n * server-side rather than client-side.\n *\n * Lazy-read with process-local cache: a missing template is a per-request\n * 500 (`shell_missing`), not a startup failure. The viewer's API endpoints\n * stay usable even if the asset bundle is incomplete; only `GET /` degrades.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { resolvePageKind } from \"./graph.js\";\nimport type { ViewerPage } from \"./types.js\";\n\nconst PAGE_INDEX_MARKER = \"<!--PAGE_INDEX-->\";\n\n/** Per-`assetsDir` template cache. `null` is cached too so the missing-template path doesn't hammer the disk. */\nconst templateCache = new Map<string, string | null>();\n\n/** Page-index entry shape embedded in the shell. Excludes page bodies per spec. */\ninterface EmbeddedPage {\n id: string;\n pageDirectory: ViewerPage[\"pageDirectory\"];\n slug: string;\n title: string;\n /** Frontmatter `kind`, used by the sidebar to group concepts on first paint. */\n kind: string;\n}\n\n/**\n * Read the shell template from `assetsDir/index.html`. Returns null when the\n * file is missing — the caller turns that into a `shell_missing` 500 so the\n * server keeps serving the rest of its routes. Caches the file bytes per\n * `assetsDir` in process memory; the cache is invalidated only by process\n * restart (consistent with the v1 \"no live-watch\" snapshot lifecycle).\n */\nexport async function loadShellTemplate(assetsDir: string): Promise<string | null> {\n const cached = templateCache.get(assetsDir);\n if (cached !== undefined) return cached;\n let bytes: string | null;\n try {\n bytes = await readFile(path.join(assetsDir, \"index.html\"), \"utf-8\");\n } catch {\n bytes = null;\n }\n templateCache.set(assetsDir, bytes);\n return bytes;\n}\n\n/** Clear the in-memory template cache. Tests use this between scenarios. */\nexport function resetShellTemplateCache(): void {\n templateCache.clear();\n}\n\n/**\n * Substitute the `<!--PAGE_INDEX-->` marker in `template` with a JSON-escaped\n * `<script type=\"application/json\">` block carrying the trimmed page list.\n *\n * The embedded payload is a subset of `/api/pages.pages` — only the fields\n * the client needs for first-paint sidebar rendering. Page bodies are never\n * included. JSON serialization is followed by an HTML-safety pass that\n * replaces every literal less-than character with the JSON unicode escape\n * for less-than (backslash-u-003c), so a `</script>` substring, a `<!`\n * sequence, or bare angle brackets in any page title cannot break out of\n * the embedded tag. `JSON.parse` on the client round-trips that escape\n * back into a literal less-than character.\n */\nexport function substitutePageIndex(template: string, pages: ViewerPage[]): string {\n const embedded: EmbeddedPage[] = pages.map((page) => ({\n id: page.id,\n pageDirectory: page.pageDirectory,\n slug: page.slug,\n title: page.title,\n kind: resolvePageKind(page.frontmatter),\n }));\n const json = JSON.stringify({ pages: embedded }).replace(/</g, \"\\\\u003c\");\n const block = `<script type=\"application/json\" id=\"page-index\">${json}</script>`;\n return template.replace(PAGE_INDEX_MARKER, block);\n}\n","/**\n * Path-confined static file server for the viewer's bundled assets.\n *\n * Owns three things:\n * - `ASSETS_DIR` — the absolute filesystem path to the asset bundle,\n * resolved once at module load from `import.meta.url`. The tsup\n * `onSuccess` hook copies `src/viewer/assets/` here at build time.\n * - The route handler for `GET /assets/*`: decode → `assertSafeSlug` →\n * `realpath` → confine under `ASSETS_DIR` → extension allowlist.\n * - The `ASSET_CONTENT_TYPES` allowlist that doubles as the\n * served-extensions filter.\n *\n * `ASSETS_DIR` is exported because `src/viewer/server.ts`'s shell handler\n * also reads from the same directory via `loadShellTemplate`. Sharing the\n * constant keeps the two surfaces honest about pointing at one place.\n */\n\nimport { readFile, realpath } from \"fs/promises\";\nimport path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport type { ServerResponse } from \"http\";\nimport { assertSafeSlug, PathSafetyError } from \"./path-safety.js\";\n\n/**\n * Resolve the directory the viewer's static assets live in. Computed once\n * at module load, relative to wherever this file ended up after tsup\n * bundling — i.e. `<dist>/viewer/assets/` next to `dist/cli.js`. The\n * `copy-viewer-assets.mjs` tsup `onSuccess` hook is responsible for\n * populating that location; if it failed to run, the shell handler in\n * `server.ts` surfaces a per-request `shell_missing` 500 and this module\n * surfaces `asset_not_found` 404s instead of crashing startup.\n */\nexport const ASSETS_DIR = path.join(\n path.dirname(fileURLToPath(import.meta.url)),\n \"viewer/assets\",\n);\n\n/** Allowlist of asset extensions the static handler is willing to serve. */\nconst ASSET_CONTENT_TYPES: Record<string, string> = {\n \".html\": \"text/html; charset=utf-8\",\n \".css\": \"text/css; charset=utf-8\",\n \".js\": \"application/javascript; charset=utf-8\",\n \".svg\": \"image/svg+xml\",\n \".png\": \"image/png\",\n};\n\n/**\n * Serve a single asset file from `ASSETS_DIR`. Path-confined to the\n * canonical assets dir (catching symlinked entries that try to escape)\n * and limited to the small extension allowlist in `ASSET_CONTENT_TYPES`.\n */\nexport async function handleAsset(res: ServerResponse, pathname: string): Promise<void> {\n const segments = decodeAssetSegments(pathname);\n if (!segments) {\n writeAssetError(res, 400, \"bad_asset_path\", \"Bad asset path.\");\n return;\n }\n if (segments.length === 0) {\n writeAssetError(res, 404, \"asset_not_found\", \"Asset not found.\");\n return;\n }\n const contentType = ASSET_CONTENT_TYPES[\n path.extname(segments[segments.length - 1]).toLowerCase()\n ];\n if (!contentType) {\n writeAssetError(res, 404, \"asset_not_found\", \"Asset not found.\");\n return;\n }\n const resolved = await resolveAssetPath(segments);\n if (!resolved) {\n writeAssetError(res, 404, \"asset_not_found\", \"Asset not found.\");\n return;\n }\n try {\n const body = await readFile(resolved);\n res.statusCode = 200;\n res.setHeader(\"Content-Type\", contentType);\n res.end(body);\n } catch {\n writeAssetError(res, 404, \"asset_not_found\", \"Asset not found.\");\n }\n}\n\n/**\n * Split the `/assets/...` URL path into decoded, structurally-safe\n * segments. Returns null when any segment is malformed (invalid\n * percent-encoding, separator, NUL, or traversal). An empty asset\n * path (`/assets/`) returns an empty array — the caller decides\n * whether to 404.\n */\nfunction decodeAssetSegments(pathname: string): string[] | null {\n const trimmed = pathname.replace(/^\\/assets\\//, \"\");\n if (trimmed.length === 0) return [];\n const decoded: string[] = [];\n for (const raw of trimmed.split(\"/\")) {\n let segment: string;\n try {\n segment = decodeURIComponent(raw);\n } catch {\n return null;\n }\n try {\n assertSafeSlug(segment);\n } catch (err) {\n if (err instanceof PathSafetyError) return null;\n throw err;\n }\n decoded.push(segment);\n }\n return decoded;\n}\n\n/**\n * Join `segments` under `ASSETS_DIR`, `realpath` both sides, and return\n * the resolved path only when it stays inside the canonical assets dir.\n * Returns null when the file is missing or escapes confinement.\n */\nasync function resolveAssetPath(segments: string[]): Promise<string | null> {\n const candidate = path.join(ASSETS_DIR, ...segments);\n let resolved: string;\n try {\n resolved = await realpath(candidate);\n } catch {\n return null;\n }\n const baseReal = await realpath(ASSETS_DIR).catch(() => ASSETS_DIR);\n if (resolved === baseReal) return resolved;\n const prefix = baseReal.endsWith(path.sep) ? baseReal : baseReal + path.sep;\n return resolved.startsWith(prefix) ? resolved : null;\n}\n\n/**\n * Write a `{ error: { code, message } }` JSON envelope for asset\n * failures. `message` is a hardcoded human string, never the request\n * pathname — reflecting untrusted input into the response body is\n * uneven with the rest of the server's error contract and would let\n * a noisy client write garbage into downstream response logs.\n */\nfunction writeAssetError(\n res: ServerResponse,\n status: number,\n code: string,\n message: string,\n): void {\n res.statusCode = status;\n res.setHeader(\"Content-Type\", \"application/json; charset=utf-8\");\n res.end(JSON.stringify({ error: { code, message } }));\n}\n","/**\n * Path-safety primitives for the local web viewer.\n *\n * Three layered checks form the v1 path-confinement chain. The HTTP layer\n * (Slice 2) is responsible for calling them in order at route entry:\n *\n * 1. Decode the URL path segment exactly once with `decodeURIComponent`.\n * 2. `assertSafeSlug(decoded)` — reject separators, NUL, traversal-as-slug.\n * 3. `resolveUnderRoot(root, ...segments)` — `realpath` both sides, confirm\n * the joined target stays inside `realpath(root)`. Catches symlink\n * escapes that survive a clean slug.\n * 4. `assertViewerSubtree(root, resolved)` — named-allowlist confinement\n * to `wiki/`, `sources/`, and `.llmwiki/last-lint.json` only. Anything\n * else under root (including other `.llmwiki/*`, `.git/`, `node_modules/`)\n * is rejected even though `realpath` resolved it cleanly.\n */\n\nimport { realpath } from \"fs/promises\";\nimport path from \"path\";\nimport {\n CONCEPTS_DIR,\n QUERIES_DIR,\n SOURCES_DIR,\n LAST_LINT_FILE,\n} from \"../utils/constants.js\";\n\n/** Error thrown when any path-safety check rejects an input. */\nexport class PathSafetyError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PathSafetyError\";\n }\n}\n\n/**\n * Reject decoded slug values that would let a request escape its intended\n * file. Run AFTER `decodeURIComponent` — percent-encoded traversal becomes\n * literal `..` here and is caught. Unicode slugs (any letter or number\n * code point) are accepted; only structural metacharacters are rejected.\n */\nexport function assertSafeSlug(decodedSlug: string): void {\n if (typeof decodedSlug !== \"string\") {\n throw new PathSafetyError(\"slug must be a string\");\n }\n if (decodedSlug.length === 0) {\n throw new PathSafetyError(\"slug must not be empty\");\n }\n if (decodedSlug === \".\" || decodedSlug === \"..\") {\n throw new PathSafetyError(`slug must not be \"${decodedSlug}\"`);\n }\n if (decodedSlug.includes(\"/\") || decodedSlug.includes(\"\\\\\")) {\n throw new PathSafetyError(\"slug must not contain path separators\");\n }\n if (decodedSlug.includes(\"\\0\")) {\n throw new PathSafetyError(\"slug must not contain NUL bytes\");\n }\n if (path.sep !== \"/\" && decodedSlug.includes(path.sep)) {\n throw new PathSafetyError(`slug must not contain platform separator \"${path.sep}\"`);\n }\n}\n\n/**\n * Join `root` and `safeSegments`, resolve symlinks on both ends, and\n * confirm the target stays inside `realpath(root)`. Absolute segments are\n * rejected up-front so a single bad input cannot pivot the join away from\n * `root`. Callers MUST run `assertSafeSlug` on every untrusted segment\n * before this — `resolveUnderRoot` does not re-validate slug shape.\n */\nexport async function resolveUnderRoot(\n root: string,\n ...safeSegments: string[]\n): Promise<string> {\n for (const segment of safeSegments) {\n if (typeof segment !== \"string\" || segment.length === 0) {\n throw new PathSafetyError(\"path segment must be a non-empty string\");\n }\n if (path.isAbsolute(segment)) {\n throw new PathSafetyError(\"path segment must not be absolute\");\n }\n }\n const joined = path.join(root, ...safeSegments);\n const realRoot = await realpath(root);\n const realTarget = await realpath(joined);\n const rootWithSep = realRoot.endsWith(path.sep) ? realRoot : realRoot + path.sep;\n if (realTarget !== realRoot && !realTarget.startsWith(rootWithSep)) {\n throw new PathSafetyError(\"resolved path escapes project root\");\n }\n return realTarget;\n}\n\n/**\n * Confine an already-resolved path to the viewer's read allowlist. The\n * spec says \"read-only metadata under `.llmwiki/`\" — encoded here as a\n * named allowlist (the single file `.llmwiki/last-lint.json`) rather than\n * the full subtree, so a future viewer addition cannot accidentally\n * expose arbitrary `.llmwiki/*` contents over HTTP.\n *\n * The function is async because both `root` and the allowlist entries\n * are canonicalized via `fs.realpath` before comparison — without that,\n * a project root that itself is a symlink (a common workflow: cloning\n * into `~/code/project` which is a symlink to `/Volumes/Work/project`)\n * would false-reject every legitimate file. Callers may pass the raw\n * project root from `process.cwd()` and trust this helper to do the\n * canonicalization. Allowlist entries that do not exist on disk fall\n * back to a non-canonicalized join, so an empty project (no `sources/`\n * yet) still has a sensible allowlist.\n */\nexport async function assertViewerSubtree(root: string, resolvedPath: string): Promise<void> {\n const canonicalRoot = await canonicalizeOrFallback(root);\n const allowlistDirs = await Promise.all([\n canonicalizeOrFallback(path.join(canonicalRoot, \"wiki\")),\n canonicalizeOrFallback(path.join(canonicalRoot, CONCEPTS_DIR)),\n canonicalizeOrFallback(path.join(canonicalRoot, QUERIES_DIR)),\n canonicalizeOrFallback(path.join(canonicalRoot, SOURCES_DIR)),\n ]);\n const lintCachePath = await canonicalizeOrFallback(path.join(canonicalRoot, LAST_LINT_FILE));\n\n for (const dir of allowlistDirs) {\n if (isInsideOrEqual(resolvedPath, dir)) return;\n }\n if (resolvedPath === lintCachePath) return;\n\n throw new PathSafetyError(\"path is outside the viewer-approved subtrees\");\n}\n\n/**\n * Canonicalize `candidate` via `realpath`. Falls back to a normalized\n * stripped-trailing-separator string when the entry does not exist —\n * the allowlist tolerates absent directories (an empty project may have\n * no `sources/` yet) without rejecting every later check.\n */\nasync function canonicalizeOrFallback(candidate: string): Promise<string> {\n try {\n return await realpath(candidate);\n } catch {\n return candidate.endsWith(path.sep) ? candidate.slice(0, -1) : candidate;\n }\n}\n\n/** True when `candidate` equals `parent` or sits beneath it. */\nfunction isInsideOrEqual(candidate: string, parent: string): boolean {\n if (candidate === parent) return true;\n const parentWithSep = parent.endsWith(path.sep) ? parent : parent + path.sep;\n return candidate.startsWith(parentWithSep);\n}\n","/**\n * Server-side markdown → sanitized HTML renderer for the viewer.\n *\n * Pipeline:\n * 1. `markdown-it` with raw HTML disabled parses the body.\n * 2. Two custom inline rules (`wikilink-rule`, `citation-rule`) are\n * registered AFTER the built-in `link` rule so a wikilink or\n * citation embedded in markdown link text (`[outer [[alpha]] text](url)`)\n * gets folded into the outer link's text rather than emitting a\n * nested anchor. Inside that recursive parse the `linkLevel`\n * guard (and the silent-mode decline) keep the custom rules from\n * firing, so `[[…]]` and `^[…]` markers in link text, code spans,\n * fenced code blocks, and escaped sequences all render as literal\n * text. See `markdown-it-helpers.ts::shouldDeferInlineRule`.\n * 3. `sanitize-html` enforces the spec's tag/attribute/protocol\n * allowlist. The same policy applies to every rendered surface\n * (`/api/page/...` and `/api/index`).\n *\n * Returns HTML only. The structured `citations: ClaimCitation[]` field\n * on every page payload comes from `ViewerPage.citations` (produced by\n * Slice 1's `extractClaimCitations`), never from the renderer — so the\n * page record and the rendered HTML cannot drift on what the page cites.\n */\n\nimport MarkdownIt from \"markdown-it\";\nimport sanitizeHtml from \"sanitize-html\";\nimport type { IOptions } from \"sanitize-html\";\nimport { registerWikilink } from \"./wikilink-rule.js\";\nimport { registerCitation } from \"./citation-rule.js\";\nimport type { ViewerSnapshot } from \"./types.js\";\n\n/** Per-render configuration the server passes in. */\ninterface RenderOptions {\n /**\n * True when the viewer is bound to loopback (`127.0.0.1` or `::1`).\n * Controls whether citation chips include `data-absolute-path` and\n * editor-link payloads — both omitted on LAN binds per spec\n * §Support Rail.\n */\n isLoopback: boolean;\n}\n\n/**\n * Render a page body to sanitized HTML. The renderer is constructed per\n * call so the wikilink and citation rules can capture the current\n * snapshot in their closures without leaking across requests.\n */\nexport function renderPageHtml(\n body: string,\n snapshot: ViewerSnapshot,\n options: RenderOptions,\n): { html: string } {\n const md = buildMarkdownIt(snapshot, options);\n const rendered = md.render(body);\n const html = sanitizeHtml(rendered, buildSanitizerPolicy(options));\n return { html };\n}\n\n/** Construct a fresh markdown-it instance with the viewer's inline rules wired in. */\nfunction buildMarkdownIt(snapshot: ViewerSnapshot, options: RenderOptions): MarkdownIt {\n const md = new MarkdownIt({\n html: false,\n linkify: false,\n breaks: false,\n });\n registerWikilink(md, { pages: snapshot.pages });\n registerCitation(md, {\n root: snapshot.root,\n sourceFiles: new Set(snapshot.sourceFilenames),\n isLoopback: options.isLoopback,\n });\n return md;\n}\n\n/**\n * Build the sanitize-html policy. The spec's allowlist is encoded here\n * exactly once; every test that asserts a policy decision points at this\n * one source of truth so future changes show up in a single diff.\n *\n * Exported so the defense-in-depth security tests can exercise\n * `sanitizeHtml` directly against raw HTML the markdown parser would\n * normally escape — the sanitizer is the last line if a future change\n * ever flips the parser's `html` flag or admits an HTML-emitting plugin.\n */\nexport function buildSanitizerPolicy(options: RenderOptions): IOptions {\n const allowedSchemes = [\"http\", \"https\", \"mailto\"];\n const allowedSchemesAppliedToAttributes = [\"href\", \"src\", \"cite\"];\n return {\n allowedTags: [\n \"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\",\n \"p\", \"br\", \"hr\",\n \"ul\", \"ol\", \"li\",\n \"blockquote\",\n \"strong\", \"em\", \"b\", \"i\", \"s\", \"u\",\n \"code\", \"pre\",\n \"table\", \"thead\", \"tbody\", \"tfoot\", \"tr\", \"th\", \"td\",\n \"a\", \"img\", \"span\", \"div\",\n ],\n disallowedTagsMode: \"discard\",\n allowedAttributes: {\n a: [\"href\", \"title\", \"class\", \"id\", \"data-*\", \"aria-*\"],\n img: [\"src\", \"alt\", \"title\", \"class\", \"id\"],\n span: [\"class\", \"id\", \"data-*\", \"aria-*\"],\n div: [\"class\", \"id\", \"data-*\", \"aria-*\"],\n th: [\"scope\", \"colspan\", \"rowspan\", \"class\", \"id\"],\n td: [\"colspan\", \"rowspan\", \"class\", \"id\"],\n table: [\"class\", \"id\"],\n code: [\"class\"],\n \"*\": [\"class\", \"id\"],\n },\n allowedSchemes,\n allowedSchemesByTag: {\n a: buildAnchorSchemes(),\n img: [\"http\", \"https\", \"data\"],\n },\n allowedSchemesAppliedToAttributes,\n allowProtocolRelative: false,\n // `allowedAttributes` above whitelists `class` everywhere via `*`,\n // so no further class-name allowlist is needed; leaving\n // `allowedClasses` unset lets every class value through.\n allowedStyles: {},\n allowedIframeHostnames: [],\n transformTags: {\n a: filterAnchorHref(),\n img: filterImgSrc,\n span: filterSpanForLanBind(options),\n },\n // sanitize-html's URL filter does not enforce hash-only links by\n // default; the anchor transform above whitelists `#/…` explicitly.\n };\n}\n\n/**\n * On non-loopback binds, strip `data-absolute-path` and\n * `data-editor-href` from any `<span>` regardless of who produced them.\n * The citation rule already gates these at the producer; this transform\n * is defense-in-depth so a future markdown-it plugin or hand-crafted\n * raw HTML can't smuggle the user's filesystem layout onto a LAN\n * surface. On loopback binds the attributes pass through untouched\n * (citation chips need them to render the editor link).\n */\nfunction filterSpanForLanBind(options: RenderOptions) {\n return function transformSpan(tagName: string, attribs: Record<string, string>): {\n tagName: string;\n attribs: Record<string, string>;\n } {\n if (options.isLoopback) return { tagName, attribs };\n if (!(\"data-absolute-path\" in attribs) && !(\"data-editor-href\" in attribs)) {\n return { tagName, attribs };\n }\n const stripped: Record<string, string> = {};\n for (const [key, value] of Object.entries(attribs)) {\n if (key === \"data-absolute-path\" || key === \"data-editor-href\") continue;\n stripped[key] = value;\n }\n return { tagName, attribs: stripped };\n };\n}\n\n/**\n * Anchor protocols allowed in the rendered output. Intentionally does\n * NOT include `vscode://` even on loopback: citation chips emit the\n * editor link on a `<span data-editor-href>`, not on an `<a href>`, so\n * markdown-authored anchors like `[click](vscode://file//etc/passwd)`\n * get their href stripped and cannot trick the user into opening\n * arbitrary local files in their editor.\n */\nfunction buildAnchorSchemes(): string[] {\n return [\"http\", \"https\", \"mailto\"];\n}\n\n/**\n * Filter anchor `href` values. Allows http/https/mailto, the viewer's\n * `#/…` hash links, and bare-fragment anchors. Anything else\n * (including any `vscode://` URI — see `buildAnchorSchemes`) loses the\n * `href` attribute entirely.\n */\nfunction filterAnchorHref() {\n return function transformAnchor(tagName: string, attribs: Record<string, string>): {\n tagName: string;\n attribs: Record<string, string>;\n } {\n const href = attribs.href;\n if (typeof href !== \"string\" || href.length === 0) return { tagName, attribs };\n if (isAllowedAnchorHref(href)) return { tagName, attribs };\n const stripped = { ...attribs };\n delete stripped.href;\n return { tagName, attribs: stripped };\n };\n}\n\n/** Filter `img` src to image-typed `data:` URIs and http(s) only. */\nfunction filterImgSrc(tagName: string, attribs: Record<string, string>): {\n tagName: string;\n attribs: Record<string, string>;\n} {\n const src = attribs.src;\n if (typeof src !== \"string\" || src.length === 0) return { tagName, attribs };\n if (isAllowedImgSrc(src)) return { tagName, attribs };\n const stripped = { ...attribs };\n delete stripped.src;\n return { tagName, attribs: stripped };\n}\n\n/**\n * Allow `http://`, `https://`, `mailto:`, and bare-fragment `#…`/`#/…`.\n * On loopback binds, additionally allow `vscode://file/<path>` (the\n * spec's \"safe local editor link\" carve-out for citation chips). The\n * carve-out is intentionally narrow — arbitrary `vscode://` URIs can\n * invoke commands (e.g. `vscode://vscode.git/...`), so a tighter prefix\n * keeps a hostile markdown source from smuggling command invocations\n * through user-authored anchors.\n */\nfunction isAllowedAnchorHref(href: string): boolean {\n if (href.startsWith(\"#\")) return true;\n if (href.startsWith(\"http://\") || href.startsWith(\"https://\")) return true;\n if (href.startsWith(\"mailto:\")) return true;\n return false;\n}\n\n/** Allow http(s) and `data:image/...` only. */\nfunction isAllowedImgSrc(src: string): boolean {\n if (src.startsWith(\"http://\") || src.startsWith(\"https://\")) return true;\n if (src.startsWith(\"data:image/\")) return true;\n return false;\n}\n","/**\n * Shared low-level wiki page collector.\n *\n * Walks `wiki/concepts/` and `wiki/queries/`, derives the slug from each\n * filename stem (NOT through `slugify()` — filename slugs are the canonical\n * filesystem-truth identifier; slugifying them would shift routes, exports,\n * and citation lookups), parses frontmatter via `parseFrontmatterStatus`,\n * and returns one `RawWikiPage` per readable `.md` file with a `parseStatus`\n * field describing structural problems.\n *\n * Content semantics: this layer does not drop pages for parse-level\n * failures (missing frontmatter, malformed YAML, missing title, orphaned\n * flag). Those are surfaced as `parseStatus` flags so the caller decides.\n *\n * Path-safety: this layer DOES drop entries that fail confinement to\n * their expected canonical directory. Specifically — a symlinked\n * `wiki/concepts/` directory (even pointing in-root), a symlinked\n * `.md` file whose `realpath` resolves anywhere other than under the\n * expected concepts/queries directory, and any unreadable entry — are\n * silently excluded. Two callers consume it:\n *\n * - `src/export/collect.ts` filters on `parseStatus.orphaned` and\n * `parseStatus.hasTitle` to preserve the existing export semantics.\n * - `src/viewer/collect.ts` retains every record and maps `parseStatus`\n * flags into `ViewerWarning` objects so users can diagnose malformed\n * pages in the UI.\n */\n\nimport { readdir, readFile, realpath } from \"fs/promises\";\nimport path from \"path\";\nimport { parseFrontmatterStatus, slugify } from \"../utils/markdown.js\";\nimport { CONCEPTS_DIR, QUERIES_DIR } from \"../utils/constants.js\";\nimport type { PageDirectory } from \"../export/types.js\";\n\n/** Regex that matches `[[wikilink]]` or `[[wikilink|alias]]` patterns. */\nconst WIKILINK_RE = /\\[\\[([^\\]|]+)(?:\\|([^\\]]+))?\\]\\]/g;\n\n/**\n * Structural status of a single page's frontmatter, surfaced to callers so\n * they can decide whether to filter, warn, or pass through.\n */\ninterface RawPageParseStatus {\n /** True when the file begins with a `---\\n…\\n---` block. */\n hasFrontmatterBlock: boolean;\n /** True when the frontmatter block exists but YAML failed to parse. */\n malformedFrontmatter: boolean;\n /** True when frontmatter contains a non-empty string `title`. */\n hasTitle: boolean;\n /** True when frontmatter explicitly sets `orphaned: true`. */\n orphaned: boolean;\n}\n\n/**\n * Raw page record returned by the shared collector. Lower-level than\n * `ExportPage` or `ViewerPage`: no decoration, no filtering, no warnings.\n */\nexport interface RawWikiPage {\n /** Filename stem (filename without the trailing `.md`). */\n slug: string;\n /** Which wiki/ subdirectory the page came from. */\n pageDirectory: PageDirectory;\n /** Absolute path on disk, useful for diagnostics and editor links. */\n filePath: string;\n /** Title from frontmatter when present; undefined otherwise. */\n title?: string;\n /** Parsed frontmatter (empty object when missing or malformed). */\n frontmatter: Record<string, unknown>;\n /** Markdown body with the frontmatter block stripped. */\n body: string;\n /** Structural status flags consumed by export and viewer callers. */\n parseStatus: RawPageParseStatus;\n}\n\n/**\n * Extract the slugs of all pages linked via `[[wikilinks]]` in the body.\n * Wikilink targets ARE slugified — the human-typed link text may not match\n * the on-disk filename verbatim, so we normalize to the same shape `slugify`\n * produces. Returns deduplicated targets.\n */\nexport function extractWikilinkSlugs(body: string): string[] {\n const slugs = new Set<string>();\n WIKILINK_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK_RE.exec(body)) !== null) {\n slugs.add(slugify(match[1].trim()));\n }\n return [...slugs];\n}\n\n/**\n * Like `extractWikilinkSlugs` but also preserves the original human-typed\n * text for each target. Used to give dangling-link ghost nodes a readable\n * title instead of a slugified identifier.\n */\nexport function extractWikilinkTargets(body: string): { slug: string; display: string }[] {\n const seen = new Set<string>();\n const targets: { slug: string; display: string }[] = [];\n WIKILINK_RE.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = WIKILINK_RE.exec(body)) !== null) {\n const target = match[1].trim();\n const alias = match[2]?.trim();\n const slug = slugify(target);\n const display = alias ?? target;\n if (!seen.has(slug)) {\n seen.add(slug);\n targets.push({ slug, display });\n }\n }\n return targets;\n}\n\n/**\n * `realpath` wrapper that returns null instead of throwing on missing\n * files. Used everywhere we resolve a possibly-absent or possibly-broken\n * symlink and want to fall through to \"skip this entry.\"\n */\nasync function safeRealpath(p: string): Promise<string | null> {\n try {\n return await realpath(p);\n } catch {\n return null;\n }\n}\n\n/** True when `child` equals `dir` or sits beneath it. */\nfunction isInsideDir(child: string, dir: string): boolean {\n if (child === dir) return true;\n const prefix = dir.endsWith(path.sep) ? dir : dir + path.sep;\n return child.startsWith(prefix);\n}\n\n/**\n * Parse a single markdown file into a `RawWikiPage`. Returns null only when\n * the file cannot be read — every other failure mode (missing frontmatter,\n * malformed YAML, missing title, orphaned flag) is preserved as a\n * `parseStatus` flag so the caller decides how to handle it.\n */\nasync function parsePageFile(\n filePath: string,\n slug: string,\n pageDirectory: PageDirectory,\n): Promise<RawWikiPage | null> {\n let raw: string;\n try {\n raw = await readFile(filePath, \"utf-8\");\n } catch {\n return null;\n }\n\n const { meta, body, hasFrontmatterBlock, malformedFrontmatter } = parseFrontmatterStatus(raw);\n const title = typeof meta.title === \"string\" && meta.title.length > 0 ? meta.title : undefined;\n return {\n slug,\n pageDirectory,\n filePath,\n title,\n frontmatter: meta,\n body,\n parseStatus: {\n hasFrontmatterBlock,\n malformedFrontmatter,\n hasTitle: title !== undefined,\n orphaned: meta.orphaned === true,\n },\n };\n}\n\n/**\n * Collect every readable `.md` file from a single wiki subdirectory.\n *\n * Confinement is stricter than \"stays under project root\": the\n * directory itself must resolve via `realpath` to the exact expected\n * path under `canonicalRoot` (so a symlinked `wiki/concepts/` is\n * skipped wholesale even when its target is also inside the project),\n * and each `.md` entry must resolve to a path under that canonical\n * expected directory (so a symlinked `wiki/concepts/leak.md` pointing\n * at `<root>/README.md` or `<root>/wiki/queries/x.md` is dropped).\n */\nasync function collectFromDir(\n canonicalRoot: string,\n pageDirectory: PageDirectory,\n subdir: string,\n): Promise<RawWikiPage[]> {\n const expectedDir = path.join(canonicalRoot, subdir);\n const realDir = await safeRealpath(expectedDir);\n if (realDir !== expectedDir) return [];\n let files: string[];\n try {\n files = await readdir(realDir);\n } catch {\n return [];\n }\n const pages: RawWikiPage[] = [];\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const candidate = path.join(realDir, file);\n const resolved = await safeRealpath(candidate);\n if (!resolved || !isInsideDir(resolved, realDir)) continue;\n const slug = file.replace(/\\.md$/, \"\");\n const page = await parsePageFile(resolved, slug, pageDirectory);\n if (page) pages.push(page);\n }\n return pages;\n}\n\n/**\n * Collect all readable wiki pages from `wiki/concepts/` and `wiki/queries/`.\n * Entries dropped for path-safety reasons (see `collectFromDir`) are\n * silently excluded. Pages are returned in filesystem order within each\n * directory, with concepts before queries; callers that need a stable\n * total order should sort.\n */\nexport async function collectRawWikiPages(root: string): Promise<RawWikiPage[]> {\n const canonicalRoot = await safeRealpath(root);\n if (!canonicalRoot) return [];\n const [concepts, queries] = await Promise.all([\n collectFromDir(canonicalRoot, \"concepts\", CONCEPTS_DIR),\n collectFromDir(canonicalRoot, \"queries\", QUERIES_DIR),\n ]);\n return [...concepts, ...queries];\n}\n","/**\n * Viewer-facing page collector.\n *\n * Consumes the structural records produced by `src/wiki/collect.ts` and\n * decorates each one with the fields the HTTP server needs:\n * - namespaced `id` (`concepts/<slug>` or `queries/<slug>`)\n * - `outgoingLinks` resolved against the in-memory page list using the\n * bare-slug precedence rule (concepts win over queries)\n * - `citations` extracted via `extractClaimCitations`\n * - stable `ViewerWarning` objects derived from `parseStatus` flags\n *\n * Unlike the export collector, this layer never drops a page: pages with\n * missing or malformed frontmatter are retained with a warning so users\n * can navigate to them and see what is wrong.\n */\n\nimport { collectRawWikiPages, extractWikilinkSlugs, extractWikilinkTargets } from \"../wiki/collect.js\";\nimport type { RawWikiPage } from \"../wiki/collect.js\";\nimport { extractClaimCitations } from \"../utils/markdown.js\";\nimport type { PageId, ViewerPage, ViewerWarning } from \"./types.js\";\n\n/** Minimal page shape `resolveBareSlug` needs to find a target. */\ntype PageIndexEntry = { id: PageId; pageDirectory: ViewerPage[\"pageDirectory\"]; slug: string };\n\n/**\n * Build the decorated page list for a project root. Each `ViewerPage`\n * carries its namespaced id, resolved outgoing links, citations, and any\n * `ViewerWarning` objects derived from the underlying `parseStatus` flags.\n * Returns pages in collector order (concepts then queries).\n */\nexport async function collectViewerPages(root: string): Promise<ViewerPage[]> {\n const raw = await collectRawWikiPages(root);\n return decoratePages(raw);\n}\n\n/**\n * Resolve a bare-slug wikilink target to a namespaced `PageId`. The\n * precedence rule (concepts before queries) matches the spec and is the\n * same logic used for both per-page outgoing links and `/api/index` link\n * resolution; exporting it here keeps callers from re-implementing the\n * order and accidentally diverging.\n */\nexport function resolveBareSlug(\n slug: string,\n pages: ReadonlyArray<PageIndexEntry>,\n): PageId | null {\n if (slug.length === 0) return null;\n const concept = pages.find((p) => p.pageDirectory === \"concepts\" && p.slug === slug);\n if (concept) return concept.id;\n const query = pages.find((p) => p.pageDirectory === \"queries\" && p.slug === slug);\n if (query) return query.id;\n return null;\n}\n\n/**\n * Resolve a list of bare-slug wikilink targets against an in-memory page\n * index and deduplicate the resulting `PageId`s while preserving first-\n * occurrence order. Unresolved targets are dropped.\n */\nexport function resolveBareSlugList(\n targets: string[],\n pages: ReadonlyArray<PageIndexEntry>,\n): PageId[] {\n const seen = new Set<PageId>();\n const ordered: PageId[] = [];\n for (const target of targets) {\n const resolved = resolveBareSlug(target, pages);\n if (resolved && !seen.has(resolved)) {\n seen.add(resolved);\n ordered.push(resolved);\n }\n }\n return ordered;\n}\n\n/**\n * Two-pass decoration: build the namespaced id/title/warnings shell for\n * every page first, then resolve wikilink targets against the completed\n * shell. Single-pass would let a page miss links to pages later in the\n * list; the index has to be complete before resolution begins.\n */\nfunction decoratePages(raw: RawWikiPage[]): ViewerPage[] {\n const shells = raw.map(buildPageShell);\n for (const page of shells) {\n const slugTargets = extractWikilinkSlugs(page.body);\n const richTargets = extractWikilinkTargets(page.body);\n page.outgoingLinks = resolveBareSlugList(slugTargets, shells);\n page.danglingLinks = collectDanglingLinks(richTargets, shells);\n }\n return shells;\n}\n\n/**\n * Return targets from `targets` that `resolveBareSlug` could not find,\n * deduplicated by slug and in first-occurrence order.\n */\nfunction collectDanglingLinks(\n targets: { slug: string; display: string }[],\n pages: ReadonlyArray<PageIndexEntry>,\n): { slug: string; display: string }[] {\n const seen = new Set<string>();\n const dangling: { slug: string; display: string }[] = [];\n for (const t of targets) {\n if (resolveBareSlug(t.slug, pages) === null && !seen.has(t.slug)) {\n seen.add(t.slug);\n dangling.push(t);\n }\n }\n return dangling;\n}\n\n/**\n * Build the parts of a `ViewerPage` that do not need cross-page resolution\n * (id, title, citations, warnings). `outgoingLinks` starts empty and is\n * filled in once every shell is built.\n */\nfunction buildPageShell(page: RawWikiPage): ViewerPage {\n const id: PageId = `${page.pageDirectory}/${page.slug}`;\n return {\n id,\n slug: page.slug,\n pageDirectory: page.pageDirectory,\n title: page.title ?? page.slug,\n filePath: page.filePath,\n frontmatter: page.frontmatter,\n body: page.body,\n outgoingLinks: [],\n citations: extractClaimCitations(page.body),\n warnings: warningsFromParseStatus(page),\n };\n}\n\n/**\n * Map the structural `parseStatus` flags from `src/wiki/collect.ts` into\n * stable viewer warnings. Multiple conditions on one page produce\n * multiple warnings; the order here is the order they appear on the\n * page's `warnings[]`.\n */\nfunction warningsFromParseStatus(page: RawWikiPage): ViewerWarning[] {\n const warnings: ViewerWarning[] = [];\n if (!page.parseStatus.hasFrontmatterBlock) {\n warnings.push({\n code: \"missing_frontmatter\",\n message: `Page \"${page.slug}\" has no frontmatter block.`,\n });\n } else if (page.parseStatus.malformedFrontmatter) {\n warnings.push({\n code: \"malformed_frontmatter\",\n message: `Page \"${page.slug}\" has malformed YAML frontmatter.`,\n });\n }\n if (!page.parseStatus.hasTitle) {\n warnings.push({\n code: \"missing_title\",\n message: `Page \"${page.slug}\" has no frontmatter title; displaying slug.`,\n });\n }\n return warnings;\n}\n\n","/**\n * Shared internal helpers for the viewer's markdown-it inline rules.\n *\n * Both `wikilink-rule.ts` and `citation-rule.ts` need the same two\n * concerns: a minimal HTML escape for their render output (the rules\n * emit raw HTML strings that the sanitizer later validates), and a safe\n * read of `state.linkLevel` (a runtime property markdown-it sets while\n * parsing inside link text but `@types/markdown-it` 14 does not expose\n * on `StateInline`).\n */\n\nimport type StateInline from \"markdown-it/lib/rules_inline/state_inline.mjs\";\n\n/**\n * Minimal HTML attribute/text escape. Used by inline-rule renderers\n * that emit attribute values or visible text inside the HTML string\n * they hand back to markdown-it. The downstream sanitizer enforces the\n * tag/attribute allowlist; this escape just prevents structural breaks\n * (closing the tag early, breaking an attribute quote).\n */\nexport function escapeHtml(input: string): string {\n return input\n .replace(/&/g, \"&\")\n .replace(/</g, \"<\")\n .replace(/>/g, \">\")\n .replace(/\"/g, \""\")\n .replace(/'/g, \"'\");\n}\n\n/**\n * Read `state.linkLevel`, which markdown-it sets while parsing inside\n * link text but `@types/markdown-it` 14 does not surface on\n * `StateInline`. Falls back to 0 if the property is absent so the rule\n * still parses on older typings.\n */\nfunction currentLinkLevel(state: StateInline): number {\n const lifted = state as unknown as { linkLevel?: number };\n return typeof lifted.linkLevel === \"number\" ? lifted.linkLevel : 0;\n}\n\n/**\n * True when a custom inline rule should decline to match at the current\n * state. Two reasons it might:\n *\n * - `state.linkLevel > 0`: markdown-it is recursively parsing inline\n * content inside link text. Custom rules that emit anchors or\n * other interactive elements must not fire there, or the rendered\n * HTML carries nested anchors (invalid + a focus-trap accessibility\n * hazard).\n * - `silent === true`: markdown-it's link-label scanner uses\n * `skipToken` in silent mode to count nested `[`/`]` for bracket\n * matching. Consuming a nested `[` while silent trips the\n * `disableNested` guard inside the link rule and breaks otherwise-\n * valid markdown links of the shape `[outer [[alpha]] text](url)`.\n */\nexport function shouldDeferInlineRule(state: StateInline, silent: boolean): boolean {\n if (currentLinkLevel(state) > 0) return true;\n if (silent) return true;\n return false;\n}\n","/**\n * markdown-it inline rule for `[[wikilink]]` and `[[wikilink|alias]]`.\n *\n * Resolved wikilinks become hash-routed anchors carrying a `data-page-id`\n * attribute the client uses to mark the active sidebar entry. Unresolved\n * wikilinks render as a visible `<span data-missing=\"true\">[[slug]]</span>`\n * so the user can see (and fix) broken provenance instead of silently\n * dropping the target.\n *\n * Rule placement (handled by `registerWikilink`): registered AFTER the\n * built-in `link` rule. The link rule needs first crack at `[ … ](url)`\n * so a `[[wikilink]]` embedded in link text gets folded into the outer\n * link's text rather than emitting a nested anchor; the recursive parse\n * that happens inside link text is then suppressed by `shouldDeferInlineRule`\n * (link-level + silent-mode guards). Code spans and fenced code blocks\n * are handled earlier by markdown-it's own rules; escaped sequences are\n * stripped by the `escape` rule before this rule sees them. All four\n * contexts render the `[[…]]` marker as literal text per the spec's\n * §Slice 4 \"code-span / fenced / escaped / link-text\" audit item.\n */\n\nimport type MarkdownIt from \"markdown-it\";\nimport type StateInline from \"markdown-it/lib/rules_inline/state_inline.mjs\";\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport { resolveBareSlug } from \"./collect.js\";\nimport { slugify } from \"../utils/markdown.js\";\nimport { escapeHtml, shouldDeferInlineRule } from \"./markdown-it-helpers.js\";\nimport type { PageId, ViewerPage } from \"./types.js\";\n\nconst OPEN = \"[\";\nconst CHAR_OPEN_BRACKET = 0x5b; // \"[\"\n\n/** Internal context the parser and renderer share for a single render call. */\ninterface WikilinkContext {\n pages: ReadonlyArray<ViewerPage>;\n}\n\n/**\n * Register the wikilink inline rule and its renderer on `md`. The\n * `context` is captured by closure so the parser/renderer functions stay\n * pure of markdown-it's plugin-options shape.\n *\n * Registered AFTER the built-in `link` rule (not before): the link rule\n * needs first crack at `[ … ](url)` so a wikilink embedded in link text\n * like `[See [[alpha]] reference](url)` is consumed as part of the outer\n * link, with our wikilink later inhibited by the `linkLevel` guard while\n * the link's recursive inline parse runs.\n */\nexport function registerWikilink(md: MarkdownIt, context: WikilinkContext): void {\n md.inline.ruler.after(\"link\", \"wikilink\", buildParser(context));\n md.renderer.rules.wikilink = (tokens: Token[], idx: number): string =>\n renderWikilinkToken(tokens[idx]);\n}\n\n/** Build the inline parser closure capturing the snapshot context. */\nfunction buildParser(context: WikilinkContext) {\n return function parseWikilink(state: StateInline, silent: boolean): boolean {\n if (state.src.charCodeAt(state.pos) !== CHAR_OPEN_BRACKET) return false;\n if (state.src.charCodeAt(state.pos + 1) !== CHAR_OPEN_BRACKET) return false;\n if (shouldDeferInlineRule(state, silent)) return false;\n const closeAt = state.src.indexOf(\"]]\", state.pos + 2);\n if (closeAt < 0) return false;\n const inner = state.src.slice(state.pos + 2, closeAt);\n // Markdown convention: forbid newlines inside a single wikilink span.\n if (inner.includes(\"\\n\") || inner.includes(OPEN)) return false;\n const { rawTarget, display } = splitTargetAndAlias(inner);\n const slug = slugify(rawTarget.trim());\n const resolved = resolveBareSlug(slug, context.pages);\n pushWikilinkToken(state, resolved, slug, display);\n state.pos = closeAt + 2;\n return true;\n };\n}\n\n/** Split the inside-brackets text into a raw target and a display label. */\nfunction splitTargetAndAlias(inner: string): { rawTarget: string; display: string } {\n const pipe = inner.indexOf(\"|\");\n if (pipe < 0) return { rawTarget: inner, display: inner.trim() };\n return {\n rawTarget: inner.slice(0, pipe),\n display: inner.slice(pipe + 1).trim() || inner.slice(0, pipe).trim(),\n };\n}\n\n/** Push a single wikilink token onto the parser state. */\nfunction pushWikilinkToken(\n state: StateInline,\n resolved: PageId | null,\n slug: string,\n display: string,\n): void {\n const token = state.push(\"wikilink\", \"\", 0);\n token.meta = { resolved, slug, display };\n}\n\n/** Render a wikilink token as either an anchor or a missing-link span. */\nfunction renderWikilinkToken(token: Token): string {\n const meta = token.meta as { resolved: PageId | null; slug: string; display: string };\n const display = escapeHtml(meta.display || meta.slug);\n if (!meta.resolved) {\n return `<span data-missing=\"true\">[[${display}]]</span>`;\n }\n const href = `#/${encodeUriSegment(meta.resolved)}`;\n return `<a class=\"wikilink\" data-page-id=\"${escapeHtml(meta.resolved)}\" href=\"${escapeHtml(href)}\">${display}</a>`;\n}\n\n/** Encode a `concepts/<slug>` PageId into the URI form used by the hash router. */\nfunction encodeUriSegment(id: PageId): string {\n const [directory, slug] = id.split(\"/\");\n return `${encodeURIComponent(directory)}/${encodeURIComponent(slug)}`;\n}\n","/**\n * markdown-it inline rule for `^[source.md]` citation markers.\n *\n * Each marker can carry one or more comma-separated source entries; the\n * rule emits ONE chip per parsed span (matching the spec's\n * \"`^[a.md, b.md]` renders two chips\" rule). Span suffixes are parsed in\n * both flavours: `:42-58` and `#L42-L58`. Malformed entries (a colon\n * with no line numbers, end-before-start, etc.) are dropped per\n * `extractClaimCitations`'s contract.\n *\n * Each chip carries the source filename, optional line range,\n * resolvability flag (true iff the source filename is present in the\n * snapshot's source-file list), and — on loopback binds only —\n * `data-absolute-path` plus an editor `data-editor-href`. Non-loopback\n * binds intentionally omit both so LAN viewers cannot learn the user's\n * filesystem layout, per the spec's §Support Rail rules.\n *\n * Rule placement (handled by `registerCitation`): registered AFTER the\n * built-in `link` rule so a `^[…]` embedded in link text gets folded\n * into the outer link's text rather than emitting a chip next to a\n * nested anchor; `shouldDeferInlineRule` then suppresses the rule\n * during the link's recursive inline parse. Code spans and fenced\n * blocks are handled earlier by markdown-it's own rules; escaped\n * `\\^[…]` is stripped by the `escape` rule before this rule sees it.\n */\n\nimport type MarkdownIt from \"markdown-it\";\nimport type StateInline from \"markdown-it/lib/rules_inline/state_inline.mjs\";\nimport type Token from \"markdown-it/lib/token.mjs\";\nimport path from \"path\";\nimport { pathToFileURL } from \"url\";\nimport { extractClaimCitations } from \"../utils/markdown.js\";\nimport { escapeHtml, shouldDeferInlineRule } from \"./markdown-it-helpers.js\";\nimport type { ClaimCitation, SourceSpan } from \"../utils/types.js\";\n\nconst CHAR_CARET = 0x5e; // \"^\"\nconst CHAR_OPEN_BRACKET = 0x5b; // \"[\"\n\n/** Shared context the parser and renderer use to decorate each chip. */\ninterface CitationContext {\n /** Project root, used to compute `data-absolute-path` on loopback binds. */\n root: string;\n /** Filenames present under `sources/`, used to set the resolvability flag. */\n sourceFiles: ReadonlySet<string>;\n /** When false, omit `absolutePath` and editor links per §Support Rail. */\n isLoopback: boolean;\n}\n\n/** One chip's render-ready data. */\ninterface ChipMeta {\n file: string;\n lineStart?: number;\n lineEnd?: number;\n resolved: boolean;\n absolutePath?: string;\n editorHref?: string;\n}\n\n/**\n * Register the rule and renderer on `md`. Registered AFTER the `link`\n * rule for the same reason as the wikilink rule (so a `^[…]` embedded in\n * link text gets included in the outer link's text, with the inner\n * recursive parse blocked by the `linkLevel` guard).\n */\nexport function registerCitation(md: MarkdownIt, context: CitationContext): void {\n md.inline.ruler.after(\"link\", \"citation\", buildParser(context));\n md.renderer.rules.citation = (tokens: Token[], idx: number): string =>\n renderCitationToken(tokens[idx]);\n}\n\n/** Build the parser closure capturing the citation context. */\nfunction buildParser(context: CitationContext) {\n return function parseCitation(state: StateInline, silent: boolean): boolean {\n if (state.src.charCodeAt(state.pos) !== CHAR_CARET) return false;\n if (state.src.charCodeAt(state.pos + 1) !== CHAR_OPEN_BRACKET) return false;\n if (shouldDeferInlineRule(state, silent)) return false;\n const closeAt = state.src.indexOf(\"]\", state.pos + 2);\n if (closeAt < 0) return false;\n const inner = state.src.slice(state.pos + 2, closeAt);\n if (inner.includes(\"\\n\")) return false;\n const citations = extractClaimCitations(`^[${inner}]`);\n pushChipTokens(state, citations, context);\n state.pos = closeAt + 1;\n return true;\n };\n}\n\n/** Emit one `citation` token per parsed span (multi-source marker → multiple chips). */\nfunction pushChipTokens(\n state: StateInline,\n citations: ClaimCitation[],\n context: CitationContext,\n): void {\n for (const citation of citations) {\n for (const span of citation.spans) {\n const token = state.push(\"citation\", \"\", 0);\n token.meta = buildChipMeta(span, context);\n }\n }\n}\n\n/** Construct the chip-meta record for a single source span. */\nfunction buildChipMeta(span: SourceSpan, context: CitationContext): ChipMeta {\n const meta: ChipMeta = {\n file: span.file,\n lineStart: span.lines?.start,\n lineEnd: span.lines?.end,\n resolved: context.sourceFiles.has(span.file),\n };\n if (context.isLoopback && meta.resolved && isBareFilename(span.file)) {\n const absolutePath = path.join(context.root, \"sources\", span.file);\n meta.absolutePath = absolutePath;\n meta.editorHref = buildEditorHref(absolutePath, meta.lineStart);\n }\n return meta;\n}\n\n/**\n * Build the `vscode://file/...` editor href, percent-encoding the path\n * portion so URI delimiters inside source filenames (spaces, `#`, `?`,\n * `&`, `=`, `;`, etc.) cannot turn part of the filename into a URI\n * fragment, query, or parameter. Uses Node's `pathToFileURL().pathname`\n * because it percent-encodes every char that would change URI structure\n * while preserving path separators. Plain `encodeURI` preserves several\n * of those delimiters, so a filename like `notes #1?.md` would emit a\n * malformed href there.\n *\n * The `pathname` already starts with `/` (it is the absolute path), so\n * concatenating directly onto `vscode://file` yields a well-formed URI\n * with `file` as the authority and the encoded absolute path as the\n * path (e.g. `vscode://file/tmp/x.md`). The optional `:<line>` suffix\n * is appended AFTER encoding so vscode parses it as a line index, not\n * part of the path.\n */\nfunction buildEditorHref(absolutePath: string, lineStart: number | undefined): string {\n const encodedPath = pathToFileURL(absolutePath).pathname;\n if (lineStart === undefined) return `vscode://file${encodedPath}`;\n return `vscode://file${encodedPath}:${lineStart}`;\n}\n\n/**\n * Conservative filename check: bare basename only, no separators, no\n * traversal segments. The citation rule never trusts a path-shaped\n * filename to begin with — if the source has slashes in it, we keep the\n * chip but skip the editor-link payload.\n */\nfunction isBareFilename(file: string): boolean {\n if (file.length === 0) return false;\n if (file.includes(\"/\") || file.includes(\"\\\\\") || file.includes(\"\\0\")) return false;\n if (file === \".\" || file === \"..\") return false;\n return true;\n}\n\n/** Render one citation chip token. */\nfunction renderCitationToken(token: Token): string {\n const meta = token.meta as ChipMeta;\n const label = formatChipLabel(meta);\n const attrs = chipAttributes(meta);\n return `<span ${attrs}>${escapeHtml(label)}</span>`;\n}\n\n/** Build the chip's data-* attribute string. */\nfunction chipAttributes(meta: ChipMeta): string {\n const parts = [\n `class=\"citation-chip\"`,\n `data-file=\"${escapeHtml(meta.file)}\"`,\n `data-resolved=\"${meta.resolved ? \"true\" : \"false\"}\"`,\n ];\n if (meta.lineStart !== undefined) {\n parts.push(`data-line-start=\"${meta.lineStart}\"`);\n }\n if (meta.lineEnd !== undefined) {\n parts.push(`data-line-end=\"${meta.lineEnd}\"`);\n }\n if (meta.absolutePath !== undefined) {\n parts.push(`data-absolute-path=\"${escapeHtml(meta.absolutePath)}\"`);\n }\n if (meta.editorHref !== undefined) {\n parts.push(`data-editor-href=\"${escapeHtml(meta.editorHref)}\"`);\n }\n return parts.join(\" \");\n}\n\n/** Human-visible chip label: filename plus optional line range. */\nfunction formatChipLabel(meta: ChipMeta): string {\n if (meta.lineStart === undefined) return meta.file;\n if (meta.lineEnd === undefined || meta.lineEnd === meta.lineStart) {\n return `${meta.file}:${meta.lineStart}`;\n }\n return `${meta.file}:${meta.lineStart}-${meta.lineEnd}`;\n}\n\n","/**\n * Server-side title/body search over the startup `ViewerSnapshot`.\n *\n * V1 semantics (spec §Slice 5 \"Search semantics\"):\n * - case-insensitive\n * - whitespace-tokenized\n * - multi-token AND: every token must appear in either title or body\n * - title matches rank before body matches\n * - 200-char query cap, 50-result cap\n * - concept and query pages only — `wiki/index.md` is excluded by\n * construction (it never lives in `snapshot.pages`)\n * - no fuzzy matching, stemming, regex, or client-side search\n *\n * The search reads from the snapshot exclusively — no per-request disk\n * I/O — so it inherits the same \"frozen at startup, restart to refresh\"\n * lifecycle as the rest of the viewer's API.\n */\n\nimport type { PageId, ViewerPage, ViewerSnapshot } from \"./types.js\";\nimport type { PageDirectory } from \"../export/types.js\";\n\nconst MAX_QUERY_LENGTH = 200;\nconst MAX_RESULTS = 50;\nconst SNIPPET_RADIUS = 60;\nconst SNIPPET_ELLIPSIS = \"…\";\n\n/** Where the query matched in a result page. */\ntype SearchMatch = \"title\" | \"body\";\n\n/** One row in the `/api/search` response. */\ninterface SearchResult {\n id: PageId;\n pageDirectory: PageDirectory;\n title: string;\n snippet: string;\n matchedIn: SearchMatch;\n}\n\n/**\n * Run a search over the snapshot and return the results envelope. Pure\n * over `(snapshot, rawQuery)` — the same inputs always produce the same\n * output, which lets the route handler stay a one-line adapter.\n */\nexport function searchPages(\n snapshot: ViewerSnapshot,\n rawQuery: string,\n): { results: SearchResult[] } {\n const tokens = tokenizeQuery(rawQuery);\n if (tokens.length === 0) return { results: [] };\n const matches = collectMatches(snapshot.pages, tokens);\n matches.sort(compareResults);\n return { results: matches.slice(0, MAX_RESULTS) };\n}\n\n/**\n * Trim, lowercase, cap at 200 characters, then split on any run of\n * whitespace. Empty tokens are dropped so trailing/leading spaces or\n * a runaway over-cap query still produce sensible tokens.\n */\nfunction tokenizeQuery(rawQuery: string): string[] {\n if (typeof rawQuery !== \"string\") return [];\n const trimmed = rawQuery.trim();\n if (trimmed.length === 0) return [];\n const capped = trimmed.slice(0, MAX_QUERY_LENGTH).toLowerCase();\n return capped.split(/\\s+/).filter((t) => t.length > 0);\n}\n\n/** Iterate the snapshot pages and emit one result per match. */\nfunction collectMatches(pages: ReadonlyArray<ViewerPage>, tokens: string[]): SearchResult[] {\n const matches: SearchResult[] = [];\n for (const page of pages) {\n const result = matchPage(page, tokens);\n if (result) matches.push(result);\n }\n return matches;\n}\n\n/**\n * Decide whether `page` matches `tokens`. Per spec §Slice 5 Search\n * Semantics: \"every token must appear in title or body\" — each token\n * individually must appear in the title-or-body union. Classification\n * (`matchedIn`) is \"title\" only when every token is found in the title;\n * any token that only matched the body downgrades the page to a body\n * hit, which then ranks below title hits.\n */\nfunction matchPage(page: ViewerPage, tokens: string[]): SearchResult | null {\n const titleLower = page.title.toLowerCase();\n const bodyLower = page.body.toLowerCase();\n for (const token of tokens) {\n if (!titleLower.includes(token) && !bodyLower.includes(token)) return null;\n }\n const allInTitle = tokens.every((t) => titleLower.includes(t));\n if (allInTitle) return rowFromPage(page, page.title, \"title\");\n const snippet = buildBodySnippet(page.body, bodyLower, tokens);\n return rowFromPage(page, snippet, \"body\");\n}\n\n/** Assemble a SearchResult from a page + computed snippet. */\nfunction rowFromPage(page: ViewerPage, snippet: string, matchedIn: SearchMatch): SearchResult {\n return {\n id: page.id,\n pageDirectory: page.pageDirectory,\n title: page.title,\n snippet,\n matchedIn,\n };\n}\n\n/**\n * Extract ±SNIPPET_RADIUS chars around the earliest token match in the\n * body. Newlines are flattened to single spaces so the snippet renders\n * inline in the results panel. `…` is prepended/appended when the\n * window was truncated at either end.\n */\nfunction buildBodySnippet(body: string, bodyLower: string, tokens: string[]): string {\n const matchPos = earliestTokenPosition(bodyLower, tokens);\n const start = Math.max(0, matchPos - SNIPPET_RADIUS);\n const end = Math.min(body.length, matchPos + SNIPPET_RADIUS);\n const cleaned = stripInlineMarkdownNoise(body.slice(start, end))\n .replace(/\\s+/g, \" \")\n .trim();\n const prefix = start > 0 ? SNIPPET_ELLIPSIS : \"\";\n const suffix = end < body.length ? SNIPPET_ELLIPSIS : \"\";\n return `${prefix}${cleaned}${suffix}`;\n}\n\n/**\n * Strip common inline-markdown markers from a snippet so the search\n * results panel shows readable prose rather than `**keyword**` and\n * `[label](url)` noise. Intentionally narrow: only handles the\n * inline-marker forms a reader would mistake for typos. Block-level\n * markers (`#` headings, `>` blockquotes) are left alone — they sit at\n * the start of a line and rarely land inside a ±60-char window.\n */\nfunction stripInlineMarkdownNoise(text: string): string {\n return text\n .replace(/!\\[([^\\]]*)\\]\\([^)]*\\)/g, \"$1\")\n .replace(/\\[([^\\]]+)\\]\\([^)]*\\)/g, \"$1\")\n .replace(/\\[\\[([^\\]|\\n]+)\\|([^\\]\\n]+)\\]\\]/g, \"$2\")\n .replace(/\\[\\[([^\\]\\n]+)\\]\\]/g, \"$1\")\n .replace(/\\*\\*([^*]+)\\*\\*/g, \"$1\")\n .replace(/__([^_]+)__/g, \"$1\")\n .replace(/(?<!\\w)\\*([^*\\n]+)\\*(?!\\w)/g, \"$1\")\n .replace(/(?<!\\w)_([^_\\n]+)_(?!\\w)/g, \"$1\")\n .replace(/`([^`\\n]+)`/g, \"$1\")\n .replace(/~~([^~\\n]+)~~/g, \"$1\");\n}\n\n/** Earliest index where any token first appears in the body. */\nfunction earliestTokenPosition(bodyLower: string, tokens: string[]): number {\n let earliest = bodyLower.length;\n for (const token of tokens) {\n const idx = bodyLower.indexOf(token);\n if (idx >= 0 && idx < earliest) earliest = idx;\n }\n return earliest;\n}\n\n/**\n * Title hits sort before body hits. Within the same `matchedIn` bucket\n * the order is stable by title (alphabetical, locale-aware) so the\n * result list does not shift between requests against the same snapshot.\n */\nfunction compareResults(a: SearchResult, b: SearchResult): number {\n if (a.matchedIn !== b.matchedIn) {\n return a.matchedIn === \"title\" ? -1 : 1;\n }\n return a.title.localeCompare(b.title);\n}\n","/**\n * Build the frozen-at-startup `ViewerSnapshot` consumed by every viewer\n * endpoint. Every count, page list, and index payload that the HTTP\n * layer needs is captured here exactly once — v1 deliberately does not\n * live-watch the filesystem, so post-startup mutations are intentionally\n * invisible to the running viewer until it restarts.\n *\n * The snapshot consolidates four data sources:\n * - `collectViewerPages` for the decorated page list AND the\n * concept/query counts (deriving counts from the already-confined\n * page list means symlinked entries dropped by the collector\n * cannot quietly inflate the counts via a second unconfined scan)\n * - `readState` for the compiled-source count\n * - `countCandidates` for the pending-reviews count\n * - `readdir(sources/)` for the cheap source-file count\n */\n\nimport { readdir, readFile, realpath } from \"fs/promises\";\nimport path from \"path\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport { countCandidates } from \"../compiler/candidates.js\";\nimport { readState } from \"../utils/state.js\";\nimport { collectViewerPages, resolveBareSlugList } from \"./collect.js\";\nimport { extractWikilinkSlugs } from \"../wiki/collect.js\";\nimport { isMalformedCitationEntry } from \"../utils/markdown.js\";\nimport { buildGraphData } from \"./graph.js\";\nimport type {\n ViewerCounts,\n ViewerIndex,\n ViewerPage,\n ViewerProject,\n ViewerRecentPage,\n ViewerSnapshot,\n ViewerWarning,\n} from \"./types.js\";\n\nconst RECENT_PAGES_LIMIT = 8;\nconst INDEX_HREF = \"/#/index\";\n\n/**\n * Build the immutable startup snapshot for a project root. Reads pages,\n * counts, source state, candidates, and the optional `wiki/index.md`\n * exactly once and returns a fully populated `ViewerSnapshot`. Callers\n * must NOT re-derive any of these from disk on a per-request path —\n * `readLintCache` in `src/viewer/health.ts` is the sole exception.\n */\nexport async function buildViewerSnapshot(root: string): Promise<ViewerSnapshot> {\n const [pages, state, pendingReviews, sourceFilenames, index] = await Promise.all([\n collectViewerPages(root),\n readState(root),\n countCandidates(root),\n listSourceFiles(root),\n readIndexFile(root),\n ]);\n const project = buildProject(root);\n // Concept/query counts are derived from `pages`, the already-confined\n // viewer page list, NOT from a second unconfined directory scan.\n // Anything the collector dropped for path-safety reasons (symlinked\n // file or directory) is therefore also excluded from the counts.\n const counts: ViewerCounts = {\n concepts: pages.filter((p) => p.pageDirectory === \"concepts\").length,\n queries: pages.filter((p) => p.pageDirectory === \"queries\").length,\n sourceFiles: sourceFilenames.length,\n pendingReviews,\n compiledSources: Object.keys(state.sources).length,\n };\n const fullIndex: ViewerIndex = {\n available: index.available,\n href: INDEX_HREF,\n body: index.body,\n outgoingLinks: resolveBareSlugList(extractWikilinkSlugs(index.body), pages),\n };\n const sourceFileSet = new Set(sourceFilenames);\n const annotatedPages = pages.map((page) => annotateCitationWarnings(page, sourceFileSet));\n const graph = buildGraphData(annotatedPages);\n return {\n root,\n generatedAt: new Date().toISOString(),\n project,\n counts,\n index: fullIndex,\n recentPages: buildRecentPages(annotatedPages),\n pages: annotatedPages,\n sourceFilenames,\n graph,\n };\n}\n\n/**\n * Append `unresolved_citation` and `malformed_citation` warnings to a\n * page based on its parsed citations and the project's source-file\n * list. Slice 1 only produced parser-level warnings; citation\n * resolvability needs the snapshot's source-file list, so this is the\n * earliest layer that can decide.\n *\n * The body is re-scanned for raw `^[…]` markers (rather than iterating\n * `page.citations`) because `extractClaimCitations` drops citations\n * whose ONLY entry has an invalid line range — but those still need a\n * `malformed_citation` warning. Scanning the body gives every marker a\n * chance to be classified.\n */\nfunction annotateCitationWarnings(page: ViewerPage, sourceFiles: ReadonlySet<string>): ViewerPage {\n const extra: ViewerWarning[] = [];\n const markerPattern = /\\^\\[([^\\]\\n]+)\\]/g;\n let match: RegExpExecArray | null;\n while ((match = markerPattern.exec(page.body)) !== null) {\n appendCitationWarningsForMarker(match[1], sourceFiles, extra);\n }\n if (extra.length === 0) return page;\n return { ...page, warnings: [...page.warnings, ...extra] };\n}\n\n/** Classify every comma-separated entry inside one `^[…]` marker. */\nfunction appendCitationWarningsForMarker(\n raw: string,\n sourceFiles: ReadonlySet<string>,\n into: ViewerWarning[],\n): void {\n for (const entry of raw.split(\",\")) {\n const trimmed = entry.trim();\n if (trimmed.length === 0) continue;\n if (isMalformedCitationEntry(trimmed)) {\n into.push({\n code: \"malformed_citation\",\n message: `Malformed citation entry: ${trimmed}`,\n });\n continue;\n }\n const file = trimmed.split(/[:#]/)[0];\n if (file.length > 0 && !sourceFiles.has(file)) {\n into.push({\n code: \"unresolved_citation\",\n message: `Source not found: ${file}`,\n });\n }\n }\n}\n\n\n/** Project title and bare directory name for the dashboard header. */\nfunction buildProject(root: string): ViewerProject {\n const rootName = path.basename(root);\n return { title: rootName, rootName };\n}\n\n/**\n * List filenames directly under `sources/`. Returns an empty array when\n * the directory is missing. The Slice 4 citation renderer uses this list\n * to mark each chip `data-resolved` without per-request directory scans;\n * `counts.sourceFiles` is the cheap `.length` of the same list.\n *\n * Stricter than \"stays under project root\": `realpath(<root>/sources)`\n * must equal the literal canonical path `<canonicalRoot>/sources`. A\n * symlinked `sources/` directory — even pointing in-root — returns an\n * empty list, matching the same containment posture the wiki collector\n * uses for `wiki/concepts/` and `wiki/queries/`. Symlinked entries\n * inside the directory are excluded by `Dirent.isFile()` (which returns\n * false for symlinks since `withFileTypes` does not follow them).\n */\nasync function listSourceFiles(root: string): Promise<string[]> {\n let canonicalRoot: string;\n try {\n canonicalRoot = await realpath(root);\n } catch {\n return [];\n }\n const expectedDir = path.join(canonicalRoot, SOURCES_DIR);\n let realDir: string;\n try {\n realDir = await realpath(expectedDir);\n } catch {\n return [];\n }\n if (realDir !== expectedDir) return [];\n try {\n const entries = await readdir(realDir, { withFileTypes: true });\n return entries.filter((e) => e.isFile()).map((e) => e.name);\n } catch {\n return [];\n }\n}\n\n/**\n * Read `wiki/index.md` if present. Missing index is not an error: many\n * projects compile without an index page, and the viewer renders an\n * \"index unavailable\" placeholder for the `/#/index` route.\n *\n * Stricter than \"stays under project root\": `realpath(wiki/index.md)`\n * must equal the literal canonical path `<root>/wiki/index.md`. A\n * symlinked `wiki/index.md` is treated as unavailable, even when the\n * link target also lives inside the project — pointing the index at\n * (say) `<root>/README.md` would let the index endpoint render\n * content that has no business being the project's compiled index.\n * A symlinked `wiki/` directory is dropped by the same equality check.\n */\nasync function readIndexFile(root: string): Promise<{ available: boolean; body: string }> {\n let canonicalRoot: string;\n try {\n canonicalRoot = await realpath(root);\n } catch {\n return { available: false, body: \"\" };\n }\n const expectedIndex = path.join(canonicalRoot, \"wiki\", \"index.md\");\n let resolved: string;\n try {\n resolved = await realpath(expectedIndex);\n } catch {\n return { available: false, body: \"\" };\n }\n if (resolved !== expectedIndex) {\n return { available: false, body: \"\" };\n }\n try {\n const body = await readFile(resolved, \"utf-8\");\n return { available: true, body };\n } catch {\n return { available: false, body: \"\" };\n }\n}\n\n/**\n * Top-N recently updated pages for the dashboard. Pages without an\n * `updatedAt` frontmatter field sort to the end with an empty string so\n * the list remains deterministic.\n */\nfunction buildRecentPages(pages: ViewerPage[]): ViewerRecentPage[] {\n const rows: ViewerRecentPage[] = pages.map((page) => ({\n id: page.id,\n pageDirectory: page.pageDirectory,\n slug: page.slug,\n title: page.title,\n updatedAt:\n typeof page.frontmatter.updatedAt === \"string\" ? (page.frontmatter.updatedAt as string) : \"\",\n }));\n rows.sort((a, b) => b.updatedAt.localeCompare(a.updatedAt));\n return rows.slice(0, RECENT_PAGES_LIMIT);\n}\n\n","/**\n * Review candidate persistence for the llmwiki compile pipeline.\n *\n * When `llmwiki compile --review` runs, generated wiki pages are routed\n * here as JSON candidate records under `.llmwiki/candidates/` instead of\n * being written directly to `wiki/`. Reviewers then approve or reject the\n * proposals via the `llmwiki review` subcommands.\n *\n * Candidates are deliberately kept as standalone JSON so they survive across\n * compile runs and can be inspected manually without the CLI. Each record\n * stores the full page body so approval is a pure copy — the LLM is never\n * called again at approval time.\n */\n\nimport { readdir, rename, unlink, writeFile, mkdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { randomBytes } from \"crypto\";\nimport { atomicWrite, safeReadFile } from \"../utils/markdown.js\";\nimport * as output from \"../utils/output.js\";\nimport {\n CANDIDATES_DIR,\n CANDIDATES_ARCHIVE_DIR,\n} from \"../utils/constants.js\";\nimport type { ReviewCandidate, SourceState } from \"../utils/types.js\";\nimport type { LintResult } from \"../linter/types.js\";\n\n/** Length (bytes) of the random suffix appended to candidate ids. */\nconst ID_SUFFIX_BYTES = 4;\n\n/** Filesystem extension used for candidate JSON files. */\nconst CANDIDATE_EXT = \".json\";\n\n/** Input shape for creating a new candidate (id + timestamp generated here). */\ninterface CandidateDraft {\n title: string;\n slug: string;\n summary: string;\n sources: string[];\n body: string;\n /**\n * Per-source state entries to persist into `.llmwiki/state.json` when this\n * candidate is approved. Keyed by source filename. Optional so callers that\n * never need incremental tracking (legacy / tests) can omit it.\n */\n sourceStates?: Record<string, SourceState>;\n /**\n * Schema lint violations for the candidate body detected at compile time.\n * Omit (or pass `undefined`) when the candidate body is clean.\n */\n schemaViolations?: LintResult[];\n /**\n * Provenance lint violations for the candidate body — malformed claim\n * citations, out-of-bounds spans, or missing source files. Surfaced\n * alongside schema violations so reviewers see citation issues before\n * approving.\n */\n provenanceViolations?: LintResult[];\n}\n\n/** Build a deterministic-but-unique id from a slug and a short random suffix. */\nfunction buildCandidateId(slug: string): string {\n const suffix = randomBytes(ID_SUFFIX_BYTES).toString(\"hex\");\n return `${slug}-${suffix}`;\n}\n\n/** Absolute path to a candidate's JSON file. */\nfunction candidatePath(root: string, id: string): string {\n return path.join(root, CANDIDATES_DIR, `${id}${CANDIDATE_EXT}`);\n}\n\n/** Absolute path to the archived JSON file for a rejected candidate. */\nfunction archivePath(root: string, id: string): string {\n return path.join(root, CANDIDATES_ARCHIVE_DIR, `${id}${CANDIDATE_EXT}`);\n}\n\n/**\n * Persist a new candidate record and return it. The id is generated from the\n * slug plus a short random suffix so multiple compile runs can co-exist.\n * @param root - Project root directory.\n * @param draft - The candidate fields to persist.\n * @returns The full ReviewCandidate (with id + generatedAt populated).\n */\nexport async function writeCandidate(\n root: string,\n draft: CandidateDraft,\n): Promise<ReviewCandidate> {\n const candidate: ReviewCandidate = {\n id: buildCandidateId(draft.slug),\n title: draft.title,\n slug: draft.slug,\n summary: draft.summary,\n sources: draft.sources,\n body: draft.body,\n generatedAt: new Date().toISOString(),\n ...(draft.sourceStates ? { sourceStates: draft.sourceStates } : {}),\n ...(draft.schemaViolations ? { schemaViolations: draft.schemaViolations } : {}),\n ...(draft.provenanceViolations ? { provenanceViolations: draft.provenanceViolations } : {}),\n };\n\n await atomicWrite(candidatePath(root, candidate.id), JSON.stringify(candidate, null, 2));\n return candidate;\n}\n\n/**\n * Emit a CLI error, set exit code 1, and return null. Used by candidate load\n * helpers to avoid duplicating the error-path boilerplate.\n * @param message - Error message to display.\n */\nfunction failWithError(message: string): null {\n output.status(\"!\", output.error(message));\n process.exitCode = 1;\n return null;\n}\n\n/**\n * Load a candidate by id and, if missing, emit the standard \"not found\" CLI\n * error and set process.exitCode = 1. Returns null when the candidate is\n * missing so callers can early-return without re-implementing the same\n * error block in every review subcommand.\n * @param root - Project root directory.\n * @param id - Candidate id to look up.\n */\nexport async function loadCandidateOrFail(\n root: string,\n id: string,\n): Promise<ReviewCandidate | null> {\n const candidate = await readCandidate(root, id);\n if (!candidate) return failWithError(`Candidate not found: ${id}`);\n return candidate;\n}\n\n/**\n * Re-read a candidate under the lock and abort if it has disappeared.\n *\n * This is the authoritative TOCTOU guard: a concurrent approve or reject may\n * have removed the candidate after the pre-lock fast-fail but before the lock\n * was acquired. Returning `null` signals the caller to abort without writing\n * any output artefact.\n * @param root - Project root directory.\n * @param id - Candidate id to load.\n * @returns The candidate if still present, or `null` after setting exit code 1.\n */\nexport async function loadCandidateUnderLockOrFail(\n root: string,\n id: string,\n): Promise<ReviewCandidate | null> {\n const candidate = await readCandidate(root, id);\n if (!candidate) {\n return failWithError(`Candidate ${id} was removed by another process during review.`);\n }\n return candidate;\n}\n\n/** Parse a single candidate JSON file. Returns null when the file is missing or malformed. */\nexport async function readCandidate(\n root: string,\n id: string,\n): Promise<ReviewCandidate | null> {\n const raw = await safeReadFile(candidatePath(root, id));\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as ReviewCandidate;\n if (!isValidCandidate(parsed)) return null;\n return parsed;\n } catch {\n return null;\n }\n}\n\n/** Defensive type-guard so corrupted candidate files don't blow up the CLI. */\nfunction isValidCandidate(value: unknown): value is ReviewCandidate {\n if (!value || typeof value !== \"object\") return false;\n const candidate = value as Record<string, unknown>;\n return (\n typeof candidate.id === \"string\" &&\n typeof candidate.title === \"string\" &&\n typeof candidate.slug === \"string\" &&\n typeof candidate.body === \"string\" &&\n Array.isArray(candidate.sources)\n );\n}\n\n/**\n * List every candidate currently pending review, sorted by generation time.\n * Skips files that aren't candidate JSON (e.g. the archive subdirectory).\n * @param root - Project root directory.\n * @returns All pending review candidates.\n */\nexport async function listCandidates(root: string): Promise<ReviewCandidate[]> {\n const dir = path.join(root, CANDIDATES_DIR);\n if (!existsSync(dir)) return [];\n\n const entries = await readdir(dir, { withFileTypes: true });\n const candidates: ReviewCandidate[] = [];\n for (const entry of entries) {\n if (!entry.isFile() || !entry.name.endsWith(CANDIDATE_EXT)) continue;\n const id = entry.name.slice(0, -CANDIDATE_EXT.length);\n const candidate = await readCandidate(root, id);\n if (candidate) candidates.push(candidate);\n }\n\n candidates.sort((a, b) => a.generatedAt.localeCompare(b.generatedAt));\n return candidates;\n}\n\n/**\n * Count pending candidates using the same validity filter as listCandidates,\n * so consumers (e.g. `wiki_status.pendingCandidates`) never report counts\n * that disagree with what `review list` actually shows. Malformed JSON files\n * are skipped here exactly as they are by listCandidates.\n */\nexport async function countCandidates(root: string): Promise<number> {\n const candidates = await listCandidates(root);\n return candidates.length;\n}\n\n/** Remove a pending candidate from disk. Returns false when nothing existed to remove. */\nexport async function deleteCandidate(root: string, id: string): Promise<boolean> {\n const filePath = candidatePath(root, id);\n if (!existsSync(filePath)) return false;\n await unlink(filePath);\n return true;\n}\n\n/**\n * Move a candidate from the pending area into the archive subdirectory so\n * rejected proposals stay auditable without touching `wiki/`.\n * @param root - Project root directory.\n * @param id - Candidate id to archive.\n * @returns True when the candidate was found and archived.\n */\nexport async function archiveCandidate(root: string, id: string): Promise<boolean> {\n const sourcePath = candidatePath(root, id);\n if (!existsSync(sourcePath)) return false;\n\n const target = archivePath(root, id);\n await mkdir(path.dirname(target), { recursive: true });\n // Copy via writeFile + unlink to support cross-filesystem rename failures.\n try {\n await rename(sourcePath, target);\n } catch {\n const raw = await safeReadFile(sourcePath);\n await writeFile(target, raw, \"utf-8\");\n await unlink(sourcePath);\n }\n return true;\n}\n","/**\n * Manages .llmwiki/state.json — the persistent compilation state that tracks\n * source file hashes and their compiled concepts. Enables incremental\n * compilation by detecting which sources have changed since last compile.\n *\n * Uses atomic writes (write to .tmp, then rename) to prevent corruption\n * from interrupted compiles.\n */\n\nimport { readFile, writeFile, rename, mkdir, copyFile } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { LLMWIKI_DIR, STATE_FILE } from \"./constants.js\";\nimport type { WikiState, SourceState } from \"./types.js\";\n\nfunction emptyState(): WikiState {\n return { version: 1, indexHash: \"\", sources: {} };\n}\n\n/** Read .llmwiki/state.json, recovering from corruption gracefully. */\nexport async function readState(root: string): Promise<WikiState> {\n const filePath = path.join(root, STATE_FILE);\n\n if (!existsSync(filePath)) {\n return emptyState();\n }\n\n try {\n const raw = await readFile(filePath, \"utf-8\");\n return JSON.parse(raw) as WikiState;\n } catch {\n const bakPath = filePath + \".bak\";\n console.warn(`⚠ Corrupt state.json — backed up to ${bakPath}, starting fresh.`);\n await copyFile(filePath, bakPath);\n return emptyState();\n }\n}\n\n/** Atomically write state.json (write .tmp then rename). */\nexport async function writeState(root: string, state: WikiState): Promise<void> {\n const dir = path.join(root, LLMWIKI_DIR);\n await mkdir(dir, { recursive: true });\n\n const filePath = path.join(root, STATE_FILE);\n const tmpPath = filePath + \".tmp\";\n\n await writeFile(tmpPath, JSON.stringify(state, null, 2), \"utf-8\");\n await rename(tmpPath, filePath);\n}\n\n/**\n * Update a single source's entry in state after successful compilation.\n * Per-source granularity means interrupted compiles only reprocess incomplete sources.\n */\nexport async function updateSourceState(\n root: string,\n sourceFile: string,\n entry: SourceState,\n): Promise<void> {\n const state = await readState(root);\n state.sources[sourceFile] = entry;\n await writeState(root, state);\n}\n\n/** Remove a source entry from state (for deleted sources). */\nexport async function removeSourceState(\n root: string,\n sourceFile: string,\n): Promise<void> {\n const state = await readState(root);\n delete state.sources[sourceFile];\n await writeState(root, state);\n}\n","/**\n * Commander action for `llmwiki compile`.\n * Checks that sources exist, then delegates to the compilation orchestrator\n * to process all new and changed source files into wiki pages.\n */\n\nimport { existsSync } from \"fs\";\nimport { compile } from \"../compiler/index.js\";\nimport * as output from \"../utils/output.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport type { CompileOptions } from \"../utils/types.js\";\n\n/**\n * Run the compile command from the current working directory.\n * Exits early if no sources directory exists yet.\n * @param options - Optional behaviour overrides forwarded from the CLI flag set.\n */\nexport default async function compileCommand(options: CompileOptions = {}): Promise<void> {\n if (!existsSync(SOURCES_DIR)) {\n output.status(\n \"!\",\n output.warn('No sources found. Run `llmwiki ingest <url>` first.'),\n );\n return;\n }\n\n await compile(process.cwd(), options);\n}\n","/**\n * Compilation orchestrator for the llmwiki knowledge compiler.\n *\n * Coordinates the full pipeline: lock acquisition, change detection,\n * concept extraction via LLM, wiki page generation with streaming output,\n * orphan marking for deleted sources, interlink resolution, and index\n * generation. Supports incremental compilation — only new or changed\n * sources are processed through the LLM pipeline.\n */\n\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { readState, updateSourceState } from \"../utils/state.js\";\nimport {\n buildExtractionSourceStates,\n pickStatesForSources,\n} from \"./source-state.js\";\nimport {\n atomicWrite,\n buildFrontmatter,\n parseFrontmatter,\n safeReadFile,\n validateWikiPage,\n slugify,\n} from \"../utils/markdown.js\";\nimport { callClaude } from \"../utils/llm.js\";\nimport { acquireLock, releaseLock } from \"../utils/lock.js\";\nimport {\n CONCEPT_EXTRACTION_TOOL,\n buildExtractionPrompt,\n buildSeedPagePrompt,\n parseConcepts,\n} from \"./prompts.js\";\nimport { loadSchema, type SchemaConfig, type SeedPage } from \"../schema/index.js\";\nimport { detectChanges, hashFile } from \"./hasher.js\";\nimport {\n findAffectedSources,\n findFrozenSlugs,\n findLateAffectedSources,\n freezeFailedExtractions,\n persistFrozenSlugs,\n type ExtractionResult,\n} from \"./deps.js\";\nimport { markOrphaned, orphanUnownedFrozenPages } from \"./orphan.js\";\nimport { resolveLinks } from \"./resolver.js\";\nimport { generateIndex } from \"./indexgen.js\";\nimport { buildBudgetedCombinedContent, type SourceSlice } from \"./prompt-budget.js\";\nimport { addObsidianMeta, generateMOC } from \"./obsidian.js\";\nimport { updateEmbeddings } from \"../utils/embeddings.js\";\nimport { writeCandidate } from \"./candidates.js\";\nimport {\n checkPageBrokenCitations,\n checkPageCrossLinks,\n checkPageMalformedCitations,\n} from \"../linter/rules.js\";\nimport type { LintResult } from \"../linter/types.js\";\nimport { renderMergedPageContent } from \"./page-renderer.js\";\nimport * as output from \"../utils/output.js\";\nimport {\n COMPILE_CONCURRENCY,\n CONCEPTS_DIR,\n INDEX_FILE,\n SOURCES_DIR,\n} from \"../utils/constants.js\";\nimport pLimit from \"p-limit\";\nimport type {\n CompileOptions,\n CompileResult,\n ExtractedConcept,\n ReviewCandidate,\n SourceChange,\n SourceState,\n WikiFrontmatter,\n WikiState,\n} from \"../utils/types.js\";\n\n/** Per-source state snapshots keyed by source filename. */\ntype SourceStateMap = Record<string, SourceState>;\n\n/** Empty CompileResult used when no pipeline work runs (e.g. lock contention). */\nfunction emptyCompileResult(): CompileResult {\n return { compiled: 0, skipped: 0, deleted: 0, concepts: [], pages: [], errors: [] };\n}\n\n/**\n * Run the full compilation pipeline with lock protection.\n * Acquires .llmwiki/lock, detects changes, compiles new/changed sources,\n * marks orphaned pages, resolves interlinks, and rebuilds the index.\n * @param root - Project root directory.\n * @param options - Optional pipeline overrides (e.g. --review mode).\n */\nexport async function compile(root: string, options: CompileOptions = {}): Promise<void> {\n await compileAndReport(root, options);\n}\n\n/**\n * Run the full compilation pipeline and return a structured result.\n * Same behaviour as compile() but exposes counts, slugs, and errors so\n * non-CLI consumers (the MCP server, programmatic callers) can report\n * meaningful data without scraping terminal output.\n * @param root - Project root directory.\n * @param options - Optional pipeline overrides (e.g. --review mode).\n * @returns Structured result describing what was compiled.\n */\nexport async function compileAndReport(\n root: string,\n options: CompileOptions = {},\n): Promise<CompileResult> {\n output.header(\"llmwiki compile\");\n\n const locked = await acquireLock(root);\n if (!locked) {\n output.status(\"!\", output.error(\"Could not acquire lock. Try again later.\"));\n return {\n ...emptyCompileResult(),\n errors: [\"Could not acquire .llmwiki/lock — another compile is in progress.\"],\n };\n }\n\n try {\n return await runCompilePipeline(root, options);\n } finally {\n await releaseLock(root);\n }\n}\n\n/** Buckets of source changes used by the compile pipeline. */\ninterface ChangeBuckets {\n toCompile: SourceChange[];\n deleted: SourceChange[];\n unchanged: SourceChange[];\n}\n\n/** Sort source changes into the buckets the pipeline acts on. */\nfunction bucketChanges(changes: SourceChange[]): ChangeBuckets {\n return {\n toCompile: changes.filter((c) => c.status === \"new\" || c.status === \"changed\"),\n deleted: changes.filter((c) => c.status === \"deleted\"),\n unchanged: changes.filter((c) => c.status === \"unchanged\"),\n };\n}\n\n/** Result of phase 2: page writes plus any errors collected along the way. */\ninterface PageGenerationResult {\n pages: MergedConcept[];\n errors: string[];\n /** Candidate ids written when running in --review mode. Empty otherwise. */\n candidates: string[];\n /**\n * Slugs of seed pages written this run (overview / comparison / entity).\n * Concept pages live on `pages`; seed pages don't fit MergedConcept's\n * source-list shape, so they're tracked separately here. summarizeCompile\n * concatenates these into CompileResult.pages so downstream consumers\n * (MCP, embeddings, programmatic callers) see seed-page changes too.\n */\n seedSlugs: string[];\n}\n\n/** Phase 2: generate pages for merged concepts in parallel, capturing errors. */\nasync function generatePagesPhase(\n root: string,\n extractions: ExtractionResult[],\n frozenSlugs: Set<string>,\n schema: SchemaConfig,\n options: CompileOptions,\n): Promise<PageGenerationResult> {\n const merged = mergeExtractions(extractions, frozenSlugs);\n // Build the per-source state snapshot once so each candidate can carry the\n // exact data needed to mark its sources compiled on approval.\n const sourceStates = options.review\n ? await buildExtractionSourceStates(root, extractions)\n : {};\n const limit = pLimit(COMPILE_CONCURRENCY);\n const errors: string[] = [];\n const candidates: string[] = [];\n const pages = await Promise.all(\n merged.map((entry) => limit(async () => {\n const result = await generateMergedPage(root, entry, schema, options, sourceStates);\n if (result.error) errors.push(result.error);\n if (result.candidateId) candidates.push(result.candidateId);\n return entry;\n })),\n );\n return { pages, errors, candidates, seedSlugs: [] };\n}\n\n/** Persist source state for every extraction that produced concepts. */\nasync function persistExtractionStates(\n root: string,\n extractions: ExtractionResult[],\n): Promise<void> {\n for (const result of extractions) {\n if (result.concepts.length === 0) continue;\n await persistSourceState(root, result.sourcePath, result.sourceFile, result.concepts);\n }\n}\n\n/** Build the structured CompileResult and emit the CLI completion banner. */\nfunction summarizeCompile(\n buckets: ChangeBuckets,\n generation: PageGenerationResult,\n extractions: ExtractionResult[],\n options: CompileOptions,\n): CompileResult {\n output.header(\"Compilation complete\");\n output.status(\"✓\", output.success(\n `${buckets.toCompile.length} compiled, ${buckets.unchanged.length} skipped, ${buckets.deleted.length} deleted`,\n ));\n if (options.review && generation.candidates.length > 0) {\n output.status(\"?\", output.info(\n `${generation.candidates.length} candidate(s) awaiting review — run \\`llmwiki review list\\``,\n ));\n } else if (buckets.toCompile.length > 0) {\n output.status(\"→\", output.dim('Next: llmwiki query \"your question here\"'));\n }\n\n const errors = [...generation.errors];\n for (const result of extractions) {\n if (result.concepts.length === 0) {\n errors.push(`No concepts extracted from ${result.sourceFile}`);\n }\n }\n\n // Concept-page slugs first, then seed-page slugs from the same run, so\n // downstream consumers see every page the compile actually produced.\n // Seed pages are deterministic schema-driven writes; before this they\n // landed on disk silently and never appeared on CompileResult.pages.\n const conceptSlugs = generation.pages.map((entry) => entry.slug);\n const baseResult: CompileResult = {\n compiled: buckets.toCompile.length,\n skipped: buckets.unchanged.length,\n deleted: buckets.deleted.length,\n concepts: generation.pages.map((entry) => entry.concept.concept),\n pages: [...conceptSlugs, ...generation.seedSlugs],\n errors,\n };\n if (options.review) {\n baseResult.candidates = generation.candidates;\n }\n return baseResult;\n}\n\n/** Inner pipeline, runs under lock protection. Returns structured CompileResult. */\nasync function runCompilePipeline(\n root: string,\n options: CompileOptions,\n): Promise<CompileResult> {\n const schema = await loadSchema(root);\n reportSchemaStatus(schema);\n const state = await readState(root);\n const changes = await detectChanges(root, state);\n augmentWithAffectedSources(changes, findAffectedSources(state, changes));\n\n const buckets = bucketChanges(changes);\n if (buckets.toCompile.length === 0 && buckets.deleted.length === 0) {\n output.status(\"✓\", output.success(\"Nothing to compile — all sources up to date.\"));\n // Seed pages are cheap deterministic writes — always run them even when\n // no source files changed, so adding a seed page to schema.json takes\n // effect on the next compile without needing a source file edit.\n if (!options.review) {\n const emptyGeneration: PageGenerationResult = {\n pages: [],\n errors: [],\n candidates: [],\n seedSlugs: [],\n };\n await generateSeedPages(root, schema, emptyGeneration);\n // Rebuild index/MOC so the newly-written seed pages become discoverable,\n // and propagate any seed-page validation errors into the returned result.\n await finalizeWiki(root, emptyGeneration.pages, emptyGeneration.seedSlugs);\n return {\n ...emptyCompileResult(),\n skipped: buckets.unchanged.length,\n // Surface seed-page slugs alongside any errors so downstream\n // consumers (MCP, embeddings, programmatic callers) can see what\n // landed even on the no-source-changes early-return path.\n pages: [...emptyGeneration.seedSlugs],\n errors: emptyGeneration.errors,\n };\n }\n return { ...emptyCompileResult(), skipped: buckets.unchanged.length };\n }\n\n printChangesSummary(changes);\n // In review mode the pipeline contract is \"write candidates instead of\n // mutating wiki/\". Deletion bookkeeping (orphan marking + frozen-slug\n // persistence) writes directly into wiki/ and updates state.json, so we\n // defer it to the next non-review compile pass. Source-state persistence\n // for compiled sources is also review-deferred — those entries land at\n // approve time so unapproved candidates remain re-detectable on subsequent\n // compiles.\n if (!options.review) {\n await markDeletedAsOrphaned(root, buckets.deleted, state);\n }\n\n const frozenSlugs = findFrozenSlugs(state, changes);\n reportFrozenSlugs(frozenSlugs);\n\n const extractions = await runExtractionPhases(root, buckets.toCompile, state, changes);\n if (!options.review) {\n await freezeFailedExtractions(root, extractions, frozenSlugs);\n }\n\n const generation = await generatePagesPhase(root, extractions, frozenSlugs, schema, options);\n\n if (!options.review) {\n await persistExtractionStates(root, extractions);\n if (frozenSlugs.size > 0) {\n await orphanUnownedFrozenPages(root, frozenSlugs);\n }\n await persistFrozenSlugs(root, frozenSlugs, extractions);\n // Seed pages write directly into wiki/, so skip them in review mode\n // to honour the \"no wiki/ mutation\" contract of that mode.\n await generateSeedPages(root, schema, generation);\n await finalizeWiki(root, generation.pages, generation.seedSlugs);\n }\n return summarizeCompile(buckets, generation, extractions, options);\n}\n\n/** Log where the schema was loaded from so the user can confirm it was picked up. */\nfunction reportSchemaStatus(schema: SchemaConfig): void {\n if (schema.loadedFrom) {\n output.status(\"i\", output.dim(`Schema: ${schema.loadedFrom}`));\n }\n}\n\n/** Append affected-source changes (logging each addition) to the change list. */\nfunction augmentWithAffectedSources(changes: SourceChange[], affected: string[]): void {\n for (const file of affected) {\n output.status(\"~\", output.info(`${file} [affected by shared concept]`));\n changes.push({ file, status: \"changed\" });\n }\n}\n\n/** Mark wiki pages owned solely by deleted sources as orphaned. */\nasync function markDeletedAsOrphaned(\n root: string,\n deleted: SourceChange[],\n state: WikiState,\n): Promise<void> {\n for (const del of deleted) {\n await markOrphaned(root, del.file, state);\n }\n}\n\n/** Log frozen slugs (shared concepts whose deletion-pinned content must persist). */\nfunction reportFrozenSlugs(frozenSlugs: Set<string>): void {\n for (const slug of frozenSlugs) {\n output.status(\"i\", output.dim(`Frozen: ${slug} (shared with deleted source)`));\n }\n}\n\n/**\n * Phase 1: extract concepts for the directly-changed batch, then expand to\n * any unchanged sources whose concepts overlap with newly extracted slugs.\n */\nasync function runExtractionPhases(\n root: string,\n toCompile: SourceChange[],\n state: WikiState,\n allChanges: SourceChange[],\n): Promise<ExtractionResult[]> {\n const extractions: ExtractionResult[] = [];\n for (const change of toCompile) {\n extractions.push(await extractForSource(root, change.file));\n }\n\n const lateAffected = findLateAffectedSources(extractions, state, allChanges);\n for (const file of lateAffected) {\n output.status(\"~\", output.info(`${file} [shares concept with new source]`));\n extractions.push(await extractForSource(root, file));\n }\n\n return extractions;\n}\n\n/**\n * Resolve interlinks, regenerate index/MOC, refresh embeddings\n * post-write. Seed-page slugs are folded into both the changed-slug\n * set (so embeddings refresh covers them) and the new-slug set (so\n * inbound-link resolution scans existing pages for mentions of seed\n * titles). Without that, schema-declared seed pages would land on\n * disk but stay unlinked and absent from the embedding store.\n */\nasync function finalizeWiki(\n root: string,\n pages: MergedConcept[],\n seedSlugs: string[] = [],\n): Promise<void> {\n const conceptChangedSlugs = pages.map((entry) => entry.slug);\n const conceptNewSlugs = pages\n .filter((entry) => entry.concept.is_new)\n .map((entry) => entry.slug);\n const allChangedSlugs = [...conceptChangedSlugs, ...seedSlugs];\n const allNewSlugs = [...conceptNewSlugs, ...seedSlugs];\n\n if (allChangedSlugs.length > 0) {\n output.status(\"🔗\", output.info(\"Resolving interlinks...\"));\n await resolveLinks(root, allChangedSlugs, allNewSlugs);\n }\n\n await generateIndex(root);\n await generateMOC(root);\n await safelyUpdateEmbeddings(root, allChangedSlugs);\n}\n\n/** Print a summary of detected source file changes. */\nfunction printChangesSummary(changes: SourceChange[]): void {\n const iconMap: Record<string, string> = {\n new: \"+\", changed: \"~\", unchanged: \".\", deleted: \"-\",\n };\n const fmtMap: Record<string, (s: string) => string> = {\n new: output.success, changed: output.warn, unchanged: output.dim, deleted: output.error,\n };\n\n for (const c of changes) {\n const icon = iconMap[c.status] ?? \"?\";\n const fmt = fmtMap[c.status] ?? output.dim;\n output.status(icon, fmt(`${c.file} [${c.status}]`));\n }\n}\n\n/**\n * Phase 1: Extract concepts from a source without generating pages.\n * Returns extraction data for the generation phase.\n */\nasync function extractForSource(\n root: string,\n sourceFile: string,\n): Promise<ExtractionResult> {\n output.status(\"*\", output.info(`Extracting: ${sourceFile}`));\n\n const sourcePath = path.join(root, SOURCES_DIR, sourceFile);\n const sourceContent = await readFile(sourcePath, \"utf-8\");\n const existingIndex = await safeReadFile(path.join(root, INDEX_FILE));\n const concepts = await extractConcepts(sourceContent, existingIndex);\n\n if (concepts.length > 0) {\n const names = concepts.map((c) => c.concept).join(\", \");\n output.status(\"*\", output.dim(` Found ${concepts.length} concepts: ${names}`));\n }\n return { sourceFile, sourcePath, sourceContent, concepts };\n}\n\n/** A concept with all contributing sources merged for generation. */\ninterface MergedConcept {\n slug: string;\n concept: ExtractedConcept;\n sourceFiles: string[];\n combinedContent: string;\n}\n\n/**\n * Reconcile metadata from a later-extracted concept into an existing merged entry.\n * Called when multiple sources contribute the same slug — produces the most\n * pessimistic aggregate view of confidence, provenance, and contradictions.\n *\n * Rules:\n * - confidence: min (most pessimistic value wins)\n * - provenanceState: always 'merged' once two sources are involved\n * - contradictedBy: union by slug (deduplicating on slug identity)\n *\n * `inferredParagraphs` is no longer reconciled — it is derived from the\n * rendered page body at lint time, not from extraction metadata.\n */\nexport function reconcileConceptMetadata(\n existing: ExtractedConcept,\n incoming: ExtractedConcept,\n): ExtractedConcept {\n const reconciled = { ...existing };\n\n // Minimum confidence — the weaker source's score governs the whole page.\n if (typeof incoming.confidence === \"number\") {\n reconciled.confidence = typeof existing.confidence === \"number\"\n ? Math.min(existing.confidence, incoming.confidence)\n : incoming.confidence;\n }\n\n // Merged state is the canonical answer when multiple sources contribute.\n reconciled.provenanceState = \"merged\";\n\n // Union contradictedBy entries, deduplicating by slug.\n const refs = [...(existing.contradictedBy ?? [])];\n const seenSlugs = new Set(refs.map((r) => r.slug));\n for (const ref of incoming.contradictedBy ?? []) {\n if (!seenSlugs.has(ref.slug)) {\n refs.push(ref);\n seenSlugs.add(ref.slug);\n }\n }\n reconciled.contradictedBy = refs.length > 0 ? refs : undefined;\n\n return reconciled;\n}\n\n/**\n * Merge extractions so each concept slug maps to ALL contributing sources.\n * When sources A and B both extract concept X, the LLM receives combined\n * content from both sources, producing a single page that reflects all\n * contributing material rather than just the last source processed.\n * Metadata is reconciled across all contributing concepts via\n * reconcileConceptMetadata so contradictions from later sources are not lost.\n *\n * Combined content is then run through {@link buildBudgetedCombinedContent}\n * so popular concepts that appear in many overlapping sources do not blow\n * past the LLM provider's context window (issue #39). When the raw total\n * fits the budget, the output is byte-identical to the previous unbudgeted\n * concatenation.\n */\nfunction mergeExtractions(\n extractions: ExtractionResult[],\n frozenSlugs: Set<string>,\n): MergedConcept[] {\n const bySlug = new Map<string, MergedConcept>();\n const slicesBySlug = new Map<string, SourceSlice[]>();\n\n for (const result of extractions) {\n if (result.concepts.length === 0) continue;\n\n for (const concept of result.concepts) {\n const slug = slugify(concept.concept);\n if (frozenSlugs.has(slug)) continue;\n\n const existing = bySlug.get(slug);\n if (existing) {\n existing.concept = reconcileConceptMetadata(existing.concept, concept);\n existing.sourceFiles.push(result.sourceFile);\n } else {\n bySlug.set(slug, {\n slug,\n concept,\n sourceFiles: [result.sourceFile],\n combinedContent: \"\",\n });\n slicesBySlug.set(slug, []);\n }\n slicesBySlug.get(slug)!.push({\n file: result.sourceFile,\n content: result.sourceContent,\n });\n }\n }\n\n for (const merged of bySlug.values()) {\n const slices = slicesBySlug.get(merged.slug) ?? [];\n merged.combinedContent = buildBudgetedCombinedContent(\n merged.concept.concept,\n slices,\n );\n }\n\n return Array.from(bySlug.values());\n}\n\n/** Outcome of generating a single merged concept page. */\ninterface MergedPageOutcome {\n error?: string;\n candidateId?: string;\n}\n\n/**\n * Generate a wiki page from merged source content.\n * For shared concepts, the LLM sees content from all contributing sources\n * and frontmatter records every source file. When `options.review` is set,\n * the rendered page is persisted as a review candidate instead of being\n * written into `wiki/`.\n */\nasync function generateMergedPage(\n root: string,\n entry: MergedConcept,\n schema: SchemaConfig,\n options: CompileOptions,\n sourceStates: SourceStateMap,\n): Promise<MergedPageOutcome> {\n const fullPage = await renderMergedPageContent(root, entry, schema);\n\n if (options.review) {\n return await persistReviewCandidate(root, entry, fullPage, sourceStates, schema);\n }\n\n const pagePath = path.join(root, CONCEPTS_DIR, `${entry.slug}.md`);\n const error = await writePageIfValid(pagePath, fullPage, entry.concept.concept);\n return { error: error ?? undefined };\n}\n\n/** Persist a candidate JSON record for later review and report it on stdout. */\nasync function persistReviewCandidate(\n root: string,\n entry: MergedConcept,\n fullPage: string,\n sourceStates: SourceStateMap,\n schema: SchemaConfig,\n): Promise<MergedPageOutcome> {\n // Run schema-aware AND provenance-aware lint against the candidate body so\n // both classes of violation are visible in `review show` before a reviewer\n // approves the page. The virtual file path uses the slug so diagnostics\n // are identifiable without a real disk path. Provenance lint covers the\n // citation rules that previously only ran on the post-promotion compile.\n const virtualPath = `wiki/concepts/${entry.slug}.md`;\n const schemaViolations = checkPageCrossLinks(fullPage, virtualPath, schema);\n const provenanceViolations = await collectCandidateProvenanceViolations(\n root,\n fullPage,\n virtualPath,\n );\n\n const candidate: ReviewCandidate = await writeCandidate(root, {\n title: entry.concept.concept,\n slug: entry.slug,\n summary: entry.concept.summary,\n sources: entry.sourceFiles,\n body: fullPage,\n sourceStates: pickStatesForSources(sourceStates, entry.sourceFiles),\n schemaViolations: schemaViolations.length > 0 ? schemaViolations : undefined,\n provenanceViolations:\n provenanceViolations.length > 0 ? provenanceViolations : undefined,\n });\n output.status(\"?\", output.info(`Candidate ready: ${candidate.id} (${entry.slug})`));\n return { candidateId: candidate.id };\n}\n\n/**\n * Run the in-memory provenance lint rules against a candidate body:\n * malformed claim citations + broken-source / out-of-bounds line spans.\n * Returns the combined diagnostics so writeCandidate can persist them.\n */\nasync function collectCandidateProvenanceViolations(\n root: string,\n fullPage: string,\n virtualPath: string,\n): Promise<LintResult[]> {\n const malformed = checkPageMalformedCitations(fullPage, virtualPath);\n const broken = await checkPageBrokenCitations(\n fullPage,\n virtualPath,\n path.join(root, SOURCES_DIR),\n );\n return [...malformed, ...broken];\n}\n\n/**\n * Materialise schema-declared seed pages (overview, comparison, entity).\n * Each seed page is written under wiki/concepts/ next to concept pages so\n * existing tooling (index, MOC, lint, embeddings) treats them uniformly.\n * Slugs from generated pages this run are added so seed pages can be linked\n * deterministically without waiting for a second compile pass.\n * @param root - Project root directory.\n * @param schema - Resolved schema config.\n * @param generation - Result of the concept-page generation phase.\n */\nasync function generateSeedPages(\n root: string,\n schema: SchemaConfig,\n generation: PageGenerationResult,\n): Promise<void> {\n if (schema.seedPages.length === 0) return;\n for (const seed of schema.seedPages) {\n const result = await generateSingleSeedPage(root, schema, seed);\n if (result.error) {\n generation.errors.push(result.error);\n continue;\n }\n generation.seedSlugs.push(result.slug);\n }\n}\n\n/** Outcome of a single seed-page generation: slug always, error when the write failed validation. */\ninterface SeedPageOutcome {\n slug: string;\n error?: string;\n}\n\n/** Build, prompt, and persist a single seed page. */\nasync function generateSingleSeedPage(\n root: string,\n schema: SchemaConfig,\n seed: SeedPage,\n): Promise<SeedPageOutcome> {\n const slug = slugify(seed.title);\n const pagePath = path.join(root, CONCEPTS_DIR, `${slug}.md`);\n const relatedContent = await loadSeedRelatedPages(root, seed.relatedSlugs ?? []);\n const rule = schema.kinds[seed.kind];\n const system = buildSeedPagePrompt(seed, rule, relatedContent);\n const pageBody = await callClaude({\n system,\n messages: [{ role: \"user\", content: `Write the ${seed.kind} page titled \"${seed.title}\".` }],\n });\n\n const now = new Date().toISOString();\n const existing = await safeReadFile(pagePath);\n const existingMeta = existing ? parseFrontmatter(existing).meta : null;\n const createdAt = typeof existingMeta?.createdAt === \"string\" ? existingMeta.createdAt : now;\n const typedFields: WikiFrontmatter = {\n title: seed.title,\n summary: seed.summary,\n sources: [],\n kind: seed.kind,\n createdAt,\n updatedAt: now,\n };\n const frontmatterFields: Record<string, unknown> = { ...typedFields };\n addObsidianMeta(frontmatterFields, seed.title, []);\n const frontmatter = buildFrontmatter(frontmatterFields);\n const error = await writePageIfValid(pagePath, `${frontmatter}\\n\\n${pageBody}\\n`, seed.title);\n return error ? { slug, error } : { slug };\n}\n\n/** Load the bodies of the related concept pages a seed page should weave together. */\nasync function loadSeedRelatedPages(root: string, slugs: string[]): Promise<string> {\n if (slugs.length === 0) return \"\";\n const contents: string[] = [];\n for (const slug of slugs) {\n const pagePath = path.join(root, CONCEPTS_DIR, `${slug}.md`);\n const content = await safeReadFile(pagePath);\n if (content) contents.push(content);\n }\n return contents.join(\"\\n\\n---\\n\\n\");\n}\n\n/**\n * Call Claude to extract concepts from a source document.\n * @param sourceContent - Full source document text.\n * @param existingIndex - Current wiki index for deduplication.\n * @returns Parsed array of extracted concepts.\n */\nasync function extractConcepts(\n sourceContent: string,\n existingIndex: string,\n): Promise<ExtractedConcept[]> {\n const system = buildExtractionPrompt(sourceContent, existingIndex);\n const rawOutput = await callClaude({\n system,\n messages: [{ role: \"user\", content: \"Extract the key concepts from this source.\" }],\n tools: [CONCEPT_EXTRACTION_TOOL],\n });\n\n return parseConcepts(rawOutput);\n}\n\n/**\n * Validate and atomically write a wiki page, logging the result.\n * @param pagePath - Absolute path to write the page.\n * @param content - Full page content including frontmatter.\n * @param conceptTitle - Title for logging purposes.\n */\nasync function writePageIfValid(\n pagePath: string,\n content: string,\n conceptTitle: string,\n): Promise<string | null> {\n if (!validateWikiPage(content)) {\n output.status(\"!\", output.warn(`Invalid page for \"${conceptTitle}\" — skipped.`));\n return `Invalid page for \"${conceptTitle}\" — failed validation`;\n }\n\n await atomicWrite(pagePath, content);\n return null;\n}\n\n/**\n * Refresh the embeddings store without failing compilation.\n * Semantic search is a non-critical enhancement — missing API keys or\n * transient provider errors should produce a warning, not a broken build.\n */\nasync function safelyUpdateEmbeddings(root: string, changedSlugs: string[]): Promise<void> {\n try {\n await updateEmbeddings(root, changedSlugs);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.warn(`Skipped embeddings update: ${message}`));\n }\n}\n\n/**\n * Update the persisted state for a compiled source file.\n * @param root - Project root directory.\n * @param sourcePath - Absolute path to the source file.\n * @param sourceFile - Filename within sources/.\n * @param concepts - Concepts extracted from this source.\n */\nasync function persistSourceState(\n root: string,\n sourcePath: string,\n sourceFile: string,\n concepts: ReturnType<typeof parseConcepts>,\n): Promise<void> {\n const hash = await hashFile(sourcePath);\n const entry: SourceState = {\n hash,\n concepts: concepts.map((c) => slugify(c.concept)),\n compiledAt: new Date().toISOString(),\n };\n\n await updateSourceState(root, sourceFile, entry);\n}\n","/**\n * Source-state snapshot helpers shared between the live compile path and the\n * review-candidate path.\n *\n * The compile pipeline normally persists a `SourceState` entry for every\n * extracted source so subsequent compiles can skip unchanged inputs. When\n * compile runs in `--review` mode, page writes are deferred — but the same\n * per-source state still needs to land on approval, otherwise approved\n * sources stay marked as \"new/changed\" forever and reproduce duplicate\n * candidates on every compile.\n *\n * This module produces a `Record<sourceFile, SourceState>` snapshot from the\n * extraction results so it can ride along inside each `ReviewCandidate` and\n * be flushed to `.llmwiki/state.json` at approval time.\n */\n\nimport path from \"path\";\nimport { hashFile } from \"./hasher.js\";\nimport { slugify } from \"../utils/markdown.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport type { ExtractionResult } from \"./deps.js\";\nimport type { SourceState } from \"../utils/types.js\";\n\n/**\n * Compute a per-source state snapshot keyed by source filename.\n *\n * Hashes every contributing source once so each candidate carries the\n * incremental-state payload required to mark its sources compiled on\n * approval. Sources with no extracted concepts are skipped — we only mark\n * sources compiled when extraction succeeded, mirroring the live path's\n * behaviour.\n *\n * @param root - Project root directory.\n * @param extractions - Extraction results from the compile pipeline.\n * @returns Map of source filename → SourceState ready for state.json.\n */\nexport async function buildExtractionSourceStates(\n root: string,\n extractions: ExtractionResult[],\n): Promise<Record<string, SourceState>> {\n const snapshot: Record<string, SourceState> = {};\n const compiledAt = new Date().toISOString();\n\n for (const result of extractions) {\n if (result.concepts.length === 0) continue;\n snapshot[result.sourceFile] = await buildEntry(root, result, compiledAt);\n }\n\n return snapshot;\n}\n\n/** Build a single SourceState entry for one extraction result. */\nasync function buildEntry(\n root: string,\n result: ExtractionResult,\n compiledAt: string,\n): Promise<SourceState> {\n const filePath = path.join(root, SOURCES_DIR, result.sourceFile);\n const hash = await hashFile(filePath);\n return {\n hash,\n concepts: result.concepts.map((concept) => slugify(concept.concept)),\n compiledAt,\n };\n}\n\n/**\n * Filter a global source-state snapshot down to entries relevant to a\n * specific candidate. A candidate carries only the source-state entries\n * for sources that actually contributed to it, so on approval we can\n * persist a minimal, accurate slice into state.json.\n *\n * @param allStates - Global per-source snapshot from buildExtractionSourceStates.\n * @param sourceFiles - Source filenames that contributed to the candidate.\n */\nexport function pickStatesForSources(\n allStates: Record<string, SourceState>,\n sourceFiles: string[],\n): Record<string, SourceState> {\n const picked: Record<string, SourceState> = {};\n for (const file of sourceFiles) {\n const entry = allStates[file];\n if (entry) picked[file] = entry;\n }\n return picked;\n}\n","/**\n * Source file hashing for change detection.\n * Computes SHA-256 hashes of source files and compares them against\n * previously stored state to determine which files need recompilation.\n * This enables incremental compilation — only changed or new sources\n * are sent through the LLM pipeline.\n */\n\nimport { createHash } from \"node:crypto\";\nimport { readFile, readdir } from \"fs/promises\";\nimport path from \"path\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport type { WikiState, SourceChange } from \"../utils/types.js\";\n\n/**\n * Read a file and compute its SHA-256 hash.\n * @param filePath - Absolute path to the file to hash.\n * @returns Hex-encoded SHA-256 digest of the file contents.\n */\nexport async function hashFile(filePath: string): Promise<string> {\n const content = await readFile(filePath, \"utf-8\");\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\n/**\n * Scan the sources/ directory and compare file hashes against previous state\n * to identify new, changed, unchanged, and deleted source files.\n * @param root - Project root directory containing the sources/ folder.\n * @param prevState - The previously persisted WikiState to compare against.\n * @returns Array of SourceChange entries describing each file's status.\n */\nexport async function detectChanges(\n root: string,\n prevState: WikiState,\n): Promise<SourceChange[]> {\n const sourcesPath = path.join(root, SOURCES_DIR);\n const currentFiles = await listSourceFiles(sourcesPath);\n const changes: SourceChange[] = [];\n\n for (const file of currentFiles) {\n const status = await classifyFile(root, file, prevState);\n changes.push({ file, status });\n }\n\n const deletedChanges = findDeletedFiles(currentFiles, prevState);\n changes.push(...deletedChanges);\n\n return changes;\n}\n\n/**\n * List all markdown files in the sources directory.\n * @param sourcesPath - Absolute path to the sources/ directory.\n * @returns Array of filenames (not full paths).\n */\nasync function listSourceFiles(sourcesPath: string): Promise<string[]> {\n try {\n const entries = await readdir(sourcesPath);\n return entries.filter((f) => f.endsWith(\".md\"));\n } catch {\n return [];\n }\n}\n\n/**\n * Classify a single source file as new, changed, or unchanged.\n * @param root - Project root directory.\n * @param file - Filename within sources/.\n * @param prevState - Previous compilation state.\n * @returns The change status for this file.\n */\nasync function classifyFile(\n root: string,\n file: string,\n prevState: WikiState,\n): Promise<SourceChange[\"status\"]> {\n const filePath = path.join(root, SOURCES_DIR, file);\n const hash = await hashFile(filePath);\n const prev = prevState.sources[file];\n\n if (!prev) return \"new\";\n if (prev.hash !== hash) return \"changed\";\n return \"unchanged\";\n}\n\n/**\n * Find source files present in previous state but missing from disk.\n * @param currentFiles - Files currently on disk.\n * @param prevState - Previous compilation state.\n * @returns Array of SourceChange entries for deleted files.\n */\nfunction findDeletedFiles(\n currentFiles: string[],\n prevState: WikiState,\n): SourceChange[] {\n const currentSet = new Set(currentFiles);\n return Object.keys(prevState.sources)\n .filter((file) => !currentSet.has(file))\n .map((file) => ({ file, status: \"deleted\" as const }));\n}\n","/**\n * OpenAI LLM provider implementation.\n *\n * Wraps the openai npm package to implement the LLMProvider interface.\n * Translates Anthropic-style tool schemas (input_schema) to OpenAI format (parameters).\n */\n\nimport OpenAI from \"openai\";\nimport type { LLMProvider, LLMMessage, LLMTool } from \"../utils/provider.js\";\nimport { EMBEDDING_MODELS, OPENAI_DEFAULT_TIMEOUT_MS } from \"../utils/constants.js\";\n\n/** Construction options for an OpenAI-compatible provider. */\ninterface OpenAIProviderOptions {\n baseURL?: string;\n apiKey?: string;\n embeddingsBaseURL?: string;\n embeddingModel?: string;\n /**\n * Per-request timeout in milliseconds. Defaults to 10 minutes for cloud\n * OpenAI (matches the SDK default). Long compile-time completions on\n * slower local models can exceed this — see {@link OllamaProvider} which\n * raises the default and reads LLMWIKI_REQUEST_TIMEOUT_MS / OLLAMA_TIMEOUT_MS.\n */\n timeoutMs?: number;\n}\n\n/**\n * Read an integer-millisecond timeout from an env var. Returns undefined when\n * the env var is unset, empty, non-numeric, zero, or negative — so the caller\n * silently falls back to the next source in its resolution chain (env-var\n * typos like `OLLAMA_TIMEOUT_MS=30m` are not surfaced to the user).\n */\nexport function readTimeoutEnv(name: string): number | undefined {\n const raw = process.env[name]?.trim();\n if (!raw) return undefined;\n const parsed = Number(raw);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;\n}\n\n/** Resolve the OpenAI client timeout from LLMWIKI_REQUEST_TIMEOUT_MS, if set. */\nfunction resolveOpenAITimeoutMs(): number | undefined {\n return readTimeoutEnv(\"LLMWIKI_REQUEST_TIMEOUT_MS\");\n}\n\n/** Translate an Anthropic-style LLMTool to an OpenAI ChatCompletionTool. */\nexport function translateToolToOpenAI(\n tool: LLMTool,\n): OpenAI.ChatCompletionTool {\n return {\n type: \"function\",\n function: {\n name: tool.name,\n description: tool.description,\n parameters: tool.input_schema,\n },\n };\n}\n\n/** OpenAI-backed LLM provider. */\nexport class OpenAIProvider implements LLMProvider {\n protected readonly client: OpenAI;\n protected readonly embeddingsClient: OpenAI;\n protected readonly model: string;\n protected readonly configuredEmbeddingModel?: string;\n\n constructor(model: string, options: OpenAIProviderOptions = {}) {\n this.model = model;\n this.configuredEmbeddingModel = options.embeddingModel;\n // The OpenAI SDK validates OPENAI_API_KEY at construction time.\n // Pass the key explicitly so the provider controls when validation happens.\n const resolvedKey = options.apiKey ?? process.env.OPENAI_API_KEY ?? \"\";\n const timeout = options.timeoutMs ?? resolveOpenAITimeoutMs() ?? OPENAI_DEFAULT_TIMEOUT_MS;\n this.client = new OpenAI({\n apiKey: resolvedKey,\n baseURL: options.baseURL ?? null,\n timeout,\n });\n this.embeddingsClient = options.embeddingsBaseURL\n ? new OpenAI({ apiKey: resolvedKey, baseURL: options.embeddingsBaseURL, timeout })\n : this.client;\n }\n\n /** Send a single non-streaming completion request. */\n async complete(system: string, messages: LLMMessage[], maxTokens: number): Promise<string> {\n const response = await this.client.chat.completions.create({\n model: this.model,\n max_tokens: maxTokens,\n messages: [{ role: \"system\", content: system }, ...messages],\n });\n\n return response.choices[0]?.message?.content ?? \"\";\n }\n\n /** Stream a completion, invoking onToken for each text chunk. */\n async stream(\n system: string,\n messages: LLMMessage[],\n maxTokens: number,\n onToken?: (text: string) => void,\n ): Promise<string> {\n const stream = await this.client.chat.completions.create({\n model: this.model,\n max_tokens: maxTokens,\n messages: [{ role: \"system\", content: system }, ...messages],\n stream: true,\n });\n\n let fullText = \"\";\n for await (const chunk of stream) {\n const delta = chunk.choices[0]?.delta?.content;\n if (delta) {\n fullText += delta;\n onToken?.(delta);\n }\n }\n\n return fullText;\n }\n\n /** Call the model with tool definitions and return the parsed tool input as JSON. */\n async toolCall(\n system: string,\n messages: LLMMessage[],\n tools: LLMTool[],\n maxTokens: number,\n ): Promise<string> {\n const openaiTools = tools.map(translateToolToOpenAI);\n\n const response = await this.client.chat.completions.create({\n model: this.model,\n max_tokens: maxTokens,\n messages: [{ role: \"system\", content: system }, ...messages],\n tools: openaiTools,\n tool_choice: \"required\",\n });\n\n const toolCalls = response.choices[0]?.message?.tool_calls;\n if (toolCalls && toolCalls.length > 0) {\n return toolCalls[0].function.arguments;\n }\n\n return response.choices[0]?.message?.content ?? \"\";\n }\n\n /**\n * Produce a single embedding vector via the OpenAI embeddings API.\n * Subclasses (e.g. Ollama) override embeddingModel() to pick a different model.\n */\n async embed(text: string): Promise<number[]> {\n const response = await this.embeddingsClient.embeddings.create({\n model: this.embeddingModel(),\n input: text,\n });\n\n const vector = response.data[0]?.embedding;\n if (!Array.isArray(vector)) {\n throw new Error(\"OpenAI embeddings response did not include a vector.\");\n }\n return vector;\n }\n\n /** Default embedding model for this provider. Subclasses may override. */\n protected embeddingModel(): string {\n return this.configuredEmbeddingModel ?? EMBEDDING_MODELS.openai;\n }\n}\n","/**\n * Ollama LLM provider implementation.\n *\n * Extends OpenAIProvider since Ollama exposes an OpenAI-compatible API.\n * Overrides only the constructor to set baseURL and disable API key auth.\n */\n\nimport { OpenAIProvider, readTimeoutEnv } from \"./openai.js\";\nimport { EMBEDDING_MODELS, OLLAMA_DEFAULT_TIMEOUT_MS } from \"../utils/constants.js\";\n\n/** Construction options for an Ollama-compatible provider. */\ninterface OllamaProviderOptions {\n baseURL: string;\n embeddingsBaseURL?: string;\n embeddingModel?: string;\n /**\n * Per-request timeout in milliseconds. Defaults to 30 minutes for Ollama\n * because local models on modest hardware can take much longer than the\n * cloud-OpenAI default of 10. Override with OLLAMA_TIMEOUT_MS or the\n * provider-agnostic LLMWIKI_REQUEST_TIMEOUT_MS env var.\n */\n timeoutMs?: number;\n}\n\n/** Resolve the Ollama timeout: explicit option → OLLAMA_TIMEOUT_MS → LLMWIKI_REQUEST_TIMEOUT_MS → default. */\nfunction resolveOllamaTimeoutMs(explicit?: number): number {\n return (\n explicit ??\n readTimeoutEnv(\"OLLAMA_TIMEOUT_MS\") ??\n readTimeoutEnv(\"LLMWIKI_REQUEST_TIMEOUT_MS\") ??\n OLLAMA_DEFAULT_TIMEOUT_MS\n );\n}\n\n/** Ollama-backed LLM provider using the OpenAI-compatible endpoint. */\nexport class OllamaProvider extends OpenAIProvider {\n constructor(model: string, options: OllamaProviderOptions) {\n super(model, {\n baseURL: options.baseURL,\n apiKey: \"ollama\",\n embeddingsBaseURL: options.embeddingsBaseURL,\n embeddingModel: options.embeddingModel,\n timeoutMs: resolveOllamaTimeoutMs(options.timeoutMs),\n });\n }\n\n /** Ollama ships a dedicated embedding model (nomic-embed-text). */\n protected override embeddingModel(): string {\n return this.configuredEmbeddingModel ?? EMBEDDING_MODELS.ollama;\n }\n}\n","/**\n * MiniMax LLM provider implementation.\n *\n * Extends OpenAIProvider since MiniMax exposes an OpenAI-compatible API.\n * Overrides only the constructor to set MiniMax's base URL and API key.\n */\n\nimport { OpenAIProvider } from \"./openai.js\";\n\n/** MiniMax API base URL. */\nconst MINIMAX_BASE_URL = \"https://api.minimax.io/v1\";\n\n/** MiniMax-backed LLM provider using the OpenAI-compatible endpoint. */\nexport class MiniMaxProvider extends OpenAIProvider {\n constructor(model: string, apiKey: string) {\n super(model, { baseURL: MINIMAX_BASE_URL, apiKey });\n }\n}\n","/**\n * GitHub Copilot LLM provider implementation.\n *\n * Uses the GitHub Copilot API (https://api.githubcopilot.com), which exposes\n * an OpenAI-compatible chat endpoint. Requires a GitHub OAuth token with\n * Copilot access — use `gh auth token` to obtain one. Classic PATs are NOT\n * supported by this endpoint.\n *\n * Note: GitHub Copilot does not expose an embeddings API. Calling embed() will\n * throw with a helpful message. For workflows that require semantic search\n * (query with chunked retrieval), use the openai provider with OPENAI_API_KEY.\n */\n\nimport { OpenAIProvider } from \"./openai.js\";\nimport { COPILOT_BASE_URL } from \"../utils/constants.js\";\n\n/** GitHub Copilot-backed LLM provider using the OpenAI-compatible endpoint. */\nexport class CopilotProvider extends OpenAIProvider {\n constructor(model: string, apiKey: string) {\n super(model, { baseURL: COPILOT_BASE_URL, apiKey });\n }\n\n /**\n * GitHub Copilot has no native embeddings API.\n * Throws an informative error directing the user to an alternative.\n */\n override async embed(_text: string): Promise<number[]> {\n throw new Error(\n \"GitHub Copilot does not support embeddings.\\n\" +\n \" For semantic search (llmwiki query), switch to the OpenAI provider:\\n\" +\n \" export LLMWIKI_PROVIDER=openai\\n\" +\n \" export OPENAI_API_KEY=sk-...\",\n );\n }\n}\n","/**\n * LLM provider abstraction layer.\n *\n * Defines the LLMProvider interface and a factory function that reads\n * LLMWIKI_PROVIDER and LLMWIKI_MODEL env vars to instantiate the\n * appropriate backend (Anthropic, OpenAI, Ollama, or MiniMax).\n */\n\nimport { DEFAULT_PROVIDER, PROVIDER_MODELS, OLLAMA_DEFAULT_HOST } from \"./constants.js\";\nimport { AnthropicProvider } from \"../providers/anthropic.js\";\nimport { OpenAIProvider } from \"../providers/openai.js\";\nimport { OllamaProvider } from \"../providers/ollama.js\";\nimport { MiniMaxProvider } from \"../providers/minimax.js\";\nimport { CopilotProvider } from \"../providers/copilot.js\";\nimport {\n resolveAnthropicAuthFromEnv,\n resolveAnthropicBaseURLFromEnv,\n resolveAnthropicModelFromEnv,\n} from \"./claude-settings.js\";\n\n/** A single message in an LLM conversation. */\nexport interface LLMMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\n/** A tool definition in Anthropic-style format (used as the canonical shape). */\nexport interface LLMTool {\n name: string;\n description: string;\n input_schema: Record<string, unknown>;\n}\n\n/** Provider-agnostic interface for LLM backends. */\nexport interface LLMProvider {\n complete(system: string, messages: LLMMessage[], maxTokens: number): Promise<string>;\n stream(\n system: string,\n messages: LLMMessage[],\n maxTokens: number,\n onToken?: (text: string) => void,\n ): Promise<string>;\n toolCall(\n system: string,\n messages: LLMMessage[],\n tools: LLMTool[],\n maxTokens: number,\n ): Promise<string>;\n /** Return a single embedding vector for the given text. */\n embed(text: string): Promise<number[]>;\n}\n\nconst SUPPORTED_PROVIDERS: ReadonlySet<string> = new Set([\"anthropic\", \"openai\", \"ollama\", \"minimax\", \"copilot\"]);\n\n/**\n * Factory that returns the appropriate LLMProvider based on env vars.\n * Reads LLMWIKI_PROVIDER (default \"anthropic\") and LLMWIKI_MODEL\n * (defaults per provider from PROVIDER_MODELS).\n *\n * Direct process.env access is acceptable here as this is a system boundary.\n */\nexport function getProvider(): LLMProvider {\n const providerName = getProviderName();\n\n switch (providerName) {\n case \"anthropic\":\n return getAnthropicProvider();\n case \"openai\":\n return new OpenAIProvider(getModelForProvider(\"openai\"), {\n baseURL: readOptionalEnv(\"OPENAI_BASE_URL\"),\n embeddingsBaseURL: readOptionalEnv(\"OPENAI_EMBEDDINGS_BASE_URL\"),\n embeddingModel: readOptionalEnv(\"LLMWIKI_EMBEDDING_MODEL\"),\n });\n case \"ollama\":\n return new OllamaProvider(getModelForProvider(\"ollama\"), {\n baseURL: readOptionalEnv(\"OLLAMA_HOST\") ?? OLLAMA_DEFAULT_HOST,\n embeddingsBaseURL: readOptionalEnv(\"OLLAMA_EMBEDDINGS_HOST\"),\n embeddingModel: readOptionalEnv(\"LLMWIKI_EMBEDDING_MODEL\"),\n });\n case \"minimax\":\n return getMiniMaxProvider();\n case \"copilot\":\n return getCopilotProvider();\n default:\n throw new Error(`Unhandled provider: ${providerName}`);\n }\n}\n\nfunction readOptionalEnv(name: string): string | undefined {\n const value = process.env[name]?.trim();\n return value ? value : undefined;\n}\n\nfunction getModelForProvider(providerName: \"openai\" | \"ollama\" | \"minimax\" | \"copilot\"): string {\n return process.env.LLMWIKI_MODEL ?? PROVIDER_MODELS[providerName];\n}\n\nfunction getMiniMaxProvider(): MiniMaxProvider {\n const apiKey = process.env.MINIMAX_API_KEY;\n if (!apiKey) {\n throw new Error(\n \"MiniMax provider requires MINIMAX_API_KEY environment variable.\\n\" +\n ' Set it with: export MINIMAX_API_KEY=your_key',\n );\n }\n return new MiniMaxProvider(getModelForProvider(\"minimax\"), apiKey);\n}\n\nfunction getCopilotProvider(): CopilotProvider {\n const apiKey = process.env.GITHUB_TOKEN;\n if (!apiKey) {\n throw new Error(\n \"GitHub Copilot provider requires GITHUB_TOKEN environment variable.\\n\" +\n \" Run: gh auth refresh --scopes copilot\\n\" +\n \" Then set it with: export GITHUB_TOKEN=$(gh auth token)\\n\" +\n \" The token must belong to a GitHub account with an active Copilot subscription.\",\n );\n }\n return new CopilotProvider(getModelForProvider(\"copilot\"), apiKey);\n}\n\nfunction getAnthropicProvider(): AnthropicProvider {\n const model = resolveAnthropicModelFromEnv() ?? PROVIDER_MODELS.anthropic;\n const baseURL = resolveAnthropicBaseURLFromEnv();\n const auth = resolveAnthropicAuthFromEnv();\n\n return new AnthropicProvider(model, {\n baseURL,\n ...auth,\n });\n}\n\nfunction getProviderName(): string {\n const providerName = process.env.LLMWIKI_PROVIDER ?? DEFAULT_PROVIDER;\n if (!SUPPORTED_PROVIDERS.has(providerName)) {\n throw new Error(\n `Unknown provider \"${providerName}\". Supported: ${[...SUPPORTED_PROVIDERS].join(\", \")}`,\n );\n }\n return providerName;\n}\n\n/** Expose the resolved provider name for callers that need model lookup. */\nexport function getActiveProviderName(): string {\n return getProviderName();\n}\n","/**\n * Shared LLM helper with provider abstraction.\n *\n * Provides callClaude() for backward compatibility — delegates to the\n * active LLMProvider while preserving retry logic with exponential backoff.\n * The provider is selected via LLMWIKI_PROVIDER env var (see provider.ts).\n */\n\nimport { RETRY_COUNT, RETRY_BASE_MS, RETRY_MULTIPLIER } from \"./constants.js\";\nimport { getProvider } from \"./provider.js\";\nimport type { LLMMessage, LLMTool } from \"./provider.js\";\n\n/** Sleep for a given number of milliseconds. */\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n\n// Matches 4xx status codes at the start of an error message, excluding 429\n// (rate-limit), which is transient and worth retrying.\nconst NON_RETRIABLE_RE = /^4(?!29)\\d\\d\\b/;\n\n/** Return true for client errors that will never succeed on retry (e.g. 401, 403). */\nfunction isNonRetriable(error: unknown): boolean {\n const msg = error instanceof Error ? error.message : String(error);\n return NON_RETRIABLE_RE.test(msg);\n}\n\ninterface CallClaudeOptions {\n system: string;\n messages: LLMMessage[];\n tools?: LLMTool[];\n maxTokens?: number;\n stream?: boolean;\n onToken?: (text: string) => void;\n}\n\n/**\n * Call the active LLM provider with retry logic.\n * Supports streaming, tool-use, and basic completion modes.\n * Preserves the original callClaude interface for backward compatibility.\n */\nexport async function callClaude(options: CallClaudeOptions): Promise<string> {\n const { system, messages, tools, maxTokens = 4096, stream = false, onToken } = options;\n const provider = getProvider();\n\n for (let attempt = 0; attempt <= RETRY_COUNT; attempt++) {\n try {\n if (stream) {\n return await provider.stream(system, messages, maxTokens, onToken);\n }\n\n if (tools && tools.length > 0) {\n return await provider.toolCall(system, messages, tools, maxTokens);\n }\n\n return await provider.complete(system, messages, maxTokens);\n } catch (error) {\n if (attempt === RETRY_COUNT || isNonRetriable(error)) throw error;\n\n const delayMs = RETRY_BASE_MS * Math.pow(RETRY_MULTIPLIER, attempt);\n const errMsg = error instanceof Error ? error.message : String(error);\n console.warn(`⚠ API call failed (attempt ${attempt + 1}/${RETRY_COUNT + 1}): ${errMsg}`);\n console.warn(` Retrying in ${delayMs / 1000}s...`);\n await sleep(delayMs);\n }\n }\n\n throw new Error(\"Unreachable\");\n}\n","/**\n * PID-based lock file for preventing concurrent compilation.\n *\n * Fresh acquisition uses O_CREAT | O_EXCL (the 'wx' flag) for atomic lock\n * creation — the kernel guarantees only one process can create the file.\n *\n * Stale lock reclamation uses a two-lock protocol:\n * 1. Acquire a reclamation lock (.llmwiki/lock.reclaim) via 'wx' to serialize\n * all processes attempting to reclaim the same stale main lock.\n * 2. Re-verify the main lock is still stale (another reclaimer may have\n * already fixed it).\n * 3. unlink + tryCreateLock('wx') on the main lock — safe because we hold\n * exclusive reclamation access.\n * 4. Release the reclamation lock in a finally block.\n *\n * The reclamation lock itself can become stale if a process crashes during\n * the brief reclamation window. When that happens, acquireReclaimLock only\n * cleans up the stale file — it does NOT retry acquisition in the same call.\n * This eliminates the unlink-then-create race that would allow two processes\n * to both hold the reclaim lock. The outer retry loop in acquireLock handles\n * convergence: first pass cleans up the stale reclaim lock, second pass\n * acquires it cleanly via 'wx'.\n */\n\nimport { open, readFile, unlink, mkdir } from \"fs/promises\";\nimport path from \"path\";\nimport { LLMWIKI_DIR, LOCK_FILE } from \"./constants.js\";\nimport * as output from \"./output.js\";\n\nconst RECLAIM_SUFFIX = \".reclaim\";\nconst MAX_ACQUIRE_ATTEMPTS = 2;\n\n/** Check whether a process with the given PID is still running. */\nfunction isProcessAlive(pid: number): boolean {\n try {\n process.kill(pid, 0);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Acquire the compilation lock. Returns true if acquired, false if busy.\n *\n * Retries up to MAX_ACQUIRE_ATTEMPTS times to handle the case where the\n * first attempt cleans up a stale reclamation lock but cannot acquire it\n * in the same call (to avoid the double-winner race).\n */\nexport async function acquireLock(root: string): Promise<boolean> {\n const lockPath = path.join(root, LOCK_FILE);\n await mkdir(path.join(root, LLMWIKI_DIR), { recursive: true });\n\n for (let attempt = 0; attempt < MAX_ACQUIRE_ATTEMPTS; attempt++) {\n // Try atomic create — fails if file already exists\n const created = await tryCreateLock(lockPath);\n if (created) return true;\n\n // Lock exists. Check if the holding process is dead.\n const stale = await isLockStale(lockPath);\n if (!stale) {\n output.status(\"!\", output.warn(\"Another compilation is running.\"));\n return false;\n }\n\n // Stale lock — serialize reclamation via a second lock.\n const reclaimed = await reclaimStaleLock(root, lockPath);\n if (reclaimed) return true;\n\n // Reclamation failed (e.g. cleaned up stale reclaim lock). Retry.\n }\n\n output.status(\"!\", output.warn(\"Could not acquire lock after retrying.\"));\n return false;\n}\n\n/**\n * Reclaim a stale main lock using a serialized two-lock protocol.\n *\n * Acquires .llmwiki/lock.reclaim (via 'wx') so that only one process performs\n * the unlink + recreate sequence at a time. Re-verifies staleness under\n * the reclamation lock in case another process already fixed it.\n * @param root - Project root directory.\n * @param lockPath - Absolute path to the main lock file.\n */\nasync function reclaimStaleLock(root: string, lockPath: string): Promise<boolean> {\n const reclaimPath = lockPath + RECLAIM_SUFFIX;\n\n const gotReclaimLock = await acquireReclaimLock(reclaimPath);\n if (!gotReclaimLock) return false;\n\n try {\n // Re-verify under exclusive reclamation access.\n // Another reclaimer may have already fixed the main lock.\n if (!(await isLockStale(lockPath))) {\n return false;\n }\n\n // Still stale. Safe to reclaim — we're the only reclaimer.\n try { await unlink(lockPath); } catch { /* already gone */ }\n\n const acquired = await tryCreateLock(lockPath);\n if (acquired) {\n output.status(\"i\", output.dim(\"Reclaimed stale lock from dead process.\"));\n }\n return acquired;\n } finally {\n try { await unlink(reclaimPath); } catch { /* cleanup best-effort */ }\n }\n}\n\n/**\n * Acquire the reclamation lock. Uses 'wx' for atomic creation.\n *\n * If the reclaim lock is stale (holder crashed during reclamation), this\n * function ONLY cleans up the stale file and returns false. It does NOT\n * retry acquisition in the same call. This is the key safety property:\n * unlink and create never happen in the same call, so two processes that\n * both see a stale reclaim lock will both clean up (harmless — second\n * unlink gets ENOENT) and both return false. Neither holds the reclaim\n * lock, so neither proceeds to touch the main lock. The outer retry loop\n * in acquireLock converges on the next attempt via a clean 'wx'.\n * @param reclaimPath - Absolute path to the reclamation lock file.\n */\nasync function acquireReclaimLock(reclaimPath: string): Promise<boolean> {\n if (await tryCreateLock(reclaimPath)) return true;\n\n // Reclaim lock exists. If its holder is alive, back off.\n if (!(await isLockStale(reclaimPath))) return false;\n\n // Stale reclaim lock — clean it up but do NOT retry in this call.\n // Retrying here would reintroduce the unlink+create race.\n try { await unlink(reclaimPath); } catch { /* already gone */ }\n return false;\n}\n\n/**\n * Atomically create the lock file with our PID.\n * Returns true if we created it, false if it already exists.\n */\nasync function tryCreateLock(lockPath: string): Promise<boolean> {\n try {\n const fd = await open(lockPath, \"wx\");\n await fd.writeFile(String(process.pid), \"utf-8\");\n await fd.close();\n return true;\n } catch (err: unknown) {\n if (err instanceof Error && \"code\" in err && (err as NodeJS.ErrnoException).code === \"EEXIST\") {\n return false;\n }\n throw err;\n }\n}\n\n/** Check if an existing lock is stale (holding process is dead). */\nasync function isLockStale(lockPath: string): Promise<boolean> {\n try {\n const content = await readFile(lockPath, \"utf-8\");\n const pid = parseInt(content.trim(), 10);\n if (isNaN(pid)) return true;\n return !isProcessAlive(pid);\n } catch {\n return true;\n }\n}\n\n/** Release the compilation lock. Safe to call even if lock doesn't exist. */\nexport async function releaseLock(root: string): Promise<void> {\n const lockPath = path.join(root, LOCK_FILE);\n try {\n await unlink(lockPath);\n } catch {\n // Lock already removed or never existed\n }\n}\n","/**\n * Output-language configuration for LLM-generated wiki content (issue #37).\n *\n * Resolves the user's chosen target language for compile and query\n * prompts. The CLI's `--lang <code>` flag and the `LLMWIKI_OUTPUT_LANG`\n * environment variable both write into the same env slot, so prompt\n * builders only need to read one source of truth.\n *\n * When unset, the resolver returns null — preserving the historical\n * behaviour where the LLM follows its own default (typically the\n * source-document language, often English).\n */\n\nconst LANG_ENV_VAR = \"LLMWIKI_OUTPUT_LANG\";\n\n/**\n * Read the configured output language. Returns null when the user has\n * not opted into a specific target language.\n */\nexport function getOutputLanguage(): string | null {\n const raw = process.env[LANG_ENV_VAR];\n if (!raw) return null;\n const trimmed = raw.trim();\n return trimmed.length > 0 ? trimmed : null;\n}\n\n/**\n * Build the language-directive line to inject into a system prompt.\n * Returns an empty string when no language is configured, which lets\n * callers concatenate unconditionally without producing an extra blank\n * line in the default case.\n */\nexport function languageDirective(): string {\n const lang = getOutputLanguage();\n if (!lang) return \"\";\n return `Write the output in ${lang}.`;\n}\n\n/**\n * Apply a CLI `--lang <code>` value into the shared env slot so prompt\n * builders pick it up downstream. No-op when the caller did not pass the\n * flag. Trims whitespace so accidental padding does not leak into the\n * language directive.\n *\n * Lives in this module (not cli.ts) so every CLI verb plus the upcoming\n * quickstart command share one implementation and the resolution order\n * cannot drift between them.\n */\nexport function applyLanguageOption(lang: string | undefined): void {\n if (lang && lang.trim().length > 0) {\n process.env.LLMWIKI_OUTPUT_LANG = lang.trim();\n }\n}\n","/**\n * LLM prompt templates and tool schemas for the compilation pipeline.\n * Contains the Anthropic tool definition for concept extraction,\n * prompt builders for both extraction and page generation phases,\n * and a parser for the structured tool output.\n */\n\nimport type {\n ContradictionRef,\n ExtractedConcept,\n ProvenanceState,\n} from \"../utils/types.js\";\nimport type { PageKindRule, SeedPage } from \"../schema/index.js\";\nimport { languageDirective } from \"../utils/output-language.js\";\n\n/**\n * Build a list of optional prompt lines, omitting empty entries so the\n * default-case prompt is byte-identical to the previous version. Used by\n * the prompt builders to splice in the output-language directive only\n * when the user opted in.\n */\nfunction withLangLine(...lines: string[]): string[] {\n const lang = languageDirective();\n return lang ? [...lines, lang] : lines;\n}\n\n/** Allowed provenance state strings emitted by the LLM tool schema. */\nconst PROVENANCE_STATE_VALUES: ProvenanceState[] = [\n \"extracted\",\n \"merged\",\n \"inferred\",\n \"ambiguous\",\n];\n\n/**\n * Anthropic Tool definition for extracting knowledge concepts from a source.\n * Used with callClaude's tool_use mode to get structured concept data.\n */\nexport const CONCEPT_EXTRACTION_TOOL = {\n name: \"extract_concepts\",\n description: \"Extract knowledge concepts from a source document\",\n input_schema: {\n type: \"object\" as const,\n properties: {\n concepts: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n concept: {\n type: \"string\",\n description: \"Human-readable concept title\",\n },\n summary: {\n type: \"string\",\n description: \"One-line description\",\n },\n is_new: {\n type: \"boolean\",\n description: \"True if this is a new concept not in existing wiki\",\n },\n tags: {\n type: \"array\",\n items: { type: \"string\" },\n description:\n \"2-4 categorical tags for organizing this concept (e.g., 'machine-learning', 'optimization')\",\n },\n confidence: {\n type: \"number\",\n description:\n \"Confidence in this concept on a 0..1 scale (1 = directly stated, 0 = highly speculative).\",\n },\n provenance_state: {\n type: \"string\",\n enum: PROVENANCE_STATE_VALUES,\n description:\n \"How this concept was produced: 'extracted' (direct from source), 'merged' (synthesised across sources), 'inferred' (model deduction), or 'ambiguous' (sources disagree).\",\n },\n contradicted_by: {\n type: \"array\",\n items: {\n type: \"object\",\n properties: {\n slug: { type: \"string\", description: \"Slug of the contradicting concept.\" },\n reason: { type: \"string\", description: \"Brief reason for the contradiction.\" },\n },\n required: [\"slug\"],\n },\n description: \"Slugs of other concepts whose evidence contradicts this one.\",\n },\n },\n required: [\"concept\", \"summary\", \"is_new\"],\n },\n },\n },\n required: [\"concepts\"],\n },\n};\n\n/**\n * Build the system prompt for the concept extraction phase.\n * Instructs the LLM to analyze a source document and identify distinct concepts.\n * @param sourceContent - The full text of the source document.\n * @param existingIndex - The current wiki index.md contents (may be empty).\n * @returns System prompt string for the extraction call.\n */\nexport function buildExtractionPrompt(\n sourceContent: string,\n existingIndex: string,\n): string {\n const indexSection = existingIndex\n ? `\\n\\nHere is the existing wiki index — avoid duplicating concepts already covered:\\n\\n${existingIndex}`\n : \"\\n\\nNo existing wiki pages yet.\";\n\n return [\n ...withLangLine(\n \"You are a knowledge extraction engine. Analyze the following source document\",\n \"and identify 3-8 distinct, meaningful concepts worth documenting as wiki pages.\",\n \"Each concept should be a standalone topic that someone might look up.\",\n \"Focus on key ideas, techniques, patterns, or entities — not trivial details.\",\n \"Use the extract_concepts tool to return your findings.\",\n ),\n \"\",\n \"For every concept, emit provenance metadata so downstream tools can reason\",\n \"about reliability:\",\n \" - confidence: 0..1 — how certain you are the source supports this concept.\",\n \" - provenance_state: 'extracted' if directly stated, 'merged' if synthesised\",\n \" from multiple parts of the source, 'inferred' if reasoned from context,\",\n \" or 'ambiguous' if the source is contradictory or unclear.\",\n \" - contradicted_by: slugs of other concepts (in this batch or the index)\",\n \" whose evidence conflicts with this one.\",\n indexSection,\n \"\\n\\n--- SOURCE DOCUMENT ---\\n\\n\",\n sourceContent,\n ].join(\"\\n\");\n}\n\n/**\n * Build the system prompt for wiki page generation.\n * Instructs the LLM to write a complete wiki page for a single concept.\n * @param concept - The concept title to write about.\n * @param sourceContent - The source material to draw from.\n * @param existingPage - The current page content if updating (empty for new pages).\n * @param relatedPages - Concatenated content of related wiki pages for context.\n * @returns System prompt string for the page generation call.\n */\nexport function buildPagePrompt(\n concept: string,\n sourceContent: string,\n existingPage: string,\n relatedPages: string,\n): string {\n const existingSection = existingPage\n ? `\\n\\nExisting page to update:\\n\\n${existingPage}`\n : \"\";\n\n const relatedSection = relatedPages\n ? `\\n\\nRelated wiki pages for cross-referencing:\\n\\n${relatedPages}`\n : \"\";\n\n return [\n ...withLangLine(\n `You are a wiki author. Write a clear, well-structured markdown page about \"${concept}\".`,\n \"Draw facts only from the provided source material.\",\n \"Include a ## Sources section at the end listing the source document.\",\n \"Suggest [[wikilinks]] to related concepts where appropriate.\",\n \"Write in a neutral, informative tone. Be concise but thorough.\",\n ),\n \"\",\n \"Source attribution: at the end of each prose paragraph, append a citation\",\n \"marker identifying which source file(s) and line range the paragraph drew from.\",\n \"PREFERRED format: ^[filename.md:START-END] where START and END are the line numbers\",\n \"shown in the numbered source content below (e.g. ' 42 | some text' → line 42).\",\n \"Use this whenever you can identify the specific numbered lines supporting the claim.\",\n \"Fallback format: ^[filename.md] when the claim draws from the source broadly and\",\n \"no specific line range applies. For multi-source paragraphs: ^[a.md:1-5, b.md:10-12].\",\n \"Place citations only at the end of prose paragraphs or sentences — not on\",\n \"headings, list items, or code blocks.\",\n \"Do not cite YAML frontmatter lines (the --- ... --- block at the top of a file) as\",\n \"source evidence for substantive claims — those lines are metadata, not content.\",\n \"If a claim relates to a metadata field (e.g. document date or author), leave it uncited.\",\n \"Source filenames are visible as `--- SOURCE: filename.md ---` headers in the content below.\",\n \"\",\n \"If a paragraph is your inference rather than a direct extraction, leave it\",\n \"uncited — downstream lint rules will count uncited paragraphs as 'inferred'\",\n \"so lint can surface excess-inferred-paragraphs warnings on review.\",\n existingSection,\n relatedSection,\n \"\\n\\n--- SOURCE MATERIAL ---\\n\\n\",\n sourceContent,\n ].join(\"\\n\");\n}\n\n/** Raw concept shape as it arrives from the tool JSON. */\ninterface RawConcept {\n concept: unknown;\n summary: unknown;\n is_new: unknown;\n tags?: unknown;\n confidence?: unknown;\n provenance_state?: unknown;\n contradicted_by?: unknown;\n}\n\n/** True if the raw concept has the required string/boolean fields. */\nfunction isValidRawConcept(c: RawConcept): boolean {\n return (\n typeof c.concept === \"string\" &&\n typeof c.summary === \"string\" &&\n typeof c.is_new === \"boolean\" &&\n (c.tags === undefined || Array.isArray(c.tags))\n );\n}\n\n/** Coerce raw contradiction entries from the tool into typed refs. */\nfunction coerceContradictedBy(raw: unknown): ContradictionRef[] | undefined {\n if (!Array.isArray(raw)) return undefined;\n const refs: ContradictionRef[] = [];\n for (const entry of raw) {\n if (!entry || typeof entry !== \"object\") continue;\n const obj = entry as { slug?: unknown; reason?: unknown };\n if (typeof obj.slug !== \"string\" || obj.slug.trim().length === 0) continue;\n const ref: ContradictionRef = { slug: obj.slug.trim() };\n if (typeof obj.reason === \"string\") ref.reason = obj.reason;\n refs.push(ref);\n }\n return refs.length > 0 ? refs : undefined;\n}\n\n/** Map a validated raw concept into an ExtractedConcept. */\nfunction mapRawConcept(c: RawConcept): ExtractedConcept {\n const provenance = typeof c.provenance_state === \"string\" &&\n PROVENANCE_STATE_VALUES.includes(c.provenance_state as ProvenanceState)\n ? (c.provenance_state as ProvenanceState)\n : undefined;\n return {\n concept: c.concept as string,\n summary: c.summary as string,\n is_new: c.is_new as boolean,\n tags: Array.isArray(c.tags) ? (c.tags as string[]) : undefined,\n confidence: typeof c.confidence === \"number\" ? c.confidence : undefined,\n provenanceState: provenance,\n contradictedBy: coerceContradictedBy(c.contradicted_by),\n };\n}\n\n/**\n * Build a system prompt for generating a seed page (overview / comparison /\n * entity) declared in the project's schema config. Seed pages weave together\n * material from related concept pages rather than from raw source files.\n * @param seed - Seed page definition pulled from the schema.\n * @param rule - Per-kind rule (used for the description and link minimum).\n * @param relatedPagesContent - Concatenated content of related concept pages.\n * @returns System prompt string for the page generation call.\n */\nexport function buildSeedPagePrompt(\n seed: SeedPage,\n rule: PageKindRule,\n relatedPagesContent: string,\n): string {\n const minLinks = rule.minWikilinks;\n const linkExpectation = minLinks > 0\n ? `Include at least ${minLinks} [[wikilinks]] to related pages.`\n : \"Use [[wikilinks]] when referencing other pages.\";\n return [\n ...withLangLine(\n `You are a wiki author. Write a ${seed.kind} page titled \"${seed.title}\".`,\n `Page-kind guidance: ${rule.description}`,\n `Summary line for context: ${seed.summary}`,\n \"Draw facts only from the related wiki pages provided below.\",\n linkExpectation,\n \"Write in a neutral, informative tone. Be concise but thorough.\",\n ),\n \"\\n\\n--- RELATED PAGES ---\\n\\n\",\n relatedPagesContent,\n ].join(\"\\n\");\n}\n\n/**\n * Parse the JSON tool output from concept extraction into typed objects.\n * @param toolOutput - Raw JSON string returned from the extract_concepts tool.\n * @returns Array of ExtractedConcept objects.\n */\nexport function parseConcepts(toolOutput: string): ExtractedConcept[] {\n try {\n const parsed = JSON.parse(toolOutput);\n const concepts: RawConcept[] = parsed.concepts ?? [];\n return concepts.filter(isValidRawConcept).map(mapRawConcept);\n } catch {\n return [];\n }\n}\n","/**\n * Type definitions for the wiki schema layer.\n *\n * The schema layer turns llmwiki from a flat compiler pipeline into a shaped\n * knowledge system. It declares the kinds of pages a project supports\n * (`concept`, `entity`, `comparison`, `overview`) and the cross-link\n * expectations that lint and review enforce per kind.\n *\n * Types live in their own module so that compile, lint, CLI, and tests can\n * depend on the schema vocabulary without pulling in YAML/JSON loaders.\n */\n\n/** All page kinds the schema layer recognises. */\nexport type PageKind = \"concept\" | \"entity\" | \"comparison\" | \"overview\";\n\n/** All recognised page kinds, exported for validation and CLI display. */\nexport const PAGE_KINDS: readonly PageKind[] = [\n \"concept\",\n \"entity\",\n \"comparison\",\n \"overview\",\n] as const;\n\n/** Per-kind policy: minimum cross-links and a human description used in prompts. */\nexport interface PageKindRule {\n /** Minimum number of [[wikilinks]] a page of this kind should contain. */\n minWikilinks: number;\n /** Short human-readable description; surfaced in prompts and review output. */\n description: string;\n}\n\n/** Optional declarative seed for non-concept pages the compiler can generate. */\nexport interface SeedPage {\n /** Display title; also used to derive the page slug. */\n title: string;\n /** Page kind — must be one of `PAGE_KINDS`. */\n kind: PageKind;\n /** One-line summary written into frontmatter. */\n summary: string;\n /**\n * For `overview` and `comparison` kinds, the slugs the page should weave\n * together. The compiler passes these to the LLM as the source material.\n */\n relatedSlugs?: string[];\n}\n\n/** Resolved schema config the rest of the compiler reads. */\nexport interface SchemaConfig {\n /** Schema format version. Currently always `1`. */\n version: 1;\n /** Kind assigned to pages that don't declare a kind in frontmatter. */\n defaultKind: PageKind;\n /** Per-kind rules keyed by `PageKind`. */\n kinds: Record<PageKind, PageKindRule>;\n /** Optional seed pages the compiler should materialise on each run. */\n seedPages: SeedPage[];\n /** Path the schema was loaded from, or `null` when defaults are used. */\n loadedFrom: string | null;\n}\n\n/** Raw schema file contents — every field is optional so partial files work. */\nexport interface PartialSchemaFile {\n version?: number;\n defaultKind?: string;\n kinds?: Partial<Record<string, Partial<PageKindRule>>>;\n seedPages?: Array<Partial<SeedPage>>;\n}\n","/**\n * Default schema constants.\n *\n * Projects without a schema file fall back to these defaults so the compiler\n * keeps working on day one. Every existing wiki — created before the schema\n * layer existed — is treated as a wiki of `concept` pages with no\n * cross-link minimums, preserving backward compatibility.\n */\n\nimport type { PageKind, PageKindRule, SchemaConfig } from \"./types.js\";\n\n/** Minimum cross-links per kind, chosen to match each kind's purpose. */\nconst DEFAULT_MIN_LINKS: Record<PageKind, number> = {\n concept: 0,\n entity: 1,\n comparison: 2,\n overview: 3,\n};\n\n/** Human-readable descriptions used in prompts and review output. */\nconst DEFAULT_DESCRIPTIONS: Record<PageKind, string> = {\n concept: \"A standalone idea, technique, or pattern worth documenting.\",\n entity: \"A specific thing — a person, product, organization, or named artifact.\",\n comparison: \"A side-by-side analysis weighing two or more concepts or entities.\",\n overview: \"A top-down map page that situates several concepts within a domain.\",\n};\n\n/** Build the default per-kind rule table. */\nfunction buildDefaultKindRules(): Record<PageKind, PageKindRule> {\n return {\n concept: { minWikilinks: DEFAULT_MIN_LINKS.concept, description: DEFAULT_DESCRIPTIONS.concept },\n entity: { minWikilinks: DEFAULT_MIN_LINKS.entity, description: DEFAULT_DESCRIPTIONS.entity },\n comparison: {\n minWikilinks: DEFAULT_MIN_LINKS.comparison,\n description: DEFAULT_DESCRIPTIONS.comparison,\n },\n overview: {\n minWikilinks: DEFAULT_MIN_LINKS.overview,\n description: DEFAULT_DESCRIPTIONS.overview,\n },\n };\n}\n\n/** The schema returned when no schema file exists. */\nexport function buildDefaultSchema(): SchemaConfig {\n return {\n version: 1,\n defaultKind: \"concept\",\n kinds: buildDefaultKindRules(),\n seedPages: [],\n loadedFrom: null,\n };\n}\n","/**\n * Schema config loader.\n *\n * Discovers a project's schema file from a fixed list of candidate paths,\n * parses it (JSON or YAML), and merges it onto the default schema. Missing\n * files are not an error — the compiler falls back to defaults so existing\n * projects continue to work without any migration.\n */\n\nimport { existsSync } from \"fs\";\nimport { readFile } from \"fs/promises\";\nimport path from \"path\";\nimport yaml from \"js-yaml\";\nimport type {\n PageKind,\n PageKindRule,\n PartialSchemaFile,\n SchemaConfig,\n SeedPage,\n} from \"./types.js\";\nimport { PAGE_KINDS } from \"./types.js\";\nimport { buildDefaultSchema } from \"./defaults.js\";\n\n/** Candidate schema file paths searched in priority order. */\nconst SCHEMA_CANDIDATE_PATHS = [\n \".llmwiki/schema.json\",\n \".llmwiki/schema.yaml\",\n \".llmwiki/schema.yml\",\n \"wiki/.schema.yaml\",\n \"wiki/.schema.yml\",\n];\n\n/** Find the first existing schema candidate path under `root`, or null. */\nfunction findSchemaPath(root: string): string | null {\n for (const candidate of SCHEMA_CANDIDATE_PATHS) {\n const absolute = path.join(root, candidate);\n if (existsSync(absolute)) return absolute;\n }\n return null;\n}\n\n/** Decide whether to parse the file as JSON or YAML based on its extension. */\nfunction parseSchemaFile(filePath: string, content: string): PartialSchemaFile {\n const isJson = filePath.endsWith(\".json\");\n const parsed = isJson ? JSON.parse(content) : yaml.load(content);\n if (parsed && typeof parsed === \"object\") return parsed as PartialSchemaFile;\n return {};\n}\n\n/** Type-guard checking whether a string is one of the supported page kinds. */\nfunction isPageKind(value: unknown): value is PageKind {\n return typeof value === \"string\" && (PAGE_KINDS as readonly string[]).includes(value);\n}\n\n/** Merge a single per-kind rule from the file onto the default rule. */\nfunction mergeKindRule(\n defaults: PageKindRule,\n override: Partial<PageKindRule> | undefined,\n): PageKindRule {\n if (!override) return defaults;\n const minWikilinks = typeof override.minWikilinks === \"number\"\n ? override.minWikilinks\n : defaults.minWikilinks;\n const description = typeof override.description === \"string\"\n ? override.description\n : defaults.description;\n return { minWikilinks, description };\n}\n\n/** Merge per-kind rule overrides onto the default rule table. */\nfunction mergeKinds(\n defaults: Record<PageKind, PageKindRule>,\n overrides: PartialSchemaFile[\"kinds\"],\n): Record<PageKind, PageKindRule> {\n const merged = { ...defaults };\n if (!overrides) return merged;\n\n for (const kind of PAGE_KINDS) {\n merged[kind] = mergeKindRule(defaults[kind], overrides[kind]);\n }\n return merged;\n}\n\n/** Validate and coerce a single seed page entry. Returns null when invalid. */\nfunction normalizeSeedPage(entry: Partial<SeedPage>): SeedPage | null {\n if (typeof entry.title !== \"string\" || entry.title.trim() === \"\") return null;\n if (!isPageKind(entry.kind)) return null;\n const summary = typeof entry.summary === \"string\" ? entry.summary : \"\";\n const relatedSlugs = Array.isArray(entry.relatedSlugs)\n ? entry.relatedSlugs.filter((slug): slug is string => typeof slug === \"string\")\n : undefined;\n return { title: entry.title, kind: entry.kind, summary, relatedSlugs };\n}\n\n/** Coerce raw seed page entries into validated SeedPage objects. */\nfunction normalizeSeedPages(entries: PartialSchemaFile[\"seedPages\"]): SeedPage[] {\n if (!Array.isArray(entries)) return [];\n return entries\n .map(normalizeSeedPage)\n .filter((entry): entry is SeedPage => entry !== null);\n}\n\n/** Apply a parsed partial-schema onto the defaults, returning the resolved config. */\nfunction applyOverrides(\n defaults: SchemaConfig,\n overrides: PartialSchemaFile,\n loadedFrom: string,\n): SchemaConfig {\n const defaultKind = isPageKind(overrides.defaultKind)\n ? overrides.defaultKind\n : defaults.defaultKind;\n return {\n version: 1,\n defaultKind,\n kinds: mergeKinds(defaults.kinds, overrides.kinds),\n seedPages: normalizeSeedPages(overrides.seedPages),\n loadedFrom,\n };\n}\n\n/**\n * Load the schema for `root`, falling back to defaults when no file is present.\n * Throws on parse failure so the user sees a clear error rather than a silent\n * default — silent fallback would mask real config bugs.\n * @param root - Project root directory.\n * @returns Resolved schema config.\n */\nexport async function loadSchema(root: string): Promise<SchemaConfig> {\n const defaults = buildDefaultSchema();\n const schemaPath = findSchemaPath(root);\n if (!schemaPath) return defaults;\n\n const raw = await readFile(schemaPath, \"utf-8\");\n const parsed = parseSchemaFile(schemaPath, raw);\n return applyOverrides(defaults, parsed, schemaPath);\n}\n\n/** Expose candidate paths so the CLI `schema init` command can pick one. */\nexport function defaultSchemaInitPath(root: string): string {\n return path.join(root, SCHEMA_CANDIDATE_PATHS[0]);\n}\n","/**\n * Schema helper utilities shared by compile, lint, and CLI.\n *\n * Kept separate from `loader.ts` so callers that just need to interpret a\n * page's kind or count its wikilinks don't pull the YAML/JSON parser into\n * their dependency graph.\n */\n\nimport yaml from \"js-yaml\";\nimport type { PageKind, SchemaConfig } from \"./types.js\";\nimport { PAGE_KINDS } from \"./types.js\";\n\n/** Pattern matching [[Wikilink Title]] references in markdown content. */\nconst WIKILINK_PATTERN = /\\[\\[([^\\]]+)\\]\\]/g;\n\n/**\n * Resolve a page's kind from its raw frontmatter value, falling back to the\n * schema default when no explicit kind is set or the value is invalid.\n * @param rawKind - Raw `kind` value pulled from frontmatter (untyped).\n * @param schema - Resolved schema config.\n * @returns The resolved page kind.\n */\nexport function resolvePageKind(rawKind: unknown, schema: SchemaConfig): PageKind {\n if (typeof rawKind === \"string\" && (PAGE_KINDS as readonly string[]).includes(rawKind)) {\n return rawKind as PageKind;\n }\n return schema.defaultKind;\n}\n\n/**\n * Count the [[wikilinks]] in a page body.\n * Pure function so the linter can apply per-kind minimums without re-parsing.\n * @param body - Markdown body text.\n * @returns Number of wikilink references found.\n */\nexport function countWikilinks(body: string): number {\n const matches = body.match(WIKILINK_PATTERN);\n return matches ? matches.length : 0;\n}\n\n/**\n * Serialise a schema config to YAML for `llmwiki schema init` to write to disk.\n * The `loadedFrom` field is omitted because it's a runtime-only annotation.\n * @param schema - Resolved schema config.\n * @returns YAML string suitable for writing to a schema file.\n */\nexport function serializeSchemaToYaml(schema: SchemaConfig): string {\n const serializable = {\n version: schema.version,\n defaultKind: schema.defaultKind,\n kinds: schema.kinds,\n seedPages: schema.seedPages,\n };\n return yaml.dump(serializable, { lineWidth: -1, quotingType: '\"' });\n}\n","/**\n * Semantic dependency tracking for cross-source concept sharing.\n *\n * When multiple source files contribute to the same concept, a change in one\n * source should trigger recompilation of that concept using content from ALL\n * contributing sources. This module builds a reverse index from concepts back\n * to their source files, then identifies which unchanged sources are affected\n * by changes to other sources that share concepts with them.\n *\n * Without this, if sources A and B both produce concept X and source A changes,\n * concept X would be regenerated using only source A's content — losing source\n * B's contribution entirely.\n */\n\nimport { readState, updateSourceState, writeState } from \"../utils/state.js\";\nimport { slugify } from \"../utils/markdown.js\";\nimport * as output from \"../utils/output.js\";\nimport type { WikiState, SourceChange, ExtractedConcept } from \"../utils/types.js\";\n\nexport interface ExtractionResult {\n sourceFile: string;\n sourcePath: string;\n sourceContent: string;\n concepts: ExtractedConcept[];\n}\n\n/**\n * Build a reverse map from concept slugs to the source files that produced them.\n * @param sources - The sources record from WikiState.\n * @returns Map where keys are concept slugs and values are arrays of source filenames.\n */\nfunction buildConceptToSourcesMap(\n sources: WikiState[\"sources\"],\n): Map<string, string[]> {\n const conceptMap = new Map<string, string[]>();\n\n for (const [sourceFile, entry] of Object.entries(sources)) {\n for (const slug of entry.concepts) {\n const existing = conceptMap.get(slug);\n if (existing) {\n existing.push(sourceFile);\n } else {\n conceptMap.set(slug, [sourceFile]);\n }\n }\n }\n\n return conceptMap;\n}\n\n/** Extract filenames from changes matching a given status. */\nfunction filesByStatus(\n changes: SourceChange[],\n ...statuses: SourceChange[\"status\"][]\n): Set<string> {\n const statusSet = new Set(statuses);\n return new Set(\n changes.filter((c) => statusSet.has(c.status)).map((c) => c.file),\n );\n}\n\n/**\n * Collect co-contributors for a source's concepts, skipping files in the\n * exclusion sets. Mutates `out` by adding newly discovered contributors.\n */\nfunction collectSharedContributors(\n sourceFile: string,\n state: WikiState,\n conceptMap: Map<string, string[]>,\n excludeSets: Set<string>[],\n out: Set<string>,\n): void {\n const sourceEntry = state.sources[sourceFile];\n if (!sourceEntry) return;\n\n for (const slug of sourceEntry.concepts) {\n const contributors = conceptMap.get(slug);\n if (!contributors || contributors.length < 2) continue;\n\n for (const contributor of contributors) {\n const isExcluded = excludeSets.some((s) => s.has(contributor));\n if (!isExcluded) out.add(contributor);\n }\n }\n}\n\n/**\n * Identify unchanged sources that need recompilation because they share\n * concepts with directly changed sources. This enables correct cross-source\n * concept regeneration — ensuring shared concepts are rebuilt with content\n * from ALL contributing sources.\n *\n * Deleted sources are intentionally excluded: recompiling a concept-mate of\n * a deleted source would regenerate the page from fewer sources, losing\n * content. Shared concepts from deleted sources are preserved as-is by\n * markOrphaned (which skips shared concepts).\n *\n * @param state - The current persisted WikiState.\n * @param directChanges - Changes detected by hash comparison.\n * @returns Filenames of indirectly affected sources not already in the changed list.\n */\nexport function findAffectedSources(\n state: WikiState,\n directChanges: SourceChange[],\n): string[] {\n const changedFiles = filesByStatus(directChanges, \"new\", \"changed\");\n const deletedFiles = filesByStatus(directChanges, \"deleted\");\n const conceptMap = buildConceptToSourcesMap(state.sources);\n const affected = new Set<string>();\n\n for (const changedFile of changedFiles) {\n collectSharedContributors(\n changedFile, state, conceptMap,\n [changedFiles, deletedFiles, affected],\n affected,\n );\n }\n\n return Array.from(affected);\n}\n\n/**\n * Find concept slugs that must NOT be regenerated during this compile batch.\n * A slug is \"frozen\" when it was shared between a deleted source and at least\n * one surviving source. Regenerating it would overwrite the existing page\n * (which has combined content from all prior contributors) with content from\n * only the surviving sources, silently losing the deleted source's contribution.\n * @param state - Current persisted state.\n * @param changes - All detected source changes in this batch.\n * @returns Set of concept slugs that compileSource should skip.\n */\nexport function findFrozenSlugs(\n state: WikiState,\n changes: SourceChange[],\n): Set<string> {\n // Start with persisted frozen slugs from prior batches.\n const frozen = new Set<string>(state.frozenSlugs ?? []);\n\n // Add new frozen slugs from deletions in this batch.\n const deletedFiles = changes\n .filter((c) => c.status === \"deleted\")\n .map((c) => c.file);\n\n const conceptMap = buildConceptToSourcesMap(state.sources);\n\n for (const file of deletedFiles) {\n const entry = state.sources[file];\n if (!entry) continue;\n\n for (const slug of entry.concepts) {\n const contributors = conceptMap.get(slug);\n if (contributors && contributors.length > 1) {\n frozen.add(slug);\n }\n }\n }\n\n return frozen;\n}\n\n/**\n * Unfreeze slugs that were successfully regenerated by all their current\n * contributors, then persist the remaining frozen set to state.\n * A slug is safe to unfreeze when every source that claims it in state\n * was compiled in this batch and successfully extracted it.\n */\nexport async function persistFrozenSlugs(\n root: string,\n frozenSlugs: Set<string>,\n successfulExtractions: ExtractionResult[],\n): Promise<void> {\n const currentState = await readState(root);\n const conceptMap = buildConceptToSourcesMap(currentState.sources);\n\n // Concepts successfully extracted in this batch, keyed by slug.\n const extractedBy = new Set<string>();\n for (const result of successfulExtractions) {\n if (result.concepts.length === 0) continue;\n for (const c of result.concepts) {\n extractedBy.add(slugify(c.concept));\n }\n }\n const compiledFiles = new Set(\n successfulExtractions\n .filter((r) => r.concepts.length > 0)\n .map((r) => r.sourceFile),\n );\n\n const remaining = new Set<string>();\n for (const slug of frozenSlugs) {\n const owners = conceptMap.get(slug) ?? [];\n // Unfreeze only if ALL current owners were compiled and extracted it.\n const allOwnersCompiled = owners.length > 0\n && owners.every((f) => compiledFiles.has(f))\n && extractedBy.has(slug);\n\n if (!allOwnersCompiled) remaining.add(slug);\n }\n\n const stateToSave = { ...currentState, frozenSlugs: Array.from(remaining) };\n await writeState(root, stateToSave);\n}\n\n/**\n * Collect concept slugs from extractions that were not in the source's\n * previous concept list — these are \"newly gained\" concepts that\n * findAffectedSources could not have matched pre-extraction.\n */\nfunction collectFreshSlugs(\n extractions: ExtractionResult[],\n state: WikiState,\n): Set<string> {\n const freshSlugs = new Set<string>();\n\n for (const result of extractions) {\n const oldConcepts = new Set(state.sources[result.sourceFile]?.concepts ?? []);\n for (const c of result.concepts) {\n const slug = slugify(c.concept);\n if (!oldConcepts.has(slug)) freshSlugs.add(slug);\n }\n }\n\n return freshSlugs;\n}\n\n/**\n * Find unchanged sources that own any of the given slugs, excluding files\n * present in the provided exclusion sets.\n */\nfunction findSlugOwners(\n slugs: Set<string>,\n conceptMap: Map<string, string[]>,\n excludeSets: Set<string>[],\n): string[] {\n const affected = new Set<string>();\n\n for (const slug of slugs) {\n const owners = conceptMap.get(slug);\n if (!owners) continue;\n for (const owner of owners) {\n const isExcluded = excludeSets.some((s) => s.has(owner));\n if (!isExcluded) affected.add(owner);\n }\n }\n\n return Array.from(affected);\n}\n\n/**\n * Post-extraction check for compiled sources whose freshly extracted concepts\n * overlap with unchanged sources not already in the batch. Covers two cases\n * that findAffectedSources (pre-extraction) cannot detect:\n * 1. New sources have no state entry, so their concepts are unknown.\n * 2. Changed sources may gain concepts they didn't previously have.\n * @param extractions - Results from Phase 1 extraction.\n * @param state - Current persisted state.\n * @param allChanges - Full changes array including deleted/unchanged entries.\n * @returns Filenames of unchanged sources that share concepts with compiled sources.\n */\nexport function findLateAffectedSources(\n extractions: ExtractionResult[],\n state: WikiState,\n allChanges: SourceChange[],\n): string[] {\n const compilingFiles = filesByStatus(allChanges, \"new\", \"changed\");\n const deletedFiles = filesByStatus(allChanges, \"deleted\");\n const conceptMap = buildConceptToSourcesMap(state.sources);\n const freshSlugs = collectFreshSlugs(extractions, state);\n\n return findSlugOwners(freshSlugs, conceptMap, [compilingFiles, deletedFiles]);\n}\n\n/**\n * Find concept slugs from a source that are also produced by other sources.\n * Used by markOrphaned to skip orphaning shared concepts when a source is\n * deleted — preserving combined content from prior compilations.\n * @param sourceFile - The source being checked.\n * @param state - Current persisted state.\n * @returns Set of slugs that have at least one other contributing source.\n */\nexport function findSharedConcepts(\n sourceFile: string,\n state: WikiState,\n): Set<string> {\n const shared = new Set<string>();\n const sourceEntry = state.sources[sourceFile];\n if (!sourceEntry) return shared;\n\n const conceptMap = buildConceptToSourcesMap(state.sources);\n\n for (const slug of sourceEntry.concepts) {\n const contributors = conceptMap.get(slug);\n if (contributors && contributors.length > 1) {\n shared.add(slug);\n }\n }\n\n return shared;\n}\n\n/**\n * Freeze concepts from failed extractions and persist their state with a\n * blank hash so they retry on the next compile. Preserves old concept lists\n * to keep dependency tracking intact.\n */\nexport async function freezeFailedExtractions(\n root: string,\n results: ExtractionResult[],\n frozenSlugs: Set<string>,\n): Promise<void> {\n for (const result of results) {\n if (result.concepts.length > 0) continue;\n\n output.status(\"!\", output.warn(`${result.sourceFile}: no concepts — will retry.`));\n const currentState = await readState(root);\n const oldConcepts = currentState.sources[result.sourceFile]?.concepts ?? [];\n for (const slug of oldConcepts) frozenSlugs.add(slug);\n\n await updateSourceState(root, result.sourceFile, {\n hash: \"\",\n concepts: oldConcepts,\n compiledAt: new Date().toISOString(),\n });\n }\n}\n","/**\n * Orphan management for deleted source files.\n *\n * When a source is deleted, its exclusively-owned concept pages are marked\n * orphaned (orphaned: true in frontmatter). Shared concepts are preserved\n * to avoid losing combined content from prior compilations.\n *\n * After compilation, frozen slugs (shared concepts that lost a contributor)\n * are checked against the updated state. Any that lost ALL owners are\n * orphaned as a cleanup pass.\n */\n\nimport path from \"path\";\nimport { readState, removeSourceState } from \"../utils/state.js\";\nimport {\n atomicWrite,\n safeReadFile,\n parseFrontmatter,\n} from \"../utils/markdown.js\";\nimport { findSharedConcepts } from \"./deps.js\";\nimport * as output from \"../utils/output.js\";\nimport { CONCEPTS_DIR } from \"../utils/constants.js\";\n\n/**\n * Mark wiki pages as orphaned when their source is deleted.\n * Only orphans concepts exclusively owned by the deleted source.\n * Shared concepts (contributed to by other live sources) are preserved\n * as-is to avoid losing combined content from prior compilations.\n */\nexport async function markOrphaned(\n root: string,\n sourceFile: string,\n state: Awaited<ReturnType<typeof readState>>,\n): Promise<void> {\n const sourceEntry = state.sources[sourceFile];\n if (!sourceEntry) return;\n\n const sharedSlugs = findSharedConcepts(sourceFile, state);\n\n for (const slug of sourceEntry.concepts) {\n if (sharedSlugs.has(slug)) {\n output.status(\"i\", output.dim(`Kept: ${slug}.md (shared with other sources)`));\n continue;\n }\n\n await orphanPage(root, slug, \"source deleted\");\n }\n\n await removeSourceState(root, sourceFile);\n}\n\n/**\n * Check frozen slugs against the updated state after compilation.\n * If no source still claims a frozen slug, orphan its page so it doesn't\n * linger as an untracked stale file.\n */\nexport async function orphanUnownedFrozenPages(\n root: string,\n frozenSlugs: Set<string>,\n): Promise<void> {\n const currentState = await readState(root);\n const ownedSlugs = new Set<string>();\n for (const entry of Object.values(currentState.sources)) {\n for (const slug of entry.concepts) ownedSlugs.add(slug);\n }\n\n for (const slug of frozenSlugs) {\n if (ownedSlugs.has(slug)) continue;\n await orphanPage(root, slug, \"no remaining sources\");\n }\n}\n\n/**\n * Mark a single concept page as orphaned if it exists and isn't already marked.\n * @param root - Project root directory.\n * @param slug - Concept slug to orphan.\n * @param reason - Human-readable reason for the log message.\n */\nasync function orphanPage(root: string, slug: string, reason: string): Promise<void> {\n const pagePath = path.join(root, CONCEPTS_DIR, `${slug}.md`);\n const content = await safeReadFile(pagePath);\n if (!content) return;\n\n const { meta } = parseFrontmatter(content);\n if (meta.orphaned === true) return;\n\n const updated = content.replace(\"---\\n\", \"---\\norphaned: true\\n\");\n await atomicWrite(pagePath, updated);\n output.status(\"⚠\", output.warn(`Orphaned: ${slug}.md (${reason})`));\n}\n","/**\n * Interlink resolution for wiki pages.\n *\n * Rule-based (not LLM-based) pass that scans wiki pages for concept title\n * mentions and wraps them in [[slug|Title]] wikilinks. The piped alias form\n * keeps Obsidian link resolution stable when a page's filename (slug) differs\n * from its display title.\n *\n * Complexity: O(changed * total) per incremental compile.\n * Full recompile degrades to O(total^2).\n */\n\nimport { readdir, readFile } from \"fs/promises\";\nimport path from \"path\";\nimport { existsSync } from \"fs\";\nimport { atomicWrite, parseFrontmatter } from \"../utils/markdown.js\";\nimport { CONCEPTS_DIR } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\n\ninterface PageInfo {\n slug: string;\n title: string;\n filePath: string;\n}\n\n/** Build an index of all wiki page titles from the concepts directory. */\nasync function buildTitleIndex(root: string): Promise<PageInfo[]> {\n const conceptsDir = path.join(root, CONCEPTS_DIR);\n if (!existsSync(conceptsDir)) return [];\n\n const files = await readdir(conceptsDir);\n const pages: PageInfo[] = [];\n\n for (const file of files) {\n if (!file.endsWith(\".md\")) continue;\n\n const filePath = path.join(conceptsDir, file);\n const content = await readFile(filePath, \"utf-8\");\n const { meta } = parseFrontmatter(content);\n\n if (meta.title && typeof meta.title === \"string\" && !meta.orphaned) {\n pages.push({\n slug: file.replace(/\\.md$/, \"\"),\n title: meta.title,\n filePath,\n });\n }\n }\n\n return pages;\n}\n\n/** Check if a position is inside an existing [[wikilink]]. */\nfunction isInsideWikilink(text: string, position: number): boolean {\n const before = text.lastIndexOf(\"[[\", position);\n const after = text.indexOf(\"]]\", position);\n if (before === -1 || after === -1) return false;\n\n const closeBefore = text.indexOf(\"]]\", before);\n return closeBefore >= position;\n}\n\n/** Check if a position is inside a ^[...] citation marker. */\nfunction isInsideCitation(text: string, position: number): boolean {\n const before = text.lastIndexOf(\"^[\", position);\n const after = text.indexOf(\"]\", position);\n if (before === -1 || after === -1) return false;\n\n const closeBefore = text.indexOf(\"]\", before);\n return closeBefore >= position;\n}\n\n/** Check if a match is at a word boundary. */\nfunction isWordBoundary(text: string, start: number, end: number): boolean {\n const before = start === 0 || /[\\s,.:;!?()\\[\\]{}/\"']/.test(text[start - 1]);\n const after = end >= text.length || /[\\s,.:;!?()\\[\\]{}/\"']/.test(text[end]);\n return before && after;\n}\n\n/** Find all regex matches for a title in the text, returned as position spans. */\nfunction findTitleMatches(text: string, title: string): { start: number; end: number }[] {\n const escaped = title.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regex = new RegExp(escaped, \"gi\");\n const matches: { start: number; end: number }[] = [];\n let match;\n\n while ((match = regex.exec(text)) !== null) {\n matches.push({ start: match.index, end: match.index + match[0].length });\n }\n\n return matches;\n}\n\n/** Determine whether a match position is eligible for wikilink insertion. */\nfunction isLinkablePosition(text: string, start: number, end: number): boolean {\n if (isInsideWikilink(text, start)) return false;\n if (isInsideCitation(text, start)) return false;\n return isWordBoundary(text, start, end);\n}\n\n/**\n * Add [[wikilinks]] to a page's body for any title mentions.\n * Skips already-linked text and non-word-boundary matches.\n */\nfunction addWikilinks(body: string, titles: PageInfo[], selfTitle: string): string {\n let result = body;\n const selfLower = selfTitle.toLowerCase();\n\n for (const page of titles) {\n if (page.title.toLowerCase() === selfLower) continue;\n\n const matches = findTitleMatches(result, page.title);\n\n // Process matches in reverse to preserve positions\n for (const m of matches.reverse()) {\n if (!isLinkablePosition(result, m.start, m.end)) continue;\n result = result.slice(0, m.start) + `[[${page.slug}|${page.title}]]` + result.slice(m.end);\n }\n }\n\n return result;\n}\n\n/**\n * Run interlink resolution on changed and affected pages.\n *\n * Two passes:\n * 1. Outbound: changed pages get [[wikilinks]] for any title they mention.\n * 2. Inbound: ALL pages get scanned for mentions of newly created titles.\n * This ensures existing pages link to new concepts without a full recompile.\n *\n * Complexity: O(changed * total) for outbound, O(newTitles * total) for inbound.\n */\nexport async function resolveLinks(\n root: string,\n changedSlugs: string[],\n newSlugs: string[],\n): Promise<number> {\n const titleIndex = await buildTitleIndex(root);\n if (titleIndex.length === 0) return 0;\n\n let linkCount = 0;\n\n // Pass 1: outbound links on changed pages\n linkCount += await resolveOutboundLinks(titleIndex, changedSlugs);\n\n // Pass 2: inbound links on all pages for new titles\n linkCount += await resolveInboundLinks(titleIndex, newSlugs);\n\n if (linkCount > 0) {\n output.status(\"🔗\", output.dim(`Resolved links in ${linkCount} page(s)`));\n }\n\n return linkCount;\n}\n\n/** Add outbound [[wikilinks]] to changed pages for any title they mention. */\nasync function resolveOutboundLinks(\n titleIndex: PageInfo[],\n changedSlugs: string[],\n): Promise<number> {\n let count = 0;\n\n for (const page of titleIndex) {\n if (!changedSlugs.includes(page.slug)) continue;\n const didLink = await linkPage(page, titleIndex);\n if (didLink) count++;\n }\n\n return count;\n}\n\n/** Scan ALL pages for mentions of newly created concept titles. */\nasync function resolveInboundLinks(\n titleIndex: PageInfo[],\n newSlugs: string[],\n): Promise<number> {\n if (newSlugs.length === 0) return 0;\n\n const newTitles = titleIndex.filter((p) => newSlugs.includes(p.slug));\n if (newTitles.length === 0) return 0;\n\n let count = 0;\n\n for (const page of titleIndex) {\n // Skip pages that were already processed in outbound pass\n if (newSlugs.includes(page.slug)) continue;\n\n const content = await readFile(page.filePath, \"utf-8\");\n const { body } = parseFrontmatter(content);\n const linked = addWikilinks(body, newTitles, page.title);\n\n if (linked !== body) {\n const newContent = content.replace(body, linked);\n await atomicWrite(page.filePath, newContent);\n count++;\n }\n }\n\n return count;\n}\n\n/** Add wikilinks to a single page, writing atomically if changed. */\nasync function linkPage(page: PageInfo, titleIndex: PageInfo[]): Promise<boolean> {\n const content = await readFile(page.filePath, \"utf-8\");\n const { body } = parseFrontmatter(content);\n const linked = addWikilinks(body, titleIndex, page.title);\n\n if (linked === body) return false;\n\n const newContent = content.replace(body, linked);\n await atomicWrite(page.filePath, newContent);\n return true;\n}\n","/**\n * Wiki index generator.\n *\n * Scans all concept pages in wiki/concepts/, extracts frontmatter metadata,\n * and produces wiki/index.md with a sorted list of all concepts and their\n * summaries. Used after each compilation pass.\n */\n\nimport { readdir } from \"fs/promises\";\nimport path from \"path\";\nimport { atomicWrite, safeReadFile, parseFrontmatter } from \"../utils/markdown.js\";\nimport { CONCEPTS_DIR, QUERIES_DIR, INDEX_FILE } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\nimport type { PageSummary } from \"../utils/types.js\";\n\n/**\n * Generate the wiki/index.md listing all concept pages with summaries.\n * @param root - Project root directory.\n */\nexport async function generateIndex(root: string): Promise<void> {\n output.status(\"*\", output.info(\"Generating index...\"));\n\n const conceptsPath = path.join(root, CONCEPTS_DIR);\n const queriesPath = path.join(root, QUERIES_DIR);\n const concepts = await collectPageSummaries(conceptsPath);\n const queries = await collectPageSummaries(queriesPath);\n\n concepts.sort((a, b) => a.title.localeCompare(b.title));\n queries.sort((a, b) => a.title.localeCompare(b.title));\n\n const indexContent = buildIndexContent(concepts, queries);\n const indexPath = path.join(root, INDEX_FILE);\n await atomicWrite(indexPath, indexContent);\n\n const total = concepts.length + queries.length;\n output.status(\"+\", output.success(`Index updated with ${total} pages.`));\n}\n\n/** A scanned page paired with its parsed frontmatter. */\ninterface ScannedPage {\n slug: string;\n meta: Record<string, unknown>;\n}\n\n/**\n * Scan a wiki directory and return every .md page paired with its parsed\n * frontmatter. Read-only utility shared by index generation and the MCP\n * server's status tool.\n * @param dirPath - Absolute path to a wiki page directory.\n * @returns Array of {slug, meta} entries — empty when the directory is missing.\n */\nexport async function scanWikiPages(dirPath: string): Promise<ScannedPage[]> {\n let files: string[];\n try {\n files = await readdir(dirPath);\n } catch {\n return [];\n }\n\n const scanned: ScannedPage[] = [];\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const content = await safeReadFile(path.join(dirPath, file));\n const { meta } = parseFrontmatter(content);\n scanned.push({ slug: file.replace(/\\.md$/, \"\"), meta });\n }\n return scanned;\n}\n\n/**\n * Project a wiki directory into PageSummary entries (excludes orphaned and\n * untitled pages). Built on top of scanWikiPages so the MCP server can share\n * the underlying scan logic without re-reading the directory.\n * @param conceptsPath - Absolute path to wiki/concepts/.\n * @returns Array of page summary objects.\n */\nexport async function collectPageSummaries(\n conceptsPath: string,\n): Promise<PageSummary[]> {\n const scanned = await scanWikiPages(conceptsPath);\n return scanned\n .filter(({ meta }) => meta.title && typeof meta.title === \"string\" && !meta.orphaned)\n .map(({ slug, meta }) => ({\n title: meta.title as string,\n slug,\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n }));\n}\n\n/** Strip [[wikilink]] brackets from text, leaving the inner text intact. */\nfunction stripWikilinks(text: string): string {\n return text.replace(/\\[\\[([^\\]]+)\\]\\]/g, \"$1\");\n}\n\n/**\n * Build the index.md markdown content from page summaries.\n * @param pages - Sorted array of page summaries.\n * @returns Full index.md content string.\n */\nfunction buildIndexContent(concepts: PageSummary[], queries: PageSummary[]): string {\n const lines = [\"# Knowledge Wiki\", \"\", \"## Concepts\", \"\"];\n\n for (const page of concepts) {\n lines.push(`- **[[${page.slug}|${page.title}]]** — ${stripWikilinks(page.summary)}`);\n }\n\n if (queries.length > 0) {\n lines.push(\"\", \"## Saved Queries\", \"\");\n for (const page of queries) {\n lines.push(`- **[[${page.slug}|${page.title}]]** — ${stripWikilinks(page.summary)}`);\n }\n }\n\n const total = concepts.length + queries.length;\n lines.push(\"\");\n lines.push(`_${total} pages | Generated ${new Date().toISOString()}_`);\n lines.push(\"\");\n\n return lines.join(\"\\n\");\n}\n","/**\n * Per-concept prompt-budget enforcement (issue #39).\n *\n * When the same concept is extracted from many overlapping sources, the\n * page-generation prompt would otherwise concatenate every full source\n * — linear in source count — and reliably blow past the LLM provider's\n * context window. This module clips each contributing source's slice to\n * a fair share of a configurable total budget and emits a single warning\n * when truncation kicks in.\n *\n * The fix is deliberately defensive (proportional truncation) rather than\n * smart (semantic ranking / summarisation). It prevents crashes while a\n * deeper retrieval-driven solution is designed.\n */\n\nimport * as output from \"../utils/output.js\";\nimport {\n DEFAULT_PROMPT_BUDGET_CHARS,\n PROMPT_BUDGET_ENV_VAR,\n} from \"../utils/constants.js\";\n\n/** Marker appended to a source slice when it was truncated to fit the budget. */\nconst TRUNCATION_MARKER = \"\\n\\n[…truncated for prompt budget — see #39…]\";\n\n/** A single source's contribution to the combined per-concept content. */\nexport interface SourceSlice {\n /** Source filename (e.g. \"ml-paper.md\") shown as a section header in the prompt. */\n file: string;\n /** Raw extracted source content, before any budgeting. */\n content: string;\n}\n\n/**\n * Resolve the active prompt-budget character cap. Reads the\n * `LLMWIKI_PROMPT_BUDGET_CHARS` env var when present and parseable; falls\n * back to `DEFAULT_PROMPT_BUDGET_CHARS`. Invalid values (non-numeric or\n * <= 0) are ignored so a typo can't accidentally truncate every prompt\n * to nothing.\n */\nexport function resolvePromptBudgetChars(): number {\n const raw = process.env[PROMPT_BUDGET_ENV_VAR];\n if (!raw) return DEFAULT_PROMPT_BUDGET_CHARS;\n const parsed = Number.parseInt(raw, 10);\n if (!Number.isFinite(parsed) || parsed <= 0) return DEFAULT_PROMPT_BUDGET_CHARS;\n return parsed;\n}\n\n/**\n * Combine per-source slices into the single content blob the LLM prompt\n * receives, applying a fair-share budget when the raw total would exceed\n * the configured ceiling. When no truncation is needed the output is\n * byte-identical to the previous unbudgeted concatenation, so existing\n * compile output is unchanged for typical workloads.\n *\n * @param concept - Human-readable concept title (used in the warning only).\n * @param slices - One entry per contributing source, in arrival order.\n * @returns The combined content string suitable for buildPagePrompt.\n */\nexport function buildBudgetedCombinedContent(\n concept: string,\n slices: SourceSlice[],\n): string {\n const budget = resolvePromptBudgetChars();\n const totalRaw = slices.reduce((sum, s) => sum + s.content.length, 0);\n\n if (totalRaw <= budget) {\n return formatSlices(slices);\n }\n\n const perSource = Math.max(1, Math.floor(budget / slices.length));\n warnTruncation(concept, totalRaw, slices.length, perSource, budget);\n\n const trimmed = slices.map((s) =>\n s.content.length > perSource\n ? { ...s, content: s.content.slice(0, perSource) + TRUNCATION_MARKER }\n : s,\n );\n return formatSlices(trimmed);\n}\n\n/**\n * Prepend right-aligned 1-indexed line numbers to each line of source content.\n * Gives the LLM explicit anchors so its ^[file.md:N-M] citations are accurate.\n */\nfunction numberLines(content: string): string {\n const lines = content.split(\"\\n\");\n const width = String(lines.length).length;\n return lines\n .map((line, i) => `${String(i + 1).padStart(width)} | ${line}`)\n .join(\"\\n\");\n}\n\n/** Render the slice list using the same `--- SOURCE: ---` headers the LLM is taught to read. */\nfunction formatSlices(slices: SourceSlice[]): string {\n return slices\n .map((s) => `--- SOURCE: ${s.file} ---\\n\\n${numberLines(s.content)}`)\n .join(\"\\n\\n\");\n}\n\n/** Emit a single, actionable warning when the budget kicks in for a concept. */\nfunction warnTruncation(\n concept: string,\n totalRaw: number,\n sourceCount: number,\n perSource: number,\n budget: number,\n): void {\n output.status(\n \"!\",\n output.warn(\n `Combined source content for \"${concept}\" (${totalRaw.toLocaleString()} chars across ` +\n `${sourceCount} sources) exceeds the ${budget.toLocaleString()}-char prompt budget; ` +\n `truncating each source to ~${perSource.toLocaleString()} chars. ` +\n `Raise via ${PROMPT_BUDGET_ENV_VAR} when running against larger-context models.`,\n ),\n );\n}\n","/**\n * Obsidian integration helpers for the llmwiki knowledge compiler.\n *\n * Provides two capabilities:\n * 1. Enriching wiki page frontmatter with tags and aliases for better\n * Obsidian graph navigation and search.\n * 2. Generating a Map of Content (MOC) page that groups concept pages\n * by tag for easy browsing.\n */\n\nimport { readdir } from \"fs/promises\";\nimport path from \"path\";\nimport { slugify, atomicWrite, safeReadFile, parseFrontmatter } from \"../utils/markdown.js\";\nimport { CONCEPTS_DIR, MOC_FILE } from \"../utils/constants.js\";\n\n/** Minimum word count to generate an abbreviation alias. */\nconst ABBREVIATION_MIN_WORDS = 3;\n\n/** Conjunctions that trigger a word-swap alias. */\nconst SWAP_CONJUNCTIONS = [\" and \", \" or \"];\n\n/**\n * Enrich a frontmatter object with Obsidian-specific tags and aliases.\n * Mutates the frontmatter object in place.\n * @param frontmatter - The frontmatter object to enrich.\n * @param conceptTitle - The human-readable concept title.\n * @param tags - Tags from extraction (may be empty).\n */\nexport function addObsidianMeta(\n frontmatter: Record<string, unknown>,\n conceptTitle: string,\n tags: string[],\n): void {\n frontmatter.tags = tags;\n frontmatter.aliases = generateAliases(conceptTitle);\n}\n\n/**\n * Generate deterministic aliases from a concept title.\n * Produces up to three alias variants:\n * - Slug form (e.g., \"gradient-descent\")\n * - Word-swap around conjunctions (e.g., \"Optimization and Gradient Descent\")\n * - Abbreviation from first letters for 3+ word titles (e.g., \"RAG\")\n * @param title - The concept title to derive aliases from.\n * @returns Array of aliases that differ from the original title.\n */\nfunction generateAliases(title: string): string[] {\n const aliases: string[] = [];\n const slug = slugify(title);\n\n if (slug !== title) {\n aliases.push(slug);\n }\n\n const swapAlias = generateSwapAlias(title);\n if (swapAlias) {\n aliases.push(swapAlias);\n }\n\n const abbreviation = generateAbbreviation(title);\n if (abbreviation) {\n aliases.push(abbreviation);\n }\n\n return aliases;\n}\n\n/**\n * Generate a word-swap alias by reversing parts around a conjunction.\n * E.g., \"Gradient Descent and Optimization\" becomes \"Optimization and Gradient Descent\".\n * @param title - The concept title.\n * @returns The swapped alias, or null if no conjunction found.\n */\nfunction generateSwapAlias(title: string): string | null {\n for (const conjunction of SWAP_CONJUNCTIONS) {\n const index = title.toLowerCase().indexOf(conjunction);\n if (index === -1) continue;\n\n const before = title.slice(0, index);\n const after = title.slice(index + conjunction.length);\n const originalConjunction = title.slice(index, index + conjunction.length);\n return `${after}${originalConjunction}${before}`;\n }\n return null;\n}\n\n/**\n * Generate an abbreviation from first letters of each word for titles with 3+ words.\n * E.g., \"Retrieval Augmented Generation\" becomes \"RAG\".\n * @param title - The concept title.\n * @returns The abbreviation, or null if title has fewer than 3 words.\n */\nfunction generateAbbreviation(title: string): string | null {\n const words = title.split(/\\s+/);\n if (words.length < ABBREVIATION_MIN_WORDS) return null;\n\n const abbreviation = words.map((w) => w[0].toUpperCase()).join(\"\");\n if (abbreviation === title) return null;\n\n return abbreviation;\n}\n\n/**\n * Generate a Map of Content (MOC) page grouping concept pages by tag.\n * Reads all concept pages, extracts their tags from frontmatter, and writes\n * a structured MOC.md with sections per tag and an Uncategorized section.\n * @param root - Project root directory.\n */\nexport async function generateMOC(root: string): Promise<void> {\n const conceptsPath = path.join(root, CONCEPTS_DIR);\n const pages = await loadConceptPages(conceptsPath);\n\n const tagGroups = groupPagesByTag(pages);\n const content = buildMOCContent(tagGroups);\n\n await atomicWrite(path.join(root, MOC_FILE), content);\n}\n\n/** Minimal page info needed for MOC generation. */\ninterface PageInfo {\n slug: string;\n title: string;\n tags: string[];\n}\n\n/**\n * Load all concept pages and extract their title and tags.\n * @param conceptsPath - Absolute path to the concepts directory.\n * @returns Array of page info objects.\n */\nasync function loadConceptPages(conceptsPath: string): Promise<PageInfo[]> {\n let files: string[];\n try {\n files = await readdir(conceptsPath);\n } catch {\n return [];\n }\n\n const pages: PageInfo[] = [];\n for (const file of files) {\n if (!file.endsWith(\".md\")) continue;\n\n const content = await safeReadFile(path.join(conceptsPath, file));\n if (!content) continue;\n\n const { meta } = parseFrontmatter(content);\n if (meta.orphaned) continue;\n\n const slug = file.replace(/\\.md$/, \"\");\n const title = typeof meta.title === \"string\" ? meta.title : slug;\n const tags = Array.isArray(meta.tags) ? (meta.tags as string[]) : [];\n pages.push({ slug, title, tags });\n }\n\n return pages;\n}\n\n/**\n * Group pages by their tags into a map. Pages with no tags go under \"Uncategorized\".\n * @param pages - Array of page info objects.\n * @returns Map of tag name to array of page titles.\n */\nfunction groupPagesByTag(pages: PageInfo[]): Map<string, PageInfo[]> {\n const groups = new Map<string, PageInfo[]>();\n\n for (const page of pages) {\n if (page.tags.length === 0) {\n appendToGroup(groups, \"Uncategorized\", page);\n continue;\n }\n\n for (const tag of page.tags) {\n appendToGroup(groups, tag, page);\n }\n }\n\n return groups;\n}\n\n/** Append a page to a group, creating the group if needed. */\nfunction appendToGroup(groups: Map<string, PageInfo[]>, key: string, page: PageInfo): void {\n const existing = groups.get(key);\n if (existing) {\n existing.push(page);\n } else {\n groups.set(key, [page]);\n }\n}\n\n/**\n * Build the MOC markdown content from grouped pages.\n * @param tagGroups - Map of tag name to array of page titles.\n * @returns Complete MOC markdown string.\n */\nfunction buildMOCContent(tagGroups: Map<string, PageInfo[]>): string {\n const lines: string[] = [\"# Map of Content\", \"\"];\n\n const sortedTags = [...tagGroups.keys()].sort((a, b) => {\n // \"Uncategorized\" always goes last\n if (a === \"Uncategorized\") return 1;\n if (b === \"Uncategorized\") return -1;\n return a.localeCompare(b);\n });\n\n for (const tag of sortedTags) {\n const pages = tagGroups.get(tag) ?? [];\n lines.push(`## ${tag}`, \"\");\n for (const page of pages.sort((a, b) => a.title.localeCompare(b.title))) {\n lines.push(`- [[${page.slug}|${page.title}]]`);\n }\n lines.push(\"\");\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * Embedding-based semantic search utilities.\n *\n * Maintains a persistent store of page and chunk embeddings in\n * .llmwiki/embeddings.json and provides cosine-similarity retrieval so the\n * query command can narrow hundreds of pages down to a small top-K before\n * calling the selection LLM.\n *\n * The store is additive: successful embedding calls update entries; failures\n * degrade gracefully (caller falls back to full-index selection).\n *\n * The store has two on-disk versions:\n * - v1: page-level entries only (legacy; still readable).\n * - v2: page-level entries plus optional chunk-level entries that enable\n * paragraph-precision retrieval, content-hash-aware incremental updates,\n * and reranking before final page selection.\n */\n\nimport { readFile, readdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { getProvider, getActiveProviderName } from \"./provider.js\";\nimport { atomicWrite, safeReadFile, parseFrontmatter } from \"./markdown.js\";\nimport {\n CONCEPTS_DIR,\n QUERIES_DIR,\n EMBEDDINGS_FILE,\n EMBEDDING_TOP_K,\n EMBEDDING_MODELS,\n} from \"./constants.js\";\nimport { hashChunkText, splitIntoChunks } from \"./retrieval.js\";\nimport * as output from \"./output.js\";\n\n/** Current store version; bumped from 1 → 2 when chunk entries were added. */\nconst STORE_VERSION = 2 as const;\n\n/** A single embedded page record. */\nexport interface EmbeddingEntry {\n slug: string;\n title: string;\n summary: string;\n vector: number[];\n updatedAt: string;\n}\n\n/** A single embedded chunk drawn from a page body. */\nexport interface ChunkEmbeddingEntry {\n slug: string;\n title: string;\n chunkIndex: number;\n contentHash: string;\n text: string;\n vector: number[];\n updatedAt: string;\n}\n\n/** Root shape of .llmwiki/embeddings.json. */\nexport interface EmbeddingStore {\n version: 1 | 2;\n model: string;\n dimensions: number;\n entries: EmbeddingEntry[];\n /** Optional in v2 stores; absent in v1 stores. */\n chunks?: ChunkEmbeddingEntry[];\n}\n\n/** A retrievable page record on disk (concepts/ or queries/). */\ninterface PageRecord {\n slug: string;\n title: string;\n summary: string;\n body: string;\n}\n\n/**\n * Cosine similarity between two equal-length vectors.\n * Returns 0 when either vector has zero magnitude (safer than NaN for ranking).\n */\nexport function cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length || a.length === 0) return 0;\n\n let dot = 0;\n let magA = 0;\n let magB = 0;\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n magA += a[i] * a[i];\n magB += b[i] * b[i];\n }\n\n if (magA === 0 || magB === 0) return 0;\n return dot / (Math.sqrt(magA) * Math.sqrt(magB));\n}\n\n/** Return the top-K entries most similar to the query vector, sorted descending. */\nexport function findTopK(\n queryVec: number[],\n store: EmbeddingStore,\n k: number,\n): EmbeddingEntry[] {\n const scored = store.entries.map((entry) => ({\n entry,\n score: cosineSimilarity(queryVec, entry.vector),\n }));\n scored.sort((left, right) => right.score - left.score);\n return scored.slice(0, k).map((item) => item.entry);\n}\n\n/** Score and sort chunk entries by cosine similarity, returning the top-K. */\nexport function findTopKChunks(\n queryVec: number[],\n chunks: ChunkEmbeddingEntry[],\n k: number,\n): Array<{ chunk: ChunkEmbeddingEntry; score: number }> {\n const scored = chunks.map((chunk) => ({\n chunk,\n score: cosineSimilarity(queryVec, chunk.vector),\n }));\n scored.sort((left, right) => right.score - left.score);\n return scored.slice(0, k);\n}\n\n/** Read .llmwiki/embeddings.json, returning null if it does not exist. */\nexport async function readEmbeddingStore(root: string): Promise<EmbeddingStore | null> {\n const filePath = path.join(root, EMBEDDINGS_FILE);\n if (!existsSync(filePath)) return null;\n const raw = await readFile(filePath, \"utf-8\");\n return JSON.parse(raw) as EmbeddingStore;\n}\n\n/** Atomically persist the embedding store. */\nexport async function writeEmbeddingStore(root: string, store: EmbeddingStore): Promise<void> {\n const filePath = path.join(root, EMBEDDINGS_FILE);\n await atomicWrite(filePath, JSON.stringify(store, null, 2));\n}\n\n/**\n * Embed the question, look up top-K matches, and return lightweight page records.\n * Returns [] when no store exists so callers can transparently fall back.\n */\nexport async function findRelevantPages(\n root: string,\n question: string,\n): Promise<Array<{ slug: string; title: string; summary: string }>> {\n const store = await loadActiveStore(root, (s) => s.entries.length > 0);\n if (!store) return [];\n\n const queryVec = await getProvider().embed(question);\n return findTopK(queryVec, store, EMBEDDING_TOP_K).map((entry) => ({\n slug: entry.slug,\n title: entry.title,\n summary: entry.summary,\n }));\n}\n\n/**\n * Look up top-K chunks similar to the question. Returns [] when no chunk-level\n * store exists so callers can fall back to page-level retrieval.\n */\nexport async function findRelevantChunks(\n root: string,\n question: string,\n k: number,\n): Promise<Array<{ chunk: ChunkEmbeddingEntry; score: number }>> {\n const store = await loadActiveStore(root, (s) => Boolean(s.chunks && s.chunks.length > 0));\n if (!store) return [];\n const queryVec = await getProvider().embed(question);\n return findTopKChunks(queryVec, store.chunks ?? [], k);\n}\n\n/**\n * Read the embedding store, returning null when it is missing, empty (per the\n * caller's predicate), or built with a stale model. Centralises the \"is this\n * store usable for semantic lookup right now?\" check.\n */\nasync function loadActiveStore(\n root: string,\n hasContent: (store: EmbeddingStore) => boolean,\n): Promise<EmbeddingStore | null> {\n const store = await readEmbeddingStore(root);\n if (!store || !hasContent(store)) return null;\n const activeModel = resolveEmbeddingModel();\n if (store.model !== activeModel) {\n warnStaleEmbeddingStore(store.model, activeModel);\n return null;\n }\n return store;\n}\n\n/** Scan concepts/ and queries/ directories, returning retrievable pages. */\nasync function collectPageRecords(root: string): Promise<PageRecord[]> {\n const records: PageRecord[] = [];\n for (const dir of [CONCEPTS_DIR, QUERIES_DIR]) {\n const absDir = path.join(root, dir);\n let files: string[];\n try {\n files = await readdir(absDir);\n } catch {\n continue;\n }\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const record = await readPageRecord(absDir, file);\n if (record) records.push(record);\n }\n }\n return records;\n}\n\n/** Parse a single page file into a PageRecord, skipping orphans/untitled pages. */\nasync function readPageRecord(absDir: string, file: string): Promise<PageRecord | null> {\n const content = await safeReadFile(path.join(absDir, file));\n const { meta, body } = parseFrontmatter(content);\n if (meta.orphaned || typeof meta.title !== \"string\") return null;\n return {\n slug: file.replace(/\\.md$/, \"\"),\n title: meta.title,\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n body,\n };\n}\n\n/** Build the text that represents a page in the embedding space. */\nfunction buildEmbeddingText(record: PageRecord): string {\n return record.summary\n ? `${record.title}\\n\\n${record.summary}`\n : record.title;\n}\n\n/**\n * Embed every page in `records` whose slug appears in `slugsToEmbed`,\n * returning the new entries. Failures bubble up to the caller.\n */\nasync function embedPages(\n records: PageRecord[],\n slugsToEmbed: Set<string>,\n): Promise<EmbeddingEntry[]> {\n const provider = getProvider();\n const now = new Date().toISOString();\n const fresh: EmbeddingEntry[] = [];\n\n for (const record of records) {\n if (!slugsToEmbed.has(record.slug)) continue;\n const vector = await provider.embed(buildEmbeddingText(record));\n fresh.push({\n slug: record.slug,\n title: record.title,\n summary: record.summary,\n vector,\n updatedAt: now,\n });\n }\n return fresh;\n}\n\n/** Tracks which (stored, active) model pairs have already been warned about. */\nconst warnedStaleModels = new Set<string>();\n\n/** Warn once per (stored, active) model pair so queries stay quiet on repeat runs. */\nfunction warnStaleEmbeddingStore(storedModel: string, activeModel: string): void {\n const key = `${storedModel}→${activeModel}`;\n if (warnedStaleModels.has(key)) return;\n warnedStaleModels.add(key);\n output.status(\n \"!\",\n output.warn(\n `Embedding store was built with \"${storedModel}\" but active embedding model is \"${activeModel}\". ` +\n `Falling back to full-index selection. Run 'llmwiki compile' to rebuild embeddings.`,\n ),\n );\n}\n\n/** Test-only hook: clear the warned-pair cache so each test sees a fresh warning. */\nexport function resetStaleEmbeddingWarnings(): void {\n warnedStaleModels.clear();\n}\n\n/** Choose the active embedding model name, defaulting to anthropic's voyage model. */\nexport function resolveEmbeddingModel(): string {\n const providerName = getActiveProviderName();\n const configuredModel = process.env.LLMWIKI_EMBEDDING_MODEL?.trim();\n if (configuredModel && (providerName === \"openai\" || providerName === \"ollama\")) {\n return configuredModel;\n }\n return EMBEDDING_MODELS[providerName] ?? EMBEDDING_MODELS.anthropic;\n}\n\n/** Merge fresh embeddings into an existing store, dropping slugs not in liveSlugs. */\nfunction mergeEntries(\n existing: EmbeddingEntry[],\n fresh: EmbeddingEntry[],\n liveSlugs: Set<string>,\n): EmbeddingEntry[] {\n const bySlug = new Map<string, EmbeddingEntry>();\n for (const entry of existing) {\n if (liveSlugs.has(entry.slug)) bySlug.set(entry.slug, entry);\n }\n for (const entry of fresh) {\n bySlug.set(entry.slug, entry);\n }\n return Array.from(bySlug.values());\n}\n\n/**\n * Refresh chunk embeddings for the given pages, reusing existing chunk vectors\n * whose contentHash still matches. Pages absent from `records` are pruned.\n */\nasync function refreshChunkEmbeddings(\n records: PageRecord[],\n existing: ChunkEmbeddingEntry[],\n forceAll: boolean,\n): Promise<ChunkEmbeddingEntry[]> {\n const liveSlugs = new Set(records.map((r) => r.slug));\n const existingByKey = indexChunksByKey(existing.filter((c) => liveSlugs.has(c.slug)));\n const now = new Date().toISOString();\n const fresh: ChunkEmbeddingEntry[] = [];\n\n for (const record of records) {\n const pageChunks = await embedRecordChunks(record, existingByKey, forceAll, now);\n fresh.push(...pageChunks);\n }\n return fresh;\n}\n\n/**\n * Embed (or reuse) every chunk for a single page, in order. Reused chunks have\n * their `title` refreshed so a renamed page propagates to the chunk metadata.\n */\nasync function embedRecordChunks(\n record: PageRecord,\n existingByKey: Map<string, ChunkEmbeddingEntry>,\n forceAll: boolean,\n now: string,\n): Promise<ChunkEmbeddingEntry[]> {\n const provider = getProvider();\n const chunkTexts = splitIntoChunks(record.body);\n const out: ChunkEmbeddingEntry[] = [];\n\n for (let i = 0; i < chunkTexts.length; i++) {\n const text = chunkTexts[i];\n const contentHash = hashChunkText(text);\n const reused = pickReusableChunk(existingByKey, record.slug, i, contentHash, forceAll);\n if (reused) {\n out.push({ ...reused, title: record.title });\n continue;\n }\n const vector = await provider.embed(text);\n out.push({\n slug: record.slug, title: record.title, chunkIndex: i,\n contentHash, text, vector, updatedAt: now,\n });\n }\n return out;\n}\n\n/** Index existing chunks by `${slug}#${chunkIndex}` for O(1) reuse lookup. */\nfunction indexChunksByKey(chunks: ChunkEmbeddingEntry[]): Map<string, ChunkEmbeddingEntry> {\n const byKey = new Map<string, ChunkEmbeddingEntry>();\n for (const chunk of chunks) byKey.set(chunkKey(chunk.slug, chunk.chunkIndex), chunk);\n return byKey;\n}\n\n/** Compose the index key for a chunk lookup. */\nfunction chunkKey(slug: string, chunkIndex: number): string {\n return `${slug}#${chunkIndex}`;\n}\n\n/** Return the existing chunk vector when its hash still matches and reuse is allowed. */\nfunction pickReusableChunk(\n byKey: Map<string, ChunkEmbeddingEntry>,\n slug: string,\n chunkIndex: number,\n contentHash: string,\n forceAll: boolean,\n): ChunkEmbeddingEntry | null {\n if (forceAll) return null;\n const existing = byKey.get(chunkKey(slug, chunkIndex));\n if (!existing) return null;\n return existing.contentHash === contentHash ? existing : null;\n}\n\n/**\n * Re-embed the given changed slugs and prune any entries whose pages no longer\n * exist on disk. Changed slugs not present as live pages are silently skipped.\n */\nexport async function updateEmbeddings(root: string, changedSlugs: string[]): Promise<void> {\n const records = await collectPageRecords(root);\n const liveSlugs = new Set(records.map((r) => r.slug));\n const embeddingModel = resolveEmbeddingModel();\n const existingStore = await readEmbeddingStore(root);\n const modelChanged = Boolean(existingStore && existingStore.model !== embeddingModel);\n const toEmbed = new Set(changedSlugs.filter((slug) => liveSlugs.has(slug)));\n const previousEntries = modelChanged ? [] : existingStore?.entries ?? [];\n const previousChunks = modelChanged ? [] : existingStore?.chunks ?? [];\n\n // Cold start: embed every page so the store is immediately useful.\n // Also treat an empty on-disk store as a cold start so that a project\n // with no ingested pages yet (or a wiped store) gets populated the next\n // time `compile` runs without needing an explicit slug change.\n const isEmptyStore = isStoreEmpty(existingStore);\n if (!existingStore || modelChanged || (isEmptyStore && liveSlugs.size > 0)) {\n for (const record of records) toEmbed.add(record.slug);\n }\n\n if (!shouldRunEmbedding(modelChanged, toEmbed, previousEntries, previousChunks, liveSlugs)) {\n return;\n }\n\n const freshEntries = await embedPages(records, toEmbed);\n const mergedEntries = mergeEntries(previousEntries, freshEntries, liveSlugs);\n const mergedChunks = await refreshChunkEmbeddings(records, previousChunks, modelChanged);\n\n await persistRefreshedStore(root, embeddingModel, mergedEntries, mergedChunks);\n}\n\n/** Persist a freshly merged store and emit a friendly status line. */\nasync function persistRefreshedStore(\n root: string,\n embeddingModel: string,\n entries: EmbeddingEntry[],\n chunks: ChunkEmbeddingEntry[],\n): Promise<void> {\n const dimensions = entries[0]?.vector.length ?? chunks[0]?.vector.length ?? 0;\n const store: EmbeddingStore = {\n version: STORE_VERSION,\n model: embeddingModel,\n dimensions,\n entries,\n chunks,\n };\n await writeEmbeddingStore(root, store);\n output.status(\n \"*\",\n output.dim(`Embeddings updated (${entries.length} pages, ${chunks.length} chunks).`),\n );\n}\n\n/** Return true when a store exists on disk but has neither page nor chunk entries. */\nfunction isStoreEmpty(store: EmbeddingStore | null): boolean {\n if (!store) return false;\n return store.entries.length === 0 && (!store.chunks || store.chunks.length === 0);\n}\n\n/** Decide whether updateEmbeddings has work to do beyond a no-op. */\nfunction shouldRunEmbedding(\n modelChanged: boolean,\n toEmbed: Set<string>,\n previousEntries: EmbeddingEntry[],\n previousChunks: ChunkEmbeddingEntry[],\n liveSlugs: Set<string>,\n): boolean {\n if (modelChanged) return true;\n if (toEmbed.size > 0) return true;\n if (!previousEntries.every((e) => liveSlugs.has(e.slug))) return true;\n if (!previousChunks.every((c) => liveSlugs.has(c.slug))) return true;\n // Cold-start case where we have entries but no chunks yet.\n if (previousEntries.length > 0 && previousChunks.length === 0 && liveSlugs.size > 0) return true;\n return false;\n}\n","/**\n * Chunked retrieval helpers: text splitting, content hashing, and BM25 reranking.\n *\n * The query pipeline relies on these utilities to:\n * 1. Split a wiki page into paragraph-aligned chunks for embedding.\n * 2. Detect unchanged chunks via a stable content hash so embedding refreshes\n * can skip work.\n * 3. Rerank a candidate set with a lightweight BM25 score over chunk text,\n * improving precision over pure cosine similarity for keyword-heavy\n * questions.\n *\n * No network calls happen here — these are deterministic CPU-side helpers\n * that are easy to unit test and safe to invoke from any code path.\n */\n\nimport { createHash } from \"crypto\";\nimport {\n CHUNK_MAX_CHARS,\n CHUNK_MIN_CHARS,\n CHUNK_TARGET_CHARS,\n} from \"./constants.js\";\n\n/** Stable content hash used to detect chunk-level changes between runs. */\nexport function hashChunkText(text: string): string {\n return createHash(\"sha256\").update(text, \"utf8\").digest(\"hex\").slice(0, 16);\n}\n\n/**\n * Split a page body into paragraph-aligned chunks bounded by CHUNK_TARGET_CHARS.\n * Trailing fragments smaller than CHUNK_MIN_CHARS are merged into the previous\n * chunk so we never emit a tiny dangling piece. Paragraphs longer than\n * CHUNK_MAX_CHARS are sentence-split before being added.\n *\n * @param body - Raw page body (frontmatter already stripped).\n * @returns Ordered chunk strings; empty array when body has no usable text.\n */\nexport function splitIntoChunks(body: string): string[] {\n const paragraphs = extractParagraphs(body);\n if (paragraphs.length === 0) return [];\n\n const chunks: string[] = [];\n let buffer = \"\";\n\n for (const paragraph of paragraphs) {\n for (const piece of splitOversizedParagraph(paragraph)) {\n buffer = appendParagraph(buffer, piece, chunks);\n }\n }\n\n if (buffer.length > 0) chunks.push(buffer);\n return mergeTrailingFragment(chunks);\n}\n\n/** Append a paragraph to the buffer, flushing when the target size is exceeded. */\nfunction appendParagraph(buffer: string, paragraph: string, chunks: string[]): string {\n const candidate = buffer ? `${buffer}\\n\\n${paragraph}` : paragraph;\n if (candidate.length <= CHUNK_TARGET_CHARS) return candidate;\n\n if (buffer.length > 0) {\n chunks.push(buffer);\n return paragraph;\n }\n // Single paragraph already exceeds target — emit it as a standalone chunk.\n chunks.push(candidate);\n return \"\";\n}\n\n/**\n * Merge a too-small trailing chunk back into its predecessor for cleaner\n * ranking. We only merge when the combined size would still respect\n * CHUNK_MAX_CHARS — otherwise the tiny tail stays standalone.\n */\nfunction mergeTrailingFragment(chunks: string[]): string[] {\n if (chunks.length < 2) return chunks;\n const last = chunks[chunks.length - 1];\n if (last.length >= CHUNK_MIN_CHARS) return chunks;\n const previous = chunks[chunks.length - 2];\n // +2 covers the \"\\n\\n\" separator length we insert between paragraphs.\n if (previous.length + last.length + 2 > CHUNK_MAX_CHARS) return chunks;\n const merged = chunks.slice(0, -2);\n merged.push(`${previous}\\n\\n${last}`);\n return merged;\n}\n\n/** Strip whitespace-only paragraphs from a markdown body. */\nfunction extractParagraphs(body: string): string[] {\n return body\n .split(/\\n{2,}/)\n .map((p) => p.trim())\n .filter((p) => p.length > 0);\n}\n\n/**\n * Sentence-split a paragraph that exceeds CHUNK_MAX_CHARS so the resulting\n * pieces still respect the upper bound. A single sentence longer than the\n * cap is hard-cut at CHUNK_MAX_CHARS — preferable to dropping content.\n */\nfunction splitOversizedParagraph(paragraph: string): string[] {\n if (paragraph.length <= CHUNK_MAX_CHARS) return [paragraph];\n\n const sentences = paragraph.split(/(?<=[.!?])\\s+/);\n const pieces: string[] = [];\n let buffer = \"\";\n\n for (const sentence of sentences) {\n if ((buffer + \" \" + sentence).length > CHUNK_MAX_CHARS && buffer.length > 0) {\n pieces.push(buffer.trim());\n buffer = sentence;\n } else {\n buffer = buffer ? `${buffer} ${sentence}` : sentence;\n }\n }\n\n if (buffer.length > 0) pieces.push(buffer.trim());\n return pieces.flatMap(hardCut);\n}\n\n/** Hard-cut a string longer than CHUNK_MAX_CHARS into fixed-size pieces. */\nfunction hardCut(text: string): string[] {\n if (text.length <= CHUNK_MAX_CHARS) return [text];\n const pieces: string[] = [];\n for (let start = 0; start < text.length; start += CHUNK_MAX_CHARS) {\n pieces.push(text.slice(start, start + CHUNK_MAX_CHARS));\n }\n return pieces;\n}\n\n/** A scored candidate that the BM25 reranker accepts. */\ninterface RankableCandidate {\n text: string;\n /** Initial similarity score; preserved for debug output and tie-breaking. */\n baseScore: number;\n}\n\n/** Result of a BM25 rerank: original candidate plus the rerank score. */\ninterface RankedCandidate<T extends RankableCandidate> {\n candidate: T;\n score: number;\n}\n\n/**\n * Rerank candidates with BM25 over their `text` field given a free-text query.\n * BM25 is a deterministic keyword-overlap metric that complements semantic\n * similarity well: it boosts chunks that literally mention the query terms.\n *\n * @param query - Natural-language query.\n * @param candidates - Items to rerank; their `baseScore` is used as a tiebreaker.\n * @returns Sorted descending by combined score.\n */\nexport function rerankWithBm25<T extends RankableCandidate>(\n query: string,\n candidates: T[],\n): Array<RankedCandidate<T>> {\n if (candidates.length === 0) return [];\n const queryTerms = tokenize(query);\n if (queryTerms.length === 0) {\n return candidates.map((candidate) => ({ candidate, score: candidate.baseScore }));\n }\n\n const docs = candidates.map((c) => tokenize(c.text));\n const stats = buildCorpusStats(docs);\n return rankByBm25Score(candidates, docs, queryTerms, stats);\n}\n\n/** Rank candidates by combined BM25 + base semantic score. */\nfunction rankByBm25Score<T extends RankableCandidate>(\n candidates: T[],\n docs: string[][],\n queryTerms: string[],\n stats: CorpusStats,\n): Array<RankedCandidate<T>> {\n const scored = candidates.map((candidate, index) => {\n const lexical = bm25Score(queryTerms, docs[index], stats);\n return { candidate, score: lexical + candidate.baseScore * BASE_SCORE_WEIGHT };\n });\n scored.sort((a, b) => b.score - a.score);\n return scored;\n}\n\n/** Tokenise a string into lowercase alphanumeric tokens for BM25. */\nfunction tokenize(text: string): string[] {\n return text.toLowerCase().match(/[a-z0-9]+/g) ?? [];\n}\n\ninterface CorpusStats {\n /** Document frequency per term: how many docs contain the term. */\n docFreq: Map<string, number>;\n /** Average document length across the corpus. */\n avgDocLen: number;\n /** Total document count. */\n totalDocs: number;\n}\n\n/** Precompute BM25 corpus statistics from the tokenised candidate set. */\nfunction buildCorpusStats(docs: string[][]): CorpusStats {\n const docFreq = new Map<string, number>();\n let totalLen = 0;\n for (const tokens of docs) {\n totalLen += tokens.length;\n const unique = new Set(tokens);\n for (const term of unique) docFreq.set(term, (docFreq.get(term) ?? 0) + 1);\n }\n const totalDocs = docs.length;\n const avgDocLen = totalDocs > 0 ? totalLen / totalDocs : 0;\n return { docFreq, avgDocLen, totalDocs };\n}\n\n/** BM25 saturation parameter; higher = slower term-frequency saturation. */\nconst BM25_K1 = 1.5;\n/** BM25 length normalisation strength; 0 disables, 1 is full normalisation. */\nconst BM25_B = 0.75;\n/** How much weight the original semantic score retains in the rerank tie-break. */\nconst BASE_SCORE_WEIGHT = 0.5;\n\n/** Compute BM25 score for one document against a tokenised query. */\nfunction bm25Score(queryTerms: string[], docTokens: string[], stats: CorpusStats): number {\n if (docTokens.length === 0 || stats.totalDocs === 0) return 0;\n const termFreq = countTerms(docTokens);\n const lengthRatio = docTokens.length / (stats.avgDocLen || 1);\n\n let total = 0;\n for (const term of queryTerms) {\n const tf = termFreq.get(term) ?? 0;\n if (tf === 0) continue;\n const idf = idfWeight(stats.docFreq.get(term) ?? 0, stats.totalDocs);\n const numerator = tf * (BM25_K1 + 1);\n const denominator = tf + BM25_K1 * (1 - BM25_B + BM25_B * lengthRatio);\n total += idf * (numerator / denominator);\n }\n return total;\n}\n\n/** BM25 inverse-document-frequency component (Robertson-Spärck Jones form). */\nfunction idfWeight(docFrequency: number, totalDocs: number): number {\n const numerator = totalDocs - docFrequency + 0.5;\n const denominator = docFrequency + 0.5;\n // +1 inside log keeps idf non-negative even when a term appears in every doc.\n return Math.log(1 + numerator / denominator);\n}\n\n/** Count token occurrences in a single document. */\nfunction countTerms(tokens: string[]): Map<string, number> {\n const counts = new Map<string, number>();\n for (const token of tokens) counts.set(token, (counts.get(token) ?? 0) + 1);\n return counts;\n}\n","/**\n * Lint rules for wiki quality checks.\n *\n * Each rule is a function that takes a project root path and returns\n * an array of LintResult diagnostics. Rules perform pure static analysis\n * with no LLM calls — they inspect frontmatter, wikilinks, citations,\n * and file structure to find potential issues.\n */\n\nimport { readdir, readFile } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport {\n isMalformedCitationEntry,\n parseFrontmatter,\n parseProvenanceMetadata,\n safeReadFile,\n slugify,\n splitCitationMarker,\n} from \"../utils/markdown.js\";\nimport {\n CONCEPTS_DIR,\n LOW_CONFIDENCE_THRESHOLD,\n MAX_INFERRED_PARAGRAPHS_WITHOUT_CITATIONS,\n QUERIES_DIR,\n SOURCES_DIR,\n} from \"../utils/constants.js\";\nimport type { LintResult } from \"./types.js\";\nimport {\n countWikilinks,\n resolvePageKind,\n type SchemaConfig,\n} from \"../schema/index.js\";\n\n/** Minimum body length (in characters) for a page to be considered non-empty. */\nconst MIN_BODY_LENGTH = 50;\n\n/** Pattern matching [[Wikilink Title]] references in markdown content. */\nconst WIKILINK_PATTERN = /\\[\\[([^\\]]+)\\]\\]/g;\n\n/** Pattern matching ^[filename.md] citation markers in markdown content. */\nconst CITATION_PATTERN = /\\^\\[([^\\]]+)\\]/g;\n\n/** Match result with its line number and captured group. */\ninterface LineMatch {\n captured: string;\n line: number;\n}\n\n/**\n * Scan all lines of a page's content and return regex matches with line numbers.\n * Shared by rules that need to locate patterns within page bodies.\n */\nfunction findMatchesInContent(content: string, pattern: RegExp): LineMatch[] {\n const results: LineMatch[] = [];\n const lines = content.split(\"\\n\");\n for (let i = 0; i < lines.length; i++) {\n const matches = lines[i].matchAll(pattern);\n for (const match of matches) {\n results.push({ captured: match[1], line: i + 1 });\n }\n }\n return results;\n}\n\n/**\n * Read all .md files from a directory, returning their paths and parsed content.\n * Returns an empty array if the directory does not exist.\n */\nasync function readMarkdownFiles(\n dirPath: string,\n): Promise<Array<{ filePath: string; content: string }>> {\n if (!existsSync(dirPath)) return [];\n\n const entries = await readdir(dirPath);\n const mdFiles = entries.filter((f) => f.endsWith(\".md\"));\n\n const results = await Promise.all(\n mdFiles.map(async (fileName) => {\n const filePath = path.join(dirPath, fileName);\n const content = await readFile(filePath, \"utf-8\");\n return { filePath, content };\n }),\n );\n\n return results;\n}\n\n/**\n * Collect all wiki pages from both concepts/ and queries/ directories.\n */\nexport async function collectAllPages(\n root: string,\n): Promise<Array<{ filePath: string; content: string }>> {\n const conceptPages = await readMarkdownFiles(path.join(root, CONCEPTS_DIR));\n const queryPages = await readMarkdownFiles(path.join(root, QUERIES_DIR));\n return [...conceptPages, ...queryPages];\n}\n\n/**\n * Build a set of slugs for all existing wiki pages.\n * Used to verify that wikilink targets actually exist.\n */\nfunction buildPageSlugSet(\n pages: Array<{ filePath: string }>,\n): Set<string> {\n const slugs = new Set<string>();\n for (const page of pages) {\n const baseName = path.basename(page.filePath, \".md\");\n slugs.add(baseName.toLowerCase());\n }\n return slugs;\n}\n\n/** Find [[Title]] wikilinks that don't match any existing wiki page. */\nexport async function checkBrokenWikilinks(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const existingSlugs = buildPageSlugSet(pages);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n for (const { captured, line } of findMatchesInContent(page.content, WIKILINK_PATTERN)) {\n const linkTarget = captured.split(\"|\")[0].trim();\n const linkSlug = slugify(linkTarget);\n if (!existingSlugs.has(linkSlug)) {\n results.push({\n rule: \"broken-wikilink\",\n severity: \"error\",\n file: page.filePath,\n message: `Broken wikilink [[${captured}]] — no matching page found`,\n line,\n });\n }\n }\n }\n\n return results;\n}\n\n/** Find pages with `orphaned: true` in their frontmatter. */\nexport async function checkOrphanedPages(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n if (meta.orphaned === true) {\n results.push({\n rule: \"orphaned-page\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page is marked as orphaned`,\n });\n }\n }\n\n return results;\n}\n\n/** Find pages with empty or missing `summary` in frontmatter. */\nexport async function checkMissingSummaries(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n const summary = meta.summary;\n const isMissing = !summary || (typeof summary === \"string\" && summary.trim() === \"\");\n\n if (isMissing) {\n results.push({\n rule: \"missing-summary\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page has no summary in frontmatter`,\n });\n }\n }\n\n return results;\n}\n\n/** Find multiple pages whose titles match case-insensitively. */\nexport async function checkDuplicateConcepts(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const titleMap = new Map<string, string[]>();\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n const title = typeof meta.title === \"string\" ? meta.title : \"\";\n if (!title) continue;\n\n const normalizedTitle = title.toLowerCase().trim();\n const existing = titleMap.get(normalizedTitle) ?? [];\n existing.push(page.filePath);\n titleMap.set(normalizedTitle, existing);\n }\n\n const results: LintResult[] = [];\n for (const [title, files] of titleMap) {\n if (files.length <= 1) continue;\n for (const file of files) {\n results.push({\n rule: \"duplicate-concept\",\n severity: \"error\",\n file,\n message: `Duplicate title \"${title}\" — also in ${files.filter((f) => f !== file).join(\", \")}`,\n });\n }\n }\n\n return results;\n}\n\n/** Find pages with frontmatter but very short or empty body content. */\nexport async function checkEmptyPages(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta, body } = parseFrontmatter(page.content);\n const hasTitle = typeof meta.title === \"string\" && meta.title.trim() !== \"\";\n const isBodyEmpty = body.trim().length < MIN_BODY_LENGTH;\n\n if (hasTitle && isBodyEmpty) {\n results.push({\n rule: \"empty-page\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page body is empty or too short (< ${MIN_BODY_LENGTH} chars)`,\n });\n }\n }\n\n return results;\n}\n\n/** Strip an optional `:start-end` or `#Lstart-Lend` span suffix from a citation entry. */\nfunction stripSpanSuffix(entry: string): string {\n const colonIdx = entry.indexOf(\":\");\n const hashIdx = entry.indexOf(\"#\");\n const cuts = [colonIdx, hashIdx].filter((i) => i >= 0);\n if (cuts.length === 0) return entry;\n return entry.slice(0, Math.min(...cuts));\n}\n\n/**\n * Flag pages whose frontmatter declares confidence below the threshold.\n * Pages without a confidence field are silently skipped to preserve\n * backward-compatibility with pre-existing wikis.\n */\nexport async function checkLowConfidencePages(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n const { confidence } = parseProvenanceMetadata(meta);\n if (confidence === undefined || confidence >= LOW_CONFIDENCE_THRESHOLD) continue;\n results.push({\n rule: \"low-confidence\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page confidence ${confidence.toFixed(2)} is below ${LOW_CONFIDENCE_THRESHOLD}`,\n });\n }\n\n return results;\n}\n\n/** Flag pages whose frontmatter records contradictions with other pages. */\nexport async function checkContradictedPages(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { meta } = parseFrontmatter(page.content);\n const { contradictedBy } = parseProvenanceMetadata(meta);\n if (!contradictedBy || contradictedBy.length === 0) continue;\n const slugs = contradictedBy.map((r) => r.slug).join(\", \");\n results.push({\n rule: \"contradicted-page\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page contradicts: ${slugs}`,\n });\n }\n\n return results;\n}\n\n/**\n * Flag pages with too many inferred paragraphs unsupported by direct\n * citations. Always derived from the rendered page body — the body is\n * the single source of truth, no metadata field is consulted. Earlier\n * versions trusted an LLM-estimated `inferredParagraphs` frontmatter\n * field, but that estimate was made before the page even existed and\n * routinely disagreed with what the model actually produced. Counting\n * uncited prose paragraphs in the rendered body matches what a\n * reviewer would see and survives hand-edits.\n */\nexport async function checkInferredWithoutCitations(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n\n for (const page of pages) {\n const { body } = parseFrontmatter(page.content);\n const inferred = countUncitedProseParagraphs(body);\n if (inferred <= MAX_INFERRED_PARAGRAPHS_WITHOUT_CITATIONS) continue;\n results.push({\n rule: \"excess-inferred-paragraphs\",\n severity: \"warning\",\n file: page.filePath,\n message: `Page has ${inferred} inferred paragraphs without citations (max ${MAX_INFERRED_PARAGRAPHS_WITHOUT_CITATIONS})`,\n });\n }\n\n return results;\n}\n\n/**\n * Match a paragraph that looks like prose (not a heading, list, or code\n * block). Uses the Unicode `Letter` property so non-ASCII pages\n * generated via `--lang Chinese`, `--lang Japanese`, etc. (#46) are\n * still detected — the previous `[A-Za-z]` form silently dropped CJK,\n * Cyrillic, Greek, and Arabic prose, leaving\n * `excess-inferred-paragraphs` blind on those pages.\n */\nconst PROSE_PARAGRAPH_LEAD = /^\\p{L}/u;\n\n/** Count prose paragraphs in a body that lack a ^[citation] marker. */\nfunction countUncitedProseParagraphs(body: string): number {\n const paragraphs = body.split(/\\n\\s*\\n/);\n let count = 0;\n for (const block of paragraphs) {\n const trimmed = block.trim();\n if (trimmed.length === 0) continue;\n if (!PROSE_PARAGRAPH_LEAD.test(trimmed)) continue;\n if (CITATION_PATTERN.test(trimmed)) {\n CITATION_PATTERN.lastIndex = 0;\n continue;\n }\n CITATION_PATTERN.lastIndex = 0;\n count += 1;\n }\n return count;\n}\n\n/** Regex matching the `:start-end` span suffix on a citation entry. */\nconst COLON_SPAN_PATTERN = /^[^:#]+:(\\d+)(?:[,-]\\s*(\\d+))?$/;\n\n/** Regex matching the `#Lstart-Lend` span suffix on a citation entry. */\nconst HASH_SPAN_PATTERN = /^[^:#]+#L(\\d+)(?:-L(\\d+))?$/;\n\n/** Parsed line range from a citation entry, or null if no range is present. */\ninterface ParsedLineRange {\n start: number;\n end: number;\n}\n\n/**\n * Enforce per-kind cross-link minimums declared in the schema.\n * For each page, resolve its kind, look up the rule, and warn when the page\n * body has fewer wikilinks than the rule requires. Pages with kind `concept`\n * and a minimum of 0 (the default) generate no diagnostics, so existing\n * projects without a schema file see no behaviour change.\n *\n * Implementation delegates to {@link checkPageCrossLinks} per page so the\n * actual rule logic lives in exactly one place — the on-disk walker just\n * fans the per-page helper across `collectAllPages`.\n * @param root - Project root directory.\n * @param schema - Resolved schema config.\n */\nexport async function checkSchemaCrossLinks(\n root: string,\n schema: SchemaConfig,\n): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n for (const page of pages) {\n results.push(...checkPageCrossLinks(page.content, page.filePath, schema));\n }\n return results;\n}\n\n/**\n * Check cross-link minimums for a single page given as a raw content string.\n *\n * Unlike `checkSchemaCrossLinks`, this function operates on content already in\n * memory without reading from disk. Used by the review pipeline to attach\n * schema violations to a candidate at write time so `review show` can surface\n * them before the reviewer approves the page.\n *\n * The `filePath` parameter is embedded verbatim in each `LintResult.file` so\n * callers control how the candidate is identified in diagnostic output.\n *\n * @param content - Full page content including frontmatter.\n * @param filePath - Logical file path to embed in diagnostics (may be virtual).\n * @param schema - Resolved schema config.\n * @returns Lint results for this single page, empty when no violations found.\n */\nexport function checkPageCrossLinks(\n content: string,\n filePath: string,\n schema: SchemaConfig,\n): LintResult[] {\n const { meta, body } = parseFrontmatter(content);\n const kind = resolvePageKind(meta.kind, schema);\n const rule = schema.kinds[kind];\n if (rule.minWikilinks <= 0) return [];\n\n const linkCount = countWikilinks(body);\n if (linkCount >= rule.minWikilinks) return [];\n\n return [\n {\n rule: \"schema-cross-link-minimum\",\n severity: \"warning\",\n file: filePath,\n message:\n `Page kind \"${kind}\" requires at least ${rule.minWikilinks} ` +\n `[[wikilinks]] but only ${linkCount} found.`,\n },\n ];\n}\n\n/** Extract the line range from a citation entry string, or return null if there is none. */\nfunction parseLineRange(entry: string): ParsedLineRange | null {\n const colonMatch = COLON_SPAN_PATTERN.exec(entry);\n if (colonMatch) {\n const start = Number(colonMatch[1]);\n const end = colonMatch[2] !== undefined ? Number(colonMatch[2]) : start;\n return { start, end };\n }\n const hashMatch = HASH_SPAN_PATTERN.exec(entry);\n if (hashMatch) {\n const start = Number(hashMatch[1]);\n const end = hashMatch[2] !== undefined ? Number(hashMatch[2]) : start;\n return { start, end };\n }\n return null;\n}\n\n/** Count the number of lines in a file's text content. */\nfunction countLines(content: string): number {\n if (content.length === 0) return 0;\n return content.split(\"\\n\").length;\n}\n\n/**\n * Find ^[filename.md] citations referencing source files that don't exist, and\n * flag claim-level spans whose line ranges exceed the source file's actual length.\n * Handles both single-source ^[file.md] and multi-source ^[a.md, b.md] forms,\n * plus the claim-level extension `^[file.md:42-58]` / `^[file.md#L42-L58]`.\n * Line counts are cached per source file to avoid redundant reads.\n */\nexport async function checkBrokenCitations(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const sourcesDir = path.join(root, SOURCES_DIR);\n const results: LintResult[] = [];\n const lineCountCache = new Map<string, number>();\n\n for (const page of pages) {\n const pageFindings = await checkPageBrokenCitations(\n page.content,\n page.filePath,\n sourcesDir,\n lineCountCache,\n );\n results.push(...pageFindings);\n }\n\n return results;\n}\n\n/**\n * Pure-body variant of {@link checkBrokenCitations} that inspects a single\n * page's content against an in-memory or on-disk sources directory. Used\n * by the on-disk lint walker above, and by the in-memory candidate-lint\n * path so `compile --review` surfaces broken-source-file and out-of-bounds\n * span findings before a reviewer approves the candidate.\n *\n * @param content - Full page markdown including frontmatter.\n * @param filePath - Logical path embedded in diagnostics (may be virtual).\n * @param sourcesDir - Absolute path to the project's sources/ directory.\n * @param lineCountCache - Optional cross-page cache; provide one when\n * linting many pages so source file line counts aren't re-read.\n */\nexport async function checkPageBrokenCitations(\n content: string,\n filePath: string,\n sourcesDir: string,\n lineCountCache: Map<string, number> = new Map(),\n): Promise<LintResult[]> {\n const results: LintResult[] = [];\n for (const { captured, line } of findMatchesInContent(content, CITATION_PATTERN)) {\n await collectBrokenForMarker(captured, line, filePath, sourcesDir, lineCountCache, results);\n }\n return results;\n}\n\n/** Append broken-citation diagnostics for every entry inside a single ^[...] marker. */\nasync function collectBrokenForMarker(\n captured: string,\n line: number,\n pageFile: string,\n sourcesDir: string,\n lineCountCache: Map<string, number>,\n out: LintResult[],\n): Promise<void> {\n for (const part of splitCitationMarker(captured)) {\n const trimmed = part.trim();\n if (trimmed.length === 0) continue;\n const filename = stripSpanSuffix(trimmed);\n const citedPath = path.join(sourcesDir, filename);\n if (!existsSync(citedPath)) {\n out.push({\n rule: \"broken-citation\",\n severity: \"error\",\n file: pageFile,\n message: `Broken citation ^[${filename}] — source file not found`,\n line,\n });\n continue;\n }\n const range = parseLineRange(trimmed);\n if (range === null) continue;\n const lineCount = await resolveLineCount(citedPath, filename, lineCountCache);\n if (range.end <= lineCount) continue;\n out.push({\n rule: \"broken-citation\",\n severity: \"error\",\n file: pageFile,\n message: `Claim-level span ^[${trimmed}] is out of bounds (source has only ${lineCount} lines)`,\n line,\n });\n }\n}\n\n/** Return the line count for a source file, reading and caching if necessary. */\nasync function resolveLineCount(\n citedPath: string,\n filename: string,\n cache: Map<string, number>,\n): Promise<number> {\n const cached = cache.get(filename);\n if (cached !== undefined) return cached;\n const content = await safeReadFile(citedPath);\n const lineCount = countLines(content);\n cache.set(filename, lineCount);\n return lineCount;\n}\n\n/**\n * Find ^[...] markers whose entries do not parse against the documented\n * paragraph or claim-level grammar (e.g. `^[file.md:abc]` or `^[file.md#X]`).\n * Detects malformed claim-level citations without breaking the paragraph form.\n */\nexport async function checkMalformedClaimCitations(root: string): Promise<LintResult[]> {\n const pages = await collectAllPages(root);\n const results: LintResult[] = [];\n for (const page of pages) {\n results.push(...checkPageMalformedCitations(page.content, page.filePath));\n }\n return results;\n}\n\n/**\n * Pure-body variant of {@link checkMalformedClaimCitations} that inspects\n * a single page's content. Used by both the on-disk lint walker above and\n * the in-memory candidate-lint path so `compile --review` surfaces\n * malformed claim citations before a reviewer approves the candidate.\n */\nexport function checkPageMalformedCitations(content: string, filePath: string): LintResult[] {\n const results: LintResult[] = [];\n for (const { captured, line } of findMatchesInContent(content, CITATION_PATTERN)) {\n for (const part of splitCitationMarker(captured)) {\n if (!isMalformedCitationEntry(part)) continue;\n results.push({\n rule: \"malformed-claim-citation\",\n severity: \"error\",\n file: filePath,\n message: `Malformed claim citation ^[${captured}] — expected file.md, file.md:N-N, or file.md#LN-LN`,\n line,\n });\n }\n }\n return results;\n}\n","/**\n * Wiki page rendering for the llmwiki compile pipeline.\n *\n * Encapsulates the single-page generation step: gather related pages, call\n * the LLM, build frontmatter, and produce the final markdown blob. Splitting\n * this away from the orchestrator (`compiler/index.ts`) keeps the orchestrator\n * focused on phase sequencing and lets the review-candidate code path reuse\n * the exact same renderer used for direct writes.\n */\n\nimport { readdir } from \"fs/promises\";\nimport path from \"path\";\nimport {\n buildFrontmatter,\n parseFrontmatter,\n safeReadFile,\n} from \"../utils/markdown.js\";\nimport { callClaude } from \"../utils/llm.js\";\nimport { buildPagePrompt } from \"./prompts.js\";\nimport { addObsidianMeta } from \"./obsidian.js\";\nimport { addProvenanceMeta, reportContradictionWarnings } from \"./provenance.js\";\nimport { CONCEPTS_DIR } from \"../utils/constants.js\";\nimport type { SchemaConfig } from \"../schema/index.js\";\nimport type { ExtractedConcept } from \"../utils/types.js\";\n\n/** Maximum number of existing concept pages to include as cross-reference context. */\nconst RELATED_PAGE_CONTEXT_LIMIT = 5;\n\n/** A merged-concept input from the orchestrator (multiple sources merged into one). */\ninterface RenderableConcept {\n slug: string;\n concept: ExtractedConcept;\n sourceFiles: string[];\n combinedContent: string;\n}\n\n/**\n * Render a wiki page (frontmatter + body) for a merged concept by calling\n * the LLM with cross-referencing context from existing concept pages.\n * @param root - Project root directory.\n * @param entry - The merged concept to render.\n * @param schema - Resolved schema config, used to stamp `kind` on frontmatter.\n * @returns Full markdown content (frontmatter + body, trailing newline).\n */\nexport async function renderMergedPageContent(\n root: string,\n entry: RenderableConcept,\n schema: SchemaConfig,\n): Promise<string> {\n const pagePath = path.join(root, CONCEPTS_DIR, `${entry.slug}.md`);\n const existingPage = await safeReadFile(pagePath);\n const relatedPages = await loadRelatedPages(root, entry.slug);\n\n const system = buildPagePrompt(\n entry.concept.concept,\n entry.combinedContent,\n existingPage,\n relatedPages,\n );\n\n const pageBody = await callClaude({\n system,\n messages: [\n { role: \"user\", content: `Write the wiki page for \"${entry.concept.concept}\".` },\n ],\n });\n\n const frontmatter = buildMergedFrontmatter(entry, existingPage, schema);\n reportContradictionWarnings(entry.concept.concept, entry.concept);\n return `${frontmatter}\\n\\n${pageBody}\\n`;\n}\n\n/**\n * Construct the frontmatter block for a merged concept, preserving createdAt\n * and stamping the `kind` field from the schema's default kind.\n */\nfunction buildMergedFrontmatter(\n entry: RenderableConcept,\n existingPage: string,\n schema: SchemaConfig,\n): string {\n const now = new Date().toISOString();\n const existing = existingPage ? parseFrontmatter(existingPage) : null;\n const createdAt = (existing?.meta.createdAt && typeof existing.meta.createdAt === \"string\")\n ? existing.meta.createdAt\n : now;\n const frontmatterFields: Record<string, unknown> = {\n title: entry.concept.concept,\n summary: entry.concept.summary,\n sources: entry.sourceFiles,\n kind: schema.defaultKind,\n createdAt,\n updatedAt: now,\n };\n addObsidianMeta(frontmatterFields, entry.concept.concept, entry.concept.tags ?? []);\n addProvenanceMeta(frontmatterFields, entry.concept);\n return buildFrontmatter(frontmatterFields);\n}\n\n/**\n * Load related wiki pages to provide cross-referencing context.\n * Returns concatenated content of up to RELATED_PAGE_CONTEXT_LIMIT pages.\n * @param root - Project root directory.\n * @param excludeSlug - Slug of the current page to exclude.\n * @returns Concatenated related page contents (empty when concepts dir is missing).\n */\nasync function loadRelatedPages(root: string, excludeSlug: string): Promise<string> {\n const conceptsPath = path.join(root, CONCEPTS_DIR);\n let files: string[];\n\n try {\n files = await readdir(conceptsPath);\n } catch {\n return \"\";\n }\n\n const related = files\n .filter((f) => f.endsWith(\".md\") && f !== `${excludeSlug}.md`)\n .slice(0, RELATED_PAGE_CONTEXT_LIMIT);\n\n const contents: string[] = [];\n for (const f of related) {\n const content = await safeReadFile(path.join(conceptsPath, f));\n if (!content) continue;\n const { meta } = parseFrontmatter(content);\n if (meta.orphaned) continue;\n contents.push(content);\n }\n\n return contents.join(\"\\n\\n---\\n\\n\");\n}\n","/**\n * Helpers for surfacing provenance metadata during compilation.\n *\n * Keeps the compile orchestrator small by isolating the logic that copies\n * confidence/contradiction signals from extracted concepts onto wiki page\n * frontmatter and emits compile-time warnings when contradictions are\n * reported.\n */\n\nimport * as output from \"../utils/output.js\";\nimport type { ExtractedConcept } from \"../utils/types.js\";\n\n/**\n * Copy provenance metadata fields from an extracted concept onto the\n * frontmatter record, omitting fields the LLM did not provide so existing\n * pages without these fields stay clean.\n * @param fields - Mutable frontmatter record being assembled for a page.\n * @param concept - Source concept whose provenance metadata to apply.\n */\nexport function addProvenanceMeta(\n fields: Record<string, unknown>,\n concept: ExtractedConcept,\n): void {\n if (typeof concept.confidence === \"number\") {\n fields.confidence = concept.confidence;\n }\n if (concept.provenanceState) {\n fields.provenanceState = concept.provenanceState;\n }\n if (concept.contradictedBy && concept.contradictedBy.length > 0) {\n fields.contradictedBy = concept.contradictedBy;\n }\n}\n\n/**\n * Print a compile-time warning when a concept reports contradictions with\n * other pages. Returns silently when there is nothing to report.\n * @param conceptTitle - Human-readable title of the concept being compiled.\n * @param concept - The extracted concept whose contradictions to surface.\n */\nexport function reportContradictionWarnings(\n conceptTitle: string,\n concept: ExtractedConcept,\n): void {\n const refs = concept.contradictedBy;\n if (!refs || refs.length === 0) return;\n const slugs = refs.map((r) => r.slug).join(\", \");\n output.status(\n \"!\",\n output.warn(`Contradiction reported on \"${conceptTitle}\" — conflicts with: ${slugs}`),\n );\n}\n","/**\n * Commander action for `llmwiki query <question>`.\n * Two-step LLM-powered wiki query that first selects relevant pages from the\n * wiki index, then streams an answer grounded in those pages. Optionally saves\n * the response as a new page in wiki/queries/.\n *\n * Step 1 - Page Selection: Reads wiki/index.md and asks Claude (via tool_use)\n * to pick the most relevant concept pages for the question.\n *\n * Step 2 - Answer Generation: Loads the selected pages in full and streams\n * a cited answer to the terminal.\n */\n\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { callClaude } from \"../utils/llm.js\";\nimport type { LLMTool } from \"../utils/provider.js\";\nimport { atomicWrite, safeReadFile, slugify, buildFrontmatter, parseFrontmatter } from \"../utils/markdown.js\";\nimport { languageDirective } from \"../utils/output-language.js\";\nimport { generateIndex } from \"../compiler/indexgen.js\";\nimport * as output from \"../utils/output.js\";\nimport {\n QUERY_PAGE_LIMIT,\n INDEX_FILE,\n CONCEPTS_DIR,\n QUERIES_DIR,\n CHUNK_TOP_K,\n CHUNK_RERANK_KEEP,\n} from \"../utils/constants.js\";\nimport {\n findRelevantPages,\n findRelevantChunks,\n updateEmbeddings,\n type ChunkEmbeddingEntry,\n} from \"../utils/embeddings.js\";\nimport { rerankWithBm25 } from \"../utils/retrieval.js\";\nimport type { ChunkCitation, QueryResult, RetrievalDebug } from \"../utils/types.js\";\n\n/** Directories to search when loading selected pages, in priority order. */\nconst PAGE_DIRS = [CONCEPTS_DIR, QUERIES_DIR];\n\n/** Tool schema for page selection (provider-agnostic). */\nconst PAGE_SELECTION_TOOL: LLMTool = {\n name: \"select_pages\",\n description: \"Select the most relevant wiki pages to answer a question\",\n input_schema: {\n type: \"object\" as const,\n properties: {\n pages: {\n type: \"array\",\n items: {\n type: \"string\",\n description: \"Slug of a relevant wiki page (e.g. 'llm-knowledge-bases')\",\n },\n maxItems: QUERY_PAGE_LIMIT,\n },\n reasoning: {\n type: \"string\",\n description: \"Brief explanation of why these pages were selected\",\n },\n },\n required: [\"pages\", \"reasoning\"],\n },\n};\n\ninterface PageSelectionResult {\n pages: string[];\n reasoning: string;\n}\n\n/**\n * Select the most relevant wiki pages for a question using Claude tool_use.\n * @param question - The user's natural language question.\n * @param indexContent - The full text of wiki/index.md.\n * @returns Parsed page slugs and reasoning from Claude.\n */\nexport async function selectPages(\n question: string,\n indexContent: string,\n): Promise<PageSelectionResult> {\n const systemPrompt =\n \"You are a knowledge base assistant. Given a question and a wiki index, select the most relevant pages.\";\n\n const userMessage = `Question: ${question}\\n\\nWiki Index:\\n${indexContent}`;\n\n const rawResult = await callClaude({\n system: systemPrompt,\n messages: [{ role: \"user\", content: userMessage }],\n tools: [PAGE_SELECTION_TOOL],\n });\n\n try {\n const parsed = JSON.parse(rawResult);\n return {\n pages: Array.isArray(parsed.pages) ? parsed.pages.filter((p: unknown) => typeof p === \"string\") : [],\n reasoning: typeof parsed.reasoning === \"string\" ? parsed.reasoning : \"No reasoning provided\",\n };\n } catch {\n return { pages: [], reasoning: \"Failed to parse page selection response\" };\n }\n}\n\n/** Render a list of candidate pages in the same bullet format selectPages() consumes. */\nfunction buildFilteredIndex(\n candidates: Array<{ slug: string; title: string; summary: string }>,\n): string {\n return candidates\n .map((entry) => `- **${entry.slug}**: ${entry.title} — ${entry.summary}`)\n .join(\"\\n\");\n}\n\ninterface SelectedPages {\n pages: string[];\n rawPages: string[];\n reasoning: string;\n /** Chunk citations driving the selection — empty when chunk store is absent. */\n chunks: ChunkCitation[];\n /** Debug snapshot of the retrieval pipeline (only populated in debug mode). */\n debug?: RetrievalDebug;\n}\n\n/**\n * Pick relevant pages using a chunk-aware embedding pre-filter when available,\n * falling back to page-level embeddings, then to sending the full wiki index.\n */\nasync function selectRelevantPages(\n root: string,\n question: string,\n debug: boolean,\n): Promise<SelectedPages> {\n const chunkSelection = await trySelectViaChunks(root, question, debug);\n if (chunkSelection) return chunkSelection;\n\n const candidates = await tryFindRelevantPages(root, question);\n\n if (candidates.length > 0) {\n const filteredIndex = buildFilteredIndex(candidates);\n const { pages: rawPages, reasoning } = await selectPages(question, filteredIndex);\n // Tool output holds slugs directly in the semantic path — no slugify needed.\n return { pages: rawPages, rawPages, reasoning, chunks: [] };\n }\n\n const indexContent = await safeReadFile(path.join(root, INDEX_FILE));\n const { pages: rawPages, reasoning } = await selectPages(question, indexContent);\n return { pages: rawPages.map((p) => slugify(p)), rawPages, reasoning, chunks: [] };\n}\n\n/**\n * Attempt chunk-level retrieval + reranking. Returns null when no chunk store\n * is available (caller falls back to page-level retrieval transparently).\n */\nasync function trySelectViaChunks(\n root: string,\n question: string,\n debug: boolean,\n): Promise<SelectedPages | null> {\n const ranked = await tryFindRelevantChunks(root, question);\n if (ranked.length === 0) return null;\n\n const reranked = rerankWithBm25(\n question,\n ranked.map(({ chunk, score }) => ({ text: chunk.text, baseScore: score, chunk })),\n );\n const kept = reranked.slice(0, CHUNK_RERANK_KEEP);\n const reorderingHappened = wasReordered(ranked, kept.map((k) => k.candidate.chunk));\n const chunkCitations = toChunkCitations(kept);\n const pageSlugs = collapseToPages(chunkCitations, QUERY_PAGE_LIMIT);\n const reasoning = buildChunkReasoning(chunkCitations, pageSlugs);\n\n return {\n pages: pageSlugs,\n rawPages: pageSlugs,\n reasoning,\n chunks: chunkCitations,\n debug: debug ? buildDebug(chunkCitations, pageSlugs, reorderingHappened) : undefined,\n };\n}\n\n/** Detect whether reranking actually changed the chunk order. */\nfunction wasReordered(\n before: Array<{ chunk: ChunkEmbeddingEntry }>,\n after: ChunkEmbeddingEntry[],\n): boolean {\n const limit = Math.min(before.length, after.length);\n for (let i = 0; i < limit; i++) {\n if (before[i].chunk !== after[i]) return true;\n }\n return false;\n}\n\ninterface RankedChunk {\n candidate: { chunk: ChunkEmbeddingEntry };\n score: number;\n}\n\n/** Convert reranked candidates into citation records consumed downstream. */\nfunction toChunkCitations(ranked: RankedChunk[]): ChunkCitation[] {\n return ranked.map(({ candidate, score }) => ({\n slug: candidate.chunk.slug,\n title: candidate.chunk.title,\n chunkIndex: candidate.chunk.chunkIndex,\n score,\n text: candidate.chunk.text,\n }));\n}\n\n/** Collapse chunk citations down to a deduplicated list of parent page slugs. */\nfunction collapseToPages(chunks: ChunkCitation[], limit: number): string[] {\n const slugs: string[] = [];\n const seen = new Set<string>();\n for (const chunk of chunks) {\n if (seen.has(chunk.slug)) continue;\n seen.add(chunk.slug);\n slugs.push(chunk.slug);\n if (slugs.length >= limit) break;\n }\n return slugs;\n}\n\n/** Human-readable reasoning trail for the chunk-driven selection. */\nfunction buildChunkReasoning(chunks: ChunkCitation[], pages: string[]): string {\n const top = chunks.slice(0, pages.length);\n const summary = top.map((c) => `${c.slug}#${c.chunkIndex} (${c.score.toFixed(3)})`).join(\", \");\n return `Selected ${pages.length} page(s) from ${chunks.length} reranked chunks: ${summary}`;\n}\n\n/** Snapshot used by debug mode — pure data, no side-effects. */\nfunction buildDebug(\n chunks: ChunkCitation[],\n pageSlugs: string[],\n reranked: boolean,\n): RetrievalDebug {\n const bestPerPage = new Map<string, number>();\n for (const c of chunks) {\n const prev = bestPerPage.get(c.slug);\n if (prev === undefined || c.score > prev) bestPerPage.set(c.slug, c.score);\n }\n return {\n pages: pageSlugs.map((slug) => ({ slug, score: bestPerPage.get(slug) ?? 0 })),\n chunks,\n usedChunks: true,\n reranked,\n };\n}\n\n/** Chunk-level candidate lookup that never throws. */\nasync function tryFindRelevantChunks(\n root: string,\n question: string,\n): Promise<Array<{ chunk: ChunkEmbeddingEntry; score: number }>> {\n try {\n return await findRelevantChunks(root, question, CHUNK_TOP_K);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.dim(`Chunk pre-filter unavailable (${message}); falling back.`));\n return [];\n }\n}\n\n/** Embedding-based candidate lookup that never throws. */\nasync function tryFindRelevantPages(\n root: string,\n question: string,\n): Promise<Array<{ slug: string; title: string; summary: string }>> {\n try {\n return await findRelevantPages(root, question);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.dim(`Semantic pre-filter unavailable (${message}); using full index.`));\n return [];\n }\n}\n\n/**\n * Load the full content of each selected wiki page.\n * Skips pages that don't exist and warns the user.\n * @param root - Absolute path to the project root directory.\n * @param slugs - Array of page slugs to load from wiki/concepts/.\n * @returns Combined page contents with slug headers for context.\n */\nexport async function loadSelectedPages(root: string, slugs: string[]): Promise<string> {\n const sections: string[] = [];\n\n for (const slug of slugs) {\n let content = \"\";\n for (const dir of PAGE_DIRS) {\n const candidate = await safeReadFile(path.join(root, dir, `${slug}.md`));\n if (!candidate) continue;\n const { meta } = parseFrontmatter(candidate);\n if (meta.orphaned) continue;\n content = candidate;\n break;\n }\n\n if (!content) {\n output.status(\"?\", output.warn(`Page not found: ${slug}.md — skipping`));\n continue;\n }\n\n sections.push(`--- Page: ${slug} ---\\n${content}`);\n }\n\n return sections.join(\"\\n\\n\");\n}\n\n/** Base system prompt body. The output-language directive is appended at call time. */\nconst ANSWER_SYSTEM_PROMPT_BASE =\n \"You are a knowledge assistant. Answer the question using ONLY the wiki content provided. \" +\n \"Cite specific pages using [[Page Title]] wikilinks. \" +\n \"If the wiki doesn't contain enough information, say so.\";\n\n/**\n * Build the answer-generation system prompt, appending the configured\n * output-language directive when present (issue #37).\n */\nfunction buildAnswerSystemPrompt(): string {\n const lang = languageDirective();\n return lang ? `${ANSWER_SYSTEM_PROMPT_BASE} ${lang}` : ANSWER_SYSTEM_PROMPT_BASE;\n}\n\n/**\n * Call the LLM with the loaded wiki pages as grounding context. When chunk\n * citations are available, they are attached as a \"Most relevant excerpts\"\n * section so the model can prioritise the precise paragraphs that drove\n * page selection.\n */\nasync function callAnswerLLM(\n question: string,\n pagesContent: string,\n chunks: ChunkCitation[],\n onToken?: (text: string) => void,\n): Promise<string> {\n const provenance = chunks.length > 0 ? buildChunkProvenance(chunks) : \"\";\n const userMessage =\n `Question: ${question}\\n\\nRelevant wiki pages:\\n${pagesContent}${provenance}`;\n return callClaude({\n system: buildAnswerSystemPrompt(),\n messages: [{ role: \"user\", content: userMessage }],\n stream: Boolean(onToken),\n onToken,\n });\n}\n\n/** Render the top chunk excerpts as a labelled section appended to the prompt. */\nfunction buildChunkProvenance(chunks: ChunkCitation[]): string {\n const sections = chunks.map(\n (chunk) => `--- ${chunk.slug} (chunk ${chunk.chunkIndex}) ---\\n${chunk.text}`,\n );\n return `\\n\\nMost relevant excerpts (from chunk-level retrieval):\\n${sections.join(\"\\n\\n\")}`;\n}\n\n/**\n * Generate a one-line summary from the answer for use in the wiki index.\n * Takes the first sentence (up to 120 chars) so the page-selection LLM\n * has retrieval signal beyond just the title.\n * @param answer - The full answer text.\n * @returns A short summary string.\n */\nexport function summarizeAnswer(answer: string): string {\n const firstLine = answer.trim().split(/\\n/)[0] ?? \"\";\n const firstSentence = firstLine.split(/(?<=[.!?])\\s/)[0] ?? firstLine;\n return firstSentence.slice(0, 120);\n}\n\n/**\n * Save a query answer as a wiki page in the queries/ directory,\n * then regenerate the wiki index so the answer is immediately retrievable.\n * @param root - Absolute path to the project root directory.\n * @param question - The original question used as the page title.\n * @param answer - The generated answer body.\n */\nasync function saveQueryPage(root: string, question: string, answer: string): Promise<string> {\n const slug = slugify(question);\n const filePath = path.join(root, QUERIES_DIR, `${slug}.md`);\n\n const frontmatter = buildFrontmatter({\n title: question,\n summary: summarizeAnswer(answer),\n type: \"query\",\n createdAt: new Date().toISOString(),\n });\n\n const document = `${frontmatter}\\n\\n${answer}\\n`;\n await atomicWrite(filePath, document);\n\n output.status(\n \"+\",\n output.success(`Saved query → ${output.source(filePath)}`),\n );\n\n // Regenerate the index so the saved query is immediately discoverable\n // by the next query's page-selection step.\n await generateIndex(root);\n\n // Index the new query so semantic search retrieves it on the next question.\n // Non-critical: embedding failures (e.g. missing VOYAGE_API_KEY) don't block save.\n try {\n await updateEmbeddings(root, [slug]);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.warn(`Skipped embeddings update: ${message}`));\n }\n\n return slug;\n}\n\n/** Options for generateAnswer — programmatic-friendly. */\ninterface GenerateAnswerOptions {\n /** Persist the answer as a wiki query page when set. */\n save?: boolean;\n /** Per-token callback for streaming. Omit for non-streaming usage. */\n onToken?: (text: string) => void;\n /** Callback fired once page selection completes — lets CLIs print reasoning before streaming. */\n onPageSelection?: (pages: string[], reasoning: string) => void;\n /** Capture chunk-level provenance + scoring detail in the result. */\n debug?: boolean;\n}\n\n/**\n * Run the two-step page-selection + answer-generation pipeline and return\n * a structured QueryResult. This is the programmatic entry point used by\n * the MCP server and any non-CLI consumer.\n *\n * @param root - Absolute path to the project root directory.\n * @param question - The natural language question to answer.\n * @param options - Streaming + save behaviour controls.\n * @returns Answer text, selected slugs, reasoning, and saved slug if applicable.\n */\nexport async function generateAnswer(\n root: string,\n question: string,\n options: GenerateAnswerOptions = {},\n): Promise<QueryResult> {\n if (!existsSync(path.join(root, INDEX_FILE))) {\n throw new Error(\"Wiki index not found. Run `llmwiki compile` first.\");\n }\n\n const selection = await selectRelevantPages(root, question, Boolean(options.debug));\n options.onPageSelection?.(selection.pages, selection.reasoning);\n\n const pagesContent = await loadSelectedPages(root, selection.pages);\n\n if (!pagesContent) {\n return buildEmptyResult(selection);\n }\n\n const answer = await callAnswerLLM(question, pagesContent, selection.chunks, options.onToken);\n const saved = options.save ? await saveQueryPage(root, question, answer) : undefined;\n\n return {\n answer,\n selectedPages: selection.pages,\n reasoning: selection.reasoning,\n saved,\n debug: selection.debug,\n };\n}\n\n/** Build the empty-pages result while preserving any debug/chunk context. */\nfunction buildEmptyResult(selection: SelectedPages): QueryResult {\n return {\n answer: \"\",\n selectedPages: selection.pages,\n reasoning: selection.reasoning,\n debug: selection.debug,\n };\n}\n\n/**\n * Run a two-step LLM-powered query against the knowledge wiki.\n * @param root - Absolute path to the project root directory.\n * @param question - The natural language question to answer.\n * @param options - Command options (e.g. --save to persist the answer).\n */\nexport default async function queryCommand(\n root: string,\n question: string,\n options: { save?: boolean; debug?: boolean },\n): Promise<void> {\n if (!existsSync(path.join(root, INDEX_FILE))) {\n output.status(\"!\", output.error(\"Wiki index not found. Run `llmwiki compile` first.\"));\n return;\n }\n\n output.header(\"Selecting relevant pages\");\n\n const result = await generateAnswer(root, question, {\n save: options.save,\n debug: options.debug,\n onToken: (text) => process.stdout.write(text),\n onPageSelection: (pages, reasoning) => {\n output.status(\"i\", output.dim(`Reasoning: ${reasoning}`));\n output.status(\"*\", output.info(`Selected ${pages.length} page(s): ${pages.join(\", \")}`));\n output.header(\"Generating answer\");\n },\n });\n\n // Newline after streamed answer so subsequent terminal output formats cleanly.\n process.stdout.write(\"\\n\");\n\n if (result.debug) printDebugSnapshot(result.debug);\n\n if (!result.answer) {\n output.status(\"!\", output.error(\"No matching pages found. Try refining your question.\"));\n return;\n }\n\n if (result.saved) {\n output.status(\"→\", output.dim(\"Saved. Future queries will use this answer as context.\"));\n } else {\n output.status(\"→\", output.dim(\"Tip: use --save to add this answer to your wiki\"));\n }\n}\n\n/** Render the retrieval debug snapshot to the terminal for human inspection. */\nfunction printDebugSnapshot(debug: RetrievalDebug): void {\n output.header(\"Retrieval debug\");\n output.status(\n \"i\",\n output.dim(\n `Source: ${debug.usedChunks ? \"chunk-level\" : \"page-level\"}; ` +\n `reranked: ${debug.reranked ? \"yes\" : \"no\"}`,\n ),\n );\n for (const page of debug.pages) {\n output.status(\"•\", `${page.slug} (best chunk score ${page.score.toFixed(3)})`);\n }\n for (const chunk of debug.chunks) {\n const preview = chunk.text.slice(0, DEBUG_CHUNK_PREVIEW_CHARS).replace(/\\s+/g, \" \").trim();\n output.status(\n \"·\",\n output.dim(`${chunk.slug}#${chunk.chunkIndex} score=${chunk.score.toFixed(3)} :: ${preview}…`),\n );\n }\n}\n\n/** Maximum chunk preview length printed in --debug output. */\nconst DEBUG_CHUNK_PREVIEW_CHARS = 120;\n","/**\n * Commander action for `llmwiki watch`.\n *\n * Monitors sources/ for file changes via chokidar and triggers incremental\n * recompilation automatically. Uses a debounce to batch rapid changes into\n * a single compile pass. Respects the .llmwiki/lock file — queues changes\n * if a compile is already running.\n */\n\nimport { watch as chokidarWatch } from \"chokidar\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { compile } from \"../compiler/index.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\n\nconst DEBOUNCE_MS = 500;\n\n/**\n * Start watching sources/ for changes and auto-recompile.\n * Runs until the process is killed (Ctrl+C).\n */\nexport default async function watchCommand(): Promise<void> {\n const sourcesPath = path.resolve(SOURCES_DIR);\n\n if (!existsSync(sourcesPath)) {\n output.status(\n \"!\",\n output.warn('No sources/ directory found. Run `llmwiki ingest <url>` first.'),\n );\n return;\n }\n\n output.header(\"llmwiki watch\");\n output.status(\"👁\", output.info(`Watching ${sourcesPath} for changes...`));\n output.status(\"i\", output.dim(\"Press Ctrl+C to stop.\\n\"));\n\n let compiling = false;\n let pendingRecompile = false;\n let debounceTimer: ReturnType<typeof setTimeout> | null = null;\n\n const runCompileOnce = async (): Promise<void> => {\n try {\n await compile(process.cwd());\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.error(`Compile failed: ${msg}`));\n }\n };\n\n const triggerCompile = async (): Promise<void> => {\n if (compiling) {\n pendingRecompile = true;\n return;\n }\n compiling = true;\n await runCompileOnce();\n compiling = false;\n if (pendingRecompile) {\n pendingRecompile = false;\n await triggerCompile();\n }\n };\n\n const scheduleCompile = (eventPath: string, event: string) => {\n output.status(\n \"~\",\n output.dim(`${event}: ${path.basename(eventPath)}`),\n );\n\n if (debounceTimer) clearTimeout(debounceTimer);\n debounceTimer = setTimeout(triggerCompile, DEBOUNCE_MS);\n };\n\n const watcher = chokidarWatch(sourcesPath, {\n ignoreInitial: true,\n awaitWriteFinish: { stabilityThreshold: 200 },\n });\n\n watcher\n .on(\"add\", (p) => scheduleCompile(p, \"added\"))\n .on(\"change\", (p) => scheduleCompile(p, \"changed\"))\n .on(\"unlink\", (p) => scheduleCompile(p, \"deleted\"));\n\n // Keep process alive\n await new Promise<void>(() => {});\n}\n","/**\n * Wiki linter orchestrator.\n *\n * Imports all lint rules, runs them concurrently, and aggregates\n * results into a summary with error/warning/info counts.\n * This is the main entry point for programmatic lint access.\n */\n\nimport type { LintResult, LintRule, LintSummary, SchemaAwareLintRule } from \"./types.js\";\nimport {\n checkBrokenWikilinks,\n checkOrphanedPages,\n checkMissingSummaries,\n checkDuplicateConcepts,\n checkEmptyPages,\n checkBrokenCitations,\n checkMalformedClaimCitations,\n checkLowConfidencePages,\n checkContradictedPages,\n checkInferredWithoutCitations,\n checkSchemaCrossLinks,\n} from \"./rules.js\";\nimport { loadSchema } from \"../schema/index.js\";\n\n/** Rule-only lint checks that don't depend on the schema layer. */\nconst RULES_WITHOUT_SCHEMA: LintRule[] = [\n checkBrokenWikilinks,\n checkOrphanedPages,\n checkMissingSummaries,\n checkDuplicateConcepts,\n checkEmptyPages,\n checkBrokenCitations,\n checkMalformedClaimCitations,\n checkLowConfidencePages,\n checkContradictedPages,\n checkInferredWithoutCitations,\n];\n\n/** Lint rules that need the resolved schema to know per-kind expectations. */\nconst RULES_WITH_SCHEMA: SchemaAwareLintRule[] = [checkSchemaCrossLinks];\n\n/**\n * Count occurrences of a specific severity level in the results.\n */\nfunction countBySeverity(\n results: LintResult[],\n severity: LintResult[\"severity\"],\n): number {\n return results.filter((r) => r.severity === severity).length;\n}\n\n/**\n * Run all lint rules concurrently against the wiki at the given root.\n * Loads the project schema (or defaults) so schema-aware rules can enforce\n * per-kind cross-link minimums alongside structural checks.\n * @param root - Absolute path to the project root directory.\n * @returns A summary containing all diagnostics and severity counts.\n */\nexport async function lint(root: string): Promise<LintSummary> {\n const schema = await loadSchema(root);\n const [plainResults, schemaResults] = await Promise.all([\n Promise.all(RULES_WITHOUT_SCHEMA.map((rule) => rule(root))),\n Promise.all(RULES_WITH_SCHEMA.map((rule) => rule(root, schema))),\n ]);\n\n const results = [...plainResults.flat(), ...schemaResults.flat()];\n\n return {\n errors: countBySeverity(results, \"error\"),\n warnings: countBySeverity(results, \"warning\"),\n info: countBySeverity(results, \"info\"),\n results,\n };\n}\n","/**\n * Commander action for `llmwiki lint`.\n *\n * Runs rule-based quality checks against the wiki without any LLM calls.\n * Prints colored diagnostics grouped by severity and exits with code 1\n * if any errors are found.\n */\n\nimport { lint } from \"../linter/index.js\";\nimport { writeLintCache } from \"../linter/cache.js\";\nimport * as output from \"../utils/output.js\";\nimport type { LintResult } from \"../linter/types.js\";\nimport { loadSchema } from \"../schema/index.js\";\n\n/** Map severity levels to output formatting functions. */\nconst SEVERITY_FORMATTERS: Record<LintResult[\"severity\"], (text: string) => string> = {\n error: output.error,\n warning: output.warn,\n info: output.info,\n};\n\n/** Map severity levels to display icons. */\nconst SEVERITY_ICONS: Record<LintResult[\"severity\"], string> = {\n error: \"x\",\n warning: \"!\",\n info: \"i\",\n};\n\n/** Print a single lint result with colored output. */\nfunction printResult(result: LintResult): void {\n const formatter = SEVERITY_FORMATTERS[result.severity];\n const icon = SEVERITY_ICONS[result.severity];\n const location = result.line ? `${result.file}:${result.line}` : result.file;\n output.status(icon, `${formatter(result.severity)} ${output.dim(location)} ${result.message}`);\n}\n\n/**\n * Run the lint command: execute all rules and print results.\n * Exits with code 1 if any errors are found.\n */\nexport default async function lintCommand(): Promise<void> {\n output.header(\"Linting wiki\");\n\n const schema = await loadSchema(process.cwd());\n const schemaSource = schema.loadedFrom ?? \"defaults (no schema file)\";\n output.status(\"i\", output.dim(`Schema: ${schemaSource}`));\n\n const summary = await lint(process.cwd());\n\n for (const result of summary.results) {\n printResult(result);\n }\n\n console.log();\n const summaryLine = [\n output.error(`${summary.errors} error(s)`),\n output.warn(`${summary.warnings} warning(s)`),\n output.info(`${summary.info} info`),\n ].join(\", \");\n output.status(\"*\", summaryLine);\n\n await writeLintCache(process.cwd(), summary);\n\n if (summary.errors > 0) {\n process.exit(1);\n }\n}\n","/**\n * Health score evaluator for the llmwiki eval harness.\n *\n * Aggregates the output of all 10+ lint rules into a single 0–100 score.\n * Errors on critical rules (broken links/citations, duplicates) cost 4 pts\n * each; contradicted pages cost 2 pts; all other warnings/info cost 1 pt each.\n * The final score is clamped to [0, 100].\n */\n\nimport {\n checkBrokenWikilinks,\n checkBrokenCitations,\n checkMalformedClaimCitations,\n checkOrphanedPages,\n checkMissingSummaries,\n checkDuplicateConcepts,\n checkEmptyPages,\n checkLowConfidencePages,\n checkContradictedPages,\n checkInferredWithoutCitations,\n checkSchemaCrossLinks,\n} from \"../linter/rules.js\";\nimport { loadSchema } from \"../schema/loader.js\";\nimport type { LintResult } from \"../linter/types.js\";\nimport type { HealthResult, HealthRuleResult } from \"./types.js\";\n\nconst MAX_SCORE = 100;\nconst ERROR_DEDUCTION = 4;\nconst CONTRADICTED_DEDUCTION = 2;\nconst DEFAULT_DEDUCTION = 1;\n\n/** Rules treated as high-severity errors (−4 pts per violation). */\nconst ERROR_RULES = new Set([\n \"broken-wikilink\",\n \"broken-citation\",\n \"duplicate-concept\",\n]);\n\n/** Compute the point deduction for a single lint result. */\nfunction deductionFor(result: LintResult): number {\n if (ERROR_RULES.has(result.rule)) return ERROR_DEDUCTION;\n if (result.rule === \"contradicted-page\") return CONTRADICTED_DEDUCTION;\n return DEFAULT_DEDUCTION;\n}\n\n/** Aggregate lint results into a per-rule summary with deduction totals. */\nfunction aggregateRules(results: LintResult[]): HealthRuleResult[] {\n const map = new Map<string, HealthRuleResult>();\n for (const result of results) {\n const existing = map.get(result.rule);\n const deduction = deductionFor(result);\n if (existing) {\n existing.count++;\n existing.deduction += deduction;\n } else {\n map.set(result.rule, {\n rule: result.rule,\n count: 1,\n severity: result.severity,\n deduction,\n });\n }\n }\n return Array.from(map.values());\n}\n\n/**\n * Run all lint rules against the project root and return an aggregated\n * health score plus per-rule breakdown.\n * @param root - Absolute path to the project root.\n */\nexport async function evaluateHealth(root: string): Promise<HealthResult> {\n const schema = await loadSchema(root);\n\n const allResults = (\n await Promise.all([\n checkBrokenWikilinks(root),\n checkBrokenCitations(root),\n checkMalformedClaimCitations(root),\n checkOrphanedPages(root),\n checkMissingSummaries(root),\n checkDuplicateConcepts(root),\n checkEmptyPages(root),\n checkLowConfidencePages(root),\n checkContradictedPages(root),\n checkInferredWithoutCitations(root),\n checkSchemaCrossLinks(root, schema),\n ])\n ).flat();\n\n const rules = aggregateRules(allResults);\n const totalDeduction = rules.reduce((sum, r) => sum + r.deduction, 0);\n const score = Math.max(0, MAX_SCORE - totalDeduction);\n\n return { score, maxScore: MAX_SCORE, rules };\n}\n","/**\n * Citation coverage evaluator for the llmwiki eval harness.\n *\n * Measures two things:\n * - Coverage: what fraction of prose paragraphs contain at least one citation.\n * - Precision: what fraction of citations point to a source file that exists.\n *\n * Non-prose paragraphs (headings, code blocks, lists, blank lines) are excluded\n * so that only human-readable claim text is counted.\n */\n\nimport path from \"path\";\nimport { collectAllPages } from \"../linter/rules.js\";\nimport { parseFrontmatter, extractClaimCitations } from \"../utils/markdown.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport { resolveSourceFile } from \"./source-path.js\";\nimport type { CitationCoverageResult, CitationPageResult } from \"./types.js\";\n\n/** Prose paragraphs start with a Unicode letter. */\nconst PROSE_LEAD_RE = /^\\p{L}/u;\n\ninterface PageStats {\n pageResult: CitationPageResult;\n proseParagraphs: number;\n citedParagraphs: number;\n totalCitations: number;\n validCitations: number;\n}\n\n/** Evaluate citation coverage and precision for a single page body. */\nasync function evaluatePage(slug: string, body: string, sourcesDir: string): Promise<PageStats> {\n const paragraphs = body.split(/\\n\\s*\\n/).filter((p) => PROSE_LEAD_RE.test(p.trim()));\n let citedParagraphs = 0;\n let totalCitations = 0;\n let validCitations = 0;\n\n for (const para of paragraphs) {\n const citations = extractClaimCitations(para);\n if (citations.length === 0) continue;\n citedParagraphs++;\n for (const { spans } of citations) {\n for (const span of spans) {\n totalCitations++;\n if ((await resolveSourceFile(sourcesDir, span.file)) !== null) validCitations++;\n }\n }\n }\n\n return {\n pageResult: { slug, proseParagraphs: paragraphs.length, citedParagraphs },\n proseParagraphs: paragraphs.length,\n citedParagraphs,\n totalCitations,\n validCitations,\n };\n}\n\n/**\n * Measure citation coverage and precision across all wiki pages.\n * @param root - Absolute path to the project root.\n */\nexport async function evaluateCitationCoverage(\n root: string,\n): Promise<CitationCoverageResult> {\n const pages = await collectAllPages(root);\n const sourcesDir = path.join(root, SOURCES_DIR);\n\n let totalProse = 0;\n let totalCited = 0;\n let totalCitations = 0;\n let totalValid = 0;\n const perPage: CitationPageResult[] = [];\n\n for (const { filePath, content } of pages) {\n const { body } = parseFrontmatter(content);\n const slug = path.basename(filePath, \".md\");\n const stats = await evaluatePage(slug, body, sourcesDir);\n totalProse += stats.proseParagraphs;\n totalCited += stats.citedParagraphs;\n totalCitations += stats.totalCitations;\n totalValid += stats.validCitations;\n perPage.push(stats.pageResult);\n }\n\n const coveragePercent = totalProse === 0 ? 0 : (totalCited / totalProse) * 100;\n const precisionPercent = totalCitations === 0 ? 0 : (totalValid / totalCitations) * 100;\n\n return {\n totalProseParagraphs: totalProse,\n citedParagraphs: totalCited,\n coveragePercent,\n totalCitations,\n validCitations: totalValid,\n precisionPercent,\n perPage,\n };\n}\n","/**\n * Safe source-file resolver for the eval harness.\n *\n * Citation markers in wiki pages contain free-form file paths. Before the\n * eval harness reads any file or checks its existence, the path must be\n * confined to the project's `sources/` directory. Three independent layers\n * of defense are applied in order:\n *\n * 1. Structural: reject empty strings, absolute paths, and any `..` segment.\n * 2. Pre-realpath: resolve the joined path and verify it stays under sourcesDir.\n * 3. Symlink: call `fs.realpath` on both sides and verify again, catching\n * symlinks inside sources/ that point outside the tree.\n *\n * The confinement pattern mirrors `resolveSafePath` in src/context/provenance.ts.\n */\n\nimport { realpath } from \"fs/promises\";\nimport path from \"path\";\n\n/**\n * True when any `/`- or `\\`-separated segment of `file` is literally `..`.\n * Splitting on both separators ensures a `nested\\..\\escape.md` path written\n * for a Windows consumer is also caught on Linux where `\\` is a valid filename\n * character.\n */\nfunction containsParentSegment(file: string): boolean {\n return file.split(/[/\\\\]/).some((seg) => seg === \"..\");\n}\n\n/** True when `candidate` equals `parent` or sits beneath it. */\nfunction isInside(parent: string, candidate: string): boolean {\n if (candidate === parent) return true;\n const parentWithSep = parent.endsWith(path.sep) ? parent : parent + path.sep;\n return candidate.startsWith(parentWithSep);\n}\n\n/**\n * Resolve `file` under `sourcesDir`, rejecting:\n * - empty strings and absolute paths (`/etc/passwd`)\n * - any path segment that is literally `..` (traversal, even normalizing back\n * inside sources/ is rejected — the raw marker must be unambiguous)\n * - paths that resolve outside `sourcesDir` after normalization\n * - paths whose `realpath` escapes `sourcesDir` (symlink escapes)\n *\n * Returns the canonical absolute path on success, or `null` if the path is\n * rejected, the sources directory does not exist, or the file is not found.\n *\n * @param sourcesDir - Absolute path to the project's sources/ directory.\n * @param file - Relative file path from a citation marker (untrusted input).\n */\nexport async function resolveSourceFile(\n sourcesDir: string,\n file: string,\n): Promise<string | null> {\n if (file.length === 0 || path.isAbsolute(file)) return null;\n if (containsParentSegment(file)) return null;\n const joined = path.join(sourcesDir, file);\n if (!isInside(sourcesDir, path.resolve(joined))) return null;\n try {\n const realDir = await realpath(sourcesDir);\n const realFile = await realpath(joined);\n if (!isInside(realDir, realFile)) return null;\n return realFile;\n } catch {\n return null;\n }\n}\n","/**\n * LLM citation judge for the llmwiki eval harness.\n *\n * For each cited prose paragraph in the wiki, extracts the (claim, source span)\n * pair and asks an LLM judge whether the source actually supports the claim.\n * Scoring: 0 = unsupported, 1 = partial, 2 = fully supported.\n *\n * Uses deterministic hash-based sampling so the same N citations are evaluated\n * on every run (reproducible). Results are cached in .llmwiki/eval/citation-cache.jsonl\n * so subsequent runs skip already-judged pairs.\n */\n\nimport { createHash } from \"crypto\";\nimport { readFile, appendFile, mkdir } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { collectAllPages } from \"../linter/rules.js\";\nimport { parseFrontmatter, extractClaimCitations } from \"../utils/markdown.js\";\nimport { callClaude } from \"../utils/llm.js\";\nimport { SOURCES_DIR, DEFAULT_PROVIDER, PROVIDER_MODELS } from \"../utils/constants.js\";\nimport { resolveSourceFile } from \"./source-path.js\";\nimport type { LLMTool } from \"../utils/provider.js\";\nimport type { CitationJudgement, CitationSupportResult } from \"./types.js\";\nimport type { SourceSpan } from \"../utils/types.js\";\n\nconst CACHE_DIR = path.join(\".llmwiki\", \"eval\");\nconst CACHE_FILE = path.join(CACHE_DIR, \"citation-cache.jsonl\");\nconst PROSE_LEAD_RE = /^\\p{L}/u;\n\n/** Internal pair combining a claim paragraph with its cited source span. */\ninterface CitationPair {\n claimHash: string;\n pageSlug: string;\n claimText: string;\n citedFile: string;\n spanText: string;\n lineStart: number;\n lineEnd: number;\n}\n\nconst JUDGE_TOOL: LLMTool = {\n name: \"judge_citation\",\n description: \"Rate how well the source excerpt supports the claim.\",\n input_schema: {\n type: \"object\",\n properties: {\n score: {\n type: \"integer\",\n enum: [0, 1, 2],\n description:\n \"0=not supported or contradicted, 1=partially supported, 2=fully supported\",\n },\n reason: { type: \"string\", description: \"One sentence explaining the rating.\" },\n },\n required: [\"score\", \"reason\"],\n },\n};\n\nconst JUDGE_SYSTEM =\n \"You are an expert fact-checker. Given a claim from a wiki article and a source excerpt, rate whether the source supports the claim. Be strict: partial credit only if the source addresses the claim but is incomplete. Important: if the source excerpt consists entirely of YAML frontmatter (metadata fields such as title, date, author, or tags between --- delimiters), treat it as non-evidence for any substantive claim and score it 0, unless the claim is explicitly about that metadata field.\";\n\n// Content-addressable fingerprint of the judge prompt + tool schema.\n// Any edit to JUDGE_SYSTEM or JUDGE_TOOL produces a new hash, automatically\n// invalidating cached judgements without requiring a manual version bump.\nconst JUDGE_CONFIG_HASH = createHash(\"sha256\")\n .update(JUDGE_SYSTEM + JSON.stringify(JUDGE_TOOL))\n .digest(\"hex\")\n .slice(0, 8);\n\n/** Compute a stable 16-char hex hash for a (claim, span) pair. */\nfunction hashPair(claimText: string, spanText: string): string {\n return createHash(\"sha256\").update(claimText + spanText).digest(\"hex\").slice(0, 16);\n}\n\n/** Derive a full cache key from content hash + judge config fingerprint + model. */\nfunction makeCacheKey(contentHash: string, model: string): string {\n return createHash(\"sha256\")\n .update(contentHash + JUDGE_CONFIG_HASH + model)\n .digest(\"hex\")\n .slice(0, 16);\n}\n\n/** Extract the text of specific lines from a file (1-indexed, inclusive). */\nasync function readSourceLines(filePath: string, start: number, end: number): Promise<string> {\n const content = await readFile(filePath, \"utf-8\");\n return content\n .split(\"\\n\")\n .slice(start - 1, end)\n .join(\"\\n\");\n}\n\n/** Strip `^[...]` markers from a paragraph to get the bare claim text. */\nfunction stripCitationMarkers(paragraph: string): string {\n return paragraph.replace(/\\^\\[[^\\]]+\\]/g, \"\").trim();\n}\n\n/** Build a CitationPair from one span, or null if the source file is missing or has no line range. */\nasync function buildSpanPair(\n slug: string,\n claimText: string,\n span: SourceSpan,\n sourcesDir: string,\n): Promise<CitationPair | null> {\n if (!span.lines) return null;\n const sourceFile = await resolveSourceFile(sourcesDir, span.file);\n if (sourceFile === null) return null;\n const spanText = await readSourceLines(sourceFile, span.lines.start, span.lines.end);\n return {\n claimHash: hashPair(claimText, spanText),\n pageSlug: slug,\n claimText,\n citedFile: span.file,\n spanText,\n lineStart: span.lines.start,\n lineEnd: span.lines.end,\n };\n}\n\n/** Extract citation pairs from a single cited paragraph. */\nasync function extractParagraphPairs(\n slug: string,\n para: string,\n sourcesDir: string,\n): Promise<CitationPair[]> {\n const citations = extractClaimCitations(para);\n if (citations.length === 0) return [];\n const claimText = stripCitationMarkers(para);\n const spans = citations.flatMap((c) => c.spans);\n const pairs = await Promise.all(spans.map((s) => buildSpanPair(slug, claimText, s, sourcesDir)));\n return pairs.filter((p): p is CitationPair => p !== null);\n}\n\n/** Extract citation pairs from a single page body. */\nasync function extractPagePairs(\n slug: string,\n body: string,\n sourcesDir: string,\n): Promise<CitationPair[]> {\n const paragraphs = body.split(/\\n\\s*\\n/).filter((p) => PROSE_LEAD_RE.test(p.trim()));\n const results = await Promise.all(paragraphs.map((p) => extractParagraphPairs(slug, p, sourcesDir)));\n return results.flat();\n}\n\n/**\n * Extract all (claim, source span) pairs from the wiki.\n * Only pairs with valid line ranges pointing to existing source files are included.\n * @param root - Absolute path to the project root.\n */\nexport async function extractCitationPairs(root: string): Promise<CitationPair[]> {\n const pages = await collectAllPages(root);\n const sourcesDir = path.join(root, SOURCES_DIR);\n const all: CitationPair[] = [];\n for (const { filePath, content } of pages) {\n const { body } = parseFrontmatter(content);\n const slug = path.basename(filePath, \".md\");\n const pairs = await extractPagePairs(slug, body, sourcesDir);\n all.push(...pairs);\n }\n return all;\n}\n\n/**\n * Select a stable sample of N pairs that resists churn as the corpus grows.\n *\n * Previously-sampled hashes are retained first; only empty slots are filled\n * from new pairs (sorted by claimHash for determinism). This means adding new\n * citations to the corpus never displaces an already-evaluated pair, so score\n * movement in reports reflects quality change rather than sample turnover.\n *\n * @param pairs - All extracted citation pairs.\n * @param sampleSize - Maximum number of pairs to return.\n * @param previousHashes - Hashes selected in the prior run (from the saved report).\n */\nexport function selectDeterministicSample(\n pairs: CitationPair[],\n sampleSize: number,\n previousHashes: string[] = [],\n): CitationPair[] {\n const pairByHash = new Map(pairs.map((p) => [p.claimHash, p]));\n const retained = previousHashes.flatMap((h) => {\n const p = pairByHash.get(h);\n return p ? [p] : [];\n });\n if (retained.length >= sampleSize) return retained.slice(0, sampleSize);\n const retainedSet = new Set(previousHashes);\n const newPairs = pairs\n .filter((p) => !retainedSet.has(p.claimHash))\n .sort((a, b) => a.claimHash.localeCompare(b.claimHash));\n return [...retained, ...newPairs].slice(0, sampleSize);\n}\n\n/** Load previously cached judgements keyed by claimHash. */\nasync function loadCachedJudgements(root: string): Promise<Map<string, CitationJudgement>> {\n const cachePath = path.join(root, CACHE_FILE);\n if (!existsSync(cachePath)) return new Map();\n const content = await readFile(cachePath, \"utf-8\");\n const map = new Map<string, CitationJudgement>();\n for (const line of content.trim().split(\"\\n\").filter(Boolean)) {\n try {\n const entry = JSON.parse(line) as CitationJudgement;\n map.set(entry.claimHash, entry);\n } catch {\n // Skip malformed cache lines\n }\n }\n return map;\n}\n\n/** Append a single judgement to the cache file. */\nasync function appendCachedJudgement(root: string, judgement: CitationJudgement): Promise<void> {\n await mkdir(path.join(root, CACHE_DIR), { recursive: true });\n await appendFile(path.join(root, CACHE_FILE), JSON.stringify(judgement) + \"\\n\");\n}\n\n/** Resolve the current model identifier for recording in judgements. */\nfunction resolveModel(): string {\n const provider = process.env.LLMWIKI_PROVIDER ?? DEFAULT_PROVIDER;\n return process.env.LLMWIKI_MODEL ?? PROVIDER_MODELS[provider] ?? provider;\n}\n\n/** Call the LLM judge for a single (claim, span) pair. */\nasync function callJudge(pair: CitationPair, cacheKey: string, model: string): Promise<CitationJudgement> {\n const userMessage =\n `Claim: ${pair.claimText}\\n\\nSource (${pair.citedFile}, lines ${pair.lineStart}–${pair.lineEnd}):\\n${pair.spanText}`;\n\n const raw = await callClaude({\n system: JUDGE_SYSTEM,\n messages: [{ role: \"user\", content: userMessage }],\n tools: [JUDGE_TOOL],\n maxTokens: 256,\n });\n\n const parsed = JSON.parse(raw) as { score: 0 | 1 | 2; reason: string };\n return {\n claimHash: cacheKey,\n pageSlug: pair.pageSlug,\n citedFile: pair.citedFile,\n lineStart: pair.lineStart,\n lineEnd: pair.lineEnd,\n claimText: pair.claimText,\n spanText: pair.spanText,\n score: parsed.score,\n reason: parsed.reason,\n model,\n timestamp: new Date().toISOString(),\n };\n}\n\n/** Aggregate raw judgement scores into summary statistics. */\nfunction aggregateJudgements(judgements: CitationJudgement[]): Pick<\n CitationSupportResult,\n \"meanScore\" | \"fullySupported\" | \"partiallySupported\" | \"unsupported\"\n> {\n const fullySupported = judgements.filter((j) => j.score === 2).length;\n const partiallySupported = judgements.filter((j) => j.score === 1).length;\n const unsupported = judgements.filter((j) => j.score === 0).length;\n const meanScore =\n judgements.length === 0\n ? 0\n : judgements.reduce((sum, j) => sum + j.score, 0) / judgements.length;\n return { meanScore, fullySupported, partiallySupported, unsupported };\n}\n\n/**\n * Judge each non-cached pair in the sample, returning all collected judgements\n * and an error count. Throws if every non-cached call fails (credentials missing,\n * provider down, etc.) — an empty result would be meaningless.\n */\nasync function judgeNewPairs(\n sample: CitationPair[],\n cache: Map<string, CitationJudgement>,\n root: string,\n): Promise<{ judgements: CitationJudgement[]; judgeErrors: number }> {\n const model = resolveModel();\n const judgements: CitationJudgement[] = [];\n let judgeErrors = 0;\n let newPairsAttempted = 0;\n let firstError: unknown;\n\n for (const pair of sample) {\n const cacheKey = makeCacheKey(pair.claimHash, model);\n const cached = cache.get(cacheKey);\n if (cached) {\n judgements.push(cached);\n } else {\n newPairsAttempted++;\n try {\n const judgement = await callJudge(pair, cacheKey, model);\n await appendCachedJudgement(root, judgement);\n judgements.push(judgement);\n } catch (err) {\n judgeErrors++;\n if (firstError === undefined) firstError = err;\n }\n }\n }\n\n if (newPairsAttempted > 0 && judgeErrors === newPairsAttempted) {\n const msg = firstError instanceof Error ? firstError.message : String(firstError);\n throw new Error(`Citation judge failed for all ${judgeErrors} sampled pair(s): ${msg}`);\n }\n\n return { judgements, judgeErrors };\n}\n\n/**\n * Run the citation support judge across a stable sample of wiki citations.\n * Returns null if no citable paragraphs exist.\n * @param root - Absolute path to the project root.\n * @param sampleSize - Number of citation pairs to judge per run.\n * @param previousHashes - Hashes sampled in the prior run; retained to prevent sample churn.\n */\nexport async function evaluateCitationSupport(\n root: string,\n sampleSize = 20,\n previousHashes: string[] = [],\n): Promise<CitationSupportResult | null> {\n const allPairs = await extractCitationPairs(root);\n if (allPairs.length === 0) return null;\n\n const sample = selectDeterministicSample(allPairs, sampleSize, previousHashes);\n const cache = await loadCachedJudgements(root);\n const { judgements, judgeErrors } = await judgeNewPairs(sample, cache, root);\n\n return {\n sampledCount: judgements.length,\n sampledHashes: sample.map((p) => p.claimHash),\n totalCitations: allPairs.length,\n judgeErrors,\n ...aggregateJudgements(judgements),\n judgements,\n };\n}\n","/**\n * Corpus size stats collector for the llmwiki eval harness.\n *\n * Snapshots source count, page count, total wiki character count, and\n * embedding counts. Each run appends one JSON line to .llmwiki/eval/history.jsonl\n * for trend analysis over time.\n */\n\nimport { readdir, appendFile, mkdir, readFile } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport { collectAllPages } from \"../linter/rules.js\";\nimport { parseFrontmatter } from \"../utils/markdown.js\";\nimport { readEmbeddingStore } from \"../utils/embeddings.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport type { StatsResult, EvalReport } from \"./types.js\";\n\nconst HISTORY_DIR = path.join(\".llmwiki\", \"eval\");\nconst HISTORY_FILE = path.join(HISTORY_DIR, \"history.jsonl\");\n\n/** Count the number of files in a directory (non-recursive, ignores missing dir). */\nasync function countFiles(dir: string): Promise<number> {\n if (!existsSync(dir)) return 0;\n const entries = await readdir(dir);\n return entries.filter((e) => e.endsWith(\".md\")).length;\n}\n\n/**\n * Collect a corpus size snapshot for the current project state.\n * @param root - Absolute path to the project root.\n */\nexport async function collectStats(root: string): Promise<StatsResult> {\n const [sourceCount, pages, embeddingStore] = await Promise.all([\n countFiles(path.join(root, SOURCES_DIR)),\n collectAllPages(root),\n readEmbeddingStore(root),\n ]);\n\n let totalWikiChars = 0;\n for (const { content } of pages) {\n const { body } = parseFrontmatter(content);\n totalWikiChars += body.length;\n }\n\n const pageCount = pages.length;\n const avgPageLengthChars = pageCount === 0 ? 0 : Math.round(totalWikiChars / pageCount);\n const embeddingCount = embeddingStore?.entries.length ?? 0;\n const chunkEmbeddingCount = embeddingStore?.chunks?.length ?? 0;\n\n return {\n timestamp: new Date().toISOString(),\n sourceCount,\n pageCount,\n totalWikiChars,\n embeddingCount,\n chunkEmbeddingCount,\n avgPageLengthChars,\n };\n}\n\n/**\n * Append the current eval report as a single JSON line to history.jsonl.\n * Creates the directory if it does not exist.\n * @param root - Absolute path to the project root.\n * @param report - The completed EvalReport to persist.\n */\nexport async function appendHistory(root: string, report: EvalReport): Promise<void> {\n const historyDir = path.join(root, HISTORY_DIR);\n await mkdir(historyDir, { recursive: true });\n await appendFile(path.join(root, HISTORY_FILE), JSON.stringify(report) + \"\\n\");\n}\n\n/**\n * Load the last N eval reports from history.jsonl, oldest first.\n * Returns an empty array if no history file exists or the file is empty.\n * @param root - Absolute path to the project root.\n * @param n - Maximum number of reports to return (default 10).\n */\nexport async function loadHistory(root: string, n = 10): Promise<EvalReport[]> {\n const historyPath = path.join(root, HISTORY_FILE);\n if (!existsSync(historyPath)) return [];\n\n const content = await readFile(historyPath, \"utf-8\");\n const lines = content.trim().split(\"\\n\").filter(Boolean);\n const reports: EvalReport[] = [];\n for (const line of lines.slice(-n)) {\n try {\n reports.push(JSON.parse(line) as EvalReport);\n } catch {\n // Skip malformed lines\n }\n }\n return reports;\n}\n\n/**\n * Load the most recent eval report from history.jsonl, or null if none exists.\n * @param root - Absolute path to the project root.\n */\nexport async function loadPreviousReport(root: string): Promise<EvalReport | null> {\n const historyPath = path.join(root, HISTORY_FILE);\n if (!existsSync(historyPath)) return null;\n\n const content = await readFile(historyPath, \"utf-8\");\n const lines = content.trim().split(\"\\n\").filter(Boolean);\n if (lines.length === 0) return null;\n\n try {\n return JSON.parse(lines[lines.length - 1]) as EvalReport;\n } catch {\n return null;\n }\n}\n","/**\n * Regression detection for the llmwiki eval harness.\n *\n * Computes signed metric deltas between the current eval report and the\n * previous one from history.jsonl. Positive = improvement, negative = regression.\n * Fields are omitted when either report lacks the data (e.g. citationSupportMean\n * is only present on full-suite runs).\n */\n\nimport type { EvalReport, EvalDelta } from \"./types.js\";\n\n/**\n * Compute the signed difference between current and previous eval metrics.\n * @param current - The just-completed report.\n * @param previous - The last report loaded from history.jsonl.\n */\nexport function computeDelta(current: EvalReport, previous: EvalReport): EvalDelta {\n const delta: EvalDelta = {\n healthScore: current.health.score - previous.health.score,\n citationCoveragePercent:\n current.citationCoverage.coveragePercent - previous.citationCoverage.coveragePercent,\n citationPrecisionPercent:\n current.citationCoverage.precisionPercent - previous.citationCoverage.precisionPercent,\n };\n\n if (current.citationSupport !== undefined && previous.citationSupport !== undefined) {\n delta.citationSupportMean =\n current.citationSupport.meanScore - previous.citationSupport.meanScore;\n }\n\n return delta;\n}\n","/**\n * CI threshold gating for the llmwiki eval harness.\n *\n * Reads an optional .llmwiki/eval/thresholds.yaml config and checks whether\n * the current report meets each configured threshold. Returns a list of\n * human-readable violation messages; an empty list means all thresholds pass.\n *\n * Supported threshold keys:\n * health_score — minimum health score (0–100)\n * citation_coverage_percent — minimum citation coverage (0–100)\n * citation_precision_percent — minimum citation precision (0–100)\n * citation_support_mean — minimum mean judge score (0.0–2.0)\n */\n\nimport { readFile } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport yaml from \"js-yaml\";\nimport type { EvalReport } from \"./types.js\";\n\nconst THRESHOLDS_FILE = path.join(\".llmwiki\", \"eval\", \"thresholds.yaml\");\n\ninterface ThresholdConfig {\n health_score?: number;\n citation_coverage_percent?: number;\n citation_precision_percent?: number;\n citation_support_mean?: number;\n /** Maximum number of judge call failures allowed per run (0 = any error fails CI). */\n citation_judge_error_max?: number;\n}\n\n/** Load the threshold config from disk, or return an empty config if absent. */\nasync function loadThresholds(root: string): Promise<ThresholdConfig> {\n const configPath = path.join(root, THRESHOLDS_FILE);\n if (!existsSync(configPath)) return {};\n const raw = await readFile(configPath, \"utf-8\");\n return (yaml.load(raw) as ThresholdConfig) ?? {};\n}\n\n/**\n * Check the report against configured thresholds.\n * @param report - The completed eval report.\n * @param root - Absolute path to the project root.\n * @returns List of violation messages (empty = all pass).\n */\nexport async function checkThresholds(\n report: EvalReport,\n root: string,\n): Promise<string[]> {\n const config = await loadThresholds(root);\n const violations: string[] = [];\n\n if (config.health_score !== undefined && report.health.score < config.health_score) {\n violations.push(\n `health_score ${report.health.score} is below threshold ${config.health_score}`,\n );\n }\n\n if (\n config.citation_coverage_percent !== undefined &&\n report.citationCoverage.coveragePercent < config.citation_coverage_percent\n ) {\n violations.push(\n `citation_coverage_percent ${report.citationCoverage.coveragePercent.toFixed(1)}% is below threshold ${config.citation_coverage_percent}%`,\n );\n }\n\n if (\n config.citation_precision_percent !== undefined &&\n report.citationCoverage.precisionPercent < config.citation_precision_percent\n ) {\n violations.push(\n `citation_precision_percent ${report.citationCoverage.precisionPercent.toFixed(1)}% is below threshold ${config.citation_precision_percent}%`,\n );\n }\n\n if (\n config.citation_support_mean !== undefined &&\n report.citationSupport !== undefined &&\n report.citationSupport.meanScore < config.citation_support_mean\n ) {\n violations.push(\n `citation_support_mean ${report.citationSupport.meanScore.toFixed(2)} is below threshold ${config.citation_support_mean}`,\n );\n }\n\n if (\n config.citation_judge_error_max !== undefined &&\n report.citationSupport !== undefined &&\n report.citationSupport.judgeErrors > config.citation_judge_error_max\n ) {\n violations.push(\n `citation_judge_errors ${report.citationSupport.judgeErrors} exceeds max ${config.citation_judge_error_max}`,\n );\n }\n\n return violations;\n}\n","/**\n * Terminal and JSON formatters for the llmwiki eval harness.\n *\n * formatTerminalReport renders a box-drawing table with all metric families,\n * inline regression deltas (↑/↓), and threshold violations in red.\n * formatJsonReport serialises the raw EvalReport for machine consumption.\n */\n\nimport { bold, dim, error as colorError } from \"../utils/output.js\";\nimport type { EvalReport, EvalDelta, HealthRuleResult, CitationJudgement } from \"./types.js\";\nimport type { CacheSummary } from \"./cache.js\";\n\nconst BOX_WIDTH = 49;\nconst HORIZONTAL = \"─\".repeat(BOX_WIDTH);\n\nfunction line(content = \"\"): string {\n return `│ ${content.padEnd(BOX_WIDTH - 2)} │`;\n}\n\nfunction top(): string {\n return `┌${HORIZONTAL}┐`;\n}\n\nfunction divider(): string {\n return `├${HORIZONTAL}┤`;\n}\n\nfunction bottom(): string {\n return `└${HORIZONTAL}┘`;\n}\n\n/** Format a signed delta as ↑/↓ string, or empty string if zero/absent. */\nfunction fmtDelta(value: number | undefined): string {\n if (value === undefined || value === 0) return \"\";\n const abs = Math.abs(value).toFixed(1).replace(/\\.0$/, \"\");\n return value > 0 ? dim(` (↑${abs})`) : dim(` (↓${abs})`);\n}\n\n/** Format a rule row with count and deduction. */\nfunction ruleRow(rule: HealthRuleResult): string {\n if (rule.count === 0) return \"\";\n const label = ` ${rule.rule}:`;\n const right = `${rule.count} (−${rule.deduction})`;\n const gap = BOX_WIDTH - 4 - label.length - right.length;\n return line(`${label}${\" \".repeat(Math.max(1, gap))}${right}`);\n}\n\nfunction formatHealth(report: EvalReport, delta: EvalDelta | undefined): string[] {\n const scoreDelta = fmtDelta(delta?.healthScore);\n const rows = [\n line(),\n line(bold(`Structural Health: ${report.health.score} / 100${scoreDelta}`)),\n ];\n for (const rule of report.health.rules) {\n const row = ruleRow(rule);\n if (row) rows.push(row);\n }\n return rows;\n}\n\nfunction formatCoverage(report: EvalReport, delta: EvalDelta | undefined): string[] {\n const cov = report.citationCoverage;\n const covDelta = fmtDelta(delta?.citationCoveragePercent);\n const precDelta = fmtDelta(delta?.citationPrecisionPercent);\n return [\n line(),\n line(bold(`Citation Coverage: ${cov.coveragePercent.toFixed(0)}%${covDelta}`)),\n line(` ${cov.citedParagraphs} / ${cov.totalProseParagraphs} prose paragraphs cited`),\n line(\n ` Precision: ${cov.precisionPercent.toFixed(0)}%${precDelta} (${cov.validCitations}/${cov.totalCitations} valid)`,\n ),\n ];\n}\n\nfunction formatSupport(report: EvalReport, delta: EvalDelta | undefined): string[] {\n const s = report.citationSupport;\n if (!s) return [];\n const meanDelta = fmtDelta(delta?.citationSupportMean);\n const pctOf = (n: number) =>\n s.sampledCount === 0 ? \"—\" : `${((n / s.sampledCount) * 100).toFixed(0)}%`;\n const rows = [\n line(),\n line(bold(`Citation Support (${s.sampledCount} sampled):`)),\n line(` Mean score: ${s.meanScore.toFixed(2)} / 2.0${meanDelta}`),\n line(` Fully supported: ${s.fullySupported} (${pctOf(s.fullySupported)})`),\n line(` Partially supported: ${s.partiallySupported} (${pctOf(s.partiallySupported)})`),\n line(` Unsupported: ${s.unsupported} (${pctOf(s.unsupported)})`),\n ];\n if (s.judgeErrors > 0) {\n rows.push(line(colorError(` Judge errors: ${s.judgeErrors}`)));\n }\n return rows;\n}\n\nfunction formatStats(report: EvalReport): string[] {\n const s = report.stats;\n return [\n line(),\n line(bold(\"Scale:\")),\n line(\n ` Sources: ${s.sourceCount} Pages: ${s.pageCount} Chunks: ${s.chunkEmbeddingCount}`,\n ),\n line(` Wiki size: ${s.totalWikiChars.toLocaleString()} chars`),\n ];\n}\n\nfunction formatViolations(violations: string[]): string[] {\n if (violations.length === 0) return [];\n return [line(), ...violations.map((v) => line(colorError(`[FAIL] ${v}`)))];\n}\n\n/**\n * Render a human-readable box-drawing table for terminal output.\n * @param report - The completed eval report.\n */\nexport function formatTerminalReport(report: EvalReport): string {\n const delta = report.delta;\n const rows = [\n top(),\n line(bold(\"llmwiki eval — Wiki Quality Report\")),\n divider(),\n ...formatHealth(report, delta),\n ...formatCoverage(report, delta),\n ...formatSupport(report, delta),\n ...formatStats(report),\n ...formatViolations(report.thresholdViolations),\n line(),\n bottom(),\n ];\n return rows.join(\"\\n\");\n}\n\n/**\n * Serialise the eval report as formatted JSON for machine consumption.\n * @param report - The completed eval report.\n */\nexport function formatJsonReport(report: EvalReport): string {\n return JSON.stringify(report, null, 2);\n}\n\n/** Truncate a timestamp ISO string to \"YYYY-MM-DD HH:MM\" for table display. */\nfunction fmtTimestamp(iso: string): string {\n return iso.slice(0, 16).replace(\"T\", \" \");\n}\n\n/**\n * Render a trend table of past eval runs for `llmwiki eval history`.\n * Each row shows date, suite, health score, coverage %, and mean citation support.\n * @param reports - Eval reports ordered oldest-first (from loadHistory).\n */\nexport function formatHistoryTable(reports: EvalReport[]): string {\n if (reports.length === 0) return \"No eval history found. Run `llmwiki eval` to record the first run.\";\n\n const header = `${\"Date\".padEnd(18)}${\"Suite\".padEnd(7)}${\"Health\".padEnd(8)}${\"Coverage\".padEnd(10)}Support`;\n const divider = \"─\".repeat(header.length);\n const rows = reports.map((r) => {\n const support = r.citationSupport\n ? r.citationSupport.meanScore.toFixed(2)\n : \"—\";\n return [\n fmtTimestamp(r.timestamp).padEnd(18),\n r.suite.padEnd(7),\n String(r.health.score).padEnd(8),\n `${r.citationCoverage.coveragePercent.toFixed(0)}%`.padEnd(10),\n support,\n ].join(\"\");\n });\n\n return [`Eval History (${reports.length} run${reports.length === 1 ? \"\" : \"s\"})`, divider, header, divider, ...rows].join(\"\\n\");\n}\n\nconst SCORE_LABELS: Record<number, string> = {\n 2: \"fully supported\",\n 1: \"partially supported\",\n 0: \"unsupported\",\n};\n\nfunction pct(n: number, total: number): string {\n return total === 0 ? \"0%\" : `${((n / total) * 100).toFixed(0)}%`;\n}\n\n/**\n * Render a summary of the citation cache for `llmwiki eval cache show`.\n * @param judgements - All cached judgements.\n * @param summary - Pre-computed score distribution and per-page counts.\n */\nexport function formatCacheShow(judgements: CitationJudgement[], summary: CacheSummary): string {\n const lines: string[] = [bold(`Citation Cache · ${summary.total} judgements`)];\n\n if (summary.total === 0) return lines.join(\"\\n\");\n\n lines.push(\"\");\n lines.push(` Score 2 (fully supported): ${summary.fullySupported} (${pct(summary.fullySupported, summary.total)})`);\n lines.push(` Score 1 (partially supported): ${summary.partiallySupported} (${pct(summary.partiallySupported, summary.total)})`);\n lines.push(` Score 0 (unsupported): ${summary.unsupported} (${pct(summary.unsupported, summary.total)})`);\n\n if (summary.byPage.length > 0) {\n lines.push(\"\");\n lines.push(\" Top pages:\");\n for (const { slug, count } of summary.byPage.slice(0, 10)) {\n lines.push(` ${slug}: ${count} judgement${count === 1 ? \"\" : \"s\"}`);\n }\n }\n\n // Suppress unused parameter warning — judgements reserved for future per-file listing\n void judgements;\n return lines.join(\"\\n\");\n}\n\nconst JUDGEMENT_DIVIDER = \"─\".repeat(55);\n\n/**\n * Render individual citation judgements for `llmwiki eval judgements`.\n * Each entry shows page, file+lines, claim text, source span, score, and reason.\n * @param judgements - Already-filtered list of judgements to display.\n */\nexport function formatJudgementsDisplay(judgements: CitationJudgement[]): string {\n if (judgements.length === 0) return \"No judgements to display.\";\n\n const blocks = judgements.map((j, i) => {\n const scoreLabel = SCORE_LABELS[j.score] ?? \"unknown\";\n const header = `[${i + 1}/${judgements.length}] Page: ${j.pageSlug} Score: ${j.score} (${scoreLabel})`;\n return [\n JUDGEMENT_DIVIDER,\n header,\n ` File: ${j.citedFile} Lines: ${j.lineStart}–${j.lineEnd}`,\n ` Claim: \"${j.claimText}\"`,\n ` Span: \"${j.spanText}\"`,\n ` Reason: ${j.reason}`,\n ].join(\"\\n\");\n });\n\n return [...blocks, JUDGEMENT_DIVIDER].join(\"\\n\");\n}\n","/**\n * Citation cache management for the llmwiki eval harness.\n *\n * The citation cache stores LLM judge results in .llmwiki/eval/citation-cache.jsonl\n * so subsequent `eval --suite full` runs skip already-judged (claim, span) pairs.\n * This module provides helpers to inspect and clear that cache independently of\n * running a full evaluation.\n */\n\nimport { unlink, readFile } from \"fs/promises\";\nimport { existsSync } from \"fs\";\nimport path from \"path\";\nimport type { CitationJudgement } from \"./types.js\";\n\nconst CACHE_FILE = path.join(\".llmwiki\", \"eval\", \"citation-cache.jsonl\");\n\nexport interface CacheSummary {\n total: number;\n fullySupported: number;\n partiallySupported: number;\n unsupported: number;\n byPage: Array<{ slug: string; count: number }>;\n}\n\n/**\n * Delete the citation cache file.\n * Returns true if the file existed and was deleted, false if it was already absent.\n * @param root - Absolute path to the project root.\n */\nexport async function clearCitationCache(root: string): Promise<boolean> {\n const cachePath = path.join(root, CACHE_FILE);\n if (!existsSync(cachePath)) return false;\n await unlink(cachePath);\n return true;\n}\n\n/**\n * Load all cached citation judgements as an array.\n * Returns an empty array if the cache file does not exist.\n * Malformed lines are silently skipped.\n * @param root - Absolute path to the project root.\n */\nexport async function loadCitationCache(root: string): Promise<CitationJudgement[]> {\n const cachePath = path.join(root, CACHE_FILE);\n if (!existsSync(cachePath)) return [];\n\n const content = await readFile(cachePath, \"utf-8\");\n const judgements: CitationJudgement[] = [];\n for (const line of content.trim().split(\"\\n\").filter(Boolean)) {\n try {\n judgements.push(JSON.parse(line) as CitationJudgement);\n } catch {\n // Skip malformed cache lines\n }\n }\n return judgements;\n}\n\n/**\n * Aggregate cached judgements into a summary with score distribution and per-page counts.\n * @param judgements - Array of citation judgements loaded from the cache.\n */\nexport function summarizeCitationCache(judgements: CitationJudgement[]): CacheSummary {\n let fullySupported = 0;\n let partiallySupported = 0;\n let unsupported = 0;\n const pageCounts = new Map<string, number>();\n\n for (const j of judgements) {\n if (j.score === 2) fullySupported++;\n else if (j.score === 1) partiallySupported++;\n else unsupported++;\n pageCounts.set(j.pageSlug, (pageCounts.get(j.pageSlug) ?? 0) + 1);\n }\n\n const byPage = [...pageCounts.entries()]\n .map(([slug, count]) => ({ slug, count }))\n .sort((a, b) => b.count - a.count);\n\n return { total: judgements.length, fullySupported, partiallySupported, unsupported, byPage };\n}\n","/**\n * CLI action handlers for `llmwiki eval` and its subcommands.\n *\n * Default action (evalCommand): runs the full or fast eval suite and outputs\n * a terminal table or JSON report, appending the result to history.jsonl.\n *\n * Subcommand handlers:\n * evalCacheClearCommand — delete the citation judgement cache\n * evalCacheShowCommand — print a cache summary\n * evalReportCommand — re-display the most recent eval report\n * evalHistoryCommand — show a trend table of past runs\n * evalJudgementsCommand — browse individual citation judgements with filters\n */\n\nimport { evaluateHealth } from \"../eval/health.js\";\nimport { evaluateCitationCoverage } from \"../eval/citation-coverage.js\";\nimport { evaluateCitationSupport } from \"../eval/citation-support.js\";\nimport { collectStats, appendHistory, loadPreviousReport, loadHistory } from \"../eval/stats.js\";\nimport { computeDelta } from \"../eval/delta.js\";\nimport { checkThresholds } from \"../eval/thresholds.js\";\nimport {\n formatTerminalReport,\n formatJsonReport,\n formatHistoryTable,\n formatCacheShow,\n formatJudgementsDisplay,\n} from \"../eval/report.js\";\nimport { clearCitationCache, loadCitationCache, summarizeCitationCache } from \"../eval/cache.js\";\nimport type { EvalReport } from \"../eval/types.js\";\n\ninterface EvalOptions {\n suite?: string;\n out?: string;\n sample?: string;\n}\n\nconst DEFAULT_SAMPLE_SIZE = 20;\n\ninterface ResolvedEvalOptions {\n suite: \"fast\" | \"full\";\n sampleSize: number;\n outFormat: \"terminal\" | \"json\";\n}\n\n/**\n * Parse and validate --sample as a positive integer.\n * @throws if the value is not a positive integer (rejects 0, negatives, decimals, non-numeric).\n */\nexport function parseSampleSize(raw: string): number {\n const n = Number(raw);\n if (!Number.isInteger(n) || n <= 0) {\n throw new Error(`--sample must be a positive integer (got \"${raw}\")`);\n }\n return n;\n}\n\nfunction resolveEvalOptions(options: EvalOptions): ResolvedEvalOptions {\n return {\n suite: options.suite === \"full\" ? \"full\" : \"fast\",\n sampleSize: parseSampleSize(options.sample ?? String(DEFAULT_SAMPLE_SIZE)),\n outFormat: options.out === \"json\" ? \"json\" : \"terminal\",\n };\n}\n\nasync function runEvalComponents(root: string, suite: \"fast\" | \"full\", sampleSize: number) {\n const [health, citationCoverage, stats, previousReport] = await Promise.all([\n evaluateHealth(root),\n evaluateCitationCoverage(root),\n collectStats(root),\n loadPreviousReport(root),\n ]);\n const citationSupport = suite === \"full\"\n ? await evaluateCitationSupport(root, sampleSize, previousReport?.citationSupport?.sampledHashes ?? [])\n : undefined;\n return { health, citationCoverage, stats, previousReport, citationSupport };\n}\n\nasync function buildReport(root: string, components: Awaited<ReturnType<typeof runEvalComponents>>, suite: \"fast\" | \"full\"): Promise<EvalReport> {\n const { health, citationCoverage, stats, previousReport, citationSupport } = components;\n const partial = {\n suite,\n timestamp: new Date().toISOString(),\n health,\n citationCoverage,\n stats,\n ...(citationSupport ? { citationSupport } : {}),\n };\n const delta = previousReport ? computeDelta(partial as EvalReport, previousReport) : undefined;\n const thresholdViolations = await checkThresholds(partial as EvalReport, root);\n return { ...partial, ...(delta ? { delta } : {}), thresholdViolations };\n}\n\n/**\n * Run the eval harness against the current project.\n * @param options - Parsed CLI options.\n */\nexport default async function evalCommand(options: EvalOptions = {}): Promise<void> {\n const root = process.cwd();\n const { suite, sampleSize, outFormat } = resolveEvalOptions(options);\n\n const components = await runEvalComponents(root, suite, sampleSize);\n const report = await buildReport(root, components, suite);\n await appendHistory(root, report);\n\n const output = outFormat === \"json\" ? formatJsonReport(report) : formatTerminalReport(report);\n console.log(output);\n\n if (report.thresholdViolations.length > 0) {\n process.exit(1);\n }\n}\n\n/** Delete the citation judgement cache so all pairs are re-judged on the next full run. */\nexport async function evalCacheClearCommand(): Promise<void> {\n const deleted = await clearCitationCache(process.cwd());\n console.log(deleted ? \"Citation cache cleared.\" : \"No citation cache found.\");\n}\n\n/** Print a summary of cached citation judgements (score distribution + per-page counts). */\nexport async function evalCacheShowCommand(): Promise<void> {\n const judgements = await loadCitationCache(process.cwd());\n const summary = summarizeCitationCache(judgements);\n console.log(formatCacheShow(judgements, summary));\n}\n\ninterface ReportOptions { out?: string }\n\n/** Re-display the most recent eval report from history without running a new evaluation. */\nexport async function evalReportCommand(options: ReportOptions = {}): Promise<void> {\n const report = await loadPreviousReport(process.cwd());\n if (!report) {\n console.log(\"No eval history found. Run `llmwiki eval` to record the first run.\");\n return;\n }\n const output = options.out === \"json\" ? formatJsonReport(report) : formatTerminalReport(report);\n console.log(output);\n}\n\ninterface HistoryOptions { n?: string; out?: string }\n\n/** Show a trend table of the last N eval runs from history.jsonl. */\nexport async function evalHistoryCommand(options: HistoryOptions = {}): Promise<void> {\n const n = parseInt(options.n ?? \"10\", 10);\n const reports = await loadHistory(process.cwd(), n);\n if (options.out === \"json\") {\n console.log(JSON.stringify(reports, null, 2));\n return;\n }\n console.log(formatHistoryTable(reports));\n}\n\ninterface JudgementsOptions { score?: string; page?: string; n?: string; out?: string }\n\nfunction filterJudgements(judgements: Awaited<ReturnType<typeof loadCitationCache>>, options: JudgementsOptions) {\n let result = judgements;\n if (options.score !== undefined) result = result.filter((j) => j.score === parseInt(options.score!, 10));\n if (options.page) result = result.filter((j) => j.pageSlug === options.page);\n if (options.n !== undefined) result = result.slice(0, parseInt(options.n, 10));\n return result;\n}\n\n/** Browse cached citation judgements with optional filters by score, page, and count. */\nexport async function evalJudgementsCommand(options: JudgementsOptions = {}): Promise<void> {\n const judgements = filterJudgements(await loadCitationCache(process.cwd()), options);\n if (options.out === \"json\") {\n console.log(JSON.stringify(judgements, null, 2));\n return;\n }\n console.log(formatJudgementsDisplay(judgements));\n}\n","/**\n * Commander action for `llmwiki export [--target <name>]`.\n *\n * Transforms existing wiki content into portable export artifacts and writes\n * them into dist/exports/ (relative to the project root). Supports six formats:\n *\n * llms-txt — concise index per llmstxt.org spec → llms.txt\n * llms-full-txt — full content export → llms-full.txt\n * json — pages + metadata as JSON → wiki.json\n * json-ld — Schema.org JSON-LD graph → wiki.jsonld\n * graphml — directed link graph as XML → wiki.graphml\n * marp — Marp slide deck → wiki.md\n *\n * No LLM calls are made — export is a pure transformation of wiki content.\n */\n\nimport path from \"path\";\nimport { createRequire } from \"module\";\nimport { atomicWrite } from \"../utils/markdown.js\";\nimport * as output from \"../utils/output.js\";\nimport { collectExportPages } from \"../export/collect.js\";\nimport { buildLlmsTxt, buildLlmsFullTxt } from \"../export/llms-txt.js\";\nimport { buildJsonExport } from \"../export/json-export.js\";\nimport { buildJsonLd } from \"../export/json-ld.js\";\nimport { buildGraphml } from \"../export/graphml.js\";\nimport { buildMarp } from \"../export/marp.js\";\nimport { EXPORT_TARGETS, MARP_SOURCES } from \"../export/types.js\";\nimport type { ExportPage, ExportTarget, MarpSource } from \"../export/types.js\";\n\nconst require = createRequire(import.meta.url);\n\n/** Output paths relative to dist/exports/ within the project root. */\nconst EXPORT_DIR = \"dist/exports\";\n\n/** Map each target to its output filename. */\nconst TARGET_FILENAMES: Record<ExportTarget, string> = {\n \"llms-txt\": \"llms.txt\",\n \"llms-full-txt\": \"llms-full.txt\",\n json: \"wiki.json\",\n \"json-ld\": \"wiki.jsonld\",\n graphml: \"wiki.graphml\",\n marp: \"wiki.md\",\n};\n\n/** Options accepted by exportCommand and its programmatic entry point. */\nexport interface ExportOptions {\n /** Limit export to a single target. When absent all targets are produced. */\n target?: string;\n /**\n * For the marp target: which page kinds to include.\n * Accepts \"concepts\", \"queries\", or \"all\" (default when absent).\n */\n source?: string;\n}\n\n/** Result returned by runExport for testing and MCP consumers. */\nexport interface ExportResult {\n /** Absolute paths of files that were written. */\n written: string[];\n /** Number of pages included in each export. */\n pageCount: number;\n}\n\n/** Resolve the human-readable project title from package.json, defaulting gracefully. */\nfunction resolveProjectTitle(root: string): string {\n try {\n const pkg = require(path.join(root, \"package.json\")) as { name?: string };\n return typeof pkg.name === \"string\" ? pkg.name : \"Knowledge Wiki\";\n } catch {\n return \"Knowledge Wiki\";\n }\n}\n\n/** Return true when the given string is a valid ExportTarget. */\nfunction isValidTarget(value: string): value is ExportTarget {\n return (EXPORT_TARGETS as readonly string[]).includes(value);\n}\n\n/** Return true when the given string is a valid MarpSource. */\nfunction isValidMarpSource(value: string): value is MarpSource {\n return (MARP_SOURCES as readonly string[]).includes(value);\n}\n\n/** Resolve and validate the marp source filter. Throws for unknown values. */\nfunction resolveMarpSource(rawSource: string | undefined): MarpSource {\n if (!rawSource) return \"all\";\n if (!isValidMarpSource(rawSource)) {\n throw new Error(\n `Unknown --source value \"${rawSource}\". Valid values: ${MARP_SOURCES.join(\", \")}`,\n );\n }\n return rawSource;\n}\n\n/** Build the content string for a single target. */\nfunction buildContent(\n target: ExportTarget,\n pages: ReturnType<typeof collectExportPages> extends Promise<infer T> ? T : never,\n projectTitle: string,\n marpSource: MarpSource,\n): string {\n switch (target) {\n case \"llms-txt\":\n return buildLlmsTxt(pages, projectTitle);\n case \"llms-full-txt\":\n return buildLlmsFullTxt(pages, projectTitle);\n case \"json\":\n return buildJsonExport(pages);\n case \"json-ld\":\n return buildJsonLd(pages);\n case \"graphml\":\n return buildGraphml(pages);\n case \"marp\":\n return buildMarp(pages, projectTitle, marpSource);\n }\n}\n\n/**\n * Compute the page count to report in the CLI summary. When marp is the\n * only target and --source narrows the deck, report the filtered count so\n * the summary doesn't overstate what was exported. Multi-target runs keep\n * the collected total because non-marp targets always include every page.\n */\nfunction computeReportedPageCount(\n pages: ExportPage[],\n targets: ExportTarget[],\n marpSource: MarpSource,\n): number {\n const onlyMarpTarget = targets.length === 1 && targets[0] === \"marp\";\n if (onlyMarpTarget && marpSource !== \"all\") {\n return pages.filter((p) => p.pageDirectory === marpSource).length;\n }\n return pages.length;\n}\n\n/**\n * Programmatic entry point for the export pipeline.\n * @param root - Absolute path to the project root directory.\n * @param options - Export options (optional target filter).\n * @returns Paths written and page count.\n */\nexport async function runExport(root: string, options: ExportOptions = {}): Promise<ExportResult> {\n const pages = await collectExportPages(root);\n const projectTitle = resolveProjectTitle(root);\n\n const targets = resolveTargets(options.target);\n const marpSource = resolveMarpSource(options.source);\n const written: string[] = [];\n\n for (const target of targets) {\n const content = buildContent(target, pages, projectTitle, marpSource);\n const outPath = path.join(root, EXPORT_DIR, TARGET_FILENAMES[target]);\n await atomicWrite(outPath, content);\n written.push(outPath);\n output.status(\"+\", output.success(`Exported ${target} → ${output.source(outPath)}`));\n }\n\n return { written, pageCount: computeReportedPageCount(pages, targets, marpSource) };\n}\n\n/**\n * Resolve the list of targets to run.\n * When a specific target is given it is validated; an error is thrown for unknown values.\n * Defaults to all targets.\n */\nfunction resolveTargets(rawTarget: string | undefined): ExportTarget[] {\n if (!rawTarget) return [...EXPORT_TARGETS];\n\n if (!isValidTarget(rawTarget)) {\n throw new Error(\n `Unknown export target \"${rawTarget}\". Valid targets: ${EXPORT_TARGETS.join(\", \")}`,\n );\n }\n\n return [rawTarget];\n}\n\n/**\n * CLI action for `llmwiki export`.\n * @param root - Project root directory (defaults to cwd).\n * @param options - Commander-parsed options.\n */\nexport default async function exportCommand(\n root: string,\n options: ExportOptions,\n): Promise<void> {\n output.header(\"Exporting wiki\");\n const { written, pageCount } = await runExport(root, options);\n output.status(\n \"✓\",\n output.success(`Done — ${pageCount} pages exported to ${written.length} file(s).`),\n );\n}\n","/**\n * Wiki page collector for the export subsystem.\n *\n * Thin wrapper over `src/wiki/collect.ts::collectRawWikiPages()` that\n * applies export-specific filters (drop orphaned and untitled pages) and\n * decorates each surviving record with the export-facing fields (summary,\n * sources, tags, timestamps, link slugs). The wikilink extraction regex\n * and slug-normalization helper live in `src/wiki/collect.ts` so both\n * export and viewer callers share one source.\n */\n\nimport { collectRawWikiPages, extractWikilinkSlugs } from \"../wiki/collect.js\";\nimport type { RawWikiPage } from \"../wiki/collect.js\";\nimport type { ExportPage } from \"./types.js\";\n\nexport { extractWikilinkSlugs };\n\n/**\n * Normalize a kept page into the shape every export writer consumes.\n * Caller is responsible for filtering out records that fail the export\n * gate (orphaned, untitled, unreadable).\n */\nfunction toExportPage(raw: RawWikiPage): ExportPage {\n const meta = raw.frontmatter;\n return {\n title: raw.title as string,\n slug: raw.slug,\n pageDirectory: raw.pageDirectory,\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n sources: Array.isArray(meta.sources)\n ? (meta.sources as unknown[]).filter((s): s is string => typeof s === \"string\")\n : [],\n tags: Array.isArray(meta.tags)\n ? (meta.tags as unknown[]).filter((t): t is string => typeof t === \"string\")\n : [],\n createdAt: typeof meta.createdAt === \"string\" ? meta.createdAt : new Date().toISOString(),\n updatedAt: typeof meta.updatedAt === \"string\" ? meta.updatedAt : new Date().toISOString(),\n links: extractWikilinkSlugs(raw.body),\n body: raw.body,\n };\n}\n\n/**\n * Collect all exportable wiki pages from `wiki/concepts/` and `wiki/queries/`.\n * Drops orphaned and untitled records — those are diagnosed by the viewer,\n * not exported. Returns the surviving pages sorted by title.\n */\nexport async function collectExportPages(root: string): Promise<ExportPage[]> {\n const raw = await collectRawWikiPages(root);\n const kept = raw.filter((page) => page.parseStatus.hasTitle && !page.parseStatus.orphaned);\n const pages = kept.map(toExportPage);\n pages.sort((a, b) => a.title.localeCompare(b.title));\n return pages;\n}\n","/**\n * llms.txt export format writer.\n *\n * Produces a machine-readable index per the llmstxt.org spec:\n * - H1 project title\n * - Optional blockquote description\n * - H2-delimited sections per page directory (## Concepts, ## Saved Queries)\n * - Bullet entries: [Title](path): summary | tags | sources | timestamps\n *\n * The companion llms-full.txt format appends the full body of every page\n * so a model can read the entire wiki in one file.\n *\n * Reference: https://llmstxt.org\n */\n\nimport type { ExportPage } from \"./types.js\";\n\n/**\n * Build the wiki-relative path for a page based on its source directory.\n * Concepts live in wiki/concepts/, queries in wiki/queries/.\n */\nfunction pageRelativePath(page: ExportPage): string {\n return `wiki/${page.pageDirectory}/${page.slug}.md`;\n}\n\n/**\n * Build the inline note clause for a page entry.\n * Follows the colon after the markdown link per spec.\n */\nfunction buildEntryNote(page: ExportPage): string {\n const parts: string[] = [];\n if (page.summary) parts.push(page.summary);\n if (page.tags.length > 0) parts.push(`tags: ${page.tags.join(\", \")}`);\n if (page.sources.length > 0) parts.push(`sources: ${page.sources.join(\", \")}`);\n parts.push(`created: ${page.createdAt}`);\n parts.push(`updated: ${page.updatedAt}`);\n return parts.join(\" | \");\n}\n\n/** Format a single page as a spec-compliant bullet entry. */\nfunction formatPageEntry(page: ExportPage): string {\n const note = buildEntryNote(page);\n return `- [${page.title}](${pageRelativePath(page)}): ${note}`;\n}\n\n/** Build entries for a filtered subset of pages under an H2 section. */\nfunction buildSection(heading: string, pages: ExportPage[]): string[] {\n if (pages.length === 0) return [];\n return [`## ${heading}`, \"\", ...pages.map(formatPageEntry), \"\"];\n}\n\n/**\n * Build the concise llms.txt index content per the llmstxt.org spec.\n * Pages are split into Concepts and Saved Queries sections (H2 delimited).\n * @param pages - Sorted array of export pages.\n * @param projectTitle - Human-readable wiki title shown as the H1.\n * @returns Full llms.txt string.\n */\nexport function buildLlmsTxt(pages: ExportPage[], projectTitle: string): string {\n const concepts = pages.filter((p) => p.pageDirectory === \"concepts\");\n const queries = pages.filter((p) => p.pageDirectory === \"queries\");\n\n const lines: string[] = [\n `# ${projectTitle}`,\n \"\",\n `> ${pages.length} pages — exported ${new Date().toISOString()}`,\n \"\",\n ...buildSection(\"Concepts\", concepts),\n ...buildSection(\"Saved Queries\", queries),\n ];\n\n return lines.join(\"\\n\");\n}\n\n/**\n * Build the full llms-full.txt content (index + full page bodies).\n * Each page is separated by a horizontal rule and includes its metadata block.\n * @param pages - Sorted array of export pages.\n * @param projectTitle - Human-readable wiki title shown as the H1.\n * @returns Full llms-full.txt string.\n */\nexport function buildLlmsFullTxt(pages: ExportPage[], projectTitle: string): string {\n const sections: string[] = [buildLlmsTxt(pages, projectTitle)];\n\n for (const page of pages) {\n const tags = page.tags.length > 0 ? `\\nTags: ${page.tags.join(\", \")}` : \"\";\n const sources = page.sources.length > 0 ? `\\nSources: ${page.sources.join(\", \")}` : \"\";\n const header = [\n \"---\",\n `## ${page.title}`,\n `> ${page.summary}${tags}${sources}`,\n `Created: ${page.createdAt} | Updated: ${page.updatedAt}`,\n \"\",\n ].join(\"\\n\");\n sections.push(`${header}\\n${page.body.trim()}\\n`);\n }\n\n return sections.join(\"\\n\");\n}\n","/**\n * JSON export format writer.\n *\n * Produces a structured JSON document containing all wiki pages and their\n * metadata. The schema is intentionally simple and human-readable so it can\n * be consumed directly by scripts, agents, or downstream pipelines without\n * additional transformation.\n *\n * Schema:\n * { exportedAt, pageCount, pages: ExportPage[] }\n */\n\nimport type { ExportPage } from \"./types.js\";\n\n/** Top-level shape of the JSON export file. */\ninterface JsonExportDocument {\n exportedAt: string;\n pageCount: number;\n pages: ExportPage[];\n}\n\n/**\n * Build the JSON export document from a list of export pages.\n * @param pages - Sorted array of export pages.\n * @returns Pretty-printed JSON string.\n */\nexport function buildJsonExport(pages: ExportPage[]): string {\n const doc: JsonExportDocument = {\n exportedAt: new Date().toISOString(),\n pageCount: pages.length,\n pages,\n };\n return JSON.stringify(doc, null, 2);\n}\n","/**\n * JSON-LD graph export format writer.\n *\n * Produces a JSON-LD document using schema.org vocabulary so the wiki graph\n * can be consumed by linked-data tooling, knowledge graph platforms, or any\n * agent that understands the Schema.org ontology.\n *\n * Each wiki page is represented as a schema:Article node. Links derived from\n * [[wikilinks]] are expressed as schema:mentions relationships between nodes.\n */\n\nimport type { ExportPage } from \"./types.js\";\n\n/** Base URL used for page IRIs when no external URL is configured. */\nconst LOCAL_BASE = \"urn:llmwiki:\";\n\n/** Build the IRI for a page slug. */\nfunction pageIri(slug: string): string {\n return `${LOCAL_BASE}${slug}`;\n}\n\n/** Serialise one ExportPage as a JSON-LD Article node. */\nfunction pageToJsonLd(page: ExportPage): Record<string, unknown> {\n const node: Record<string, unknown> = {\n \"@id\": pageIri(page.slug),\n \"@type\": \"Article\",\n name: page.title,\n description: page.summary,\n dateCreated: page.createdAt,\n dateModified: page.updatedAt,\n };\n\n if (page.tags.length > 0) {\n node[\"keywords\"] = page.tags;\n }\n\n // schema.org/isBasedOn is the standard property for citing source material.\n if (page.sources.length > 0) {\n node[\"isBasedOn\"] = page.sources;\n }\n\n if (page.links.length > 0) {\n node[\"mentions\"] = page.links.map((slug) => ({ \"@id\": pageIri(slug) }));\n }\n\n return node;\n}\n\n/**\n * Build the JSON-LD graph document from a list of export pages.\n * @param pages - Sorted array of export pages.\n * @returns Pretty-printed JSON-LD string.\n */\nexport function buildJsonLd(pages: ExportPage[]): string {\n const doc = {\n \"@context\": \"https://schema.org\",\n \"@graph\": pages.map(pageToJsonLd),\n };\n return JSON.stringify(doc, null, 2);\n}\n","/**\n * GraphML export format writer.\n *\n * Produces a GraphML XML document representing the wiki link graph.\n * Each page becomes a node; each [[wikilink]] between pages becomes a\n * directed edge. Node attributes carry page metadata (title, summary, tags).\n *\n * GraphML is the standard XML format for graph exchange and is supported by\n * Gephi, yEd, NetworkX, and many other graph tools.\n */\n\nimport type { ExportPage } from \"./types.js\";\n\n/** XML special characters that must be escaped in attribute values and text. */\nconst XML_ESCAPES: Record<string, string> = {\n \"&\": \"&\",\n \"<\": \"<\",\n \">\": \">\",\n '\"': \""\",\n \"'\": \"'\",\n};\n\n/** Escape a string for safe inclusion in XML. */\nfunction escapeXml(value: string): string {\n return value.replace(/[&<>\"']/g, (ch) => XML_ESCAPES[ch] ?? ch);\n}\n\n/** GraphML attribute key definitions. */\nconst KEY_DEFS = [\n '<key id=\"title\" for=\"node\" attr.name=\"title\" attr.type=\"string\"/>',\n '<key id=\"summary\" for=\"node\" attr.name=\"summary\" attr.type=\"string\"/>',\n '<key id=\"tags\" for=\"node\" attr.name=\"tags\" attr.type=\"string\"/>',\n '<key id=\"sources\" for=\"node\" attr.name=\"sources\" attr.type=\"string\"/>',\n '<key id=\"createdAt\" for=\"node\" attr.name=\"createdAt\" attr.type=\"string\"/>',\n '<key id=\"updatedAt\" for=\"node\" attr.name=\"updatedAt\" attr.type=\"string\"/>',\n].join(\"\\n \");\n\n/** Serialise one ExportPage as a GraphML <node> element. */\nfunction pageToNode(page: ExportPage): string {\n const tags = page.tags.join(\", \");\n const sources = page.sources.join(\", \");\n return [\n ` <node id=\"${escapeXml(page.slug)}\">`,\n ` <data key=\"title\">${escapeXml(page.title)}</data>`,\n ` <data key=\"summary\">${escapeXml(page.summary)}</data>`,\n ` <data key=\"tags\">${escapeXml(tags)}</data>`,\n ` <data key=\"sources\">${escapeXml(sources)}</data>`,\n ` <data key=\"createdAt\">${escapeXml(page.createdAt)}</data>`,\n ` <data key=\"updatedAt\">${escapeXml(page.updatedAt)}</data>`,\n ` </node>`,\n ].join(\"\\n\");\n}\n\n/** Build all <edge> elements for a single source page's outgoing links. */\nfunction pageToEdges(page: ExportPage, knownSlugs: Set<string>): string[] {\n return page.links\n .filter((slug) => knownSlugs.has(slug))\n .map(\n (slug) =>\n ` <edge source=\"${escapeXml(page.slug)}\" target=\"${escapeXml(slug)}\"/>`,\n );\n}\n\n/**\n * Build the GraphML document from a list of export pages.\n * Only edges whose target slug exists in the page set are included so the\n * graph contains no dangling references.\n * @param pages - Sorted array of export pages.\n * @returns GraphML XML string.\n */\nexport function buildGraphml(pages: ExportPage[]): string {\n const knownSlugs = new Set(pages.map((p) => p.slug));\n const nodes = pages.map(pageToNode).join(\"\\n\");\n const edges = pages.flatMap((p) => pageToEdges(p, knownSlugs)).join(\"\\n\");\n\n return [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<graphml xmlns=\"http://graphml.graphdrawing.org/graphml\"',\n ' xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"',\n ' xsi:schemaLocation=\"http://graphml.graphdrawing.org/graphml',\n ' http://graphml.graphdrawing.org/graphml/1.0/graphml.xsd\">',\n ` ${KEY_DEFS}`,\n ' <graph id=\"wiki\" edgedefault=\"directed\">',\n nodes,\n edges,\n \" </graph>\",\n \"</graphml>\",\n \"\",\n ].join(\"\\n\");\n}\n","/**\n * Marp slide export format writer.\n *\n * Produces a single Markdown file with Marp frontmatter that can be rendered\n * as a slide deck by the Marp CLI or VS Code Marp extension. Each wiki page\n * becomes one slide showing the title, summary, tags, sources, timestamps,\n * and an excerpt of the body (first paragraph, up to a readable limit).\n *\n * The caller may pre-filter pages by source directory (\"concepts\" |\n * \"queries\" | \"all\") using the --source option on the CLI.\n *\n * Reference: https://marp.app/\n */\n\nimport type { ExportPage, MarpSource } from \"./types.js\";\n\n/** Maximum characters of body text to include per slide. */\nconst SLIDE_BODY_MAX_CHARS = 300;\n\n/** Extract the first prose paragraph from a markdown body. */\nfunction extractFirstParagraph(body: string): string {\n const trimmed = body.trim();\n // Take the first non-empty block separated by a blank line.\n const firstBlock = trimmed.split(/\\n\\s*\\n/)[0] ?? \"\";\n // Strip markdown headings and list markers so slides read cleanly.\n const stripped = firstBlock\n .replace(/^#{1,6}\\s+/gm, \"\")\n .replace(/^[-*+]\\s+/gm, \"\")\n .trim();\n if (stripped.length <= SLIDE_BODY_MAX_CHARS) return stripped;\n return `${stripped.slice(0, SLIDE_BODY_MAX_CHARS)}…`;\n}\n\n/** Build the speaker-notes block for a slide containing metadata. */\nfunction buildSpeakerNotes(page: ExportPage): string {\n const parts: string[] = [`created: ${page.createdAt}`, `updated: ${page.updatedAt}`];\n if (page.sources.length > 0) parts.push(`sources: ${page.sources.join(\", \")}`);\n return `<!-- ${parts.join(\" | \")} -->`;\n}\n\n/** Render one ExportPage as a Marp slide. */\nfunction pageToSlide(page: ExportPage): string {\n const tagLine = page.tags.length > 0 ? `\\n_Tags: ${page.tags.join(\", \")}_` : \"\";\n const excerpt = extractFirstParagraph(page.body);\n const notes = buildSpeakerNotes(page);\n return [\n `## ${page.title}`,\n \"\",\n `> ${page.summary}${tagLine}`,\n \"\",\n excerpt,\n \"\",\n notes,\n ].join(\"\\n\");\n}\n\n/**\n * Filter pages by the requested marp source directory.\n * \"all\" returns the full list unchanged.\n */\nfunction filterBySource(pages: ExportPage[], source: MarpSource): ExportPage[] {\n if (source === \"all\") return pages;\n return pages.filter((p) => p.pageDirectory === source);\n}\n\n/**\n * Build the Marp slide deck content from a list of export pages.\n * @param pages - Array of all export pages.\n * @param projectTitle - Shown on the title slide.\n * @param source - Which page directories to include (default \"all\").\n * @returns Full Marp markdown string.\n */\nexport function buildMarp(\n pages: ExportPage[],\n projectTitle: string,\n source: MarpSource = \"all\",\n): string {\n const filtered = filterBySource(pages, source);\n\n const frontmatter = [\n \"---\",\n \"marp: true\",\n \"theme: default\",\n \"paginate: true\",\n `title: \"${projectTitle}\"`,\n \"---\",\n ].join(\"\\n\");\n\n const titleSlide = [\n \"\",\n `# ${projectTitle}`,\n \"\",\n `${filtered.length} pages | ${new Date().toISOString()}`,\n ].join(\"\\n\");\n\n const slides = filtered.map((p) => `---\\n\\n${pageToSlide(p)}`);\n\n return [frontmatter, titleSlide, ...slides, \"\"].join(\"\\n\\n\");\n}\n","/**\n * Shared types for the llmwiki export subsystem.\n *\n * ExportPage is the normalised in-memory representation of a wiki page used\n * by every export format. It is derived from the page's YAML frontmatter plus\n * the wikilink graph extracted from the body.\n */\n\n/**\n * Which wiki/ subdirectory a page lives in.\n *\n * Intentionally distinct from the schema layer's `PageKind`\n * (concept/entity/comparison/overview) — this is a filesystem location, not\n * a semantic typology. Renaming avoids field collision when JSON export and\n * schema metadata are consumed by the same downstream tooling.\n */\nexport type PageDirectory = \"concepts\" | \"queries\";\n\n/** A fully-resolved wiki page ready for export serialisation. */\nexport interface ExportPage {\n /** Human-readable page title (from frontmatter). */\n title: string;\n /** Filesystem slug (filename without .md). */\n slug: string;\n /** Whether this page came from wiki/concepts or wiki/queries. */\n pageDirectory: PageDirectory;\n /** One-line page summary (from frontmatter). */\n summary: string;\n /** Source filenames cited in the page body. */\n sources: string[];\n /** Taxonomy tags (from frontmatter). */\n tags: string[];\n /** ISO-8601 creation timestamp. */\n createdAt: string;\n /** ISO-8601 last-updated timestamp. */\n updatedAt: string;\n /** Slugs of other pages this page links to via [[wikilinks]]. */\n links: string[];\n /** Full markdown body (without frontmatter). */\n body: string;\n}\n\n/**\n * Source filter for marp export: which page kinds to include.\n * \"all\" includes both concepts and queries (the default).\n */\nexport type MarpSource = \"concepts\" | \"queries\" | \"all\";\n\n/** All recognised marp source values — used for validation. */\nexport const MARP_SOURCES: readonly MarpSource[] = [\"concepts\", \"queries\", \"all\"];\n\n/** Supported export target identifiers. */\nexport type ExportTarget =\n | \"llms-txt\"\n | \"llms-full-txt\"\n | \"json\"\n | \"json-ld\"\n | \"graphml\"\n | \"marp\";\n\n/** All recognised export target names — used for validation. */\nexport const EXPORT_TARGETS: readonly ExportTarget[] = [\n \"llms-txt\",\n \"llms-full-txt\",\n \"json\",\n \"json-ld\",\n \"graphml\",\n \"marp\",\n];\n","/**\n * Commander actions for `llmwiki schema` subcommands.\n *\n * Exposes two operations:\n * - `schema init` writes a starter schema file seeded with sensible defaults\n * so users can customise page kinds and cross-link minimums without\n * hand-rolling the format.\n * - `schema show` prints the resolved schema a project would use, including\n * which file (if any) it was loaded from — helpful for debugging.\n */\n\nimport { existsSync } from \"fs\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport path from \"path\";\nimport * as output from \"../utils/output.js\";\nimport {\n buildDefaultSchema,\n defaultSchemaInitPath,\n loadSchema,\n serializeSchemaToYaml,\n} from \"../schema/index.js\";\n\n/**\n * Write a starter schema file to `.llmwiki/schema.json` under the project root.\n * Refuses to overwrite an existing file so `schema init` is safe to re-run.\n */\nexport async function schemaInitCommand(): Promise<void> {\n const root = process.cwd();\n const defaults = buildDefaultSchema();\n const targetPath = defaultSchemaInitPath(root);\n\n if (existsSync(targetPath)) {\n output.status(\"!\", output.warn(`Schema file already exists at ${targetPath}`));\n return;\n }\n\n await mkdir(path.dirname(targetPath), { recursive: true });\n const serializable = {\n version: defaults.version,\n defaultKind: defaults.defaultKind,\n kinds: defaults.kinds,\n seedPages: defaults.seedPages,\n };\n await writeFile(targetPath, `${JSON.stringify(serializable, null, 2)}\\n`, \"utf-8\");\n output.status(\"+\", output.success(`Wrote schema to ${targetPath}`));\n}\n\n/**\n * Print the resolved schema for the current project, showing defaults and\n * whichever file (if any) supplied overrides.\n */\nexport async function schemaShowCommand(): Promise<void> {\n const schema = await loadSchema(process.cwd());\n const loadedFrom = schema.loadedFrom ?? \"(defaults — no schema file found)\";\n output.header(`Schema (${loadedFrom})`);\n console.log(serializeSchemaToYaml(schema));\n}\n","/**\n * Commander action for `llmwiki review list`.\n *\n * Prints every pending review candidate (id, slug, sources, generated time)\n * so reviewers can pick one to inspect with `llmwiki review show <id>`.\n */\n\nimport { listCandidates } from \"../compiler/candidates.js\";\nimport * as output from \"../utils/output.js\";\n\n/** List every pending candidate from .llmwiki/candidates/. */\nexport default async function reviewListCommand(): Promise<void> {\n output.header(\"Pending review candidates\");\n\n const candidates = await listCandidates(process.cwd());\n if (candidates.length === 0) {\n output.status(\"✓\", output.success(\"No pending candidates.\"));\n return;\n }\n\n for (const candidate of candidates) {\n const sources = candidate.sources.join(\", \");\n const meta = output.dim(`${candidate.generatedAt} | sources: ${sources}`);\n output.status(\"?\", `${output.info(candidate.id)} → ${candidate.slug} ${meta}`);\n }\n\n output.status(\n \"→\",\n output.dim(`Use \\`llmwiki review show <id>\\` to inspect a candidate.`),\n );\n}\n","/**\n * Commander action for `llmwiki review show <id>`.\n *\n * Prints a single candidate's metadata header followed by its full body so\n * reviewers can read the proposed page before approving or rejecting.\n */\n\nimport { loadCandidateOrFail } from \"../compiler/candidates.js\";\nimport * as output from \"../utils/output.js\";\n\n/** Print a single candidate's full content to stdout. */\nexport default async function reviewShowCommand(id: string): Promise<void> {\n const candidate = await loadCandidateOrFail(process.cwd(), id);\n if (!candidate) return;\n\n output.header(`Candidate ${candidate.id}`);\n output.status(\"i\", output.dim(`title: ${candidate.title}`));\n output.status(\"i\", output.dim(`slug: ${candidate.slug}`));\n output.status(\"i\", output.dim(`summary: ${candidate.summary}`));\n output.status(\"i\", output.dim(`sources: ${candidate.sources.join(\", \")}`));\n output.status(\"i\", output.dim(`generated: ${candidate.generatedAt}`));\n\n console.log();\n console.log(candidate.body);\n\n if (candidate.schemaViolations && candidate.schemaViolations.length > 0) {\n console.log();\n output.header(\"Schema violations\");\n for (const v of candidate.schemaViolations) {\n output.status(\"!\", output.warn(`[${v.severity}] ${v.message}`));\n }\n }\n\n if (candidate.provenanceViolations && candidate.provenanceViolations.length > 0) {\n console.log();\n output.header(\"Provenance violations\");\n for (const v of candidate.provenanceViolations) {\n output.status(\"!\", output.warn(`[${v.severity}] ${v.message}`));\n }\n }\n}\n","/**\n * Commander action for `llmwiki review approve <id>`.\n *\n * Promotes a pending candidate into the live wiki: writes the page body to\n * wiki/concepts/<slug>.md, refreshes the index/MOC, updates embeddings, and\n * removes the candidate file. Approval never re-invokes the LLM — the body\n * stored in the candidate is written verbatim.\n *\n * All mutations are performed under `.llmwiki/lock` to prevent races with a\n * concurrent compile or sibling approve/reject. The candidate is re-read under\n * the lock (TOCTOU guard) — if it disappears between the fast-fail check and\n * lock acquisition (e.g. a concurrent reject ran first), the approval aborts\n * cleanly rather than writing a page from a stale in-memory snapshot.\n */\n\nimport path from \"path\";\nimport {\n atomicWrite,\n validateWikiPage,\n} from \"../utils/markdown.js\";\nimport {\n deleteCandidate,\n listCandidates,\n} from \"../compiler/candidates.js\";\nimport { generateIndex } from \"../compiler/indexgen.js\";\nimport { generateMOC } from \"../compiler/obsidian.js\";\nimport { resolveLinks } from \"../compiler/resolver.js\";\nimport { updateEmbeddings } from \"../utils/embeddings.js\";\nimport { updateSourceState } from \"../utils/state.js\";\nimport { CONCEPTS_DIR } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\nimport type { ReviewCandidate } from \"../utils/types.js\";\nimport { runReviewUnderLock, readCandidateUnderLock } from \"./review-helpers.js\";\n\n/** Approve a pending candidate by promoting its body into wiki/concepts/. */\nexport default async function reviewApproveCommand(id: string): Promise<void> {\n await runReviewUnderLock(id, approveUnderLock);\n}\n\n/**\n * Perform all wiki mutations for an approval while holding the lock.\n *\n * Re-reads the candidate under the lock so that a concurrent reject that ran\n * between the pre-lock fast-fail and lock acquisition is detected. Aborts with\n * exit code 1 if the candidate has disappeared or fails page validation.\n */\nasync function approveUnderLock(root: string, id: string): Promise<void> {\n const candidate = await readCandidateUnderLock(root, id);\n if (!candidate) return;\n\n if (!validateWikiPage(candidate.body)) {\n output.status(\"!\", output.error(`Candidate ${id} failed page validation; not approved.`));\n process.exitCode = 1;\n return;\n }\n\n const pagePath = path.join(root, CONCEPTS_DIR, `${candidate.slug}.md`);\n await atomicWrite(pagePath, candidate.body);\n output.status(\"+\", output.success(`Approved → ${output.source(pagePath)}`));\n\n await persistCandidateSourceStates(root, candidate);\n await refreshWikiAfterApproval(root, candidate.slug);\n await deleteCandidate(root, id);\n output.status(\"✓\", output.dim(`Candidate ${id} cleared.`));\n}\n\n/**\n * Flush the source-state snapshot stored on the candidate into\n * `.llmwiki/state.json` so the contributing source files are marked\n * compiled. Without this, approved candidates would re-appear on the next\n * `compile` run because the source still looks \"new\" or \"changed\" to the\n * change detector.\n *\n * When a single source produced multiple candidates (e.g. an extraction\n * yielded several concepts), persisting state on the first approval would\n * mark the source as fully compiled and silently strand the remaining\n * pending candidates — the next `compile --review` would skip the source\n * entirely. To avoid that, we only persist a source's state when no OTHER\n * pending candidate still references that source filename.\n */\nasync function persistCandidateSourceStates(\n root: string,\n candidate: ReviewCandidate,\n): Promise<void> {\n const states = candidate.sourceStates;\n if (!states) return;\n const otherSources = await collectOtherCandidateSources(root, candidate.id);\n for (const [sourceFile, entry] of Object.entries(states)) {\n if (otherSources.has(sourceFile)) continue;\n await updateSourceState(root, sourceFile, entry);\n }\n}\n\n/**\n * Build the set of source filenames referenced by every pending candidate\n * other than the one currently being approved. Used to defer source-state\n * persistence until the LAST candidate from a given source is reviewed.\n */\nasync function collectOtherCandidateSources(\n root: string,\n approvingId: string,\n): Promise<Set<string>> {\n const pending = await listCandidates(root);\n const sources = new Set<string>();\n for (const candidate of pending) {\n if (candidate.id === approvingId) continue;\n for (const source of candidate.sources) sources.add(source);\n }\n return sources;\n}\n\n/** Refresh interlinks, index, MOC, and embeddings after writing a candidate. */\nasync function refreshWikiAfterApproval(root: string, slug: string): Promise<void> {\n await resolveLinks(root, [slug], [slug]);\n await generateIndex(root);\n await generateMOC(root);\n await safelyUpdateEmbeddings(root, [slug]);\n}\n\n/**\n * Refresh the embeddings store without failing approval.\n * Mirrors the compiler's tolerance: missing API keys / transient provider\n * failures should warn, not abort the approval flow.\n */\nasync function safelyUpdateEmbeddings(root: string, slugs: string[]): Promise<void> {\n try {\n await updateEmbeddings(root, slugs);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.warn(`Skipped embeddings update: ${message}`));\n }\n}\n","/**\n * Shared helpers for review subcommands (approve and reject).\n *\n * Both commands follow the same pattern:\n * 1. Fast-fail: read the candidate before locking (cheap early exit for bad ids).\n * 2. Acquire lock: serialize against concurrent compile / approve / reject.\n * 3. Under-lock re-read: authoritative TOCTOU guard — abort if the candidate\n * was removed between steps 1 and 2 (e.g. a concurrent reject ran first).\n * 4. Run the mutation.\n * 5. Release lock.\n *\n * Extracting this pattern avoids duplicating the acquire/release boilerplate\n * in both approve and reject.\n */\n\nimport {\n loadCandidateOrFail,\n loadCandidateUnderLockOrFail,\n} from \"../compiler/candidates.js\";\nimport { acquireLock, releaseLock } from \"../utils/lock.js\";\nimport * as output from \"../utils/output.js\";\n\n/** Re-export for use by the under-lock mutation functions in approve/reject. */\nexport { loadCandidateUnderLockOrFail as readCandidateUnderLock };\n\n/**\n * Run a review mutation under the `.llmwiki/lock`.\n *\n * Performs the pre-lock fast-fail, acquires the lock, then delegates to the\n * provided `underLock` callback. The lock is released in a `finally` block.\n *\n * @param id - Candidate id to review.\n * @param underLock - Async mutation to run while holding the lock.\n */\nexport async function runReviewUnderLock(\n id: string,\n underLock: (root: string, id: string) => Promise<void>,\n): Promise<void> {\n const root = process.cwd();\n\n // Fast-fail: surface a clear error for obviously missing ids.\n // The authoritative check happens under the lock via loadCandidateUnderLockOrFail.\n const preCheck = await loadCandidateOrFail(root, id);\n if (!preCheck) return;\n\n const locked = await acquireLock(root);\n if (!locked) {\n output.status(\"!\", output.error(\"Could not acquire lock. Try again later.\"));\n process.exitCode = 1;\n return;\n }\n\n try {\n await underLock(root, id);\n } finally {\n await releaseLock(root);\n }\n}\n","/**\n * Commander action for `llmwiki review reject <id>`.\n *\n * Removes a candidate from the pending area without touching `wiki/`.\n * Rejected candidates are moved into .llmwiki/candidates/archive/ so they\n * remain auditable but never appear in `llmwiki review list` again.\n *\n * The archive mutation is performed under `.llmwiki/lock` to serialize\n * concurrent approve/reject and approve-vs-compile operations, matching\n * the lock discipline used by compile and approve.\n *\n * The candidate is re-read under the lock (TOCTOU guard) — if it disappears\n * between the pre-lock fast-fail and lock acquisition, the rejection aborts\n * cleanly rather than silently succeeding on a stale handle.\n */\n\nimport { archiveCandidate } from \"../compiler/candidates.js\";\nimport * as output from \"../utils/output.js\";\nimport { runReviewUnderLock, readCandidateUnderLock } from \"./review-helpers.js\";\n\n/** Reject a pending candidate by archiving its JSON record. */\nexport default async function reviewRejectCommand(id: string): Promise<void> {\n await runReviewUnderLock(id, rejectUnderLock);\n}\n\n/**\n * Perform the archive mutation while holding the lock.\n *\n * Re-reads the candidate under the lock so that a concurrent approve that ran\n * between the pre-lock fast-fail and lock acquisition is detected. Aborts with\n * exit code 1 if the candidate has disappeared.\n */\nasync function rejectUnderLock(root: string, id: string): Promise<void> {\n const candidate = await readCandidateUnderLock(root, id);\n if (!candidate) return;\n\n await archiveCandidate(root, id);\n output.status(\n \"-\",\n output.warn(`Rejected candidate ${id} (${candidate.slug}) — archived, wiki unchanged.`),\n );\n}\n","/**\n * Read-only project-state collector for `llmwiki next`.\n *\n * Inspects an llmwiki project root and reports counts, lint cache status,\n * and structural warnings without acquiring locks, mutating disk, or\n * running compile/lint. Designed to be cheap on small projects and to\n * stay cheap on large wikis by using directory mtimes as a stale-lint\n * proxy instead of stat-ing every page.\n *\n * Stale-lint detection is intentionally approximate per the spec:\n * directory-structure changes after the last lint run produce a\n * `lint-cache-stale` warning. Content-only edits may not trigger it on\n * every filesystem; exact lint freshness belongs to `llmwiki lint`.\n *\n * Candidate reads are race-tolerant: this collector delegates to\n * `countCandidates`, which skips malformed/partial candidate JSON the\n * same way `review list` does.\n */\n\nimport { stat, readdir, readFile } from \"fs/promises\";\nimport path from \"path\";\nimport {\n SOURCES_DIR,\n CONCEPTS_DIR,\n QUERIES_DIR,\n LLMWIKI_DIR,\n LAST_LINT_FILE,\n INDEX_FILE,\n} from \"../utils/constants.js\";\nimport { countCandidates } from \"../compiler/candidates.js\";\nimport { LINT_CACHE_TIMESTAMP_PATTERN } from \"../linter/cache.js\";\nimport type { LintCacheEntry } from \"../linter/cache.js\";\n\n/** Markdown extension treated as a wiki page for counting purposes. */\nconst MARKDOWN_EXT = \".md\";\n\n/** Warning codes the v1 collector may emit. Pinned by tests. */\ntype ProjectStateWarningCode =\n | \"lint-cache-stale\"\n | \"lint-cache-unparseable\"\n | \"index-missing\"\n | \"sources-not-compiled\"\n | \"pending-candidates\"\n | \"project-unreadable\";\n\n/** One structural warning surfaced alongside the primary state. */\nexport interface ProjectStateWarning {\n code: ProjectStateWarningCode;\n message: string;\n}\n\n/** Three-state lint-cache shape per the JSON contract. */\ninterface LintCacheStatus {\n /** True when a cache file exists on disk, even if it failed to parse. */\n present: boolean;\n /** Parsed cache entry; null when no file exists OR file failed to parse. */\n entry: LintCacheEntry | null;\n}\n\n/** Full project-state snapshot consumed by `recommendNextAction` and renderers. */\nexport interface ProjectState {\n root: string;\n hasSourcesDir: boolean;\n hasWikiDir: boolean;\n hasInternalDir: boolean;\n sourceCount: number;\n conceptCount: number;\n queryCount: number;\n pendingCandidates: number;\n hasIndex: boolean;\n lint: LintCacheStatus;\n latestWikiMtimeMs: number | null;\n latestSourceMtimeMs: number | null;\n warnings: ProjectStateWarning[];\n}\n\n/**\n * Collect a full ProjectState snapshot for `root`. Never throws on\n * expected absent-file paths — those become `false`/`0`/`null` fields.\n * Returns a `project-unreadable` warning when the root itself cannot be\n * stat-ed; callers escalate that to the `broken-project` primary state.\n */\nexport async function collectProjectState(root: string): Promise<ProjectState> {\n const rootReadable = await isDirectory(root);\n if (!rootReadable) return brokenProjectState(root);\n const dirs = await collectDirPresence(root);\n const counts = await collectPageCounts(root, dirs);\n const lint = await collectLintCacheStatus(root);\n const mtimes = await collectMtimes(root, dirs);\n return assembleState({ root, dirs, counts, lint, mtimes });\n}\n\n/** State returned when `root` itself cannot be inspected. */\nfunction brokenProjectState(root: string): ProjectState {\n return {\n root,\n hasSourcesDir: false,\n hasWikiDir: false,\n hasInternalDir: false,\n sourceCount: 0,\n conceptCount: 0,\n queryCount: 0,\n pendingCandidates: 0,\n hasIndex: false,\n lint: { present: false, entry: null },\n latestWikiMtimeMs: null,\n latestSourceMtimeMs: null,\n warnings: [\n {\n code: \"project-unreadable\",\n message: `Project root is unreadable or could not be inspected: ${root}`,\n },\n ],\n };\n}\n\n/** Cheap presence probes for the three structural directories the recommender keys on. */\ninterface DirPresence {\n hasSourcesDir: boolean;\n hasWikiDir: boolean;\n hasInternalDir: boolean;\n}\n\n/** Raw page counts surfaced into ProjectState; computed in one pass to keep collectProjectState short. */\ninterface PageCounts {\n sourceCount: number;\n conceptCount: number;\n queryCount: number;\n pendingCandidates: number;\n hasIndex: boolean;\n}\n\n/** Mtime proxies used for the cheap stale-lint approximation. */\ninterface MtimePair {\n latestWikiMtimeMs: number | null;\n latestSourceMtimeMs: number | null;\n}\n\n/** Probe the three structural directories without listing their contents. */\nasync function collectDirPresence(root: string): Promise<DirPresence> {\n const [hasSourcesDir, hasWikiDir, hasInternalDir] = await Promise.all([\n isDirectory(path.join(root, SOURCES_DIR)),\n isDirectory(path.join(root, \"wiki\")),\n isDirectory(path.join(root, LLMWIKI_DIR)),\n ]);\n return { hasSourcesDir, hasWikiDir, hasInternalDir };\n}\n\n/** Compute page counts plus the wiki/index.md presence flag. */\nasync function collectPageCounts(root: string, dirs: DirPresence): Promise<PageCounts> {\n const [sourceCount, conceptCount, queryCount, pendingCandidates, hasIndex] = await Promise.all([\n dirs.hasSourcesDir ? countMarkdownFiles(path.join(root, SOURCES_DIR)) : 0,\n dirs.hasWikiDir ? countMarkdownFiles(path.join(root, CONCEPTS_DIR)) : 0,\n dirs.hasWikiDir ? countMarkdownFiles(path.join(root, QUERIES_DIR)) : 0,\n dirs.hasInternalDir ? safeCountCandidates(root) : 0,\n dirs.hasWikiDir ? isFile(path.join(root, INDEX_FILE)) : false,\n ]);\n return { sourceCount, conceptCount, queryCount, pendingCandidates, hasIndex };\n}\n\n/** Read directory mtimes only when the directories exist; absent dirs report null. */\nasync function collectMtimes(root: string, dirs: DirPresence): Promise<MtimePair> {\n const [latestWikiMtimeMs, latestSourceMtimeMs] = await Promise.all([\n dirs.hasWikiDir ? safeMtime(path.join(root, \"wiki\")) : Promise.resolve(null),\n dirs.hasSourcesDir ? safeMtime(path.join(root, SOURCES_DIR)) : Promise.resolve(null),\n ]);\n return { latestWikiMtimeMs, latestSourceMtimeMs };\n}\n\n/** Read and validate the lint cache, distinguishing missing from corrupt. */\nasync function collectLintCacheStatus(root: string): Promise<LintCacheStatus> {\n const cachePath = path.join(root, LAST_LINT_FILE);\n const exists = await isFile(cachePath);\n if (!exists) return { present: false, entry: null };\n const entry = await readLintCacheEntry(cachePath);\n return { present: true, entry };\n}\n\n/** Strict parse + validate, returning null for any unreadable/corrupt cache file. */\nasync function readLintCacheEntry(cachePath: string): Promise<LintCacheEntry | null> {\n let raw: string;\n try {\n raw = await readFile(cachePath, \"utf-8\");\n } catch {\n return null;\n }\n try {\n const parsed = JSON.parse(raw) as unknown;\n return validateCacheShape(parsed);\n } catch {\n return null;\n }\n}\n\n/** Mirror of the cache module's validator; kept local to avoid leaking internal helpers. */\nfunction validateCacheShape(value: unknown): LintCacheEntry | null {\n if (typeof value !== \"object\" || value === null) return null;\n const candidate = value as Record<string, unknown>;\n const { warnings, errors, at } = candidate;\n if (!isNonNegativeInteger(warnings)) return null;\n if (!isNonNegativeInteger(errors)) return null;\n if (typeof at !== \"string\" || !LINT_CACHE_TIMESTAMP_PATTERN.test(at)) return null;\n return { warnings, errors, at };\n}\n\n/** True for finite, non-negative integers. NaN and Infinity fail Number.isInteger. */\nfunction isNonNegativeInteger(value: unknown): value is number {\n return typeof value === \"number\" && Number.isInteger(value) && value >= 0;\n}\n\n/** Inputs to {@link assembleState}, grouped to keep the function's arity within limits. */\ninterface AssembleStateInput {\n root: string;\n dirs: DirPresence;\n counts: PageCounts;\n lint: LintCacheStatus;\n mtimes: MtimePair;\n}\n\n/** Combine the per-section reads into the final ProjectState + warning list. */\nfunction assembleState(input: AssembleStateInput): ProjectState {\n const { root, dirs, counts, lint, mtimes } = input;\n const warnings = buildWarnings({ dirs, counts, lint, mtimes });\n return { root, ...dirs, ...counts, lint, ...mtimes, warnings };\n}\n\n/** Inputs for warning derivation; passed as a record to keep argument lists short. */\ninterface WarningInput {\n dirs: DirPresence;\n counts: PageCounts;\n lint: LintCacheStatus;\n mtimes: MtimePair;\n}\n\n/** Derive every structural warning from the already-collected state slices. */\nfunction buildWarnings(input: WarningInput): ProjectStateWarning[] {\n const warnings: ProjectStateWarning[] = [];\n appendLintWarnings(warnings, input.lint, input.mtimes.latestWikiMtimeMs);\n appendStructuralWarnings(warnings, input.dirs, input.counts);\n return warnings;\n}\n\n/** Lint-cache warnings: unparseable file or stale-vs-wiki-mtime. */\nfunction appendLintWarnings(\n warnings: ProjectStateWarning[],\n lint: LintCacheStatus,\n latestWikiMtimeMs: number | null,\n): void {\n if (lint.present && lint.entry === null) {\n warnings.push({\n code: \"lint-cache-unparseable\",\n message: \"Lint cache file exists but could not be parsed. Re-run `llmwiki lint`.\",\n });\n return;\n }\n if (lint.entry && isLintCacheStale(lint.entry, latestWikiMtimeMs)) {\n warnings.push({\n code: \"lint-cache-stale\",\n message: \"Lint cache is older than the wiki directory; pages were added or removed since the last lint.\",\n });\n }\n}\n\n/** Structural warnings: missing index, pending candidates, uncompiled sources. */\nfunction appendStructuralWarnings(\n warnings: ProjectStateWarning[],\n dirs: DirPresence,\n counts: PageCounts,\n): void {\n const hasPages = counts.conceptCount > 0 || counts.queryCount > 0;\n if (hasPages && !counts.hasIndex) {\n warnings.push({\n code: \"index-missing\",\n message: \"wiki/index.md is missing even though pages exist.\",\n });\n }\n if (counts.pendingCandidates > 0) {\n warnings.push({\n code: \"pending-candidates\",\n message: `${counts.pendingCandidates} generated candidate${counts.pendingCandidates === 1 ? \"\" : \"s\"} waiting for review.`,\n });\n }\n if (dirs.hasSourcesDir && counts.sourceCount > 0 && !hasPages) {\n warnings.push({\n code: \"sources-not-compiled\",\n message: \"Sources exist but no wiki pages were found. Run `llmwiki compile`.\",\n });\n }\n}\n\n/** Cheap structural staleness: last-lint timestamp older than wiki dir mtime. */\nfunction isLintCacheStale(entry: LintCacheEntry, latestWikiMtimeMs: number | null): boolean {\n if (latestWikiMtimeMs === null) return false;\n const lintMs = Date.parse(entry.at);\n if (Number.isNaN(lintMs)) return false;\n return latestWikiMtimeMs > lintMs;\n}\n\n/** True when `target` is a directory; false for missing paths or any other type. */\nasync function isDirectory(target: string): Promise<boolean> {\n try {\n const stats = await stat(target);\n return stats.isDirectory();\n } catch {\n return false;\n }\n}\n\n/** True when `target` is a regular file; false for missing paths or directories. */\nasync function isFile(target: string): Promise<boolean> {\n try {\n const stats = await stat(target);\n return stats.isFile();\n } catch {\n return false;\n }\n}\n\n/** Directory mtime in ms epoch, or null when the directory cannot be stat-ed. */\nasync function safeMtime(target: string): Promise<number | null> {\n try {\n const stats = await stat(target);\n return stats.mtimeMs;\n } catch {\n return null;\n }\n}\n\n/** Count `.md` files in a directory; 0 for missing dirs or any other error. */\nasync function countMarkdownFiles(dir: string): Promise<number> {\n try {\n const entries = await readdir(dir, { withFileTypes: true });\n let count = 0;\n for (const entry of entries) {\n if (entry.isFile() && entry.name.endsWith(MARKDOWN_EXT)) count += 1;\n }\n return count;\n } catch {\n return 0;\n }\n}\n\n/** Race-tolerant candidate count; defers to `countCandidates` and swallows IO errors. */\nasync function safeCountCandidates(root: string): Promise<number> {\n try {\n return await countCandidates(root);\n } catch {\n return 0;\n }\n}\n","/**\n * Pure recommendation rules for `llmwiki next`.\n *\n * Classifies a {@link ProjectState} snapshot into exactly one of seven\n * primary states and produces a primary {@link RecommendedAction} plus\n * the per-state `otherActions` table from the implementation plan.\n *\n * Actions with user-supplied input (a source path, a question, a\n * candidate id) are templates: the display `command` carries a\n * `<placeholder>` and `executable.placeholders` lists the slots. Agents\n * must populate placeholders themselves; the contract never returns a\n * shell-ready command line.\n */\n\nimport type { ProjectState } from \"./state.js\";\n\n/** Primary state enum surfaced as `state` in the JSON envelope. */\nexport type ProjectStateKind =\n | \"fresh\"\n | \"sources-only\"\n | \"review-pending\"\n | \"lint-attention\"\n | \"wiki-ready\"\n | \"empty-wiki\"\n | \"broken-project\";\n\n/** Single recommended action; `command` is for display, `executable` is for agents. */\nexport interface RecommendedAction {\n command: string | null;\n reason: string;\n executable: ExecutableSpec | null;\n}\n\n/** Structured form of an executable command. Placeholders are slot names, not literals. */\ninterface ExecutableSpec {\n binary: \"llmwiki\";\n args: string[];\n placeholders?: string[];\n}\n\n/** Full recommendation envelope returned to the renderer. */\nexport interface Recommendation {\n state: ProjectStateKind;\n recommended: RecommendedAction;\n otherActions: RecommendedAction[];\n}\n\n/**\n * Classify a ProjectState and produce its primary + secondary actions.\n * Precedence (top wins): broken-project, fresh, sources-only,\n * review-pending, lint-attention, wiki-ready, empty-wiki.\n */\nexport function recommendNextAction(state: ProjectState): Recommendation {\n const kind = classifyState(state);\n return { state: kind, recommended: primaryAction(kind), otherActions: otherActionsFor(kind) };\n}\n\n/** Apply the precedence rules from the spec to choose the primary state kind. */\nfunction classifyState(state: ProjectState): ProjectStateKind {\n if (isBrokenProject(state)) return \"broken-project\";\n if (isSourcesOnly(state)) return \"sources-only\";\n if (state.pendingCandidates > 0) return \"review-pending\";\n if (hasLintErrors(state)) return \"lint-attention\";\n if (hasWikiPages(state)) return \"wiki-ready\";\n if (isEmptyWiki(state)) return \"empty-wiki\";\n return \"fresh\";\n}\n\n/** True when the collector flagged the root as unreadable. */\nfunction isBrokenProject(state: ProjectState): boolean {\n return state.warnings.some((w) => w.code === \"project-unreadable\");\n}\n\n/**\n * `empty-wiki` requires `wiki/` to actually exist with zero pages. The\n * spec wording is \"wiki/ exists but page count is zero\", and the human\n * renderer says so — we must not surface that line when wiki/ is missing.\n */\nfunction isEmptyWiki(state: ProjectState): boolean {\n return state.hasWikiDir && !hasWikiPages(state);\n}\n\n/** Sources exist but no concept or query pages have been compiled yet. */\nfunction isSourcesOnly(state: ProjectState): boolean {\n return state.sourceCount > 0 && !hasWikiPages(state);\n}\n\n/** True when the lint cache parsed and reports at least one error. */\nfunction hasLintErrors(state: ProjectState): boolean {\n return state.lint.entry !== null && state.lint.entry.errors > 0;\n}\n\n/** True when the wiki carries any concept or query page. */\nfunction hasWikiPages(state: ProjectState): boolean {\n return state.conceptCount > 0 || state.queryCount > 0;\n}\n\n/** Map a state kind to its primary recommended action. */\nfunction primaryAction(kind: ProjectStateKind): RecommendedAction {\n return PRIMARY_ACTIONS[kind];\n}\n\n/** Map a state kind to its `otherActions` list. */\nfunction otherActionsFor(kind: ProjectStateKind): RecommendedAction[] {\n return OTHER_ACTIONS[kind].map((a) => ({ ...a }));\n}\n\n/** Template action: `llmwiki quickstart <source>` with a `source` placeholder. */\nconst QUICKSTART_ACTION: RecommendedAction = {\n command: \"llmwiki quickstart <source>\",\n reason: \"Ingest a source and compile a wiki in one step.\",\n executable: { binary: \"llmwiki\", args: [\"quickstart\"], placeholders: [\"source\"] },\n};\n\n/** Template action: `llmwiki ingest <source>`. */\nconst INGEST_ACTION: RecommendedAction = {\n command: \"llmwiki ingest <source>\",\n reason: \"Add sources manually before compiling.\",\n executable: { binary: \"llmwiki\", args: [\"ingest\"], placeholders: [\"source\"] },\n};\n\n/** Bare action: `llmwiki compile`. */\nconst COMPILE_ACTION: RecommendedAction = {\n command: \"llmwiki compile\",\n reason: \"Compile sources/ into wiki pages.\",\n executable: { binary: \"llmwiki\", args: [\"compile\"] },\n};\n\n/** Bare action: `llmwiki review list`. */\nconst REVIEW_LIST_ACTION: RecommendedAction = {\n command: \"llmwiki review list\",\n reason: \"List pending candidate pages.\",\n executable: { binary: \"llmwiki\", args: [\"review\", \"list\"] },\n};\n\n/** Template action: `llmwiki review approve <id>`. */\nconst REVIEW_APPROVE_ACTION: RecommendedAction = {\n command: \"llmwiki review approve <id>\",\n reason: \"Approve a candidate after inspecting it with `llmwiki review show <id>`.\",\n executable: { binary: \"llmwiki\", args: [\"review\", \"approve\"], placeholders: [\"id\"] },\n};\n\n/** Bare action: `llmwiki lint`. */\nconst LINT_ACTION: RecommendedAction = {\n command: \"llmwiki lint\",\n reason: \"Re-run lint to inspect outstanding errors.\",\n executable: { binary: \"llmwiki\", args: [\"lint\"] },\n};\n\n/** Bare action: `llmwiki view --open`. */\nconst VIEW_OPEN_ACTION: RecommendedAction = {\n command: \"llmwiki view --open\",\n reason: \"Browse the compiled wiki in the local viewer.\",\n executable: { binary: \"llmwiki\", args: [\"view\", \"--open\"] },\n};\n\n/** Template action: `llmwiki query \"<question>\"`. */\nconst QUERY_ACTION: RecommendedAction = {\n command: 'llmwiki query \"<question>\"',\n reason: \"Ask a natural-language question against the compiled wiki.\",\n executable: { binary: \"llmwiki\", args: [\"query\"], placeholders: [\"question\"] },\n};\n\n/** Sentinel for the broken-project state: no command, no executable. */\nconst BROKEN_PROJECT_ACTION: RecommendedAction = {\n command: null,\n reason: \"Project root is unreadable or could not be inspected.\",\n executable: null,\n};\n\n/** Primary recommendation for each state kind. */\nconst PRIMARY_ACTIONS: Record<ProjectStateKind, RecommendedAction> = {\n \"broken-project\": BROKEN_PROJECT_ACTION,\n fresh: { ...QUICKSTART_ACTION, reason: \"No sources or wiki pages were found.\" },\n \"sources-only\": { ...COMPILE_ACTION, reason: \"Sources exist but no wiki pages have been compiled.\" },\n \"review-pending\": { ...REVIEW_LIST_ACTION, reason: \"Generated candidates are waiting for review.\" },\n \"lint-attention\": { ...LINT_ACTION, reason: \"Lint has reported errors; rerun lint to inspect them.\" },\n \"wiki-ready\": { ...VIEW_OPEN_ACTION, reason: \"Wiki pages are ready to browse.\" },\n \"empty-wiki\": {\n ...COMPILE_ACTION,\n reason: \"wiki/ exists but is empty; run compile or add sources first.\",\n },\n};\n\n/** Per-state otherActions table from the implementation plan (Slice 1 scope). */\nconst OTHER_ACTIONS: Record<ProjectStateKind, RecommendedAction[]> = {\n fresh: [INGEST_ACTION, QUICKSTART_ACTION],\n \"sources-only\": [COMPILE_ACTION, QUICKSTART_ACTION],\n \"empty-wiki\": [COMPILE_ACTION, INGEST_ACTION],\n \"review-pending\": [REVIEW_LIST_ACTION, REVIEW_APPROVE_ACTION],\n \"lint-attention\": [LINT_ACTION, VIEW_OPEN_ACTION],\n \"wiki-ready\": [VIEW_OPEN_ACTION, QUERY_ACTION],\n \"broken-project\": [],\n};\n","/**\n * `llmwiki next` — read-only project-state orientation command.\n *\n * Inspects the current working directory, classifies it into one of the\n * seven project states, and prints either a short human summary or a\n * stable JSON envelope (`--json`). The JSON envelope is `version: 1`\n * and is incremented independently from `quickstart`'s JSON version.\n *\n * Never writes files. Never acquires locks. Exits 0 for any successful\n * inspection (including states with lint errors); exits 1 only for\n * unexpected filesystem failures.\n */\n\nimport { collectProjectState } from \"../project/state.js\";\nimport type { ProjectState, ProjectStateWarning } from \"../project/state.js\";\nimport { recommendNextAction } from \"../project/recommendations.js\";\nimport type {\n ProjectStateKind,\n Recommendation,\n RecommendedAction,\n} from \"../project/recommendations.js\";\n\ntype StateLineRenderer = (state: ProjectState) => string;\n\n/** Options surfaced by Commander for `llmwiki next`. */\ninterface NextCommandOptions {\n json?: boolean;\n}\n\n/** Versioned JSON envelope shape — incremented independently from quickstart. */\nconst NEXT_JSON_VERSION = 1;\n\nconst STATE_LINE_RENDERERS: Record<ProjectStateKind, StateLineRenderer> = {\n \"broken-project\": () => \"project root unreadable\",\n fresh: () => \"no llmwiki project detected\",\n \"sources-only\": (state) =>\n `${state.sourceCount} source${plural(state.sourceCount)}, no wiki pages yet`,\n \"review-pending\": (state) =>\n `review pending, ${state.pendingCandidates} candidate${plural(state.pendingCandidates)}`,\n \"lint-attention\": formatLintAttentionLine,\n \"empty-wiki\": () => \"wiki/ exists but is empty\",\n \"wiki-ready\": formatWikiReadyLine,\n};\n\n/**\n * Run the `next` command. Stdout receives the human or JSON payload;\n * never throws on filesystem absence (handled by the collector). Returns\n * the exit code the CLI wrapper should propagate.\n */\nexport default async function nextCommand(options: NextCommandOptions = {}): Promise<number> {\n const state = await collectProjectState(process.cwd());\n const recommendation = recommendNextAction(state);\n if (options.json) {\n process.stdout.write(`${JSON.stringify(buildJsonPayload(state, recommendation), null, 2)}\\n`);\n } else {\n process.stdout.write(`${renderHuman(state, recommendation)}\\n`);\n }\n return 0;\n}\n\n/** JSON envelope for `--json`. Field order and shape pinned by snapshot tests. */\nfunction buildJsonPayload(state: ProjectState, recommendation: Recommendation): JsonPayload {\n return {\n version: NEXT_JSON_VERSION,\n projectRoot: state.root,\n state: recommendation.state,\n summary: buildSummary(state),\n recommended: recommendation.recommended,\n otherActions: recommendation.otherActions,\n warnings: state.warnings,\n };\n}\n\n/** Top-level JSON envelope. */\ninterface JsonPayload {\n version: number;\n projectRoot: string;\n state: ProjectStateKind;\n summary: JsonSummary;\n recommended: RecommendedAction;\n otherActions: RecommendedAction[];\n warnings: ProjectStateWarning[];\n}\n\n/** `summary` sub-envelope; lint follows the three-state contract. */\ninterface JsonSummary {\n sourceCount: number;\n conceptCount: number;\n queryCount: number;\n pendingCandidates: number;\n hasIndex: boolean;\n hasLintCache: boolean;\n lint: { warnings: number; errors: number; at: string } | null;\n}\n\n/** Build the `summary` block, mapping LintCacheStatus to the documented shape. */\nfunction buildSummary(state: ProjectState): JsonSummary {\n return {\n sourceCount: state.sourceCount,\n conceptCount: state.conceptCount,\n queryCount: state.queryCount,\n pendingCandidates: state.pendingCandidates,\n hasIndex: state.hasIndex,\n hasLintCache: state.lint.present,\n lint: state.lint.entry,\n };\n}\n\n/** Render the human output. Header + state line + recommended + other actions. */\nfunction renderHuman(state: ProjectState, recommendation: Recommendation): string {\n const lines: string[] = [];\n lines.push(\"llmwiki next\");\n lines.push(\"------------\");\n lines.push(\"\");\n lines.push(`Project: ${state.root}`);\n lines.push(`State: ${describeStateLine(state, recommendation.state)}`);\n appendHumanRecommendation(lines, recommendation.recommended);\n appendHumanOtherActions(lines, recommendation.otherActions);\n appendHumanWarnings(lines, state.warnings);\n return lines.join(\"\\n\");\n}\n\n/** Append the recommended-action block (label + command line). */\nfunction appendHumanRecommendation(lines: string[], action: RecommendedAction): void {\n lines.push(\"\");\n lines.push(\"Recommended next action:\");\n lines.push(` ${action.command ?? action.reason}`);\n}\n\n/** Append the other-actions list when present; quietly skip when empty. */\nfunction appendHumanOtherActions(lines: string[], actions: RecommendedAction[]): void {\n if (actions.length === 0) return;\n lines.push(\"\");\n lines.push(\"Other useful actions:\");\n for (const action of actions) {\n if (action.command) lines.push(` ${action.command}`);\n }\n}\n\n/** Surface structural warnings as a short \"Notes\" block under the actions. */\nfunction appendHumanWarnings(lines: string[], warnings: ProjectStateWarning[]): void {\n if (warnings.length === 0) return;\n lines.push(\"\");\n lines.push(\"Notes:\");\n for (const warning of warnings) lines.push(` - ${warning.message}`);\n}\n\n/** Human-friendly state-line description; pairs the state kind with key counters. */\nfunction describeStateLine(state: ProjectState, kind: ProjectStateKind): string {\n return STATE_LINE_RENDERERS[kind](state);\n}\n\n/** Ready-state summary keeps page and pending-candidate counts visible. */\nfunction formatWikiReadyLine(state: ProjectState): string {\n return `wiki ready, ${pageTotal(state)} page${plural(pageTotal(state))}, ${state.pendingCandidates} pending candidate${plural(state.pendingCandidates)}`;\n}\n\n/** Lint-attention line surfaces error and warning counts the user just learned about. */\nfunction formatLintAttentionLine(state: ProjectState): string {\n const entry = state.lint.entry;\n if (!entry) return \"lint cache reports errors\";\n return `lint reports ${entry.errors} error${plural(entry.errors)}, ${entry.warnings} warning${plural(entry.warnings)}`;\n}\n\n/** Total compiled page count across concepts and queries. */\nfunction pageTotal(state: ProjectState): number {\n return state.conceptCount + state.queryCount;\n}\n\n/** Return \"s\" for any count other than 1; used for inline pluralization. */\nfunction plural(count: number): string {\n return count === 1 ? \"\" : \"s\";\n}\n","/**\n * `llmwiki quickstart <source>` — first-run wrapper that ingests a single\n * source then compiles it into a browsable wiki.\n *\n * Honours the JSON contract from the next-quickstart implementation plan:\n * - `version: 1` envelope, incremented independently from `next`.\n * - `--json` implies `--no-open` and never starts the foreground viewer.\n * `viewer.opened` stays `false` and `viewer.url` stays `null` in JSON\n * output, regardless of project state.\n * - `--review` skips the viewer regardless of `--open` / `--no-open`.\n * - `compile.ok` is true only when `compileAndReport()` returned and\n * `CompileResult.errors` is empty. Returned errors land on\n * `compile.errors` verbatim; thrown errors land on `compile.error`.\n * - After ingest succeeds, `compile.pendingCandidates` is read from\n * `countCandidates(root)` at exit so prior pending candidates show up\n * even on compile failure paths. Ingest failures deliberately skip\n * project inspection and report `pendingCandidates: 0`.\n *\n * Viewer handoff (Slice 3): in human mode only, when wiki pages exist and\n * none of `--no-open`, `--json`, or `--review` is set, the human summary\n * is emitted first, then `Starting viewer. Press Ctrl+C to stop.`, then\n * `viewCommand({ open: true })` takes over the foreground exactly like\n * `llmwiki view --open`. The viewer's own `Viewer ready at <url>` line\n * follows. Ctrl+C / SIGTERM are owned by viewCommand's shutdown handler.\n *\n * Provider credentials are validated AFTER ingest so a credential-free\n * ingest still preserves the source on disk before quickstart reports\n * the compile failure.\n */\n\nimport path from \"path\";\nimport { ingestSource } from \"./ingest.js\";\nimport viewCommand from \"./view.js\";\nimport { compileAndReport } from \"../compiler/index.js\";\nimport { countCandidates } from \"../compiler/candidates.js\";\nimport { collectProjectState } from \"../project/state.js\";\nimport type { ProjectState } from \"../project/state.js\";\nimport { recommendNextAction } from \"../project/recommendations.js\";\nimport type { RecommendedAction } from \"../project/recommendations.js\";\nimport { ensureProviderAvailable } from \"../utils/provider-guard.js\";\nimport { applyLanguageOption } from \"../utils/output-language.js\";\nimport * as output from \"../utils/output.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\nimport type { CompileResult, IngestResult } from \"../utils/types.js\";\n\n/** CLI-supplied options for `llmwiki quickstart`. */\nexport interface QuickstartOptions {\n /** Run compile in --review mode (candidates instead of wiki/ writes). */\n review?: boolean;\n /** When false, skip viewer handoff. Defaults to true via Commander's --no-open. */\n open?: boolean;\n /** Override LLMWIKI_PROVIDER for this process only. */\n provider?: string;\n /** Forwarded to applyLanguageOption before compile. */\n lang?: string;\n /** Emit the JSON envelope instead of human output (implies --no-open). */\n json?: boolean;\n}\n\n/** Versioned JSON envelope shape — incremented independently from `next`. */\nconst QUICKSTART_JSON_VERSION = 1;\nconst VIEW_OPEN_ARGS_KEY = \"view\\u0000--open\";\n\ntype EnvRestorer = () => void;\n\nconst NOOP_RESTORE: EnvRestorer = () => {};\n\n/** Ingest sub-envelope. */\ninterface IngestEnvelope {\n ok: boolean;\n path: string | null;\n sourceType: string | null;\n error: ErrorEnvelope | null;\n}\n\n/** Compile sub-envelope. */\ninterface CompileEnvelope {\n ok: boolean;\n compiled: number;\n skipped: number;\n deleted: number;\n pendingCandidates: number;\n errors: string[] | null;\n error: ErrorEnvelope | null;\n}\n\n/** Common error shape used by both ingest and compile failure paths. */\ninterface ErrorEnvelope {\n code: string;\n message: string;\n recoverable: boolean;\n}\n\n/**\n * Viewer sub-envelope. Stays `{opened:false,url:null}` for the JSON path\n * because `--json` implies `--no-open`. The human-mode handoff prints\n * the viewer URL via viewCommand's own readiness line, after the\n * envelope has already been rendered to stdout.\n */\ninterface ViewerEnvelope {\n opened: boolean;\n url: string | null;\n}\n\n/** Next-action sub-envelope. Reuses Slice 1's recommendation shape. */\ninterface NextEnvelope {\n command: string | null;\n reason: string;\n executable: RecommendedAction[\"executable\"];\n}\n\n/** Full top-level envelope. */\ninterface QuickstartEnvelope {\n version: number;\n source: string;\n ingest: IngestEnvelope;\n compile: CompileEnvelope;\n viewer: ViewerEnvelope;\n next: NextEnvelope;\n}\n\n/** Outcome aggregator carried through the pipeline before rendering. */\ninterface QuickstartRun {\n source: string;\n ingest: IngestEnvelope;\n compile: CompileEnvelope;\n viewer: ViewerEnvelope;\n}\n\n/**\n * Execute the quickstart pipeline. Returns the exit code the CLI shim\n * should propagate (0 for full or partial-but-resumable success, 1 for\n * hard failures like ingest errors).\n *\n * Stdout quiet-mode and per-process env overrides (`--provider`, `--lang`)\n * are scoped to this call via paired finally blocks so callers that\n * reuse the same Node process — e.g. future in-process MCP composition\n * or test harnesses — do not see leaked state from a prior invocation.\n */\nexport default async function quickstartCommand(\n source: string,\n options: QuickstartOptions = {},\n): Promise<number> {\n const jsonMode = options.json === true;\n output.setQuiet(jsonMode);\n const restoreEnv = applyEnvOverrides(options);\n try {\n return await runQuickstart(source, options, jsonMode);\n } finally {\n restoreEnv();\n output.setQuiet(false);\n }\n}\n\n/** Inner orchestrator wrapped by the quiet-mode + env lifecycle. */\nasync function runQuickstart(\n source: string,\n options: QuickstartOptions,\n jsonMode: boolean,\n): Promise<number> {\n const root = process.cwd();\n const ingest = await runIngestStep(source);\n if (!ingest.ok) {\n return finalizeFailure({ source, ingest, jsonMode });\n }\n\n const compile = await runCompileStep(root, options.review === true);\n const viewer = buildViewerEnvelope();\n const run: QuickstartRun = { source, ingest, compile, viewer };\n return await finalizeSuccess(run, options, jsonMode, root);\n}\n\n/**\n * Apply `--provider` and `--lang` into the process env and return a\n * restorer that puts both back exactly as they were. We snapshot\n * `undefined` separately from `\"\"` so a previously-unset variable is\n * unset again on restore (rather than turning into an empty string,\n * which downstream resolvers normalise differently).\n */\nfunction applyEnvOverrides(options: QuickstartOptions): EnvRestorer {\n return combineRestorers([\n applyProviderOverride(options.provider),\n applyLanguageOverride(options.lang),\n ]);\n}\n\n/** Compose scoped env cleanup callbacks so callers restore with one function. */\nfunction combineRestorers(restorers: EnvRestorer[]): EnvRestorer {\n return () => {\n for (const restore of restorers) restore();\n };\n}\n\n/** Apply the provider override if present, otherwise return a no-op restorer. */\nfunction applyProviderOverride(provider: string | undefined): EnvRestorer {\n const trimmed = provider?.trim();\n if (!trimmed) return NOOP_RESTORE;\n const restore = snapshotEnv(\"LLMWIKI_PROVIDER\");\n process.env.LLMWIKI_PROVIDER = trimmed;\n return restore;\n}\n\n/** Apply the language override if present, otherwise return a no-op restorer. */\nfunction applyLanguageOverride(lang: string | undefined): EnvRestorer {\n const trimmed = lang?.trim();\n if (!trimmed) return NOOP_RESTORE;\n const restore = snapshotEnv(\"LLMWIKI_OUTPUT_LANG\");\n applyLanguageOption(trimmed);\n return restore;\n}\n\n/** Capture a single env var's current value (including absence) so it can be restored later. */\nfunction snapshotEnv(name: string): EnvRestorer {\n const previous = process.env[name];\n return () => {\n if (previous === undefined) {\n delete process.env[name];\n } else {\n process.env[name] = previous;\n }\n };\n}\n\n/** Run ingest and report the structured envelope; never throws. */\nasync function runIngestStep(source: string): Promise<IngestEnvelope> {\n output.header(\"llmwiki quickstart\");\n output.status(\"*\", output.info(`Ingesting ${source}`));\n try {\n const result = await ingestSource(source);\n const relPath = path.join(SOURCES_DIR, result.filename);\n output.status(\"+\", output.success(`Ingested → ${relPath}`));\n return buildIngestSuccess(result, relPath);\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.error(`Ingest failed: ${message}`));\n return buildIngestFailure(message);\n }\n}\n\n/** Build the ingest envelope for a successful ingest. */\nfunction buildIngestSuccess(result: IngestResult, relPath: string): IngestEnvelope {\n return {\n ok: true,\n path: relPath,\n sourceType: result.sourceType ?? null,\n error: null,\n };\n}\n\n/** Build the ingest envelope for a thrown ingest failure. */\nfunction buildIngestFailure(message: string): IngestEnvelope {\n return {\n ok: false,\n path: null,\n sourceType: null,\n error: { code: \"ingest_failed\", message, recoverable: false },\n };\n}\n\n/** Default zero-compile envelope used when compile never ran. */\nfunction emptyCompileEnvelope(): CompileEnvelope {\n return {\n ok: false,\n compiled: 0,\n skipped: 0,\n deleted: 0,\n pendingCandidates: 0,\n errors: null,\n error: null,\n };\n}\n\n/** Error codes the compile sub-envelope uses. */\ntype CompileErrorCode = \"provider_unavailable\" | \"compile_failed\";\n\n/** Human label for each compile-error code; used in the stderr-style status line. */\nconst COMPILE_ERROR_LABEL: Record<CompileErrorCode, string> = {\n provider_unavailable: \"Compile prerequisite\",\n compile_failed: \"Compile\",\n};\n\n/**\n * Translate a thrown failure (provider guard OR compile pipeline) into the\n * structured compile envelope. Reads the post-failure candidate count so\n * prior pending candidates still surface even on the failure path.\n */\nasync function buildCompileFailureEnvelope(\n root: string,\n code: CompileErrorCode,\n err: unknown,\n): Promise<CompileEnvelope> {\n const message = err instanceof Error ? err.message : String(err);\n output.status(\"!\", output.error(`${COMPILE_ERROR_LABEL[code]} failed: ${message}`));\n return {\n ...emptyCompileEnvelope(),\n pendingCandidates: await safeCountCandidates(root),\n error: { code, message, recoverable: true },\n };\n}\n\n/** Run compile after ingest. Translates throws into a structured envelope. */\nasync function runCompileStep(root: string, review: boolean): Promise<CompileEnvelope> {\n try {\n ensureProviderAvailable();\n } catch (err) {\n return await buildCompileFailureEnvelope(root, \"provider_unavailable\", err);\n }\n try {\n const result = await compileAndReport(root, { review });\n return await buildCompileEnvelopeFromResult(root, result);\n } catch (err) {\n return await buildCompileFailureEnvelope(root, \"compile_failed\", err);\n }\n}\n\n/**\n * Translate a returned {@link CompileResult} into the envelope.\n * `compile.ok` is true only when `errors` is empty; numeric counters are\n * preserved verbatim either way.\n */\nasync function buildCompileEnvelopeFromResult(\n root: string,\n result: CompileResult,\n): Promise<CompileEnvelope> {\n const pendingCandidates = await safeCountCandidates(root);\n const hasErrors = result.errors.length > 0;\n return {\n ok: !hasErrors,\n compiled: result.compiled,\n skipped: result.skipped,\n deleted: result.deleted,\n pendingCandidates,\n errors: hasErrors ? [...result.errors] : [],\n error: null,\n };\n}\n\n/** Race-tolerant candidate count; returns 0 when the candidate dir is missing. */\nasync function safeCountCandidates(root: string): Promise<number> {\n try {\n return await countCandidates(root);\n } catch {\n return 0;\n }\n}\n\n/**\n * Build the static JSON-side viewer envelope. The handoff path prints\n * the live viewer URL via viewCommand's own readiness line; the JSON\n * field stays static because `--json` never starts the viewer.\n */\nfunction buildViewerEnvelope(): ViewerEnvelope {\n return { opened: false, url: null };\n}\n\n/** Inputs for the ingest-failure finalisation path. */\ninterface FailureContext {\n source: string;\n ingest: IngestEnvelope;\n jsonMode: boolean;\n}\n\n/**\n * Print or emit an envelope for an ingest failure and return exit code 1.\n *\n * Per the implementation plan: \"Invalid/missing source exits 1 without\n * project inspection or mutation.\" So compile is left at its empty\n * default — we do NOT read `.llmwiki/candidates`, do not stat the root,\n * and do not run the post-compile recommendation engine. The next\n * action steers the user back to a manual `ingest` they can debug.\n */\nfunction finalizeFailure(ctx: FailureContext): number {\n const compile: CompileEnvelope = emptyCompileEnvelope();\n const next: NextEnvelope = {\n command: `llmwiki ingest ${ctx.source}`,\n reason: \"Source could not be ingested. Inspect the input and rerun ingest.\",\n executable: { binary: \"llmwiki\", args: [\"ingest\"], placeholders: [\"source\"] },\n };\n const envelope: QuickstartEnvelope = {\n version: QUICKSTART_JSON_VERSION,\n source: ctx.source,\n ingest: ctx.ingest,\n compile,\n viewer: buildViewerEnvelope(),\n next,\n };\n emitEnvelope(envelope, ctx.jsonMode);\n return 1;\n}\n\n/**\n * Print or emit an envelope for a completed (or post-ingest failed)\n * quickstart, optionally hand off to the foreground viewer, and return\n * the appropriate exit code. Non-zero only when the failure isn't\n * already covered by the documented partial-success envelope (resumable\n * compile failures keep exit 0 so agents reading the JSON envelope\n * drive the recovery; ingest failures are the one exit-1 path and\n * finalize separately).\n */\nasync function finalizeSuccess(\n run: QuickstartRun,\n options: QuickstartOptions,\n jsonMode: boolean,\n root: string,\n): Promise<number> {\n const projectState = await safeCollectState(root);\n const next = deriveNextAction(run, options, projectState);\n const handoff = shouldStartViewer({ options, jsonMode, compile: run.compile, projectState });\n const envelope = buildSuccessEnvelope(run, suppressRedundantViewerNext(next, handoff));\n emitEnvelope(envelope, jsonMode);\n if (handoff) await handoffToViewer();\n return 0;\n}\n\n/** Assemble the top-level success envelope from the per-section state. */\nfunction buildSuccessEnvelope(run: QuickstartRun, next: NextEnvelope): QuickstartEnvelope {\n return {\n version: QUICKSTART_JSON_VERSION,\n source: run.source,\n ingest: run.ingest,\n compile: run.compile,\n viewer: run.viewer,\n next,\n };\n}\n\n/**\n * Collect the post-compile project state cheaply, returning null on\n * inspection failure so handoff and recommendation fall back to their\n * conservative defaults rather than throwing the whole quickstart.\n */\nasync function safeCollectState(root: string): Promise<ProjectState | null> {\n try {\n return await collectProjectState(root);\n } catch {\n return null;\n }\n}\n\n/** Pick the per-scenario next-action recommendation using already-collected state. */\nfunction deriveNextAction(\n run: QuickstartRun,\n options: QuickstartOptions,\n projectState: ProjectState | null,\n): NextEnvelope {\n if (options.review === true && run.compile.ok) return reviewListAction();\n if (!run.compile.ok) return resumeCompileAction(run.compile);\n return postCompileRecommendation(projectState);\n}\n\n/** Fixed recommendation for a successful `--review` run. */\nfunction reviewListAction(): NextEnvelope {\n return {\n command: \"llmwiki review list\",\n reason: \"Generated candidates are waiting for review.\",\n executable: { binary: \"llmwiki\", args: [\"review\", \"list\"] },\n };\n}\n\n/**\n * Recommendation when compile failed (returned errors or threw). Reason\n * mirrors the documented partial-success guidance.\n */\nfunction resumeCompileAction(compile: CompileEnvelope): NextEnvelope {\n const reason = compile.error\n ? \"Source was ingested, but compile did not complete.\"\n : \"Compile reported errors. Address them and rerun compile.\";\n return {\n command: \"llmwiki compile\",\n reason,\n executable: { binary: \"llmwiki\", args: [\"compile\"] },\n };\n}\n\n/**\n * Use Slice 1's recommendation engine for the happy path so the trailing\n * `next` field stays consistent with what `llmwiki next` would report\n * from the same project state. Falls back to a static view-open hint\n * when the post-compile state inspection itself failed earlier.\n */\nfunction postCompileRecommendation(projectState: ProjectState | null): NextEnvelope {\n if (!projectState) {\n return {\n command: \"llmwiki view --open\",\n reason: \"Wiki pages are ready to browse.\",\n executable: { binary: \"llmwiki\", args: [\"view\", \"--open\"] },\n };\n }\n const { recommended } = recommendNextAction(projectState);\n return {\n command: recommended.command,\n reason: recommended.reason,\n executable: recommended.executable,\n };\n}\n\n/** Inputs for the viewer-handoff gate; grouped so the predicate stays one screen. */\ninterface HandoffGate {\n options: QuickstartOptions;\n jsonMode: boolean;\n compile: CompileEnvelope;\n projectState: ProjectState | null;\n}\n\ntype HandoffBlocker = (gate: HandoffGate) => boolean;\n\nconst VIEWER_HANDOFF_BLOCKERS: HandoffBlocker[] = [\n (gate) => gate.jsonMode,\n (gate) => gate.options.open === false,\n (gate) => gate.options.review === true,\n (gate) => !gate.compile.ok,\n (gate) => !hasRenderablePages(gate.projectState),\n];\n\n/**\n * Viewer Lifecycle start condition (plan §Viewer Lifecycle): wiki pages\n * exist, `--no-open` is false, `--json` is false, and `--review` is\n * false. Compile must also have completed cleanly — opening the viewer\n * on a compile-failure path would surface stale or partial wiki state\n * to the user.\n */\nfunction shouldStartViewer(gate: HandoffGate): boolean {\n return VIEWER_HANDOFF_BLOCKERS.every((isBlocked) => !isBlocked(gate));\n}\n\n/** A viewer handoff is useful only when there is at least one rendered page. */\nfunction hasRenderablePages(state: ProjectState | null): boolean {\n return conceptCountOf(state) + queryCountOf(state) > 0;\n}\n\n/** Null-safe concept count for the viewer handoff predicate. */\nfunction conceptCountOf(state: ProjectState | null): number {\n return state === null ? 0 : state.conceptCount;\n}\n\n/** Null-safe saved-query count for the viewer handoff predicate. */\nfunction queryCountOf(state: ProjectState | null): number {\n return state === null ? 0 : state.queryCount;\n}\n\n/**\n * Drop the `Next:` line ONLY when the recommendation is the redundant\n * `llmwiki view --open` action and the viewer is about to start —\n * printing it right above the handoff line would just echo the action\n * that's about to take over the foreground. Other recommendations\n * (e.g. `llmwiki lint` when a populated lint cache has errors,\n * `llmwiki review list` when candidates are pending) must survive\n * handoff so the user still sees the follow-up to run after Ctrl+C.\n * The JSON envelope is unaffected because handoff only happens in\n * human mode and `--json` always implies `--no-open`.\n */\nfunction suppressRedundantViewerNext(next: NextEnvelope, handoff: boolean): NextEnvelope {\n if (!handoff) return next;\n if (!isViewOpenAction(next)) return next;\n return { command: null, reason: next.reason, executable: null };\n}\n\n/**\n * True when `next` is the literal `llmwiki view --open` action. Matches\n * on the executable arg shape rather than the display string so a\n * future cosmetic tweak to `command` doesn't silently break the\n * suppression invariant.\n */\nfunction isViewOpenAction(next: NextEnvelope): boolean {\n return next.executable?.args.join(\"\\u0000\") === VIEW_OPEN_ARGS_KEY;\n}\n\n/**\n * Announce the foreground transition, then hand control to viewCommand.\n * viewCommand resolves once the HTTP server is bound; the listening\n * socket keeps the event loop alive until SIGINT/SIGTERM lands on\n * viewCommand's shutdown handler. Quickstart's own promise chain\n * unwinds normally — we never block past the await here in practice.\n */\nasync function handoffToViewer(): Promise<void> {\n process.stdout.write(\"\\nStarting viewer. Press Ctrl+C to stop.\\n\");\n await viewCommand({ open: true });\n}\n\n/** Emit the envelope as JSON or render a human summary depending on mode. */\nfunction emitEnvelope(envelope: QuickstartEnvelope, jsonMode: boolean): void {\n if (jsonMode) {\n process.stdout.write(`${JSON.stringify(envelope, null, 2)}\\n`);\n return;\n }\n renderHuman(envelope);\n}\n\n/** Human summary: short multi-line block keyed to the envelope contents. */\nfunction renderHuman(envelope: QuickstartEnvelope): void {\n const lines: string[] = [];\n appendIngestLine(lines, envelope.ingest);\n appendCompileLines(lines, envelope.compile);\n appendNextLines(lines, envelope.next);\n process.stdout.write(`\\n${lines.join(\"\\n\")}\\n`);\n}\n\n/** Ingest summary line — success path or short failure note. */\nfunction appendIngestLine(lines: string[], ingest: IngestEnvelope): void {\n if (ingest.ok && ingest.path) {\n lines.push(`1. Ingested source → ${ingest.path}`);\n return;\n }\n lines.push(\"1. Ingest failed — see error above.\");\n}\n\n/** Compile summary lines — success counts, review-pending count, or failure note. */\nfunction appendCompileLines(lines: string[], compile: CompileEnvelope): void {\n const rule = COMPILE_LINE_RULES.find((candidate) => candidate.matches(compile));\n if (rule) lines.push(rule.render(compile));\n}\n\ninterface CompileLineRule {\n matches: (compile: CompileEnvelope) => boolean;\n render: (compile: CompileEnvelope) => string;\n}\n\nconst COMPILE_LINE_RULES: CompileLineRule[] = [\n {\n matches: (compile) => compile.error !== null,\n render: () => \"2. Compile did not complete.\",\n },\n {\n matches: (compile) => (compile.errors?.length ?? 0) > 0,\n render: (compile) => `2. Compile reported ${compile.errors?.length ?? 0} error(s).`,\n },\n {\n matches: (compile) => compile.ok && compile.pendingCandidates > 0,\n render: (compile) => `2. Compiled review candidates → ${compile.pendingCandidates} pending`,\n },\n {\n matches: (compile) => compile.ok,\n render: (compile) => `2. Compiled wiki → ${compile.compiled} new, ${compile.skipped} skipped`,\n },\n];\n\n/** Trailing \"Next:\" line so the user sees the recommended follow-up. */\nfunction appendNextLines(lines: string[], next: NextEnvelope): void {\n if (!next.command) return;\n lines.push(\"\");\n lines.push(\"Next:\");\n lines.push(` ${next.command}`);\n}\n","/**\n * Single provider-credential guard shared by every entry point that\n * needs an LLM call (CLI compile/query/watch, MCP tools, the upcoming\n * `quickstart` command).\n *\n * The guard throws on failure instead of calling `process.exit(1)`,\n * which lets every caller decide how to surface the failure:\n *\n * - CLI verbs catch the throw and print the message + exit 1.\n * - MCP tools let the throw propagate as a tool error.\n * - `quickstart` catches the throw and translates it into the\n * `compile.error = { code: \"provider_unavailable\", ... }` shape\n * documented in the next-quickstart implementation plan.\n *\n * Error messages mirror the rich CLI form (with `Set it with: export X=...`\n * hints) so the user always sees actionable guidance no matter which\n * surface fired the guard.\n */\n\nimport { DEFAULT_PROVIDER } from \"./constants.js\";\nimport { resolveAnthropicAuthFromEnv } from \"./claude-settings.js\";\n\n/** Map of provider name to the env var that satisfies it. Null = no key needed. */\nconst PROVIDER_KEY_VARS: Record<string, string | null> = {\n anthropic: \"ANTHROPIC_API_KEY\",\n openai: \"OPENAI_API_KEY\",\n ollama: null,\n minimax: \"MINIMAX_API_KEY\",\n copilot: \"GITHUB_TOKEN\",\n};\n\n/**\n * Throw if the active LLM provider is missing credentials.\n * Anthropic accepts either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN\n * (resolved through the Claude Code settings fallback chain).\n */\nexport function ensureProviderAvailable(): void {\n const provider = process.env.LLMWIKI_PROVIDER ?? DEFAULT_PROVIDER;\n\n if (provider === \"anthropic\") {\n const auth = resolveAnthropicAuthFromEnv();\n if (!auth.apiKey && !auth.authToken) {\n throw new Error(\n `Anthropic credentials are required for the \"anthropic\" provider.\\n` +\n ` Set one of: export ANTHROPIC_API_KEY=<your-key> OR export ANTHROPIC_AUTH_TOKEN=<your-token>`,\n );\n }\n return;\n }\n\n const keyVar = PROVIDER_KEY_VARS[provider];\n if (keyVar === undefined) {\n throw new Error(\n `Unknown provider \"${provider}\".\\n` +\n ` Supported: ${Object.keys(PROVIDER_KEY_VARS).join(\", \")}`,\n );\n }\n\n if (keyVar && !process.env[keyVar]) {\n throw new Error(\n `${keyVar} environment variable is required for the \"${provider}\" provider.\\n` +\n ` Set it with: export ${keyVar}=<your-key>`,\n );\n }\n}\n","/**\n * `llmwiki context` — agent-ready evidence pack over the compiled wiki.\n *\n * Slice 1 ships the stable v1 JSON envelope shape and a basic markdown\n * renderer over lexical-only ranking. Semantic retrieval, graph\n * expansion, source windows, and MCP land in later slices; the\n * envelope's field set is already complete (later-slice fields are\n * empty arrays / `null`) so agents written against Slice 1 keep\n * working as data fills in.\n *\n * No provider credentials are required. Empty wikis emit a stable\n * empty packet whose `suggestedActions[]` steers the user toward\n * `quickstart`/`ingest` via the shared recommendation engine.\n */\n\nimport path from \"path\";\nimport { buildContextPack } from \"../context/build.js\";\nimport type {\n ContextPack,\n ContextPrimary,\n ContextWarning,\n} from \"../context/types.js\";\nimport {\n DEFAULT_BUDGET_TOKENS,\n DEFAULT_DEPTH,\n DEFAULT_TOP_CHUNKS,\n DEFAULT_TOP_PAGES,\n} from \"../context/types.js\";\nimport type { RecommendedAction } from \"../project/recommendations.js\";\n\n/** CLI-supplied options for `llmwiki context`. */\nexport interface ContextCommandOptions {\n /** Token budget. Coerced from the Commander string into a number. */\n budget?: string | number;\n /** Output format; `json` wins when both `--json` and `--format` are set. */\n format?: \"json\" | \"markdown\" | string;\n /** Shortcut for `--format json`. */\n json?: boolean;\n /** Graph depth (capped at 2). 0 disables expansion the same way `--no-neighbors` does. */\n depth?: string | number;\n /** Max primary pages. */\n topPages?: string | number;\n /** Max semantic chunks; pinned default 8 even though Slice 1 emits no chunks. */\n topChunks?: string | number;\n /** Set `project.root` to `null` for privacy. */\n omitRoot?: boolean;\n /**\n * Set by Commander's `--no-neighbors` negated flag. When absent (the\n * default), graph expansion runs; when `false`, expansion is skipped\n * and both `neighbors[]` and `gaps[]` stay empty arrays.\n */\n neighbors?: boolean;\n /**\n * When true (`--include-sources`), populate\n * `primary[].sourceWindows` from claim-level citation spans, capped\n * at 20 windows per pack and 30 lines per window. Path-confined to\n * `sources/`; absolute paths, parent traversal, and symlink escapes\n * are rejected. Off by default to keep the JSON small.\n */\n includeSources?: boolean;\n}\n\n/**\n * Run the `context` command. Returns the exit code the CLI shim should\n * propagate. Always exits 0 on a successful inspection; failures\n * surface as a thrown error caught by the CLI wrapper.\n */\nexport default async function contextCommand(\n prompt: string,\n options: ContextCommandOptions = {},\n): Promise<number> {\n const pack = await buildContextPack({\n root: process.cwd(),\n prompt,\n budget: coerceNumber(options.budget, DEFAULT_BUDGET_TOKENS),\n depth: coerceNumber(options.depth, DEFAULT_DEPTH),\n topPages: coerceNumber(options.topPages, DEFAULT_TOP_PAGES),\n topChunks: coerceNumber(options.topChunks, DEFAULT_TOP_CHUNKS),\n omitRoot: options.omitRoot === true,\n neighbors: options.neighbors,\n includeSources: options.includeSources === true,\n });\n emit(pack, resolveFormat(options));\n return 0;\n}\n\n/** Coerce a Commander string/number into a non-NaN number, falling back to `fallback`. */\nfunction coerceNumber(raw: string | number | undefined, fallback: number): number {\n if (raw === undefined) return fallback;\n const value = typeof raw === \"number\" ? raw : Number(raw);\n return Number.isFinite(value) ? value : fallback;\n}\n\n/** Resolve the output format; `--json` wins over `--format`. */\nfunction resolveFormat(options: ContextCommandOptions): \"json\" | \"markdown\" {\n if (options.json === true) return \"json\";\n if (options.format === \"json\") return \"json\";\n return \"markdown\";\n}\n\n/** Write the chosen rendering to stdout. */\nfunction emit(pack: ContextPack, format: \"json\" | \"markdown\"): void {\n if (format === \"json\") {\n process.stdout.write(`${JSON.stringify(pack, null, 2)}\\n`);\n return;\n }\n process.stdout.write(`${renderMarkdown(pack)}\\n`);\n}\n\n/** Build a basic markdown rendering that mirrors the plan §Markdown Output skeleton. */\nfunction renderMarkdown(pack: ContextPack): string {\n const lines: string[] = [];\n appendHeader(lines, pack);\n appendPrimaryPages(lines, pack.primary);\n appendGraphNeighborhood(lines, pack.neighbors);\n appendWarnings(lines, pack.warnings);\n appendSuggestedActions(lines, pack.suggestedActions);\n return lines.join(\"\\n\");\n}\n\n/**\n * `## Graph Neighborhood` block listing each emitted neighbor edge.\n * Empty `neighbors[]` (e.g. depth 0, `--no-neighbors`, or no graph\n * topology) skips the section entirely so the human output stays\n * focused on what's actually in the pack.\n */\nfunction appendGraphNeighborhood(\n lines: string[],\n neighbors: ContextPack[\"neighbors\"],\n): void {\n if (neighbors.length === 0) return;\n lines.push(\"## Graph Neighborhood\");\n lines.push(\"\");\n for (const neighbor of neighbors) {\n const arrow = neighbor.direction === \"outgoing\" ? \"->\" : \"<-\";\n lines.push(\n `- \\`${neighbor.from}\\` ${arrow} \\`${neighbor.to}\\`` +\n ` (${neighbor.reason}, distance ${neighbor.distance})`,\n );\n }\n lines.push(\"\");\n}\n\n/** Top block: title, prompt echo, budget line. */\nfunction appendHeader(lines: string[], pack: ContextPack): void {\n lines.push(\"# Context Pack\");\n lines.push(\"\");\n lines.push(`Prompt: ${pack.prompt}`);\n lines.push(`Budget: ${pack.budget.estimatedTokens} / ${pack.budget.requestedTokens} estimated tokens`);\n}\n\n/** `## Primary Pages` section or an empty-state placeholder. */\nfunction appendPrimaryPages(lines: string[], primary: ContextPrimary[]): void {\n lines.push(\"\");\n lines.push(\"## Primary Pages\");\n lines.push(\"\");\n if (primary.length === 0) {\n lines.push(\"_No primary pages matched the prompt._\");\n return;\n }\n for (const page of primary) appendPrimaryPage(lines, page);\n}\n\n/** Render one `### Title` block with the page filename + ranking reasons. */\nfunction appendPrimaryPage(lines: string[], page: ContextPrimary): void {\n const pageFile = path.join(\"wiki\", page.pageDirectory, `${slugFromId(page.id)}.md`);\n lines.push(`### ${page.title} (\\`${pageFile}\\`)`);\n lines.push(\"\");\n lines.push(`Why included: ${page.reasons.join(\", \") || \"(no signals)\"}`);\n if (page.summary) {\n lines.push(\"\");\n lines.push(page.summary);\n }\n appendCitations(lines, page);\n appendSourceWindows(lines, page);\n lines.push(\"\");\n}\n\n/** `Sources: a.md:1-3, b.md` line — omitted when no citations exist. */\nfunction appendCitations(lines: string[], page: ContextPrimary): void {\n if (page.citations.length === 0) return;\n const refs = page.citations.map(renderCitation).join(\", \");\n lines.push(\"\");\n lines.push(`Sources: ${refs}`);\n}\n\n/** Single citation reference string for the human renderer. */\nfunction renderCitation(citation: ContextPrimary[\"citations\"][number]): string {\n if (citation.start !== undefined && citation.end !== undefined) {\n return `\\`${citation.file}:${citation.start}-${citation.end}\\``;\n }\n return `\\`${citation.file}\\``;\n}\n\n/** `> source.md:1-3` quoted blocks for each materialized source window. */\nfunction appendSourceWindows(lines: string[], page: ContextPrimary): void {\n if (page.sourceWindows.length === 0) return;\n for (const window of page.sourceWindows) {\n lines.push(\"\");\n lines.push(`From \\`${window.file}:${window.start}-${window.end}\\`:`);\n lines.push(\"\");\n for (const line of window.text.split(/\\r?\\n/)) lines.push(`> ${line}`);\n }\n}\n\n/** Page ID is `<directory>/<slug>`; pull the slug out for the filename hint. */\nfunction slugFromId(id: string): string {\n const idx = id.indexOf(\"/\");\n return idx === -1 ? id : id.slice(idx + 1);\n}\n\n/** `## Warnings` section; quietly skipped when nothing fired. */\nfunction appendWarnings(lines: string[], warnings: ContextWarning[]): void {\n if (warnings.length === 0) return;\n lines.push(\"## Warnings\");\n lines.push(\"\");\n for (const warning of warnings) lines.push(`- ${warning.message}`);\n lines.push(\"\");\n}\n\n/** `## Suggested Next Actions`; surfaces the recommendation prefix. */\nfunction appendSuggestedActions(lines: string[], actions: RecommendedAction[]): void {\n if (actions.length === 0) return;\n lines.push(\"## Suggested Next Actions\");\n lines.push(\"\");\n for (const action of actions) {\n if (action.command) lines.push(`- \\`${action.command}\\``);\n }\n}\n","/**\n * Provenance helpers for `llmwiki context`.\n *\n * Slice 4 ships two related pieces of work:\n * 1. Flatten `ViewerPage.citations` (`ClaimCitation[]`, each with one\n * or more `SourceSpan` entries) into the documented\n * `ContextPrimary.citations[]` shape: one object per span,\n * `file`/`start`/`end` lifted from `span.lines` when present,\n * paragraph-only citations omit `start`/`end`, de-duped by\n * `(file, start, end)`, preserved in first-seen document order\n * (plan §Provenance And Source Windows).\n * 2. Materialize bounded `ContextSourceWindow[]` for `--include-sources`\n * by reading short line ranges out of `sources/`. Path-confined:\n * traversal, absolute paths, and symlink escapes are rejected.\n * Only claim-level spans (`lines` populated) become windows;\n * paragraph-only citations are intentionally skipped because the\n * caller asked for SPECIFIC line context, not whole files.\n *\n * Citation flattening is the inner contract for `primary[].citations`;\n * source-window materialization is the outer guard rail for\n * `--include-sources` per-pack and per-window caps.\n */\n\nimport { promises as fs } from \"fs\";\nimport path from \"path\";\nimport type { ClaimCitation, SourceSpan } from \"../utils/types.js\";\nimport { SOURCES_DIR } from \"../utils/constants.js\";\n\n/** Per-pack ceiling on emitted source windows. */\nexport const MAX_SOURCE_WINDOWS = 20;\n/** Per-window ceiling on lines read out of a source file. */\nexport const MAX_LINES_PER_WINDOW = 30;\n/**\n * Visible ASCII delimiter for {@link citationKey} dedup tuples. Plain\n * spaces and dashes can legally appear inside `file` names so the key\n * must use a separator that filenames cannot contain. `' <#> '` is\n * unambiguous, ASCII-clean (no control bytes — graph.ts had the same\n * class of bug with NUL delimiters), and obvious in diagnostic output.\n */\nconst CITATION_KEY_SEPARATOR = \" <#> \";\n\n/**\n * Flat citation shape consumed by `ContextPrimary.citations[]`. Mirrors\n * the inline anonymous type in `src/context/types.ts`; kept private so\n * the ranker reaches it via structural inference rather than a named\n * cross-module import.\n */\ninterface FlatCitation {\n file: string;\n start?: number;\n end?: number;\n}\n\n/**\n * Materialized source window consumed by\n * `ContextPrimary.sourceWindows[]`. Always carries explicit line\n * numbers — Slice 4 only ever materializes claim-level spans, so\n * `start`/`end` are mandatory.\n */\ninterface SourceWindow {\n file: string;\n start: number;\n end: number;\n text: string;\n}\n\n/**\n * Flatten a page's `ClaimCitation[]` into the JSON-shape citation list.\n * Multi-source markers (`^[a.md, b.md]`) produce one object per span.\n * Paragraph-only citations stay (with no `start`/`end`).\n */\nexport function flattenCitations(citations: ClaimCitation[]): FlatCitation[] {\n const out: FlatCitation[] = [];\n const seen = new Set<string>();\n for (const citation of citations) {\n for (const span of citation.spans) {\n const flat = toFlatCitation(span);\n const key = citationKey(flat);\n if (seen.has(key)) continue;\n seen.add(key);\n out.push(flat);\n }\n }\n return out;\n}\n\n/** Lift one `SourceSpan` into the public flat shape. */\nfunction toFlatCitation(span: SourceSpan): FlatCitation {\n if (!span.lines) return { file: span.file };\n return { file: span.file, start: span.lines.start, end: span.lines.end };\n}\n\n/** Deduplication key. Uses `-` for missing line ranges; safe because lines are positive integers. */\nfunction citationKey(citation: FlatCitation): string {\n const start = citation.start ?? \"-\";\n const end = citation.end ?? \"-\";\n return [citation.file, String(start), String(end)].join(CITATION_KEY_SEPARATOR);\n}\n\n/**\n * Per-pack source-window budget accumulator passed into\n * {@link materializeSourceWindows}. The orchestrator owns one\n * instance and shares it across all primary pages so the global\n * 20-window ceiling is enforced even when many pages each carry many\n * citations.\n */\ninterface SourceWindowBudget {\n remaining: number;\n}\n\n/** Create a fresh budget seeded with the documented per-pack cap. */\nexport function createSourceWindowBudget(): SourceWindowBudget {\n return { remaining: MAX_SOURCE_WINDOWS };\n}\n\n/**\n * Materialize source windows for one page's citations. Returns an\n * empty array when the citation list is empty, when no entries carry\n * line ranges (paragraph-only), or when the budget is exhausted.\n */\nexport async function materializeSourceWindows(\n root: string,\n citations: FlatCitation[],\n budget: SourceWindowBudget,\n): Promise<SourceWindow[]> {\n if (budget.remaining <= 0) return [];\n const windows: SourceWindow[] = [];\n for (const citation of citations) {\n if (budget.remaining <= 0) break;\n if (citation.start === undefined || citation.end === undefined) continue;\n const window = await readSourceWindow(root, citation);\n if (!window) continue;\n windows.push(window);\n budget.remaining -= 1;\n }\n return windows;\n}\n\n/** Read one source window or return null when the read is rejected by the path guard. */\nasync function readSourceWindow(\n root: string,\n citation: FlatCitation,\n): Promise<SourceWindow | null> {\n if (citation.start === undefined || citation.end === undefined) return null;\n const sourcesRoot = await resolveSourcesRoot(root);\n if (!sourcesRoot) return null;\n const realPath = await resolveSafePath(sourcesRoot, citation.file);\n if (!realPath) return null;\n return readClampedWindow(realPath, citation);\n}\n\n/**\n * Resolve the canonical `sources/` directory. Returns null when the\n * directory doesn't exist; otherwise returns the realpath so\n * `isInside` checks against canonical paths on platforms with\n * symlinked parents (macOS `/var` → `/private/var`, Linux container\n * bind mounts, etc.). Path confinement still rejects files whose\n * realpath escapes this canonical root, so a symlinked sources/ tree\n * remains safe.\n */\nasync function resolveSourcesRoot(root: string): Promise<string | null> {\n const candidate = path.join(root, SOURCES_DIR);\n try {\n return await fs.realpath(candidate);\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve `file` under `sourcesRoot`, rejecting:\n * - absolute paths (`/etc/passwd`)\n * - ANY `..` segment, even one that would normalize back inside\n * sources/ (`nested/../paper.md` is rejected — the spec says\n * \"reject parent traversal\", and \"looks safe after normalization\"\n * is still traversal a reviewer must be able to spot in the raw\n * citation marker)\n * - paths whose realpath escapes `sourcesRoot` (symlinks pointing\n * outside `sources/`)\n */\nasync function resolveSafePath(sourcesRoot: string, file: string): Promise<string | null> {\n if (file.length === 0) return null;\n if (path.isAbsolute(file)) return null;\n if (containsParentSegment(file)) return null;\n const joined = path.join(sourcesRoot, file);\n const resolved = path.resolve(joined);\n // Defense in depth: even after the segment guard, if the resolved\n // path escapes sources/ (e.g., a Windows drive-letter trick or a\n // separator we did not recognize) refuse it.\n if (!isInside(sourcesRoot, resolved)) return null;\n try {\n const realPath = await fs.realpath(resolved);\n if (!isInside(sourcesRoot, realPath)) return null;\n return realPath;\n } catch {\n return null;\n }\n}\n\n/**\n * True when any segment of `file` is literally `..`. Splits on BOTH\n * `/` and the platform separator so a malicious `nested\\..\\paper.md`\n * on Linux (where `\\` is a legal filename char) is still rejected when\n * served to a Windows agent reading the same context pack.\n */\nfunction containsParentSegment(file: string): boolean {\n const segments = file.split(/[/\\\\]/);\n return segments.some((segment) => segment === \"..\");\n}\n\n/**\n * True when `candidate` is `parent` itself or a descendant of it.\n * Adds a path separator suffix to `parent` before the prefix check so\n * `/parent` does not incorrectly contain `/parentish`.\n */\nfunction isInside(parent: string, candidate: string): boolean {\n if (candidate === parent) return true;\n const normalizedParent = parent.endsWith(path.sep) ? parent : `${parent}${path.sep}`;\n return candidate.startsWith(normalizedParent);\n}\n\n/**\n * Read the requested line window, clamping to a maximum length so a\n * runaway citation cannot pull hundreds of lines into the pack. Both\n * `start` and `end` are 1-indexed inclusive (matching the\n * `SourceSpan` convention).\n */\nasync function readClampedWindow(\n realPath: string,\n citation: FlatCitation,\n): Promise<SourceWindow | null> {\n if (citation.start === undefined || citation.end === undefined) return null;\n let raw: string;\n try {\n raw = await fs.readFile(realPath, \"utf-8\");\n } catch {\n return null;\n }\n const lines = raw.split(/\\r?\\n/);\n const startIndex = Math.max(0, citation.start - 1);\n const inclusiveEnd = Math.min(lines.length, citation.end);\n if (startIndex >= inclusiveEnd) return null;\n const clampedEnd = Math.min(inclusiveEnd, startIndex + MAX_LINES_PER_WINDOW);\n const text = lines.slice(startIndex, clampedEnd).join(\"\\n\");\n return { file: citation.file, start: citation.start, end: startIndex + (clampedEnd - startIndex), text };\n}\n","/**\n * Multi-signal ranking for `llmwiki context`.\n *\n * Slice 1 wired lexical + exact-match signals. Slice 2 layers semantic\n * chunk retrieval on top of the same combiner (plan §Ranking Model);\n * the wrapper in `src/context/retrieval.ts` keeps the orchestrator\n * unaware of whether the embedding store contributed.\n *\n * Signals fed into the combiner:\n * - `searchPages().results[].matchedIn` distinguishes title vs body\n * hits without re-parsing the body.\n * - Exact slug equality (case-insensitive after lowercasing) adds a\n * strong bonus and the `exact-slug` reason.\n * - Exact title equality (case-insensitive, trimmed) adds the\n * strongest bonus and the `exact-title` reason.\n * - Semantic chunk hits add a high-weight `semantic-chunk` reason\n * and attach the chunk text/score/contentHash to the primary page;\n * additional chunks on the same page contribute a capped bonus so\n * a popular page can't shadow exact-title hits.\n *\n * Scores are normalized into [0, 1] best-effort — they're explainable\n * to the agent but not a quality guarantee. Stable tie-sort: descending\n * score, then ascending title, then ascending page ID.\n */\n\nimport { searchPages } from \"../viewer/search.js\";\nimport type { ViewerPage, ViewerSnapshot } from \"../viewer/types.js\";\nimport type { SemanticChunkHit } from \"./retrieval.js\";\nimport { flattenCitations } from \"./provenance.js\";\nimport type { ContextPrimary, PrimaryReason } from \"./types.js\";\n\n/** Per-signal weight (sums normalize so an exact-title hit lands near 1.0). */\nconst WEIGHT_TITLE_MATCH = 0.5;\nconst WEIGHT_BODY_MATCH = 0.3;\nconst WEIGHT_EXACT_SLUG = 0.4;\nconst WEIGHT_EXACT_TITLE = 0.5;\n\n/** Base weight for the first semantic chunk a page contributes. Peer of title-match. */\nconst WEIGHT_SEMANTIC_CHUNK = 0.5;\n/** Per-additional-chunk bonus once the first semantic hit on a page has landed. */\nconst WEIGHT_SEMANTIC_CHUNK_BONUS = 0.05;\n/** Max additional chunks (beyond the first) that contribute the per-chunk bonus. */\nconst MAX_SEMANTIC_BONUS_CHUNKS = 3;\n\n/** Cap so combined scores stay inside the normalized [0, 1] range. */\nconst MAX_NORMALIZED_SCORE = 1;\n\n/**\n * One semantic chunk surfaced for a primary page. Matches the inline\n * `ContextPrimary.chunks[]` shape in `types.ts`; declared locally so the\n * ranker can build it without exporting the structural anonymous type.\n */\ninterface PrimaryChunk {\n text: string;\n score: number;\n contentHash: string;\n}\n\n/** Internal accumulator: one row per candidate page. */\ninterface RankingRow {\n page: ViewerPage;\n reasons: Set<PrimaryReason>;\n weight: number;\n snippet: string;\n /** Chunks attached by `applySemanticSignals`; empty in lexical-only flows. */\n chunks: PrimaryChunk[];\n}\n\n/**\n * Rank candidate pages for `prompt` against `snapshot`, returning up to\n * `topN` populated {@link ContextPrimary} entries.\n *\n * `semanticHits` is the post-retrieval chunk list from Slice 2; pass an\n * empty array (the default) for lexical-only behaviour. Citations and\n * sourceWindows stay empty in Slice 2 — those fields land in Slice 4.\n * Page-local `warnings` is sourced from the viewer collector.\n */\nexport function rankPages(\n snapshot: ViewerSnapshot,\n prompt: string,\n topN: number,\n semanticHits: SemanticChunkHit[] = [],\n): ContextPrimary[] {\n const rows = new Map<string, RankingRow>();\n applyLexicalSignals(rows, snapshot, prompt);\n applyExactSignals(rows, snapshot, prompt);\n applySemanticSignals(rows, snapshot, semanticHits);\n const sorted = Array.from(rows.values()).sort(compareRows);\n return sorted.slice(0, Math.max(0, topN)).map(rowToPrimary);\n}\n\n/** Push every `searchPages()` hit into the ranking map. */\nfunction applyLexicalSignals(\n rows: Map<string, RankingRow>,\n snapshot: ViewerSnapshot,\n prompt: string,\n): void {\n const { results } = searchPages(snapshot, prompt);\n for (const result of results) {\n const page = snapshot.pages.find((p) => p.id === result.id);\n if (!page) continue;\n const row = ensureRow(rows, page);\n row.snippet = row.snippet || result.snippet;\n if (result.matchedIn === \"title\") {\n addReason(row, \"title-match\", WEIGHT_TITLE_MATCH);\n } else {\n addReason(row, \"body-match\", WEIGHT_BODY_MATCH);\n }\n }\n}\n\n/** Bonus pass for exact-slug and exact-title matches across all pages. */\nfunction applyExactSignals(\n rows: Map<string, RankingRow>,\n snapshot: ViewerSnapshot,\n prompt: string,\n): void {\n const normalized = prompt.trim().toLowerCase();\n if (normalized.length === 0) return;\n for (const page of snapshot.pages) {\n if (page.slug.toLowerCase() === normalized) {\n addReason(ensureRow(rows, page), \"exact-slug\", WEIGHT_EXACT_SLUG);\n }\n if (page.title.trim().toLowerCase() === normalized) {\n addReason(ensureRow(rows, page), \"exact-title\", WEIGHT_EXACT_TITLE);\n }\n }\n}\n\n/**\n * Apply semantic chunk signals: each retrieved chunk is grouped by page\n * slug, the page gets the `semantic-chunk` reason once, a capped\n * multi-chunk bonus accrues for additional chunks on the same page, and\n * the chunk records are attached to the row for `primary[].chunks[]`.\n *\n * Chunks for slugs that no longer exist in the viewer snapshot are\n * dropped silently — the embedding store can lag behind page deletions\n * and we don't want to fabricate a phantom primary entry just because\n * the store carries a stale chunk.\n */\nfunction applySemanticSignals(\n rows: Map<string, RankingRow>,\n snapshot: ViewerSnapshot,\n hits: SemanticChunkHit[],\n): void {\n if (hits.length === 0) return;\n const bySlug = groupHitsBySlug(hits);\n for (const [slug, slugHits] of bySlug) {\n const page = findPageBySlug(snapshot, slug);\n if (!page) continue;\n const row = ensureRow(rows, page);\n addReason(row, \"semantic-chunk\", WEIGHT_SEMANTIC_CHUNK);\n row.weight += semanticMultiChunkBonus(slugHits.length);\n for (const hit of slugHits) {\n row.chunks.push({\n text: hit.text,\n score: hit.score,\n contentHash: hit.contentHash,\n });\n }\n }\n}\n\n/** Group chunk hits by page slug while preserving the score-desc input order. */\nfunction groupHitsBySlug(hits: SemanticChunkHit[]): Map<string, SemanticChunkHit[]> {\n const bySlug = new Map<string, SemanticChunkHit[]>();\n for (const hit of hits) {\n const existing = bySlug.get(hit.slug);\n if (existing) existing.push(hit);\n else bySlug.set(hit.slug, [hit]);\n }\n return bySlug;\n}\n\n/** Score bump for the 2nd..N-th chunk on the same page, capped. */\nfunction semanticMultiChunkBonus(chunkCount: number): number {\n const extra = Math.max(0, Math.min(chunkCount - 1, MAX_SEMANTIC_BONUS_CHUNKS));\n return extra * WEIGHT_SEMANTIC_CHUNK_BONUS;\n}\n\n/**\n * Resolve a bare chunk slug to a snapshot page, with concepts winning\n * over queries when both contain the same slug — matches the bare-slug\n * precedence rule used by the viewer's wikilink resolver.\n */\nfunction findPageBySlug(snapshot: ViewerSnapshot, slug: string): ViewerPage | null {\n const concept = snapshot.pages.find(\n (p) => p.pageDirectory === \"concepts\" && p.slug === slug,\n );\n if (concept) return concept;\n const query = snapshot.pages.find(\n (p) => p.pageDirectory === \"queries\" && p.slug === slug,\n );\n return query ?? null;\n}\n\n/** Fetch the row for `page`, lazily allocating it on first use. */\nfunction ensureRow(rows: Map<string, RankingRow>, page: ViewerPage): RankingRow {\n const existing = rows.get(page.id);\n if (existing) return existing;\n const created: RankingRow = {\n page,\n reasons: new Set(),\n weight: 0,\n snippet: \"\",\n chunks: [],\n };\n rows.set(page.id, created);\n return created;\n}\n\n/** Record one reason + weight; reasons set de-dupes naturally. */\nfunction addReason(row: RankingRow, reason: PrimaryReason, weight: number): void {\n row.reasons.add(reason);\n row.weight += weight;\n}\n\n/** Stable sort: descending score, ascending title, ascending page ID. */\nfunction compareRows(a: RankingRow, b: RankingRow): number {\n if (a.weight !== b.weight) return b.weight - a.weight;\n const byTitle = a.page.title.localeCompare(b.page.title);\n if (byTitle !== 0) return byTitle;\n return a.page.id.localeCompare(b.page.id);\n}\n\n/**\n * Convert a ranking row into a ContextPrimary.\n *\n * Slice 4 fills `citations` from the viewer collector's already-parsed\n * `ClaimCitation[]` (one object per source span, paragraph-only spans\n * keep no line range, multi-source markers split, deduped by\n * `(file,start,end)`, document-order preserved).\n *\n * Page-local `warnings` was wired in Slice 1; it forwards the same\n * `ViewerWarning` objects the viewer surfaces, so any malformed-frontmatter\n * or unresolved-citation diagnostic the viewer already knows about\n * lands in the context pack automatically.\n *\n * `sourceWindows` stays empty here — the orchestrator owns the\n * per-pack budget and writes windows back into the primary entries\n * after ranking, so the ranker remains a pure function over the\n * snapshot.\n */\nfunction rowToPrimary(row: RankingRow): ContextPrimary {\n return {\n id: row.page.id,\n title: row.page.title,\n pageDirectory: row.page.pageDirectory,\n score: normalizeWeight(row.weight),\n reasons: Array.from(row.reasons).sort(),\n summary: row.snippet,\n chunks: row.chunks,\n citations: flattenCitations(row.page.citations),\n sourceWindows: [],\n warnings: row.page.warnings.map((w) => ({ code: w.code, message: w.message })),\n };\n}\n\n/** Squash accumulated weight into [0, 1] without inventing precision. */\nfunction normalizeWeight(weight: number): number {\n if (weight <= 0) return 0;\n if (weight >= MAX_NORMALIZED_SCORE) return MAX_NORMALIZED_SCORE;\n return Math.round(weight * 100) / 100;\n}\n","/**\n * Semantic retrieval wrapper for `llmwiki context` Slice 2.\n *\n * Wraps `readEmbeddingStore()` + `findRelevantChunks()` so the\n * orchestrator never has to special-case provider failures, missing\n * stores, or stale-model stores. Semantic retrieval is opportunistic\n * here — context packs must keep working on lexical signals alone when\n * the embedding store is absent OR the active provider has no\n * credentials. The wrapper translates both failure modes into stable\n * warning codes (`embedding-store-missing` / `query-embedding-unavailable`)\n * so the JSON contract stays predictable for agents regardless of which\n * branch fired.\n *\n * Stale-model stores are folded into the `embedding-store-missing`\n * branch. We detect them HERE — not by waiting for `findRelevantChunks`\n * to return `[]` — because the embeddings module emits a\n * `output.status(\"!\", ...)` warning to stdout via `console.log` when\n * `loadActiveStore` sees a stale model. That leak would corrupt\n * `llmwiki context --json` output (stdout must be pure JSON). Catching\n * the model mismatch up front avoids the embeddings module's warning\n * path entirely.\n *\n * Malformed embedding store files (truncated writes, hand-edits, etc.)\n * are also folded into `embedding-store-missing` rather than propagated\n * as crashes. `readEmbeddingStore` does not catch its own JSON parse\n * failures, so the wrapper guards both the read and the parse so\n * `context` keeps producing parseable output even when the store on\n * disk is broken.\n */\n\nimport {\n findRelevantChunks,\n readEmbeddingStore,\n resolveEmbeddingModel,\n} from \"../utils/embeddings.js\";\nimport type { EmbeddingStore } from \"../utils/embeddings.js\";\n\n/** Stable warning code returned when semantic retrieval did not contribute. */\nexport type SemanticRetrievalWarning =\n | \"embedding-store-missing\"\n | \"query-embedding-unavailable\"\n | \"semantic-retrieval-error\";\n\n/**\n * Slimmed chunk record passed from retrieval into ranking. Keeps\n * `ranking.ts` independent of the underlying `ChunkEmbeddingEntry`\n * shape so the embedding store can evolve without churn here.\n */\nexport interface SemanticChunkHit {\n /** Source page slug; bare-slug resolved against the viewer snapshot in ranking. */\n slug: string;\n /** Chunk body text — surfaced verbatim in `primary[].chunks[].text`. */\n text: string;\n /** Cosine similarity from `findTopKChunks`; pass-through into the chunk entry. */\n score: number;\n /** Stable hash of the chunk text; pass-through into the chunk entry. */\n contentHash: string;\n}\n\n/**\n * Outcome of one semantic retrieval call. Either `hits` is populated and\n * `warning` is `null`, or `hits` is empty and `warning` carries the\n * stable code. We use mutually-exclusive null/value rather than a tagged\n * union so callers can spread both fields into the envelope without\n * widening narrow types.\n */\nexport interface SemanticRetrievalOutcome {\n hits: SemanticChunkHit[];\n warning: SemanticRetrievalWarning | null;\n}\n\n/**\n * Best-effort semantic retrieval. Returns the top-k chunks the active\n * embedding store can offer for `prompt`, OR a warning that explains\n * why semantic retrieval contributed nothing this call.\n *\n * Precondition: caller already knows the original prompt should be\n * passed (not the truncated display copy). Slice 2 wires the orchestrator\n * to pass `NormalizedOptions.rankingPrompt`.\n */\nexport async function retrieveSemanticChunks(\n root: string,\n prompt: string,\n topChunks: number,\n): Promise<SemanticRetrievalOutcome> {\n if (topChunks <= 0) return emptyOutcome(null);\n if (await isStoreUnusable(root)) return emptyOutcome(\"embedding-store-missing\");\n\n let raw: Awaited<ReturnType<typeof findRelevantChunks>>;\n try {\n raw = await findRelevantChunks(root, prompt, topChunks);\n } catch (err) {\n // Provider/config failures are expected in credential-free context\n // runs. Unknown exceptions still fall back to lexical, but receive\n // a distinct warning so real bugs are not mislabeled as auth.\n return emptyOutcome(classifyRetrievalError(err));\n }\n\n if (raw.length === 0) {\n // Defensive: with the upfront stale-model + chunk-count checks the\n // only way to land here is a TOCTOU race where the store changed\n // between the pre-check read and `findRelevantChunks`. Surface as\n // `embedding-store-missing` so the warning vocabulary stays small.\n return emptyOutcome(\"embedding-store-missing\");\n }\n\n return { hits: raw.map(toSemanticChunkHit), warning: null };\n}\n\n/** Build the empty-hits outcome shape; centralised to keep callers terse. */\nfunction emptyOutcome(warning: SemanticRetrievalWarning | null): SemanticRetrievalOutcome {\n return { hits: [], warning };\n}\n\n/**\n * True when the on-disk embedding store cannot supply chunks: file\n * missing, JSON malformed, v1 / empty v2 store, OR built with a\n * different embedding model than the active provider is using.\n *\n * The stale-model check MUST happen here (before any `findRelevantChunks`\n * call) so the embeddings module's stale-store warning — which writes\n * to stdout via `output.status` — never fires. That warning would\n * corrupt `--json` output. Same reasoning applies to malformed-JSON\n * reads: `readEmbeddingStore` lets `JSON.parse` throw, which would\n * crash the command with exit 1 unless we catch it here.\n */\nasync function isStoreUnusable(root: string): Promise<boolean> {\n const store = await tryReadEmbeddingStore(root);\n if (!store) return true;\n if (!store.chunks || store.chunks.length === 0) return true;\n if (isStaleModel(store)) return true;\n return false;\n}\n\n/**\n * Wrap `readEmbeddingStore` so a missing OR malformed file both reduce\n * to `null`. The reader does `await readFile` + `JSON.parse` without\n * its own catch, so a broken store would otherwise surface as an\n * unhandled rejection to the caller.\n */\nasync function tryReadEmbeddingStore(root: string): Promise<EmbeddingStore | null> {\n try {\n return await readEmbeddingStore(root);\n } catch {\n return null;\n }\n}\n\n/**\n * Compare the persisted store's embedding model against the active\n * provider's resolved model. Returns true when they disagree so the\n * caller can fall back without triggering the embeddings module's\n * stdout warning. Defensive: a thrown `resolveEmbeddingModel` (e.g.\n * unknown `LLMWIKI_PROVIDER`) is also treated as stale.\n */\nfunction isStaleModel(store: EmbeddingStore): boolean {\n try {\n return store.model !== resolveEmbeddingModel();\n } catch {\n return true;\n }\n}\n\n/** Classify failures without leaking raw provider or stack text into JSON. */\nfunction classifyRetrievalError(err: unknown): SemanticRetrievalWarning {\n const message = err instanceof Error ? err.message : String(err);\n return looksLikeProviderFailure(message)\n ? \"query-embedding-unavailable\"\n : \"semantic-retrieval-error\";\n}\n\n/** Known provider/config/network failures that should keep the legacy warning code. */\nfunction looksLikeProviderFailure(message: string): boolean {\n return /api[_ -]?key|auth|credential|token|provider|voyage|openai|ollama|timeout|fetch|econn|enotfound/i\n .test(message);\n}\n\n/** Project an embedding-store chunk hit onto the ranking-facing shape. */\nfunction toSemanticChunkHit(\n raw: Awaited<ReturnType<typeof findRelevantChunks>>[number],\n): SemanticChunkHit {\n return {\n slug: raw.chunk.slug,\n text: raw.chunk.text,\n score: raw.score,\n contentHash: raw.chunk.contentHash,\n };\n}\n","/**\n * Graph neighborhood expansion for `llmwiki context` Slice 3.\n *\n * Pure function over `ViewerSnapshot.graph` (built once at snapshot\n * time by `src/viewer/graph.ts::buildGraphData`) plus the orchestrator's\n * resolved primary IDs. Returns the documented `neighbors[]` and\n * `gaps[]` arrays without rebuilding adjacency from disk and without\n * touching the embeddings store.\n *\n * Topology rules pinned by the implementation plan §Graph Expansion:\n * - real-page-only neighbors (ghosts go to `gaps[]` instead, with\n * `code: \"dangling-link\"` and `pageId` set to the page that linked\n * to the missing target)\n * - primary pages never appear in `neighbors[]`\n * - bidirectional edges (A->B and B->A) collapse to a single neighbor\n * entry via the canonical unordered key `(min(a,b), max(a,b))`\n * - depth 1 expands directly from each primary page\n * - depth 2 expands from surviving depth-1 real neighbors, skipping\n * primary back-edges and pages already at depth 1; cycles are\n * blocked by a visited set\n * - `--no-neighbors` and `--depth 0` produce empty `neighbors` /\n * `gaps` arrays without crashing\n *\n * Scores are deterministic and normalized into [0, 1]; direct neighbors\n * outrank second-hop, pages with multiple primary connections get a\n * small additive bonus, and same-kind page pairs get a smaller schema\n * affinity bonus. Stable tie-sort: descending score, then\n * ascending `to` page id (Slice 3 does not have neighbor titles handy\n * without an extra lookup, and id is already monotonic per directory).\n */\n\nimport type {\n GraphData,\n GraphNode,\n PageId,\n ViewerPage,\n} from \"../viewer/types.js\";\n\n/** Closed v1 neighbor edge label. */\nconst NEIGHBOR_REASON_WIKILINK = \"wikilink\";\n\n/**\n * Delimiter joining the two PageIds in a canonical pair key. Visible\n * ASCII (not a control byte) so the source file stays free of NUL\n * bytes that confuse grep/fallow/git tooling, and the substring\n * \" <-> \" cannot collide with any directory/slug character allowed in\n * a `PageId` (`concepts/<slug>` / `queries/<slug>`).\n */\nconst CANONICAL_PAIR_SEPARATOR = \" <-> \";\n\n/**\n * Hard cap on the total number of emitted neighbor entries (depth-1 +\n * depth-2 combined). Applied BEFORE depth-2 expansion so a high-degree\n * primary page cannot blow the context pack open by way of fan-out\n * through trimmed depth-1 bridges. Re-applied at the end so the\n * sorted, merged output never exceeds the bound either.\n */\nconst MAX_GRAPH_NEIGHBORS = 20;\n\n/** Base score for a direct (depth-1) neighbor edge. */\nconst WEIGHT_NEIGHBOR_DIRECT = 0.5;\n\n/** Base score for a second-hop (depth-2) neighbor edge. */\nconst WEIGHT_NEIGHBOR_SECOND_HOP = 0.25;\n\n/**\n * Additive bonus per additional primary connection beyond the first\n * (capped). Keeps centrally-linked neighbors above peripherally-linked\n * ones without letting a \"popular\" page accumulate unbounded weight.\n */\nconst WEIGHT_PRIMARY_CONNECTION_BONUS = 0.05;\nconst MAX_PRIMARY_CONNECTION_BONUS_HITS = 3;\n\n/** Small schema-affinity bump when both endpoints share the same page kind. */\nconst WEIGHT_SAME_KIND_BONUS = 0.03;\nconst DEFAULT_PAGE_KIND = \"concept\";\n\n/** Cap so combined scores stay inside the normalized [0, 1] range. */\nconst MAX_NORMALIZED_SCORE = 1;\n\n/** Reasons set on emitted neighbor entries. Closed v1 enum. */\ntype NeighborReason = typeof NEIGHBOR_REASON_WIKILINK;\n\n/** Direction of the underlying wikilink relative to `from`. */\ntype NeighborDirection = \"outgoing\" | \"incoming\";\n\n/** One neighbor entry in the v1 context-pack envelope. */\ninterface GraphNeighbor {\n from: PageId;\n to: PageId;\n direction: NeighborDirection;\n distance: number;\n score: number;\n reason: NeighborReason;\n}\n\n/** Gap entry surfaced when a primary page links to a missing target. */\ninterface GraphGap {\n code: \"dangling-link\";\n message: string;\n pageId: PageId;\n}\n\n/** Output of {@link expandGraphNeighborhood}. */\nexport interface GraphExpansionOutput {\n neighbors: GraphNeighbor[];\n gaps: GraphGap[];\n}\n\n/** Inputs grouped to keep argument lists short. */\ninterface GraphExpansionInput {\n graph: GraphData;\n pages: ViewerPage[];\n primaryIds: ReadonlySet<PageId>;\n /** 0 = expansion off; 1 = direct only; 2 = direct + second-hop. */\n depth: number;\n}\n\n/**\n * Expand `primaryIds` into the documented `{ neighbors, gaps }` shape.\n * Returns empty arrays — never `undefined`/`null` — so the orchestrator\n * can splice the result into the JSON envelope unconditionally.\n */\nexport function expandGraphNeighborhood(\n input: GraphExpansionInput,\n): GraphExpansionOutput {\n // Depth 0 OR no primary IDs: both `neighbors[]` and `gaps[]` stay\n // empty (plan §CLI Acceptance Criteria). `--no-neighbors` short-\n // circuits before this function is called; this branch covers\n // `--depth 0` and the empty-wiki path.\n if (input.depth <= 0 || input.primaryIds.size === 0) {\n return { neighbors: [], gaps: [] };\n }\n const adjacency = buildAdjacency(input.graph);\n const ghostIds = collectGhostIds(input.graph.nodes);\n const pageKinds = buildPageKindMap(input.pages);\n // Sort + cap depth-1 BEFORE depth-2 expansion so a trimmed-out\n // bridge can never contribute second-hop emissions. Without this\n // gate, a primary page with high fan-out could fan out further at\n // depth 2 and balloon `neighbors[]` past the documented cap.\n const depth1Raw = expandDepthOne({\n primaryIds: input.primaryIds,\n adjacency,\n ghostIds,\n pageKinds,\n });\n const depth1 = depth1Raw.sort(compareNeighbors).slice(0, MAX_GRAPH_NEIGHBORS);\n const depth2 = input.depth >= 2\n ? expandDepthTwo({\n primaryIds: input.primaryIds,\n adjacency,\n ghostIds,\n pageKinds,\n depthOneTargets: collectDepthOneTargets(depth1),\n })\n : [];\n // Re-cap after merging so depth-2 entries cannot push the total\n // above the bound either. Direct (distance 1) entries outrank\n // second-hop in the comparator so the trim drops second-hop first.\n const neighbors = [...depth1, ...depth2]\n .sort(compareNeighbors)\n .slice(0, MAX_GRAPH_NEIGHBORS);\n return { neighbors, gaps: emitGapsFromPrimary(input) };\n}\n\n/** Surface dangling-link gaps for every primary page that owns one. */\nfunction emitGapsFromPrimary(input: GraphExpansionInput): GraphGap[] {\n const gaps: GraphGap[] = [];\n for (const page of input.pages) {\n if (!input.primaryIds.has(page.id)) continue;\n for (const dangling of page.danglingLinks ?? []) {\n gaps.push({\n code: \"dangling-link\",\n message: `Page links to [[${dangling.display}]], but no page exists.`,\n pageId: page.id,\n });\n }\n }\n return gaps;\n}\n\n/** Outgoing + incoming edge maps for fast neighbor lookup keyed by PageId. */\ninterface Adjacency {\n outgoing: Map<PageId, Set<PageId>>;\n incoming: Map<PageId, Set<PageId>>;\n}\n\n/** Build the bidirectional adjacency from the snapshot's edge list. */\nfunction buildAdjacency(graph: GraphData): Adjacency {\n const outgoing = new Map<PageId, Set<PageId>>();\n const incoming = new Map<PageId, Set<PageId>>();\n for (const edge of graph.edges) {\n addToSetMap(outgoing, edge.source, edge.target);\n addToSetMap(incoming, edge.target, edge.source);\n }\n return { outgoing, incoming };\n}\n\n/** Insert `value` into the set at `key`, creating the set on first use. */\nfunction addToSetMap<K, V>(map: Map<K, Set<V>>, key: K, value: V): void {\n const existing = map.get(key);\n if (existing) existing.add(value);\n else map.set(key, new Set([value]));\n}\n\n/** Collect every ghost-node id so we can drop dangling targets from neighbors. */\nfunction collectGhostIds(nodes: GraphNode[]): Set<PageId> {\n const ghosts = new Set<PageId>();\n for (const node of nodes) if (node.isDangling) ghosts.add(node.id);\n return ghosts;\n}\n\n/** Shared input for the depth-1 expansion path. */\ninterface DepthOneInput {\n primaryIds: ReadonlySet<PageId>;\n adjacency: Adjacency;\n ghostIds: ReadonlySet<PageId>;\n pageKinds: ReadonlyMap<PageId, string>;\n}\n\n/**\n * Depth-1 expansion: for each primary page, walk outgoing + incoming\n * edges and emit one entry per real-page neighbor. Bidirectional\n * duplicates collapse via the canonical-pair key.\n */\nfunction expandDepthOne(input: DepthOneInput): GraphNeighbor[] {\n const emitted = new Map<string, GraphNeighbor>();\n const connectionCount = new Map<PageId, number>();\n for (const primary of input.primaryIds) {\n addNeighborsForPrimary({ ...input, primary, emitted, connectionCount });\n }\n applyPrimaryConnectionBonus(emitted, connectionCount);\n return Array.from(emitted.values());\n}\n\n/** Helpers passed into the per-primary depth-1 walker. */\ninterface DepthOnePerPrimary extends DepthOneInput {\n primary: PageId;\n emitted: Map<string, GraphNeighbor>;\n connectionCount: Map<PageId, number>;\n}\n\n/** Walk every edge incident to one primary page and emit canonicalized neighbors. */\nfunction addNeighborsForPrimary(ctx: DepthOnePerPrimary): void {\n walkIncidentEdges(ctx.adjacency, ctx.primary, (other, direction) => {\n tryEmitDirect({ ctx, other, direction });\n });\n}\n\n/** Inputs for the per-edge depth-1 emission check. */\ninterface EmitDirectInput {\n ctx: DepthOnePerPrimary;\n other: PageId;\n direction: NeighborDirection;\n}\n\n/**\n * Emit a depth-1 neighbor edge if it survives the no-ghost / no-primary\n * / canonical-key filters. Bumps the per-target connection counter\n * either way so multi-primary connections get the documented bonus\n * even when the second edge would otherwise be skipped as a duplicate.\n */\nfunction tryEmitDirect(input: EmitDirectInput): void {\n const { ctx, other, direction } = input;\n if (ctx.ghostIds.has(other)) return;\n if (ctx.primaryIds.has(other)) return;\n bumpConnection(ctx.connectionCount, other);\n mergeOrInsertNeighbor(ctx.emitted, {\n from: ctx.primary,\n to: other,\n direction,\n distance: 1,\n score: scoreWithSameKindBonus(\n WEIGHT_NEIGHBOR_DIRECT,\n ctx.primary,\n other,\n ctx.pageKinds,\n ),\n });\n}\n\n/** Increment the per-target connection counter (used for the multi-primary bonus). */\nfunction bumpConnection(counter: Map<PageId, number>, target: PageId): void {\n counter.set(target, (counter.get(target) ?? 0) + 1);\n}\n\n/** Empty PageId set reused across walks to avoid allocating per call. */\nconst EMPTY_NEIGHBOR_SET: ReadonlySet<PageId> = new Set<PageId>();\n\n/**\n * Walk every wikilink edge incident to `node` and invoke `onEdge` with\n * the other endpoint plus the direction relative to `node`. Centralised\n * so depth-1 and depth-2 walkers share the outgoing-then-incoming\n * iteration order without copy-pasting the boilerplate.\n */\nfunction walkIncidentEdges(\n adjacency: Adjacency,\n node: PageId,\n onEdge: (other: PageId, direction: NeighborDirection) => void,\n): void {\n const outgoing = adjacency.outgoing.get(node) ?? EMPTY_NEIGHBOR_SET;\n const incoming = adjacency.incoming.get(node) ?? EMPTY_NEIGHBOR_SET;\n for (const target of outgoing) onEdge(target, \"outgoing\");\n for (const source of incoming) onEdge(source, \"incoming\");\n}\n\n/** Candidate neighbor about to be merged into the canonical-key map. */\ninterface NeighborCandidate {\n from: PageId;\n to: PageId;\n direction: NeighborDirection;\n distance: number;\n score: number;\n}\n\n/**\n * Insert `candidate` into `emitted` under its canonical-pair key OR,\n * if an entry for that pair already exists, promote its direction\n * from `incoming` to `outgoing` when this edge represents the natural\n * `from -> to` link. The dedup keeps bidirectional pairs (A->B AND\n * B->A) collapsed to a single neighbor entry per the plan.\n */\nfunction mergeOrInsertNeighbor(\n emitted: Map<string, GraphNeighbor>,\n candidate: NeighborCandidate,\n): void {\n const key = canonicalPairKey(candidate.from, candidate.to);\n const existing = emitted.get(key);\n if (existing) {\n if (existing.direction === \"incoming\" && candidate.direction === \"outgoing\") {\n existing.direction = \"outgoing\";\n }\n return;\n }\n emitted.set(key, { ...candidate, reason: NEIGHBOR_REASON_WIKILINK });\n}\n\n/**\n * After every primary contributes, apply the per-additional-connection\n * score bonus to each neighbor entry. The first connection is the base\n * weight; only the 2nd..(MAX+1)-th add the per-hit bonus.\n */\nfunction applyPrimaryConnectionBonus(\n emitted: Map<string, GraphNeighbor>,\n connectionCount: Map<PageId, number>,\n): void {\n for (const neighbor of emitted.values()) {\n const hits = connectionCount.get(neighbor.to) ?? 0;\n const extraHits = Math.min(\n Math.max(0, hits - 1),\n MAX_PRIMARY_CONNECTION_BONUS_HITS,\n );\n neighbor.score = clampScore(\n neighbor.score + extraHits * WEIGHT_PRIMARY_CONNECTION_BONUS,\n );\n }\n}\n\n/** Inputs for depth-2 expansion. */\ninterface DepthTwoInput {\n primaryIds: ReadonlySet<PageId>;\n adjacency: Adjacency;\n ghostIds: ReadonlySet<PageId>;\n pageKinds: ReadonlyMap<PageId, string>;\n depthOneTargets: ReadonlySet<PageId>;\n}\n\n/**\n * Depth-2 expansion: walk from each surviving depth-1 neighbor and\n * emit edges to NEW real pages (not primary, not already at depth 1,\n * not ghosts). Bidirectional pairs at depth 2 also collapse via the\n * canonical-pair key. Back-edges to primary are dropped because that\n * relationship is already represented by the primary's depth-1 entry.\n */\nfunction expandDepthTwo(input: DepthTwoInput): GraphNeighbor[] {\n const emitted = new Map<string, GraphNeighbor>();\n for (const bridge of input.depthOneTargets) {\n walkDepthTwoFromBridge({ ...input, bridge, emitted });\n }\n return Array.from(emitted.values());\n}\n\n/** Per-bridge depth-2 walker context; bridge is the depth-1 neighbor we expand from. */\ninterface DepthTwoPerBridge extends DepthTwoInput {\n bridge: PageId;\n emitted: Map<string, GraphNeighbor>;\n}\n\n/** Walk every edge incident to one depth-1 bridge node. */\nfunction walkDepthTwoFromBridge(ctx: DepthTwoPerBridge): void {\n walkIncidentEdges(ctx.adjacency, ctx.bridge, (other, direction) => {\n tryEmitSecondHop({ ctx, other, direction });\n });\n}\n\n/** Inputs for the per-edge depth-2 emission check. */\ninterface EmitSecondHopInput {\n ctx: DepthTwoPerBridge;\n other: PageId;\n direction: NeighborDirection;\n}\n\n/**\n * Emit a depth-2 neighbor edge if the target is a real page that is\n * neither primary nor already in the depth-1 neighbor set. Same\n * canonical-key de-dupe rule as depth 1; direction prefers outgoing\n * on bidirectional collisions to keep the natural reading order.\n */\nfunction tryEmitSecondHop(input: EmitSecondHopInput): void {\n const { ctx, other, direction } = input;\n if (ctx.ghostIds.has(other)) return;\n if (ctx.primaryIds.has(other)) return;\n if (ctx.depthOneTargets.has(other)) return;\n mergeOrInsertNeighbor(ctx.emitted, {\n from: ctx.bridge,\n to: other,\n direction,\n distance: 2,\n score: scoreWithSameKindBonus(\n WEIGHT_NEIGHBOR_SECOND_HOP,\n ctx.bridge,\n other,\n ctx.pageKinds,\n ),\n });\n}\n\n/** Map page ID to schema/page kind, defaulting legacy pages to concept. */\nfunction buildPageKindMap(pages: ViewerPage[]): Map<PageId, string> {\n const kinds = new Map<PageId, string>();\n for (const page of pages) {\n const kind = page.frontmatter.kind;\n kinds.set(page.id, typeof kind === \"string\" && kind.length > 0 ? kind : DEFAULT_PAGE_KIND);\n }\n return kinds;\n}\n\n/** Add the small kind-affinity bonus when both real endpoints share a kind. */\nfunction scoreWithSameKindBonus(\n base: number,\n from: PageId,\n to: PageId,\n pageKinds: ReadonlyMap<PageId, string>,\n): number {\n return samePageKind(from, to, pageKinds)\n ? clampScore(base + WEIGHT_SAME_KIND_BONUS)\n : base;\n}\n\n/** True when both endpoints have an equal page kind in the real-page map. */\nfunction samePageKind(\n from: PageId,\n to: PageId,\n pageKinds: ReadonlyMap<PageId, string>,\n): boolean {\n const fromKind = pageKinds.get(from);\n const toKind = pageKinds.get(to);\n return fromKind !== undefined && toKind !== undefined && fromKind === toKind;\n}\n\n/** Collect just the `to` ids from a depth-1 neighbor list for fast lookups. */\nfunction collectDepthOneTargets(neighbors: GraphNeighbor[]): Set<PageId> {\n const ids = new Set<PageId>();\n for (const n of neighbors) ids.add(n.to);\n return ids;\n}\n\n/**\n * Canonical key for an unordered page pair so A->B and B->A collapse\n * to a single neighbor entry. `String.localeCompare` keeps the\n * ordering platform-stable for any unicode-bearing PageId.\n */\nfunction canonicalPairKey(a: PageId, b: PageId): string {\n return a < b\n ? `${a}${CANONICAL_PAIR_SEPARATOR}${b}`\n : `${b}${CANONICAL_PAIR_SEPARATOR}${a}`;\n}\n\n/** Squash a score into [0, 1] without inventing precision. */\nfunction clampScore(weight: number): number {\n if (weight <= 0) return 0;\n if (weight >= MAX_NORMALIZED_SCORE) return MAX_NORMALIZED_SCORE;\n return Math.round(weight * 100) / 100;\n}\n\n/**\n * Stable sort: descending score, then ascending `to` id. We don't have\n * neighbor titles at this layer; the namespaced PageId already sorts\n * deterministically per directory so agents see a fixed ordering.\n */\nfunction compareNeighbors(a: GraphNeighbor, b: GraphNeighbor): number {\n if (a.score !== b.score) return b.score - a.score;\n return a.to.localeCompare(b.to);\n}\n","/**\n * Token budget estimation + deterministic trimming for `llmwiki context`.\n *\n * V1 uses a deterministic `Math.ceil(chars / 4)` heuristic so the token\n * count is stable across runs and identical between CLI and MCP. The\n * helper is isolated so a real tokenizer can replace it later without\n * touching the orchestrator (see plan §Budgeting).\n *\n * The estimator is intentionally pessimistic on the high side: a 4-char\n * average is slightly low for English prose, so packets that pass\n * `estimatedTokens <= requestedTokens` will fit comfortably in a real\n * tokenizer's count as well.\n *\n * Trimming order (plan §Budgeting §Trimming order):\n * 1. neighbors\n * 2. source windows\n * 3. semantic chunks / excerpts\n * 4. primary pages (last resort)\n *\n * Trim functions mutate a deep-cloned copy of the pack so the caller's\n * draft is never observably modified. JSON validity is preserved at\n * every step because we drop entire array elements rather than\n * surgically slicing strings.\n */\n\nimport type { ContextBudget, ContextPack } from \"./types.js\";\n\n/** Average chars-per-token used by the cheap heuristic. */\nconst APPROX_CHARS_PER_TOKEN = 4;\n\n/** Section names that can land in `budget.trimmedSections`. */\ntype TrimmedSection = \"neighbors\" | \"sourceWindows\" | \"chunks\" | \"primary\";\n\n/**\n * Estimate token count for an arbitrary string. Always returns a\n * non-negative integer. The empty string and `null`/`undefined` map to\n * zero; non-string inputs are coerced.\n */\nexport function estimateTokens(text: unknown): number {\n if (text === null || text === undefined) return 0;\n const stringified = typeof text === \"string\" ? text : String(text);\n if (stringified.length === 0) return 0;\n return Math.ceil(stringified.length / APPROX_CHARS_PER_TOKEN);\n}\n\n/**\n * Estimate the total token weight of a serialized context pack. Uses\n * the same heuristic as {@link estimateTokens}; runs on the\n * already-rendered JSON string so trimming decisions can iterate\n * without rebuilding the structured envelope from scratch.\n */\nexport function estimatePackTokens(pack: ContextPack): number {\n return estimateTokens(JSON.stringify(pack));\n}\n\n/**\n * Build a fresh budget envelope before estimation/trimming runs.\n * Defaults preserve the v1 contract (`truncated: false`,\n * `trimmedSections: []`); the orchestrator overwrites these in\n * `finalizeBudget` once trimming decisions are known.\n */\nexport function buildBudget(requestedTokens: number, estimatedTokens: number): ContextBudget {\n return {\n requestedTokens,\n estimatedTokens,\n truncated: false,\n trimmedSections: [],\n };\n}\n\n/** Result returned by {@link trimToBudget}; pack is a NEW object, never the input. */\ninterface TrimResult {\n pack: ContextPack;\n trimmedSections: TrimmedSection[];\n}\n\n/**\n * Deterministically trim `pack` until `estimatePackTokens(pack) <=\n * requestedTokens`, in the documented order:\n * neighbors -> sourceWindows -> chunks -> primary\n *\n * Each section is drained from the END of its array so the highest-\n * scored entries survive longest (ranking output is already sorted\n * descending). The returned pack is always a deep clone — the caller's\n * draft is left untouched.\n *\n * If the minimal envelope (no neighbors, no sourceWindows, no chunks,\n * no primary) still exceeds the budget, the pack is returned as-is\n * with `trimmedSections` listing every section we touched so consumers\n * can see the failure mode.\n */\nexport function trimToBudget(pack: ContextPack, requestedTokens: number): TrimResult {\n const clone = clonePack(pack);\n const trimmed = new Set<TrimmedSection>();\n if (estimatePackTokens(clone) <= requestedTokens) {\n return { pack: clone, trimmedSections: [] };\n }\n trimNeighbors(clone, requestedTokens, trimmed);\n trimSourceWindows(clone, requestedTokens, trimmed);\n trimChunks(clone, requestedTokens, trimmed);\n trimPrimary(clone, requestedTokens, trimmed);\n return { pack: clone, trimmedSections: orderedSections(trimmed) };\n}\n\n/** Deep-clone via `structuredClone` so per-step mutations cannot leak. */\nfunction clonePack(pack: ContextPack): ContextPack {\n return structuredClone(pack);\n}\n\n/** Re-emit trimmed sections in the documented trim order, not insertion order. */\nfunction orderedSections(trimmed: Set<TrimmedSection>): TrimmedSection[] {\n const order: TrimmedSection[] = [\"neighbors\", \"sourceWindows\", \"chunks\", \"primary\"];\n return order.filter((section) => trimmed.has(section));\n}\n\n/** Drop neighbors from the end of the sorted list until the pack fits or none remain. */\nfunction trimNeighbors(\n pack: ContextPack,\n budget: number,\n trimmed: Set<TrimmedSection>,\n): void {\n while (pack.neighbors.length > 0 && estimatePackTokens(pack) > budget) {\n pack.neighbors.pop();\n trimmed.add(\"neighbors\");\n }\n}\n\n/**\n * Drop source windows page-by-page from the bottom-ranked primary\n * entry first so the most relevant page keeps its provenance evidence\n * longest. Walks until either the pack fits or every page has zero\n * windows.\n */\nfunction trimSourceWindows(\n pack: ContextPack,\n budget: number,\n trimmed: Set<TrimmedSection>,\n): void {\n for (let i = pack.primary.length - 1; i >= 0; i--) {\n while (pack.primary[i].sourceWindows.length > 0 && estimatePackTokens(pack) > budget) {\n pack.primary[i].sourceWindows.pop();\n trimmed.add(\"sourceWindows\");\n }\n if (estimatePackTokens(pack) <= budget) return;\n }\n}\n\n/** Drop semantic chunks page-by-page using the same bottom-up strategy. */\nfunction trimChunks(\n pack: ContextPack,\n budget: number,\n trimmed: Set<TrimmedSection>,\n): void {\n for (let i = pack.primary.length - 1; i >= 0; i--) {\n while (pack.primary[i].chunks.length > 0 && estimatePackTokens(pack) > budget) {\n pack.primary[i].chunks.pop();\n trimmed.add(\"chunks\");\n }\n if (estimatePackTokens(pack) <= budget) return;\n }\n}\n\n/** Final resort: drop primary pages from the bottom of the ranked list. */\nfunction trimPrimary(\n pack: ContextPack,\n budget: number,\n trimmed: Set<TrimmedSection>,\n): void {\n while (pack.primary.length > 0 && estimatePackTokens(pack) > budget) {\n pack.primary.pop();\n trimmed.add(\"primary\");\n }\n}\n","/**\n * Stable v1 JSON contract for `llmwiki context` and the future\n * `get_context_pack` MCP tool.\n *\n * Every top-level field and every documented nested key is present from\n * Slice 1 onward, even when later-slice features have not populated\n * them yet (see `localdocs/context-graph-packs-implementation-plan.md`\n * §JSON Contract). Unpopulated list fields are empty arrays; absent\n * object fields are `null`. Slices may fill data into these fields,\n * but must NEVER add or remove top-level keys without bumping\n * `version`.\n */\n\nimport type { PageId } from \"../viewer/types.js\";\nimport type { PageDirectory } from \"../export/types.js\";\nimport type { RecommendedAction } from \"../project/recommendations.js\";\n\n/** Closed v1 enum for why a page landed in `primary[]`. */\nexport type PrimaryReason =\n | \"semantic-chunk\"\n | \"title-match\"\n | \"body-match\"\n | \"exact-slug\"\n | \"exact-title\"\n | \"graph-neighbor\";\n\n/** Closed v1 enum for the edge label used in `neighbors[]`. */\ntype NeighborReason = \"wikilink\";\n\n/** Closed v1 enum for top-level `warnings[]` codes. */\ntype ContextWarningCode =\n | \"embedding-store-missing\"\n | \"query-embedding-unavailable\"\n | \"semantic-retrieval-error\"\n | \"lint-errors\"\n | \"pending-candidates\"\n | \"source-window-unavailable\"\n | \"truncated-prompt\";\n\n/** Closed v1 enum for `gaps[]` codes. */\ntype ContextGapCode = \"dangling-link\" | \"page-warning\";\n\n/**\n * Budget envelope. `estimatedTokens` uses a tokens ≈ chars/4 heuristic in v1.\n * Because `estimatedTokens` is itself serialized inside the measured JSON, the\n * reported value may differ from `estimatePackTokens(returnedPack)` by at most\n * one token of digit-count drift.\n */\nexport interface ContextBudget {\n requestedTokens: number;\n estimatedTokens: number;\n truncated: boolean;\n /** Section keys (`primary`, `neighbors`, `sourceWindows`, `chunks`) that lost data. */\n trimmedSections: string[];\n}\n\n/**\n * Cached lint summary surfaced inside `project.lint`. Matches\n * `LintCacheEntry` in `src/linter/cache.ts` but typed locally so the\n * context contract does not depend on the linter's internal shape.\n */\ninterface ContextLintSummary {\n warnings: number;\n errors: number;\n at: string;\n}\n\n/** Project block. `root` is set to `null` when `--omit-root` is supplied. */\nexport interface ContextProject {\n root: string | null;\n pages: number;\n pendingCandidates: number;\n lint: ContextLintSummary | null;\n}\n\n/** One semantic chunk surfaced for a primary page. Slice 2 populates it. */\ninterface ContextChunk {\n text: string;\n score: number;\n contentHash?: string;\n}\n\n/**\n * Flattened citation. Produced by lifting `ClaimCitation.spans` into one\n * object per span. Paragraph-only citations omit `start` and `end`.\n */\ninterface ContextCitation {\n file: string;\n start?: number;\n end?: number;\n}\n\n/** Source line window emitted only when `--include-sources` is set in Slice 4. */\ninterface ContextSourceWindow {\n file: string;\n start: number;\n end: number;\n text: string;\n}\n\n/** Page-local warning surfaced from the viewer collector. */\ninterface ContextPageWarning {\n code: string;\n message: string;\n}\n\n/** One primary page entry. `reasons` is sorted alphabetically for stable output. */\nexport interface ContextPrimary {\n id: PageId;\n title: string;\n pageDirectory: PageDirectory;\n score: number;\n reasons: PrimaryReason[];\n summary: string;\n chunks: ContextChunk[];\n citations: ContextCitation[];\n sourceWindows: ContextSourceWindow[];\n warnings: ContextPageWarning[];\n}\n\n/** One graph neighbor edge. `distance` is 1 for direct, 2 for second-hop. */\ninterface ContextNeighbor {\n from: PageId;\n to: PageId;\n direction: \"outgoing\" | \"incoming\";\n distance: number;\n score: number;\n reason: NeighborReason;\n}\n\n/** Top-level context-pack state warning. */\nexport interface ContextWarning {\n code: ContextWarningCode;\n message: string;\n}\n\n/**\n * Missing-knowledge gap. `pageId` is required in v1; every documented\n * gap code (`dangling-link`, `page-warning`) is tied to a specific\n * page. A future project-wide gap would either bump `version` or\n * introduce a new sibling field rather than retrofitting nullability\n * onto this one.\n */\ninterface ContextGap {\n code: ContextGapCode;\n message: string;\n pageId: PageId;\n}\n\n/** Top-level v1 envelope. */\nexport interface ContextPack {\n version: 1;\n prompt: string;\n budget: ContextBudget;\n project: ContextProject;\n primary: ContextPrimary[];\n neighbors: ContextNeighbor[];\n warnings: ContextWarning[];\n gaps: ContextGap[];\n suggestedActions: RecommendedAction[];\n}\n\n/** Hard cap on the echoed prompt; ranking still uses the original. */\nexport const PROMPT_ECHO_MAX_LENGTH = 1024;\n\n/** Default budget when `--budget` is omitted. */\nexport const DEFAULT_BUDGET_TOKENS = 8000;\n\n/** Default depth for graph expansion (1 = direct neighbors only). */\nexport const DEFAULT_DEPTH = 1;\n\n/** Hard upper bound on `--depth` per plan §Graph Expansion. */\nexport const MAX_DEPTH = 2;\n\n/** Default cap on `primary[]` page count. */\nexport const DEFAULT_TOP_PAGES = 5;\n\n/** Hard cap on `primary[]` page count to keep trimming and output bounded. */\nexport const MAX_TOP_PAGES = 20;\n\n/** Default value pinned for `--top-chunks` (plan §Ranking Model). */\nexport const DEFAULT_TOP_CHUNKS = 8;\n\n/** Hard cap on semantic chunks to keep budget trimming predictably small. */\nexport const MAX_TOP_CHUNKS = 50;\n","/**\n * `buildContextPack()` — Slice 1 orchestrator.\n *\n * Composes the v1 context-pack envelope by:\n * 1. Loading the frozen viewer snapshot (page metadata, frontmatter,\n * citations, warnings, etc).\n * 2. Collecting project state via the shared `collectProjectState`\n * helper so the lint cache and pending-candidate counts match\n * what `llmwiki next` would report.\n * 3. Lexically ranking pages against the prompt via the Slice-1\n * ranker.\n * 4. Delegating the recommendation prefix to\n * `recommendNextAction(state)` so `context` does not introduce a\n * second project-state engine.\n *\n * Semantic retrieval, graph expansion, source windows, and MCP are\n * intentionally left for later slices; this entry point already emits\n * the full stable v1 JSON field set with empty arrays / `null`\n * placeholders so agents written against Slice 1 will not break when\n * later slices add data.\n */\n\nimport { buildViewerSnapshot } from \"../viewer/snapshot.js\";\nimport { collectProjectState } from \"../project/state.js\";\nimport { recommendNextAction } from \"../project/recommendations.js\";\nimport type { Recommendation, RecommendedAction } from \"../project/recommendations.js\";\nimport type { ProjectState } from \"../project/state.js\";\nimport type { ViewerSnapshot } from \"../viewer/types.js\";\nimport { rankPages } from \"./ranking.js\";\nimport { retrieveSemanticChunks } from \"./retrieval.js\";\nimport type { SemanticRetrievalOutcome, SemanticRetrievalWarning } from \"./retrieval.js\";\nimport { expandGraphNeighborhood } from \"./graph.js\";\nimport type { GraphExpansionOutput } from \"./graph.js\";\nimport {\n createSourceWindowBudget,\n materializeSourceWindows,\n} from \"./provenance.js\";\nimport type { GraphData, PageId } from \"../viewer/types.js\";\nimport { buildBudget, estimatePackTokens, trimToBudget } from \"./budget.js\";\nimport {\n DEFAULT_BUDGET_TOKENS,\n DEFAULT_DEPTH,\n DEFAULT_TOP_CHUNKS,\n DEFAULT_TOP_PAGES,\n MAX_DEPTH,\n MAX_TOP_CHUNKS,\n MAX_TOP_PAGES,\n PROMPT_ECHO_MAX_LENGTH,\n} from \"./types.js\";\nimport type {\n ContextPack,\n ContextProject,\n ContextWarning,\n} from \"./types.js\";\n\n/** Caller-supplied build options; all fields except `prompt` are optional. */\ninterface BuildContextPackOptions {\n /** Project root; defaults to `process.cwd()` at the call site. */\n root: string;\n /** Free-text prompt the agent supplied. */\n prompt: string;\n /** Token budget; defaults to {@link DEFAULT_BUDGET_TOKENS}. */\n budget?: number;\n /** Graph depth; clamped to {@link MAX_DEPTH} when supplied. */\n depth?: number;\n /** Max primary pages; clamped into the documented safe range. */\n topPages?: number;\n /** Max semantic chunks; pinned to the documented safe range. */\n topChunks?: number;\n /** When true, `project.root` is emitted as `null` for privacy. */\n omitRoot?: boolean;\n /** When false, graph expansion is suppressed (neighbors + gaps stay empty). */\n neighbors?: boolean;\n /** When true, populate `primary[].sourceWindows` from claim-level citation spans. */\n includeSources?: boolean;\n}\n\n/**\n * Build the v1 context pack. Never throws on read-only filesystem\n * issues — the project-state collector returns conservative defaults\n * (`broken-project` state) which the orchestrator faithfully surfaces.\n */\nexport async function buildContextPack(options: BuildContextPackOptions): Promise<ContextPack> {\n const normalized = normalizeOptions(options);\n const snapshot = await buildViewerSnapshot(options.root);\n const state = await collectProjectState(options.root);\n const recommendation = recommendNextAction(state);\n // Semantic retrieval is opportunistic — failures surface as stable\n // warning codes on the returned outcome rather than thrown errors so\n // lexical-only flows stay the default behaviour for credential-free\n // users.\n const semantic = await retrieveSemanticChunks(\n options.root,\n normalized.rankingPrompt,\n normalized.topChunks,\n );\n const draft = assembleDraft({\n snapshot,\n state,\n recommendation,\n options: normalized,\n semantic,\n });\n // Source windows are materialized AFTER ranking so the per-pack\n // budget sees the final primary list, not every candidate.\n // Skipped entirely when `--include-sources` is off.\n const withSources = normalized.includeSources\n ? await attachSourceWindows(draft, options.root)\n : draft;\n // Project-state warnings (pending candidates, lint errors,\n // source-window unavailability) need the post-materialization\n // primary list, so they land after attachSourceWindows and BEFORE\n // budget trimming. Trimming may drop sourceWindows for budget\n // reasons; the warning still correctly reports \"windows missing\".\n const withProjectWarnings = appendProjectWarnings(withSources, state, normalized);\n const graph = normalized.neighborsEnabled && normalized.depth >= 1\n ? snapshot.graph\n : null;\n return finalizeBudget(withProjectWarnings, normalized.budget, graph);\n}\n\n/**\n * Walk the ranked primary list once with a shared\n * {@link createSourceWindowBudget}, filling in each entry's\n * `sourceWindows` from its (already-flattened) claim-level citations.\n * Pages without claim-level spans (paragraph-only or no citations)\n * return empty windows; the global 20-window cap is enforced as the\n * walk progresses.\n */\nasync function attachSourceWindows(pack: ContextPack, root: string): Promise<ContextPack> {\n const budget = createSourceWindowBudget();\n // Serial loop — `budget.remaining` is mutated between awaits inside\n // `materializeSourceWindows`, so running pages in parallel would\n // race on the shared counter and overshoot the 20-window cap.\n const primary: ContextPack[\"primary\"] = [];\n for (const entry of pack.primary) {\n const windows = await materializeSourceWindows(root, entry.citations, budget);\n primary.push({ ...entry, sourceWindows: windows });\n }\n return { ...pack, primary };\n}\n\n/**\n * Frozen, validated copy of the user-supplied options.\n *\n * The prompt is intentionally split into two fields:\n * - `displayPrompt` is the echo-safe form that lands in\n * `ContextPack.prompt` (truncated at PROMPT_ECHO_MAX_LENGTH so the\n * envelope cannot balloon on a 10KB agent input).\n * - `rankingPrompt` is the original, untruncated prompt that flows\n * into every retrieval signal — lexical, semantic (Slice 2+), and\n * exact match. Truncating before ranking would silently drop\n * content the agent expected to drive selection.\n *\n * In Slice 1 the two values are observationally equivalent at the\n * lexical layer because `searchPages` caps queries at its own internal\n * MAX_QUERY_LENGTH (200 chars) and exact-match requires whole-prompt\n * equality. Slice 2's semantic retrieval will see the full\n * `rankingPrompt` and produce different scores than it would against\n * `displayPrompt`.\n */\ninterface NormalizedOptions {\n displayPrompt: string;\n rankingPrompt: string;\n budget: number;\n depth: number;\n topPages: number;\n topChunks: number;\n omitRoot: boolean;\n neighborsEnabled: boolean;\n includeSources: boolean;\n promptTruncated: boolean;\n}\n\n/** Apply defaults and clamps so downstream code can trust the field types. */\nfunction normalizeOptions(options: BuildContextPackOptions): NormalizedOptions {\n const rankingPrompt = options.prompt ?? \"\";\n const { display, truncated } = truncatePrompt(rankingPrompt);\n return {\n displayPrompt: display,\n rankingPrompt,\n budget: clampPositive(options.budget, DEFAULT_BUDGET_TOKENS),\n depth: clampDepth(options.depth),\n topPages: clampBounded(options.topPages, DEFAULT_TOP_PAGES, MAX_TOP_PAGES),\n topChunks: clampBounded(options.topChunks, DEFAULT_TOP_CHUNKS, MAX_TOP_CHUNKS),\n omitRoot: options.omitRoot === true,\n // `--no-neighbors` is a Commander negated flag: absence means\n // expansion is ON; only `options.neighbors === false` disables it.\n neighborsEnabled: options.neighbors !== false,\n includeSources: options.includeSources === true,\n promptTruncated: truncated,\n };\n}\n\n/** Truncate the echoed prompt without mutating the prompt used for ranking. */\nfunction truncatePrompt(raw: string): { display: string; truncated: boolean } {\n if (raw.length <= PROMPT_ECHO_MAX_LENGTH) return { display: raw, truncated: false };\n return { display: raw.slice(0, PROMPT_ECHO_MAX_LENGTH), truncated: true };\n}\n\n/** Clamp a numeric option to a non-negative integer with a fallback default. */\nfunction clampPositive(value: number | undefined, fallback: number): number {\n if (value === undefined || !Number.isFinite(value)) return fallback;\n return Math.max(0, Math.floor(value));\n}\n\n/** Clamp a numeric option into `[0, max]` with a fallback default. */\nfunction clampBounded(value: number | undefined, fallback: number, max: number): number {\n return Math.min(max, clampPositive(value, fallback));\n}\n\n/** Clamp `--depth` into `[0, MAX_DEPTH]`. */\nfunction clampDepth(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value)) return DEFAULT_DEPTH;\n return Math.max(0, Math.min(MAX_DEPTH, Math.floor(value)));\n}\n\n/** Composite input passed to the assembler; grouped to keep argument lists short. */\ninterface AssembleInput {\n snapshot: ViewerSnapshot;\n state: ProjectState;\n recommendation: Recommendation;\n options: NormalizedOptions;\n semantic: SemanticRetrievalOutcome;\n}\n\n/** Build the unbudgeted draft pack before token-budget finalization. */\nfunction assembleDraft(input: AssembleInput): ContextPack {\n const { snapshot, state, recommendation, options, semantic } = input;\n const project = buildProject(snapshot, state, options.omitRoot);\n // Rank against the ORIGINAL prompt — the truncated echo is a\n // display-only courtesy and must not silently drop ranking signal.\n const primary = rankPages(snapshot, options.rankingPrompt, options.topPages, semantic.hits);\n const graphEnabled = options.neighborsEnabled && options.depth >= 1;\n const expansion = graphEnabled\n ? expandGraphNeighborhood({\n graph: snapshot.graph,\n pages: snapshot.pages,\n primaryIds: collectPrimaryIds(primary),\n depth: options.depth,\n })\n : emptyExpansion();\n // Additive `graph-neighbor` reason only: cannot promote a page into\n // primary[], only annotate entries that earned their slot via\n // semantic/lexical/exact signals AND link to another primary page.\n // Gated on `graphEnabled` so `--no-neighbors` / `--depth 0` runs\n // never surface the reason (no graph context to justify it).\n const annotatedPrimary = graphEnabled\n ? annotateGraphNeighbors(primary, snapshot.graph)\n : primary;\n return {\n version: 1,\n prompt: options.displayPrompt,\n budget: buildBudget(options.budget, 0),\n project,\n primary: annotatedPrimary,\n neighbors: expansion.neighbors,\n warnings: buildTopLevelWarnings(options.promptTruncated, semantic.warning),\n gaps: expansion.gaps,\n suggestedActions: collectSuggestedActions(recommendation, {\n hasPages: snapshot.pages.length > 0,\n semanticWarning: semantic.warning,\n }),\n };\n}\n\n/**\n * Append `graph-neighbor` to existing primary entries that have a\n * wikilink edge (outgoing OR incoming) to another primary entry. Pure:\n * never reorders, rescores, or admits new entries — only widens the\n * `reasons[]` set on entries that already passed ranking. Reasons stay\n * sorted alphabetically and de-duped so snapshot comparisons remain\n * stable.\n */\nfunction annotateGraphNeighbors(\n primary: ContextPack[\"primary\"],\n graph: GraphData,\n): ContextPack[\"primary\"] {\n if (primary.length < 2) return primary;\n const primaryIds = collectPrimaryIds(primary);\n const connected = new Set<PageId>();\n for (const edge of graph.edges) {\n if (primaryIds.has(edge.source) && primaryIds.has(edge.target)) {\n connected.add(edge.source);\n connected.add(edge.target);\n }\n }\n if (connected.size === 0) return primary;\n return primary.map((entry) => {\n if (!connected.has(entry.id)) return entry;\n if (entry.reasons.includes(\"graph-neighbor\")) return entry;\n const widened = Array.from(new Set([...entry.reasons, \"graph-neighbor\" as const])).sort();\n return { ...entry, reasons: widened };\n });\n}\n\n/** Collapse the ranked primary list into a Set keyed by PageId for fast lookups. */\nfunction collectPrimaryIds(primary: ContextPack[\"primary\"]): Set<PageId> {\n const ids = new Set<PageId>();\n for (const entry of primary) ids.add(entry.id);\n return ids;\n}\n\n/** Strip graph-derived reasons so they can be recomputed after budget trimming. */\nfunction stripGraphNeighborReason(\n primary: ContextPack[\"primary\"],\n): ContextPack[\"primary\"] {\n return primary.map((entry) => ({\n ...entry,\n reasons: entry.reasons.filter((reason) => reason !== \"graph-neighbor\"),\n }));\n}\n\n/** Reconcile graph-neighbor after trimToBudget may have removed a connected peer. */\nfunction reconcileGraphNeighborReasons(\n pack: ContextPack,\n graph: GraphData | null,\n): ContextPack {\n const stripped = stripGraphNeighborReason(pack.primary);\n const primary = graph ? annotateGraphNeighbors(stripped, graph) : stripped;\n return primary === pack.primary ? pack : { ...pack, primary };\n}\n\n/** Empty expansion used when `--no-neighbors` suppresses graph traversal. */\nfunction emptyExpansion(): GraphExpansionOutput {\n return { neighbors: [], gaps: [] };\n}\n\n/** Materialize the `project` block; `root` honors the `--omit-root` flag. */\nfunction buildProject(\n snapshot: ViewerSnapshot,\n state: ProjectState,\n omitRoot: boolean,\n): ContextProject {\n return {\n root: omitRoot ? null : snapshot.root,\n pages: snapshot.pages.length,\n pendingCandidates: state.pendingCandidates,\n lint: state.lint.entry,\n };\n}\n\n/**\n * Top-level state warnings. Slice 1 wired `truncated-prompt`; Slice 2\n * adds the two semantic-retrieval fallback codes so consumers can tell\n * \"lexical-only because no embeddings on disk\" apart from \"lexical-only\n * because the provider rejected our embed call.\"\n */\nfunction buildTopLevelWarnings(\n promptTruncated: boolean,\n retrievalWarning: SemanticRetrievalWarning | null,\n): ContextWarning[] {\n const warnings: ContextWarning[] = [];\n if (promptTruncated) {\n warnings.push({\n code: \"truncated-prompt\",\n message: `Prompt exceeded ${PROMPT_ECHO_MAX_LENGTH} characters; the echoed copy was truncated.`,\n });\n }\n if (retrievalWarning === \"embedding-store-missing\") {\n warnings.push({\n code: \"embedding-store-missing\",\n message:\n \"No usable embedding store found; semantic retrieval skipped. \" +\n \"Run `llmwiki compile` to populate embeddings.\",\n });\n } else if (retrievalWarning === \"query-embedding-unavailable\") {\n warnings.push({\n code: \"query-embedding-unavailable\",\n message:\n \"Could not embed the prompt with the active provider; \" +\n \"semantic retrieval skipped, lexical signals still applied.\",\n });\n } else if (retrievalWarning === \"semantic-retrieval-error\") {\n warnings.push({\n code: \"semantic-retrieval-error\",\n message:\n \"Semantic retrieval failed unexpectedly; \" +\n \"lexical signals still applied and raw provider errors were not exposed.\",\n });\n }\n return warnings;\n}\n\n/**\n * Flatten the recommendation engine's output into the array-shaped\n * `suggestedActions[]`. Per plan §Suggested Actions:\n * - index 0 is the primary recommendation\n * - subsequent entries are `otherActions` in declared order\n * - tests pin the prefix only; context-specific additions land in\n * later slices.\n */\ninterface ContextActionInput {\n hasPages: boolean;\n semanticWarning: SemanticRetrievalWarning | null;\n}\n\n/** Context-specific suffix: rebuild embeddings when pages exist but no store is usable. */\nconst CONTEXT_COMPILE_ACTION: RecommendedAction = {\n command: \"llmwiki compile\",\n reason: \"Refresh compiled pages and rebuild the embedding store for semantic context.\",\n executable: { binary: \"llmwiki\", args: [\"compile\"] },\n};\n\n/** Context-specific suffix: open the selected wiki when shared recommendations omit it. */\nconst CONTEXT_VIEW_ACTION: RecommendedAction = {\n command: \"llmwiki view --open\",\n reason: \"Browse the compiled wiki in the local viewer.\",\n executable: { binary: \"llmwiki\", args: [\"view\", \"--open\"] },\n};\n\n/** Context-specific suffix: use the same prompt to generate an answer after reviewing evidence. */\nconst CONTEXT_QUERY_ACTION: RecommendedAction = {\n command: 'llmwiki query \"<prompt>\"',\n reason: \"Generate an answer with the same prompt after reviewing this context pack.\",\n executable: { binary: \"llmwiki\", args: [\"query\"], placeholders: [\"prompt\"] },\n};\n\nfunction collectSuggestedActions(\n recommendation: Recommendation,\n input: ContextActionInput,\n): RecommendedAction[] {\n const actions: RecommendedAction[] = [];\n for (const action of [recommendation.recommended, ...recommendation.otherActions]) {\n appendUniqueAction(actions, action);\n }\n if (input.hasPages && input.semanticWarning === \"embedding-store-missing\") {\n appendUniqueAction(actions, CONTEXT_COMPILE_ACTION);\n }\n if (input.hasPages) {\n appendUniqueAction(actions, CONTEXT_VIEW_ACTION);\n appendUniqueAction(actions, CONTEXT_QUERY_ACTION);\n }\n return actions;\n}\n\n/** Add `candidate` unless an equivalent executable action is already present. */\nfunction appendUniqueAction(actions: RecommendedAction[], candidate: RecommendedAction): void {\n if (actions.some((action) => actionKey(action) === actionKey(candidate))) return;\n actions.push(candidate);\n}\n\n/** Dedupe by executable intent, falling back to display command for non-executable actions. */\nfunction actionKey(action: RecommendedAction): string {\n if (!action.executable) return action.command ?? action.reason;\n return `${action.executable.binary} ${action.executable.args.join(\" \")}`;\n}\n\n/**\n * Compute `budget.estimatedTokens` from the serialized draft and\n * trim down the lower-priority sections deterministically when the\n * draft overshoots `requestedTokens`. See `src/context/budget.ts`\n * for the trim order. Always emits valid JSON; the structured\n * envelope is mutated section-by-section rather than slicing the\n * serialized string.\n *\n * `budget.truncated` is `true` when EITHER any section was trimmed\n * OR the final estimated envelope still exceeds `requestedTokens`\n * (irreducible-envelope edge case: empty wiki + very small budget).\n * In the latter case `trimmedSections` stays `[]` — the closed\n * section-key contract is not bent to record \"nothing was trimmable\".\n *\n * `budget.estimatedTokens` is recomputed against the FINAL envelope\n * shape (post-trim, post-truncated-flag, post-trimmedSections) via a\n * two-pass estimate that converges on the digit-count fixed point of\n * `estimatedTokens` itself. The residual drift between the reported\n * `estimatedTokens` and `estimatePackTokens(returnedPack)` is bounded\n * to a single character of the integer's decimal representation —\n * well under one token for any realistic budget.\n */\nfunction finalizeBudget(\n draft: ContextPack,\n requestedTokens: number,\n graph: GraphData | null = null,\n): ContextPack {\n const initialEstimate = estimatePackTokens(draft);\n const trim = initialEstimate <= requestedTokens\n ? { pack: draft, trimmedSections: [] as string[] }\n : trimToBudget(draft, requestedTokens);\n const trimmedAny = trim.trimmedSections.length > 0;\n const reconciledPack = reconcileGraphNeighborReasons(trim.pack, graph);\n // First pass: write the eventual `truncated`/`trimmedSections`\n // strings into the budget block with a placeholder zero estimate so\n // the JSON shape the estimator sees matches what we'll ultimately\n // ship. Second pass: write the first estimate back and re-measure\n // so the reported value converges on the digit-count fixed point.\n const e1 = estimatePackTokens(\n applyBudget(reconciledPack, {\n requestedTokens, estimatedTokens: 0,\n truncated: trimmedAny, trimmedSections: trim.trimmedSections,\n }),\n );\n const e2 = estimatePackTokens(\n applyBudget(reconciledPack, {\n requestedTokens, estimatedTokens: e1,\n truncated: trimmedAny, trimmedSections: trim.trimmedSections,\n }),\n );\n const truncated = trimmedAny || e2 > requestedTokens;\n return applyBudget(reconciledPack, {\n requestedTokens,\n estimatedTokens: e2,\n truncated,\n trimmedSections: trim.trimmedSections,\n });\n}\n\n/** Replace `pack.budget` with `budget`, returning a fresh shallow-cloned envelope. */\nfunction applyBudget(pack: ContextPack, budget: ContextPack[\"budget\"]): ContextPack {\n return { ...pack, budget };\n}\n\n/**\n * Append `pending-candidates`, `lint-errors`, and\n * `source-window-unavailable` to the top-level warnings list.\n *\n * Runs AFTER source-window materialization so we can detect the gap\n * where `--include-sources` was on but a line-range citation\n * produced no window (path-confined rejection, missing source file,\n * 20-window cap reached, …). Runs BEFORE budget trimming so a\n * budget-induced window drop does NOT spuriously add the warning;\n * the trimmer mutates sourceWindows only after this pass.\n */\nfunction appendProjectWarnings(\n pack: ContextPack,\n state: ProjectState,\n options: NormalizedOptions,\n): ContextPack {\n const warnings = [...pack.warnings];\n if (state.pendingCandidates > 0) {\n warnings.push({\n code: \"pending-candidates\",\n message:\n `${state.pendingCandidates} review candidate${state.pendingCandidates === 1 ? \"\" : \"s\"} ` +\n \"pending approval. Run `llmwiki review list` to inspect.\",\n });\n }\n const lintErrors = state.lint.entry?.errors ?? 0;\n if (lintErrors > 0) {\n warnings.push({\n code: \"lint-errors\",\n message: `Last lint run reported ${lintErrors} error${lintErrors === 1 ? \"\" : \"s\"}.`,\n });\n }\n if (options.includeSources && hasUnmaterializedSpans(pack)) {\n warnings.push({\n code: \"source-window-unavailable\",\n message:\n \"One or more line-range citations did not produce a source window \" +\n \"(path-confined, missing source file, or per-pack window cap reached).\",\n });\n }\n return { ...pack, warnings };\n}\n\n/** True when any primary page has line-range citations missing matching sourceWindows. */\nfunction hasUnmaterializedSpans(pack: ContextPack): boolean {\n for (const entry of pack.primary) {\n const lineRangeCount = entry.citations.filter(\n (c) => c.start !== undefined && c.end !== undefined,\n ).length;\n if (lineRangeCount > entry.sourceWindows.length) return true;\n }\n return false;\n}\n","/**\n * MCP (Model Context Protocol) server entry point for llmwiki.\n *\n * Exposes llmwiki's automated pipelines (ingest, compile, query, search,\n * lint, read, status) as MCP tools so AI agents can drive the compiler\n * without scraping CLI output. Read-only wiki views are exposed as\n * MCP resources for direct context injection.\n *\n * Transport: stdio. The server reads JSON-RPC messages on stdin and\n * writes responses on stdout, which is the standard surface area for\n * Claude Desktop, Cursor, and other MCP-aware clients.\n */\n\nimport { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { StdioServerTransport } from \"@modelcontextprotocol/sdk/server/stdio.js\";\nimport { registerWikiTools } from \"./tools.js\";\nimport { registerWikiResources } from \"./resources.js\";\n\ninterface ServerOptions {\n /** Project root directory the server operates on. */\n root: string;\n /** Server version surfaced to MCP clients in the initialize handshake. */\n version: string;\n}\n\n/**\n * Start the MCP server bound to stdio transport.\n * Resolves once the transport closes (typically when the parent process exits).\n *\n * @param options - Root directory and server version (the CLI passes its own\n * version so the server doesn't need to read package.json).\n */\nexport async function startMCPServer(options: ServerOptions): Promise<void> {\n const { root, version } = options;\n const server = new McpServer({ name: \"llmwiki\", version }, {\n instructions:\n \"llmwiki is a knowledge compiler. Use ingest_source to add raw sources, \" +\n \"compile_wiki to run the LLM pipeline, query_wiki for grounded answers, \" +\n \"and search_pages to retrieve relevant pages. read_page, lint_wiki, and \" +\n \"wiki_status work without an API key.\",\n });\n\n registerWikiTools(server, root);\n registerWikiResources(server, root);\n\n const transport = new StdioServerTransport();\n await server.connect(transport);\n}\n","/**\n * MCP tool registrations for llmwiki.\n *\n * Each tool wraps an existing pipeline function (ingest, compile, query,\n * search, read, lint, status) and converts its structured result into\n * an MCP CallToolResult. Tools that need an LLM provider validate the\n * provider lazily — the server itself starts without credentials so\n * read-only tools always work.\n */\n\nimport path from \"path\";\nimport { z } from \"zod\";\nimport type { McpServer } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport { ingestSource } from \"../commands/ingest.js\";\nimport { compileAndReport } from \"../compiler/index.js\";\nimport { generateAnswer, selectPages } from \"../commands/query.js\";\nimport { lint } from \"../linter/index.js\";\nimport { collectPageSummaries, scanWikiPages } from \"../compiler/indexgen.js\";\nimport { detectChanges } from \"../compiler/hasher.js\";\nimport { countCandidates } from \"../compiler/candidates.js\";\nimport { readState } from \"../utils/state.js\";\nimport { safeReadFile, parseFrontmatter } from \"../utils/markdown.js\";\nimport { findRelevantChunks, findRelevantPages } from \"../utils/embeddings.js\";\nimport { buildContextPack } from \"../context/build.js\";\nimport {\n CONCEPTS_DIR,\n INDEX_FILE,\n QUERIES_DIR,\n CHUNK_TOP_K,\n} from \"../utils/constants.js\";\nimport { ensureProviderAvailable } from \"../utils/provider-guard.js\";\n\n/** Directories searched (in priority order) when resolving a page slug. */\nconst PAGE_DIRS = [CONCEPTS_DIR, QUERIES_DIR];\n\n/** Shape returned by search_pages for each matching page. */\ninterface PageRecord {\n slug: string;\n title: string;\n summary: string;\n body: string;\n}\n\n/**\n * Wrap an arbitrary JSON value as the standard MCP CallToolResult.\n * MCP requires content blocks even for structured payloads, so we mirror\n * the JSON in a text block for clients that don't read structuredContent.\n */\nfunction jsonResult(payload: unknown): {\n content: Array<{ type: \"text\"; text: string }>;\n structuredContent: { result: unknown };\n} {\n return {\n content: [{ type: \"text\" as const, text: JSON.stringify(payload, null, 2) }],\n structuredContent: { result: payload },\n };\n}\n\n/** Register all 8 wiki tools on the given MCP server instance. */\nexport function registerWikiTools(server: McpServer, root: string): void {\n registerIngestTool(server, root);\n registerCompileTool(server, root);\n registerQueryTool(server, root);\n registerSearchTool(server, root);\n registerReadTool(server, root);\n registerLintTool(server, root);\n registerStatusTool(server, root);\n registerContextPackTool(server, root);\n}\n\nfunction registerIngestTool(server: McpServer, root: string): void {\n server.registerTool(\n \"ingest_source\",\n {\n title: \"Ingest Source\",\n description:\n \"Fetch a URL or copy a local file into sources/. Returns the saved filename, \" +\n \"character count, and whether content was truncated to fit the size limit.\",\n inputSchema: {\n source: z\n .string()\n .describe(\"URL (http/https) or absolute path to a .md/.txt file\"),\n },\n },\n async ({ source }) => {\n const previousCwd = process.cwd();\n try {\n process.chdir(root);\n const result = await ingestSource(source);\n return jsonResult(result);\n } finally {\n process.chdir(previousCwd);\n }\n },\n );\n}\n\nfunction registerCompileTool(server: McpServer, root: string): void {\n server.registerTool(\n \"compile_wiki\",\n {\n title: \"Compile Wiki\",\n description:\n \"Run the incremental compile pipeline: extract concepts from new/changed \" +\n \"sources, generate wiki pages, resolve interlinks, and rebuild the index. \" +\n \"Requires an LLM provider with credentials.\",\n inputSchema: {},\n },\n async () => {\n ensureProviderAvailable();\n const result = await compileAndReport(root);\n return jsonResult(result);\n },\n );\n}\n\nfunction registerQueryTool(server: McpServer, root: string): void {\n server.registerTool(\n \"query_wiki\",\n {\n title: \"Query Wiki\",\n description:\n \"Ask a natural-language question. Selects relevant pages with the LLM, \" +\n \"loads them, and returns a grounded answer with citations. Set save=true \" +\n \"to persist the answer as a wiki page. Set debug=true to include the \" +\n \"selected chunks and their scores. Requires an LLM provider.\",\n inputSchema: {\n question: z.string().describe(\"The natural-language question to answer.\"),\n save: z\n .boolean()\n .optional()\n .describe(\"Persist the answer as a wiki/queries/ page when true.\"),\n debug: z\n .boolean()\n .optional()\n .describe(\"Include retrieval debug info (selected chunks/pages + scores).\"),\n },\n },\n async ({ question, save, debug }) => {\n ensureProviderAvailable();\n const result = await generateAnswer(root, question, { save, debug });\n return jsonResult(result);\n },\n );\n}\n\nfunction registerSearchTool(server: McpServer, root: string): void {\n server.registerTool(\n \"search_pages\",\n {\n title: \"Search Pages\",\n description:\n \"Select pages relevant to a question and return their full content. \" +\n \"Uses semantic embeddings when available, falling back to LLM-based \" +\n \"selection over the wiki index. Requires an LLM provider.\",\n inputSchema: {\n question: z.string().describe(\"The query used to rank pages.\"),\n },\n },\n async ({ question }) => {\n ensureProviderAvailable();\n const slugs = await pickSearchSlugs(root, question);\n const records = await loadPageRecords(root, slugs);\n return jsonResult({ pages: records });\n },\n );\n}\n\n/**\n * Resolve search candidates. Tries chunk-level retrieval first (highest\n * precision), then falls back to page-level embeddings, then to LLM-driven\n * selection over the wiki index.\n */\nasync function pickSearchSlugs(root: string, question: string): Promise<string[]> {\n try {\n const chunks = await findRelevantChunks(root, question, CHUNK_TOP_K);\n if (chunks.length > 0) return dedupePreservingOrder(chunks.map((c) => c.chunk.slug));\n } catch {\n // Chunk store unavailable — fall through to page-level embeddings.\n }\n\n try {\n const candidates = await findRelevantPages(root, question);\n if (candidates.length > 0) return candidates.map((c) => c.slug);\n } catch {\n // Embeddings unavailable — fall through to index-based selection.\n }\n\n const indexContent = await safeReadFile(path.join(root, INDEX_FILE));\n const { pages } = await selectPages(question, indexContent);\n return pages;\n}\n\n/** Deduplicate slugs while preserving the first-seen ordering. */\nfunction dedupePreservingOrder(slugs: string[]): string[] {\n const seen = new Set<string>();\n const out: string[] = [];\n for (const slug of slugs) {\n if (seen.has(slug)) continue;\n seen.add(slug);\n out.push(slug);\n }\n return out;\n}\n\nfunction registerReadTool(server: McpServer, root: string): void {\n server.registerTool(\n \"read_page\",\n {\n title: \"Read Page\",\n description:\n \"Read a single wiki page by slug. Searches concepts/ first, then queries/. \" +\n \"Returns the parsed frontmatter and body. No LLM call required.\",\n inputSchema: {\n slug: z.string().describe(\"Page slug, without .md extension.\"),\n },\n },\n async ({ slug }) => {\n const page = await readPage(root, slug);\n if (!page) {\n throw new Error(`Page not found: ${slug}`);\n }\n return jsonResult(page);\n },\n );\n}\n\nfunction registerLintTool(server: McpServer, root: string): void {\n server.registerTool(\n \"lint_wiki\",\n {\n title: \"Lint Wiki\",\n description:\n \"Run rule-based quality checks (broken wikilinks, orphans, duplicates, \" +\n \"empty pages, broken citations). Returns structured diagnostics. No LLM call.\",\n inputSchema: {},\n },\n async () => {\n const summary = await lint(root);\n return jsonResult(summary);\n },\n );\n}\n\nfunction registerStatusTool(server: McpServer, root: string): void {\n server.registerTool(\n \"wiki_status\",\n {\n title: \"Wiki Status\",\n description:\n \"Summarize the wiki: page count, source count, last compile time, \" +\n \"orphaned pages, and pending source changes. Read-only — never \" +\n \"modifies the workspace.\",\n inputSchema: {},\n },\n async () => jsonResult(await collectStatus(root)),\n );\n}\n\n/**\n * Register the `get_context_pack` tool. Delegates to the same\n * `buildContextPack()` helper as the CLI so the returned JSON matches\n * `llmwiki context --json` byte-for-byte (modulo prompt content).\n *\n * No provider guard runs here: semantic retrieval is opportunistic\n * inside `buildContextPack` and falls back to lexical with a stable\n * warning when credentials are missing. The pack is read-only and\n * never mutates the workspace, so the MCP layer needs no extra checks.\n *\n * The body is split into `contextPackToolConfig` (static metadata) and\n * `buildContextPackFromArgs` (the per-call adapter) so this function\n * stays inside the project's 40-line function ceiling.\n */\nfunction registerContextPackTool(server: McpServer, root: string): void {\n server.registerTool(\n \"get_context_pack\",\n contextPackToolConfig(),\n async (args) => jsonResult(await buildContextPackFromArgs(root, args)),\n );\n}\n\n/** Inline arg shape for {@link buildContextPackFromArgs}; matches `contextPackInputSchema`. */\ninterface ContextPackToolArgs {\n prompt: string;\n budget?: number;\n depth?: number;\n topPages?: number;\n topChunks?: number;\n omitRoot?: boolean;\n includeSources?: boolean;\n}\n\n/** Static `registerTool` metadata for `get_context_pack`. */\nfunction contextPackToolConfig(): {\n title: string;\n description: string;\n inputSchema: ReturnType<typeof contextPackInputSchema>;\n} {\n return {\n title: \"Get Context Pack\",\n description:\n \"Build an agent-ready evidence pack for `prompt` over the compiled \" +\n \"wiki: primary pages, semantic chunks, graph neighbors, citations, \" +\n \"warnings, and suggested next actions. Returns the same v1 JSON \" +\n \"envelope as `llmwiki context --json`. Read-only; no provider \" +\n \"credentials required. Use this to PREPARE evidence; use \" +\n \"`query_wiki` to GENERATE a grounded natural-language answer.\",\n inputSchema: contextPackInputSchema(),\n };\n}\n\n/**\n * Zod schema for the `get_context_pack` tool arguments. Extracted so\n * the registration function stays under the project's per-function\n * line ceiling and so the schema can be unit-tested in isolation if\n * we ever need to.\n */\nfunction contextPackInputSchema(): {\n prompt: z.ZodString;\n budget: z.ZodOptional<z.ZodNumber>;\n depth: z.ZodOptional<z.ZodNumber>;\n topPages: z.ZodOptional<z.ZodNumber>;\n topChunks: z.ZodOptional<z.ZodNumber>;\n omitRoot: z.ZodOptional<z.ZodBoolean>;\n includeSources: z.ZodOptional<z.ZodBoolean>;\n} {\n return {\n prompt: z.string().describe(\"Free-text task or topic to assemble context for.\"),\n budget: z\n .number()\n .optional()\n .describe(\"Approximate output token budget (default 8000).\"),\n depth: z\n .number()\n .optional()\n .describe(\"Graph neighborhood depth, 0..2 (default 1, 0 disables expansion).\"),\n topPages: z.number().optional().describe(\"Max primary pages (default 5, max 20).\"),\n topChunks: z\n .number()\n .optional()\n .describe(\"Max semantic chunks to surface (default 8, max 50).\"),\n omitRoot: z\n .boolean()\n .optional()\n .describe(\"Emit `project.root` as null instead of the absolute path.\"),\n includeSources: z\n .boolean()\n .optional()\n .describe(\n \"Materialize `primary[].sourceWindows` from claim-level citations \" +\n \"(reads files under `sources/` only; path-confined).\",\n ),\n };\n}\n\n/** Per-call adapter that fans the tool args into `buildContextPack`. */\nasync function buildContextPackFromArgs(\n root: string,\n args: ContextPackToolArgs,\n): Promise<Awaited<ReturnType<typeof buildContextPack>>> {\n return buildContextPack({\n root,\n prompt: args.prompt,\n budget: args.budget,\n depth: args.depth,\n topPages: args.topPages,\n topChunks: args.topChunks,\n omitRoot: args.omitRoot,\n includeSources: args.includeSources,\n });\n}\n\n/** Read-only status snapshot used by the wiki_status tool. */\nasync function collectStatus(root: string): Promise<WikiStatus> {\n const concepts = await collectPageSummaries(path.join(root, CONCEPTS_DIR));\n const queries = await collectPageSummaries(path.join(root, QUERIES_DIR));\n const state = await readState(root);\n const changes = await detectChanges(root, state);\n const orphans = await findOrphanedSlugs(root);\n const pendingCandidates = await countCandidates(root);\n const compileTimes = Object.values(state.sources).map((s) => s.compiledAt);\n const lastCompile = compileTimes.length > 0\n ? compileTimes.sort().slice(-1)[0]\n : null;\n\n return {\n pages: { concepts: concepts.length, queries: queries.length, total: concepts.length + queries.length },\n sources: Object.keys(state.sources).length,\n lastCompiledAt: lastCompile,\n orphanedPages: orphans,\n pendingCandidates,\n pendingChanges: changes\n .filter((c) => c.status !== \"unchanged\")\n .map((c) => ({ file: c.file, status: c.status })),\n };\n}\n\ninterface WikiStatus {\n pages: { concepts: number; queries: number; total: number };\n sources: number;\n lastCompiledAt: string | null;\n orphanedPages: string[];\n /** Number of compile candidates awaiting human review. */\n pendingCandidates: number;\n pendingChanges: Array<{ file: string; status: string }>;\n}\n\n/** Find concept slugs whose pages are flagged as orphaned. */\nasync function findOrphanedSlugs(root: string): Promise<string[]> {\n const scanned = await scanWikiPages(path.join(root, CONCEPTS_DIR));\n return scanned.filter(({ meta }) => meta.orphaned).map(({ slug }) => slug);\n}\n\n/** Load full content for a list of slugs, skipping missing/orphaned pages. */\nasync function loadPageRecords(root: string, slugs: string[]): Promise<PageRecord[]> {\n const records: PageRecord[] = [];\n for (const slug of slugs) {\n const page = await readPage(root, slug);\n if (page) records.push(page);\n }\n return records;\n}\n\n/**\n * Locate a page by slug across the priority-ordered page directories,\n * skipping orphaned entries to match the query pipeline's behaviour.\n */\nexport async function readPage(root: string, slug: string): Promise<PageRecord | null> {\n for (const dir of PAGE_DIRS) {\n const content = await safeReadFile(path.join(root, dir, `${slug}.md`));\n if (!content) continue;\n\n const { meta, body } = parseFrontmatter(content);\n if (meta.orphaned) continue;\n\n return {\n slug,\n title: typeof meta.title === \"string\" ? meta.title : slug,\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n body: body.trim(),\n };\n }\n return null;\n}\n","/**\n * MCP resource registrations for llmwiki.\n *\n * Resources expose read-only views of the wiki under the llmwiki:// URI\n * scheme. Hosts can attach these as context without invoking a tool —\n * useful for letting agents browse the wiki passively.\n */\n\nimport path from \"path\";\nimport { readdir } from \"fs/promises\";\nimport { McpServer, ResourceTemplate } from \"@modelcontextprotocol/sdk/server/mcp.js\";\nimport {\n CONCEPTS_DIR,\n INDEX_FILE,\n QUERIES_DIR,\n SOURCES_DIR,\n STATE_FILE,\n} from \"../utils/constants.js\";\nimport { safeReadFile, parseFrontmatter } from \"../utils/markdown.js\";\nimport { readState } from \"../utils/state.js\";\n\n/** Standard JSON content block for an MCP resource read result. */\nfunction jsonContent(uri: URL, payload: unknown): {\n uri: string;\n mimeType: string;\n text: string;\n} {\n return {\n uri: uri.href,\n mimeType: \"application/json\",\n text: JSON.stringify(payload, null, 2),\n };\n}\n\n/** Standard markdown content block for an MCP resource read result. */\nfunction markdownContent(uri: URL, text: string): {\n uri: string;\n mimeType: string;\n text: string;\n} {\n return {\n uri: uri.href,\n mimeType: \"text/markdown\",\n text,\n };\n}\n\n/** Register all 5 read-only wiki resources on the given MCP server. */\nexport function registerWikiResources(server: McpServer, root: string): void {\n registerIndexResource(server, root);\n registerSourcesResource(server, root);\n registerStateResource(server, root);\n registerConceptResource(server, root);\n registerQueryResource(server, root);\n}\n\nfunction registerIndexResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-index\",\n \"llmwiki://index\",\n {\n title: \"Wiki Index\",\n description: \"Full content of wiki/index.md (auto-generated table of contents).\",\n mimeType: \"text/markdown\",\n },\n async (uri) => {\n const content = await safeReadFile(path.join(root, INDEX_FILE));\n return { contents: [markdownContent(uri, content)] };\n },\n );\n}\n\nfunction registerSourcesResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-sources\",\n \"llmwiki://sources\",\n {\n title: \"Wiki Sources\",\n description: \"List of ingested source files with frontmatter metadata.\",\n mimeType: \"application/json\",\n },\n async (uri) => ({\n contents: [jsonContent(uri, await listSources(root))],\n }),\n );\n}\n\nfunction registerStateResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-state\",\n \"llmwiki://state\",\n {\n title: \"Compilation State\",\n description: \"Per-source hashes, concepts, and last compile times from .llmwiki/state.json.\",\n mimeType: \"application/json\",\n },\n async (uri) => {\n const state = await readState(root);\n return { contents: [jsonContent(uri, state)] };\n },\n );\n}\n\nfunction registerConceptResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-concept\",\n new ResourceTemplate(\"llmwiki://concept/{slug}\", {\n list: async () => listPagesUnder(root, CONCEPTS_DIR, \"concept\"),\n }),\n {\n title: \"Wiki Concept\",\n description: \"A single concept page from wiki/concepts/ — frontmatter plus body.\",\n mimeType: \"application/json\",\n },\n async (uri, { slug }) => ({\n contents: [jsonContent(uri, await loadPageWithMeta(root, CONCEPTS_DIR, String(slug)))],\n }),\n );\n}\n\nfunction registerQueryResource(server: McpServer, root: string): void {\n server.registerResource(\n \"wiki-query\",\n new ResourceTemplate(\"llmwiki://query/{slug}\", {\n list: async () => listPagesUnder(root, QUERIES_DIR, \"query\"),\n }),\n {\n title: \"Wiki Query\",\n description: \"A single saved query page from wiki/queries/ — frontmatter plus body.\",\n mimeType: \"application/json\",\n },\n async (uri, { slug }) => ({\n contents: [jsonContent(uri, await loadPageWithMeta(root, QUERIES_DIR, String(slug)))],\n }),\n );\n}\n\n/** Source listing: filename, frontmatter (truncation, source URL, etc.). */\nasync function listSources(root: string): Promise<Array<Record<string, unknown>>> {\n const sourcesPath = path.join(root, SOURCES_DIR);\n let files: string[];\n try {\n files = await readdir(sourcesPath);\n } catch {\n return [];\n }\n\n const records: Array<Record<string, unknown>> = [];\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const content = await safeReadFile(path.join(sourcesPath, file));\n const { meta } = parseFrontmatter(content);\n records.push({ filename: file, ...meta });\n }\n return records;\n}\n\n/** Read a single page and return a structured payload (slug, meta, body). */\nasync function loadPageWithMeta(\n root: string,\n dir: string,\n slug: string,\n): Promise<{ slug: string; meta: Record<string, unknown>; body: string }> {\n const filePath = path.join(root, dir, `${slug}.md`);\n const content = await safeReadFile(filePath);\n if (!content) {\n throw new Error(`Page not found: ${dir}/${slug}.md`);\n }\n\n const { meta, body } = parseFrontmatter(content);\n return { slug, meta, body: body.trim() };\n}\n\n/** Build a resource list payload by enumerating .md files in a wiki directory. */\nasync function listPagesUnder(\n root: string,\n dir: string,\n scheme: \"concept\" | \"query\",\n): Promise<{ resources: Array<{ uri: string; name: string }> }> {\n const pagesPath = path.join(root, dir);\n let files: string[];\n try {\n files = await readdir(pagesPath);\n } catch {\n return { resources: [] };\n }\n\n const resources = files\n .filter((f) => f.endsWith(\".md\"))\n .map((f) => {\n const slug = f.replace(/\\.md$/, \"\");\n return { uri: `llmwiki://${scheme}/${slug}`, name: slug };\n });\n\n return { resources };\n}\n"],"mappings":";;;AAQA,OAAO;AACP,SAAS,iBAAAA,sBAAqB;AAC9B,SAAS,eAAe;;;ACCxB,OAAOC,WAAU;AACjB,SAAS,YAAAC,iBAAgB;;;ACNzB,SAAS,WAAW,QAAQ,UAAU,aAAa;AACnD,OAAO,UAAU;AACjB,OAAO,UAAU;AAUjB,IAAM,0BAA0B;AAGhC,IAAM,sBAAsB;AAO5B,IAAM,0BAA0B;AAGhC,IAAM,kBAAkB;AAGxB,IAAM,0BAAwD,oBAAI,IAAI;AAAA,EACpE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAeM,SAAS,QAAQ,OAAuB;AAC7C,SAAO,MACJ,YAAY,EACZ,QAAQ,SAAS,EAAE,EACnB,QAAQ,sBAAsB,EAAE,EAChC,QAAQ,QAAQ,GAAG,EACnB,QAAQ,OAAO,GAAG,EAClB,QAAQ,UAAU,EAAE;AACzB;AAGO,SAAS,iBAAiB,QAAyC;AACxE,QAAM,SAAS,KAAK,KAAK,QAAQ,EAAE,WAAW,IAAI,aAAa,IAAI,CAAC,EAAE,QAAQ;AAC9E,SAAO;AAAA,EAAQ,MAAM;AAAA;AACvB;AAGO,SAAS,iBAAiB,SAG/B;AACA,QAAM,EAAE,MAAM,KAAK,IAAI,uBAAuB,OAAO;AACrD,SAAO,EAAE,MAAM,KAAK;AACtB;AAUO,SAAS,uBAAuB,SAKrC;AACA,QAAM,QAAQ,QAAQ,MAAM,oCAAoC;AAChE,MAAI,CAAC,OAAO;AACV,WAAO,EAAE,MAAM,CAAC,GAAG,MAAM,SAAS,qBAAqB,OAAO,sBAAsB,MAAM;AAAA,EAC5F;AAEA,MAAI,OAAgC,CAAC;AACrC,MAAI,uBAAuB;AAC3B,MAAI;AACF,UAAM,SAAS,KAAK,KAAK,MAAM,CAAC,CAAC;AACjC,QAAI,UAAU,OAAO,WAAW,UAAU;AACxC,aAAO;AAAA,IACT,WAAW,WAAW,QAAQ,WAAW,QAAW;AAElD,6BAAuB;AAAA,IACzB;AAAA,EACF,QAAQ;AACN,2BAAuB;AAAA,EACzB;AACA,SAAO,EAAE,MAAM,MAAM,MAAM,CAAC,GAAG,qBAAqB,MAAM,qBAAqB;AACjF;AAGA,eAAsB,YAAY,UAAkB,SAAgC;AAClF,QAAM,MAAM,KAAK,QAAQ,QAAQ,GAAG,EAAE,WAAW,KAAK,CAAC;AACvD,QAAM,UAAU,WAAW;AAC3B,QAAM,UAAU,SAAS,SAAS,OAAO;AACzC,QAAM,OAAO,SAAS,QAAQ;AAChC;AA8BO,SAAS,sBAAsB,MAA+B;AACnE,QAAM,YAA6B,CAAC;AACpC,MAAI;AACJ,0BAAwB,YAAY;AACpC,UAAQ,QAAQ,wBAAwB,KAAK,IAAI,OAAO,MAAM;AAC5D,UAAM,MAAM,MAAM,CAAC;AACnB,UAAM,QAAQ,qBAAqB,GAAG;AACtC,QAAI,MAAM,SAAS,EAAG,WAAU,KAAK,EAAE,KAAK,MAAM,CAAC;AAAA,EACrD;AACA,SAAO;AACT;AAWO,SAAS,oBAAoB,OAAyB;AAC3D,SAAO,MAAM,MAAM,uBAAuB;AAC5C;AAQA,SAAS,qBAAqB,OAA6B;AACzD,QAAM,QAAsB,CAAC;AAC7B,aAAW,QAAQ,oBAAoB,KAAK,GAAG;AAC7C,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,KAAK,GAAG,iBAAiB,OAAO,CAAC;AAAA,EACzC;AACA,SAAO;AACT;AAOA,SAAS,iBAAiB,OAA6B;AACrD,QAAM,QAAQ,wBAAwB,KAAK,KAAK;AAChD,MAAI,OAAO,OAAQ,QAAO,gBAAgB,MAAM,OAAO,MAAM,MAAM,OAAO,KAAK;AAC/E,QAAM,SAAS,eAAe,KAAK;AACnC,SAAO,WAAW,SAAY,CAAC,MAAM,IAAI,CAAC;AAC5C;AAGA,SAAS,gBAAgB,MAAc,UAAgC;AACrE,QAAM,QAAsB,CAAC;AAC7B,aAAW,SAAS,SAAS,MAAM,MAAM,GAAG;AAC1C,UAAM,UAAU,OAAO,KAAK;AAC5B,QAAI,iBAAiB,SAAS,OAAO,GAAG;AACtC,YAAM,KAAK,EAAE,MAAM,OAAO,EAAE,OAAO,SAAS,KAAK,QAAQ,EAAE,CAAC;AAAA,IAC9D;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,eAAe,OAAuC;AAC7D,QAAM,QAAQ,oBAAoB,KAAK,KAAK;AAC5C,MAAI,CAAC,SAAS,CAAC,MAAM,QAAQ;AAC3B,WAAO,EAAE,MAAM,MAAM;AAAA,EACvB;AACA,QAAM,EAAE,MAAM,YAAY,UAAU,WAAW,QAAQ,IAAI,MAAM;AACjE,QAAM,QAAQ,cAAc;AAC5B,QAAM,MAAM,YAAY;AACxB,MAAI,UAAU,OAAW,QAAO,EAAE,KAAK;AACvC,QAAM,YAAY,OAAO,KAAK;AAC9B,QAAM,UAAU,QAAQ,SAAY,YAAY,OAAO,GAAG;AAC1D,MAAI,CAAC,iBAAiB,WAAW,OAAO,EAAG,QAAO;AAClD,SAAO,EAAE,MAAM,OAAO,EAAE,OAAO,WAAW,KAAK,QAAQ,EAAE;AAC3D;AAGA,SAAS,iBAAiB,OAAe,KAAsB;AAC7D,SAAO,SAAS,mBAAmB,OAAO;AAC5C;AAQO,SAAS,yBAAyB,OAAwB;AAC/D,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,MAAI,CAAC,QAAQ,SAAS,GAAG,KAAK,CAAC,QAAQ,SAAS,GAAG,EAAG,QAAO;AAC7D,QAAM,QAAQ,oBAAoB,KAAK,OAAO;AAC9C,MAAI,CAAC,SAAS,CAAC,MAAM,OAAQ,QAAO;AACpC,QAAM,EAAE,YAAY,UAAU,WAAW,QAAQ,IAAI,MAAM;AAC3D,QAAM,QAAQ,cAAc;AAC5B,QAAM,MAAM,YAAY;AACxB,MAAI,UAAU,OAAW,QAAO;AAChC,QAAM,YAAY,OAAO,KAAK;AAC9B,QAAM,UAAU,QAAQ,SAAY,YAAY,OAAO,GAAG;AAC1D,SAAO,CAAC,iBAAiB,WAAW,OAAO;AAC7C;AAgCA,eAAsB,aAAa,UAAmC;AACpE,MAAI;AACF,WAAO,MAAM,SAAS,UAAU,OAAO;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,gBAAgB,KAAkC;AACzD,MAAI,OAAO,QAAQ,YAAY,CAAC,OAAO,SAAS,GAAG,EAAG,QAAO;AAC7D,MAAI,MAAM,EAAG,QAAO;AACpB,MAAI,MAAM,EAAG,QAAO;AACpB,SAAO;AACT;AAGA,SAAS,qBAAqB,KAA2C;AACvE,MAAI,OAAO,QAAQ,SAAU,QAAO;AACpC,SAAO,wBAAwB,IAAI,GAAsB,IACpD,MACD;AACN;AAGA,SAAS,yBAAyB,OAAyC;AACzE,MAAI,OAAO,UAAU,YAAY,MAAM,KAAK,EAAE,SAAS,GAAG;AACxD,WAAO,EAAE,MAAM,MAAM,KAAK,EAAE;AAAA,EAC9B;AACA,MAAI,SAAS,OAAO,UAAU,YAAY,UAAU,OAAO;AACzD,UAAM,MAAM;AACZ,QAAI,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO;AACzE,UAAM,MAAwB,EAAE,MAAM,IAAI,KAAK,KAAK,EAAE;AACtD,QAAI,OAAO,IAAI,WAAW,SAAU,KAAI,SAAS,IAAI;AACrD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,SAAS,oBAAoB,KAA8C;AACzE,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO;AAChC,QAAM,OAAO,IACV,IAAI,wBAAwB,EAC5B,OAAO,CAAC,QAAiC,QAAQ,IAAI;AACxD,SAAO,KAAK,SAAS,IAAI,OAAO;AAClC;AAcO,SAAS,wBACd,MACoB;AACpB,SAAO;AAAA,IACL,YAAY,gBAAgB,KAAK,UAAU;AAAA,IAC3C,iBAAiB,qBAAqB,KAAK,eAAe;AAAA,IAC1D,gBAAgB,oBAAoB,KAAK,cAAc;AAAA,EACzD;AACF;AAMO,SAAS,iBAAiB,SAA0B;AACzD,MAAI,CAAC,WAAW,QAAQ,KAAK,EAAE,WAAW,EAAG,QAAO;AAEpD,QAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB,OAAO;AAC/C,MAAI,CAAC,KAAK,MAAO,QAAO;AACxB,MAAI,KAAK,KAAK,EAAE,WAAW,EAAG,QAAO;AAErC,SAAO;AACT;;;ACrWA,SAAS,SAAAC,QAAO,YAAAC,WAAU,aAAAC,kBAAiB;AAC3C,OAAOC,WAAU;AACjB,SAAS,kBAAkB;;;ACZpB,IAAM,mBAAmB;AAGzB,IAAM,mBAAmB;AAgBzB,IAAM,8BAA8B;AAGpC,IAAM,wBAAwB;AAG9B,IAAM,mBAAmB;AAGzB,IAAM,sBAAsB;AAG5B,IAAM,cAAc;AACpB,IAAM,gBAAgB;AACtB,IAAM,mBAAmB;AAGzB,IAAM,mBAAmB;AAGzB,IAAM,kBAA0C;AAAA,EACrD,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AACX;AAGO,IAAM,sBAAsB;AAG5B,IAAM,mBAAmB;AAMzB,IAAM,4BAA4B,KAAK,KAAK;AAQ5C,IAAM,4BAA4B,KAAK,KAAK;AAG5C,IAAM,cAAc;AACpB,IAAM,eAAe;AACrB,IAAM,cAAc;AACpB,IAAM,cAAc;AACpB,IAAM,aAAa;AACnB,IAAM,YAAY;AAClB,IAAM,aAAa;AACnB,IAAM,WAAW;AACjB,IAAM,kBAAkB;AACxB,IAAM,iBAAiB;AAGvB,IAAM,mBAAmB,oBAAI,IAAI,CAAC,QAAQ,SAAS,QAAQ,QAAQ,OAAO,CAAC;AAG3E,IAAM,wBAAwB,oBAAI,IAAI,CAAC,QAAQ,MAAM,CAAC;AAGtD,IAAM,4BAA4B;AAGlC,IAAM,iBAAiB;AAGvB,IAAM,yBAAyB;AAG/B,IAAM,kBAAkB;AAGxB,IAAM,cAAc;AAGpB,IAAM,oBAAoB;AAG1B,IAAM,qBAAqB;AAG3B,IAAM,kBAAkB;AAGxB,IAAM,kBAAkB;AAGxB,IAAM,2BAA2B;AACjC,IAAM,4CAA4C;AAGlD,IAAM,mBAA2C;AAAA,EACtD,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AACV;;;ADxGA,IAAM,qBAAqB;AAQ3B,SAAS,kBAAkBC,SAAwB;AACjD,SAAO,WAAW,QAAQ,EAAE,OAAOA,OAAM,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,kBAAkB;AACtF;AAYA,eAAe,6BAA6B,MAAcA,SAAiC;AACzF,QAAM,YAAY,GAAG,IAAI;AACzB,QAAMC,iBAAgBC,MAAK,KAAK,aAAa,SAAS;AACtD,MAAI;AACJ,MAAI;AACF,eAAW,MAAMC,UAASF,gBAAe,OAAO;AAAA,EAClD,SAAS,KAAK;AACZ,UAAM,IAAI;AACV,QAAI,EAAE,SAAS,SAAU,QAAO;AAChC,UAAM;AAAA,EACR;AACA,QAAM,EAAE,KAAK,IAAI,iBAAiB,QAAQ;AAC1C,MAAI,OAAO,KAAK,WAAW,YAAY,KAAK,WAAWD,SAAQ;AAC7D,WAAO;AAAA,EACT;AACA,SAAO,GAAG,IAAI,IAAI,kBAAkBA,OAAM,CAAC;AAC7C;AAYA,eAAsB,WACpB,OACA,UACAA,SACiB;AACjB,QAAM,OAAO,QAAQ,KAAK;AAI1B,MAAI,CAAC,MAAM;AACT,UAAM,IAAI;AAAA,MACR,2CAA2C,KAAK;AAAA,IAGlD;AAAA,EACF;AACA,QAAMI,OAAM,aAAa,EAAE,WAAW,KAAK,CAAC;AAC5C,QAAM,WAAW,MAAM,6BAA6B,MAAMJ,OAAM;AAChE,QAAM,WAAWE,MAAK,KAAK,aAAa,QAAQ;AAChD,QAAMG,WAAU,UAAU,UAAU,OAAO;AAC3C,SAAO;AACT;;;AExFA,IAAM,QAAQ;AACd,IAAM,OAAO;AACb,IAAM,MAAM;AACZ,IAAM,QAAQ;AACd,IAAM,SAAS;AACf,IAAM,OAAO;AAEb,IAAM,OAAO;AACb,IAAM,MAAM;AAEL,SAAS,KAAK,MAAsB;AACzC,SAAO,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK;AAC/B;AAEO,SAAS,IAAI,MAAsB;AACxC,SAAO,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK;AAC9B;AAEO,SAAS,QAAQ,MAAsB;AAC5C,SAAO,GAAG,KAAK,GAAG,IAAI,GAAG,KAAK;AAChC;AAEO,SAAS,KAAK,MAAsB;AACzC,SAAO,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK;AACjC;AAEO,SAAS,KAAK,MAAsB;AACzC,SAAO,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK;AAC/B;AAEO,SAAS,MAAM,MAAsB;AAC1C,SAAO,GAAG,GAAG,GAAG,IAAI,GAAG,KAAK;AAC9B;AAEO,SAAS,OAAO,MAAsB;AAC3C,SAAO,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK;AAC/B;AAWA,IAAI,YAAY;AAGT,SAAS,SAAS,OAAsB;AAC7C,cAAY;AACd;AAGO,SAAS,OAAO,MAAc,SAAuB;AAC1D,MAAI,UAAW;AACf,UAAQ,IAAI,GAAG,IAAI,IAAI,OAAO,EAAE;AAClC;AAGO,SAAS,OAAO,OAAqB;AAC1C,MAAI,UAAW;AACf,UAAQ,IAAI;AAAA,EAAK,IAAI,GAAG,KAAK,GAAG,KAAK,EAAE;AACvC,UAAQ,IAAI,IAAI,SAAI,OAAO,KAAK,IAAI,MAAM,SAAS,GAAG,EAAE,CAAC,CAAC,CAAC;AAC7D;;;AC9DA,SAAS,aAAa;AACtB,SAAS,mBAAmB;AAC5B,OAAO,qBAAqB;AAQ5B,eAAe,cAAc,KAAgC;AAC3D,QAAM,WAAW,MAAM,MAAM,GAAG;AAChC,MAAI,CAAC,SAAS,IAAI;AAChB,UAAM,IAAI,MAAM,mBAAmB,GAAG,UAAU,SAAS,MAAM,EAAE;AAAA,EACnE;AACA,SAAO;AACT;AAGA,SAAS,uBAAuB,MAAc,KAAqD;AACjG,QAAM,MAAM,IAAI,MAAM,MAAM,EAAE,IAAI,CAAC;AACnC,QAAM,SAAS,IAAI,YAAY,IAAI,OAAO,QAAQ;AAClD,QAAM,UAAU,OAAO,MAAM;AAE7B,MAAI,CAAC,WAAW,CAAC,QAAQ,SAAS;AAChC,UAAM,IAAI,MAAM,2CAA2C,GAAG,EAAE;AAAA,EAClE;AAEA,SAAO;AAAA,IACL,OAAO,QAAQ,SAAS;AAAA,IACxB,aAAa,QAAQ;AAAA,EACvB;AACF;AAGA,SAAS,kBAAkB,MAAsB;AAC/C,QAAM,WAAW,IAAI,gBAAgB,EAAE,cAAc,MAAM,CAAC;AAC5D,SAAO,SAAS,SAAS,IAAI;AAC/B;AAQA,eAAO,UAAiC,KAAuC;AAC7E,QAAM,WAAW,MAAM,cAAc,GAAG;AACxC,QAAM,OAAO,MAAM,SAAS,KAAK;AACjC,QAAM,EAAE,OAAO,YAAY,IAAI,uBAAuB,MAAM,GAAG;AAC/D,QAAM,UAAU,kBAAkB,WAAW;AAE7C,SAAO,EAAE,OAAO,QAAQ;AAC1B;;;ACvDA,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,WAAU;;;ACAjB,OAAOC,WAAU;AAiBV,SAAS,kBAAkB,UAA0B;AAC1D,QAAM,WAAWA,MAAK,SAAS,UAAUA,MAAK,QAAQ,QAAQ,CAAC;AAC/D,SAAO,SAAS,QAAQ,UAAU,GAAG,EAAE,KAAK;AAC9C;;;ADhBA,IAAM,uBAAuB,oBAAI,IAAI,CAAC,OAAO,MAAM,CAAC;AAGpD,SAAS,cAAc,MAAsB;AAC3C,SAAO;AAAA,EAAW,IAAI;AAAA;AACxB;AAQA,eAAO,WAAkC,UAA2C;AAClF,QAAM,MAAMC,MAAK,QAAQ,QAAQ,EAAE,YAAY;AAE/C,MAAI,CAAC,qBAAqB,IAAI,GAAG,GAAG;AAClC,UAAM,IAAI;AAAA,MACR,0BAA0B,GAAG;AAAA,IAC/B;AAAA,EACF;AAEA,QAAM,MAAM,MAAMC,UAAS,UAAU,OAAO;AAC5C,QAAM,QAAQ,kBAAkB,QAAQ;AACxC,QAAM,UAAU,QAAQ,QAAQ,MAAM,cAAc,GAAG;AAEvD,SAAO,EAAE,OAAO,QAAQ;AAC1B;;;AEzBA,SAAS,YAAAC,iBAAgB;AAIlB,SAAS,aAAa,UAAkBC,OAAuB;AACpE,MAAIA,SAAQ,OAAOA,UAAS,UAAU;AACpC,UAAM,aAAcA,MAAiC,OAAO;AAC5D,QAAI,OAAO,eAAe,YAAY,WAAW,KAAK,EAAE,SAAS,GAAG;AAClE,aAAO,WAAW,KAAK;AAAA,IACzB;AAAA,EACF;AACA,SAAO,kBAAkB,QAAQ;AACnC;AAYA,eAAO,UAAiC,UAA2C;AACjF,QAAM,EAAE,SAAS,IAAI,MAAM,OAAO,WAAW;AAE7C,QAAM,SAAS,MAAMC,UAAS,QAAQ;AACtC,QAAM,SAAS,IAAI,SAAS,EAAE,MAAM,IAAI,WAAW,MAAM,EAAE,CAAC;AAE5D,MAAI;AAIF,UAAM,aAAa,MAAM,OAAO,QAAQ;AACxC,UAAM,aAAa,MAAM,OAAO,QAAQ;AAExC,UAAM,QAAQ,aAAa,UAAU,WAAW,IAAI;AACpD,UAAM,UAAU,WAAW,KAAK,KAAK;AACrC,WAAO,EAAE,OAAO,QAAQ;AAAA,EAC1B,UAAE;AACA,UAAM,OAAO,QAAQ;AAAA,EACvB;AACF;;;AC7CA,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,WAAU;AACjB,OAAOC,gBAAe;;;ACPtB,OAAO,eAAuC;AAI9C,IAAM,wBAAwB;AAcvB,SAAS,4BACd,UAAoC,CAAC,GACtB;AACf,QAAM,iBAAiB,QAAQ,SAAS,KAAK;AAC7C,QAAM,gBAAgB,QAAQ,QAAQ,KAAK;AAC3C,QAAM,mBAAmB,QAAQ,WAAW,KAAK;AAEjD,QAAM,SAAwB,CAAC;AAE/B,MAAI,eAAe;AACjB,WAAO,SAAS;AAAA,EAClB;AACA,MAAI,kBAAkB;AACpB,WAAO,YAAY;AAAA,EACrB;AAEA,MAAI,CAAC,gBAAgB;AACnB,WAAO;AAAA,EACT;AAEA,QAAM,oBACJ,eAAe,SAAS,GAAG,KAAK,eAAe,SAAS,IACpD,eAAe,MAAM,GAAG,EAAE,IAC1B;AAEN,SAAO,UAAU;AACjB,SAAO;AACT;AAIO,IAAM,oBAAN,MAA+C;AAAA,EACnC;AAAA,EACA;AAAA,EAEjB,YAAY,OAAe,UAAoC,CAAC,GAAG;AACjE,SAAK,QAAQ;AACb,SAAK,SAAS,IAAI,UAAU,4BAA4B,OAAO,CAAC;AAAA,EAClE;AAAA;AAAA,EAGA,MAAM,SAAS,QAAgB,UAAwB,WAAoC;AACzF,UAAM,WAAW,MAAM,KAAK,OAAO,SAAS,OAAO;AAAA,MACjD,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,IACF,CAAC;AAED,UAAM,YAAY,SAAS,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,MAAM;AACxE,WAAO,WAAW,SAAS,SAAS,UAAU,OAAO;AAAA,EACvD;AAAA;AAAA,EAGA,MAAM,OACJ,QACA,UACA,WACA,SACiB;AACjB,UAAM,SAAS,KAAK,OAAO,SAAS,OAAO;AAAA,MACzC,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,IACF,CAAC;AAED,QAAI,WAAW;AACf,qBAAiB,SAAS,QAAQ;AAChC,UAAI,MAAM,SAAS,yBAAyB,MAAM,MAAM,SAAS,cAAc;AAC7E,oBAAY,MAAM,MAAM;AACxB,kBAAU,MAAM,MAAM,IAAI;AAAA,MAC5B;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,SACJ,QACA,UACA,OACA,WACiB;AACjB,UAAM,iBAAmC,MAAM,IAAI,CAAC,OAAO;AAAA,MACzD,MAAM,EAAE;AAAA,MACR,aAAa,EAAE;AAAA,MACf,cAAc,EAAE;AAAA,IAClB,EAAE;AAEF,UAAM,WAAW,MAAM,KAAK,OAAO,SAAS,OAAO;AAAA,MACjD,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ;AAAA,MACA;AAAA,MACA,OAAO;AAAA,MACP,aAAa,EAAE,MAAM,MAAM;AAAA,IAC7B,CAAC;AAED,UAAM,YAAY,SAAS,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,UAAU;AAC5E,QAAI,WAAW,SAAS,YAAY;AAClC,aAAO,KAAK,UAAU,UAAU,KAAK;AAAA,IACvC;AAEA,UAAM,YAAY,SAAS,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,MAAM;AACxE,WAAO,WAAW,SAAS,SAAS,UAAU,OAAO;AAAA,EACvD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,MAAM,MAAiC;AAC3C,UAAM,SAAS,QAAQ,IAAI,gBAAgB,KAAK;AAChD,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,MAAM,uBAAuB;AAAA,MAClD,QAAQ;AAAA,MACR,SAAS;AAAA,QACP,gBAAgB;AAAA,QAChB,eAAe,UAAU,MAAM;AAAA,MACjC;AAAA,MACA,MAAM,KAAK,UAAU,EAAE,OAAO,MAAM,OAAO,iBAAiB,UAAU,CAAC;AAAA,IACzE,CAAC;AAED,QAAI,CAAC,SAAS,IAAI;AAChB,YAAM,SAAS,MAAM,SAAS,KAAK;AACnC,YAAM,IAAI,MAAM,qCAAqC,SAAS,MAAM,MAAM,MAAM,EAAE;AAAA,IACpF;AAEA,UAAM,OAAQ,MAAM,SAAS,KAAK;AAClC,UAAM,SAAS,KAAK,OAAO,CAAC,GAAG;AAC/B,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AACA,WAAO;AAAA,EACT;AACF;;;ACjKA,SAAS,oBAAoB;AAC7B,SAAS,eAAe;AACxB,OAAOC,WAAU;AAEjB,IAAM,2BAA2B;AAcjC,SAAS,SAAS,OAAkD;AAClE,SAAO,OAAO,UAAU,YAAY,UAAU;AAChD;AAEA,SAAS,UAAU,OAAoC;AACrD,MAAI,OAAO,UAAU,SAAU,QAAO;AACtC,QAAM,UAAU,MAAM,KAAK;AAC3B,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAEA,SAAS,0BAA0B,KAAgC;AACjE,SAAO,IAAI,wBAAwB,KAAKA,MAAK,KAAK,QAAQ,GAAG,WAAW,eAAe;AACzF;AAEA,SAAS,uBAAuB,cAA0C;AACxE,MAAI;AACF,WAAO,aAAa,cAAc,MAAM;AAAA,EAC1C,SAAS,KAAK;AACZ,QAAI,SAAS,GAAG,KAAK,IAAI,SAAS,UAAU;AAC1C,aAAO;AAAA,IACT;AACA,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAM,IAAI,MAAM,sCAAsC,YAAY,MAAM,OAAO,EAAE;AAAA,EACnF;AACF;AAEO,SAAS,sBAAsB,MAAyB,QAAQ,KAAoC;AACzG,QAAM,eAAe,0BAA0B,GAAG;AAClD,QAAM,MAAM,uBAAuB,YAAY;AAC/C,MAAI,CAAC,IAAK,QAAO;AAEjB,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,UAAM,IAAI,MAAM,uCAAuC,YAAY,MAAM,OAAO,EAAE;AAAA,EACpF;AAEA,MAAI,CAAC,SAAS,MAAM,KAAK,CAAC,SAAS,OAAO,GAAG,GAAG;AAC9C,WAAO;AAAA,EACT;AAEA,QAAM,SAA4B;AAAA,IAChC,mBAAmB,UAAU,OAAO,IAAI,iBAAiB;AAAA,IACzD,sBAAsB,UAAU,OAAO,IAAI,oBAAoB;AAAA,IAC/D,oBAAoB,UAAU,OAAO,IAAI,kBAAkB;AAAA,IAC3D,iBAAiB,UAAU,OAAO,IAAI,eAAe;AAAA,EACvD;AAEA,MAAI,CAAC,OAAO,qBAAqB,CAAC,OAAO,wBAAwB,CAAC,OAAO,sBAAsB,CAAC,OAAO,iBAAiB;AACtH,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAEA,SAAS,yBAAyB,KAAuD;AACvF,MAAI;AACF,WAAO,sBAAsB,GAAG;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,yBAAyB,OAAuB;AACvD,QAAM,aAAa,MAAM,KAAK;AAC9B,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,UAAU;AACjC,QAAI,OAAO,aAAa,WAAW,OAAO,aAAa,UAAU;AAC/D,YAAM,IAAI,MAAM,wCAAwC;AAAA,IAC1D;AAAA,EACF,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU;AACrD,UAAM,IAAI,MAAM,gCAAgC,UAAU,MAAM,OAAO,EAAE;AAAA,EAC3E;AACA,SAAO;AACT;AAEO,SAAS,4BAA4B,MAAyB,QAAQ,KAA0B;AACrG,QAAM,iBAAiB,UAAU,IAAI,iBAAiB;AACtD,MAAI,eAAgB,QAAO,EAAE,QAAQ,eAAe;AAEpD,QAAM,oBAAoB,UAAU,IAAI,oBAAoB;AAC5D,MAAI,kBAAmB,QAAO,EAAE,WAAW,kBAAkB;AAE7D,QAAM,WAAW,sBAAsB,GAAG;AAC1C,MAAI,UAAU,kBAAmB,QAAO,EAAE,QAAQ,SAAS,kBAAkB;AAC7E,MAAI,UAAU,qBAAsB,QAAO,EAAE,WAAW,SAAS,qBAAqB;AACtF,SAAO,CAAC;AACV;AAEO,SAAS,6BAA6B,MAAyB,QAAQ,KAAyB;AACrG,QAAM,gBAAgB,IAAI;AAC1B,MAAI,kBAAkB,OAAW,QAAO;AACxC,SAAO,yBAAyB,GAAG,GAAG;AACxC;AAEO,SAAS,+BAA+B,MAAyB,QAAQ,KAAyB;AACvG,QAAM,kBAAkB,UAAU,IAAI,kBAAkB;AACxD,MAAI,gBAAiB,QAAO,yBAAyB,eAAe;AAEpE,QAAM,kBAAkB,yBAAyB,GAAG,GAAG;AACvD,MAAI,CAAC,gBAAiB,QAAO;AAC7B,SAAO,yBAAyB,eAAe;AACjD;;;AF1GA,IAAM,oBAA6D;AAAA,EACjE,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AACX;AAGA,SAAS,qBAAqB,KAAsC;AAClE,QAAM,WAAW,kBAAkB,IAAI,YAAY,CAAC;AACpD,MAAI,CAAC,UAAU;AACb,UAAM,IAAI;AAAA,MACR,gCAAgC,GAAG,iBAAiB,OAAO,KAAK,iBAAiB,EAAE,KAAK,IAAI,CAAC;AAAA,IAC/F;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,cAAyB;AAChC,QAAM,UAAU,+BAA+B;AAC/C,QAAM,OAAO,4BAA4B;AACzC,SAAO,IAAIC,WAAU,4BAA4B,EAAE,SAAS,GAAG,KAAK,CAAC,CAAC;AACxE;AAGA,eAAe,wBACb,QACA,OACA,WACA,UACiB;AACjB,QAAM,WAAW,MAAM,OAAO,SAAS,OAAO;AAAA,IAC5C;AAAA,IACA,YAAY;AAAA,IACZ,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,SAAS;AAAA,UACP;AAAA,YACE,MAAM;AAAA,YACN,QAAQ,EAAE,MAAM,UAAU,YAAY,UAAU,MAAM,UAAU;AAAA,UAClE;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,MAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAC;AAED,QAAM,YAAY,SAAS,QAAQ,KAAK,CAAC,UAAU,MAAM,SAAS,MAAM;AACxE,SAAO,WAAW,SAAS,SAAS,UAAU,OAAO;AACvD;AAYA,eAAO,YAAmC,UAA2C;AACnF,QAAM,eAAe,QAAQ,IAAI,oBAAoB;AAErD,MAAI,iBAAiB,aAAa;AAChC,UAAM,IAAI;AAAA,MACR,6EACwB,YAAY;AAAA,IAEtC;AAAA,EACF;AAEA,QAAM,MAAMC,MAAK,QAAQ,QAAQ,EAAE,YAAY;AAC/C,QAAM,WAAW,qBAAqB,GAAG;AACzC,QAAM,cAAc,MAAMC,UAAS,QAAQ;AAC3C,QAAM,YAAY,YAAY,SAAS,QAAQ;AAE/C,QAAM,SAAS,YAAY;AAC3B,QAAM,QAAQ,6BAA6B,KAAK,gBAAgB;AAChE,QAAM,UAAU,MAAM,wBAAwB,QAAQ,OAAO,WAAW,QAAQ;AAChF,QAAM,QAAQ,kBAAkB,QAAQ;AAExC,SAAO,EAAE,OAAO,QAAQ;AAC1B;;;AGpGA,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,WAAU;AAKjB,SAAS,yBAAyB;AAGlC,IAAM,sBAAsB;AAG5B,IAAM,uBAAuB;AAG7B,IAAM,oBAAoB;AAG1B,IAAM,gBAAgB;AAGtB,IAAM,gBAAgB;AAGf,SAAS,aAAaC,SAAyB;AACpD,SAAO,oBAAoB,KAAKA,OAAM;AACxC;AAGA,SAAS,eAAe,KAAqB;AAC3C,QAAM,QAAQ,IAAI,MAAM,6BAA6B;AACrD,MAAI,CAAC,OAAO;AACV,UAAM,IAAI,MAAM,gDAAgD,GAAG,EAAE;AAAA,EACvE;AACA,SAAO,MAAM,CAAC;AAChB;AAGA,SAAS,aAAa,UAA0B;AAC9C,QAAM,UAAU,KAAK,MAAM,WAAW,aAAa;AACnD,QAAM,UAAU,KAAK,MAAO,WAAW,gBAAiB,aAAa;AACrE,SAAO,GAAG,OAAO,OAAO,EAAE,SAAS,GAAG,GAAG,CAAC,IAAI,OAAO,OAAO,EAAE,SAAS,GAAG,GAAG,CAAC;AAChF;AAGA,eAAe,uBAAuB,KAAsC;AAC1E,QAAM,UAAU,eAAe,GAAG;AAClC,QAAM,WAAW,MAAM,kBAAkB,gBAAgB,OAAO;AAEhE,MAAI,CAAC,YAAY,SAAS,WAAW,GAAG;AACtC,UAAM,IAAI,MAAM,8CAA8C,GAAG,EAAE;AAAA,EACrE;AAEA,QAAM,QAAQ,SAAS,IAAI,CAAC,QAAQ,IAAI,aAAa,IAAI,MAAM,CAAC,KAAK,IAAI,IAAI,EAAE;AAE/E,SAAO;AAAA,IACL,OAAO,sBAAsB,OAAO;AAAA,IACpC,SAAS,MAAM,KAAK,IAAI;AAAA,EAC1B;AACF;AAGA,SAAS,eAAe,SAA0B;AAChD,SAAO,kBAAkB,KAAK,OAAO,KAAK,QAAQ,SAAS,KAAK;AAClE;AAGA,SAAS,SAAS,KAAa,UAAkC;AAC/D,QAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAM,SAAmB,CAAC;AAC1B,MAAI,QAAQ;AAEZ,aAAWC,SAAQ,OAAO;AACxB,UAAM,UAAUA,MAAK,KAAK;AAC1B,QAAI,YAAY,YAAY,YAAY,IAAI;AAC1C,cAAQ;AACR;AAAA,IACF;AACA,QAAI,eAAe,OAAO,GAAG;AAC3B,aAAO,KAAK;AAAA,KAAQ,OAAO,KAAK;AAChC,cAAQ;AACR;AAAA,IACF;AACA,QAAI,SAAS,QAAQ,SAAS,GAAG;AAC/B,aAAO,KAAK,OAAO;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,kBAAkB,QAAQ,GAAG,SAAS,OAAO,KAAK,IAAI,EAAE,KAAK,EAAE;AACjF;AAGA,SAAS,SAAS,KAAa,UAAkC;AAC/D,QAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAM,SAAmB,CAAC;AAE1B,aAAWA,SAAQ,OAAO;AACxB,UAAM,UAAUA,MAAK,KAAK;AAC1B,QAAI,YAAY,MAAM,qBAAqB,KAAK,OAAO,GAAG;AACxD;AAAA,IACF;AACA,QAAI,eAAe,OAAO,GAAG;AAC3B,aAAO,KAAK;AAAA,KAAQ,OAAO,KAAK;AAChC;AAAA,IACF;AACA,QAAI,QAAQ,SAAS,GAAG;AACtB,aAAO,KAAK,OAAO;AAAA,IACrB;AAAA,EACF;AAEA,SAAO,EAAE,OAAO,kBAAkB,QAAQ,GAAG,SAAS,OAAO,KAAK,IAAI,EAAE,KAAK,EAAE;AACjF;AAGA,SAAS,qBAAqB,KAAa,UAAkC;AAG3E,SAAO,EAAE,OAAO,kBAAkB,QAAQ,GAAG,SAAS,IAAI,KAAK,EAAE;AACnE;AASA,eAAO,iBAAwCD,SAAyC;AACtF,MAAI,aAAaA,OAAM,GAAG;AACxB,WAAO,uBAAuBA,OAAM;AAAA,EACtC;AAEA,QAAM,MAAME,MAAK,QAAQF,OAAM,EAAE,YAAY;AAC7C,QAAM,MAAM,MAAMG,UAASH,SAAQ,OAAO;AAE1C,MAAI,QAAQ,OAAQ,QAAO,SAAS,KAAKA,OAAM;AAC/C,MAAI,QAAQ,OAAQ,QAAO,SAAS,KAAKA,OAAM;AAC/C,MAAI,QAAQ,OAAQ,QAAO,qBAAqB,KAAKA,OAAM;AAE3D,QAAM,IAAI;AAAA,IACR,qCAAqC,GAAG;AAAA,EAC1C;AACF;;;AZlIA,SAAS,MAAMI,SAAyB;AACtC,SAAOA,QAAO,WAAW,SAAS,KAAKA,QAAO,WAAW,UAAU;AACrE;AAGA,IAAM,kBAAkB;AAOxB,IAAM,sBAAsB;AAQ5B,IAAMC,qBAAoB;AAG1B,IAAM,wBAAwB;AAM9B,IAAM,2BAA2B;AAMjC,IAAM,wBAAwB;AAM9B,SAAS,wBAAwB,QAAqC;AACpE,QAAM,SAAS,oBAAI,IAAoB;AAEvC,sBAAoB,YAAY;AAChC,MAAI;AACJ,UAAQ,QAAQ,oBAAoB,KAAK,MAAM,OAAO,MAAM;AAC1D,UAAM,OAAO,MAAM,CAAC,EAAE,KAAK;AAC3B,WAAO,IAAI,OAAO,OAAO,IAAI,IAAI,KAAK,KAAK,CAAC;AAAA,EAC9C;AACA,SAAO;AACT;AAWA,SAAS,0BAA0B,QAAyB;AAC1D,QAAM,SAAS,wBAAwB,MAAM;AAE7C,QAAM,mBAAmB,OAAO;AAChC,QAAM,oBAAoB,oBAAoB;AAE9C,QAAM,qBAAqB,CAAC,GAAG,OAAO,OAAO,CAAC,EAAE;AAAA,IAC9C,CAAC,MAAM,KAAK;AAAA,EACd;AAEA,SAAO,qBAAqB;AAC9B;AAuBA,eAAe,uBAAuB,UAAoC;AACxE,QAAM,MAAM,MAAMC,UAAS,UAAU,OAAO;AAC5C,QAAM,SAAS,IAAI,MAAM,GAAG,eAAe;AAE3C,MAAI,0BAA0B,MAAM,EAAG,QAAO;AAE9C,QAAM,mBAAmB,OAAO,MAAM,IAAI,OAAOD,mBAAkB,QAAQ,IAAI,CAAC;AAChF,UAAQ,kBAAkB,UAAU,MAAM;AAC5C;AAUO,SAAS,iBAAiB,SAAiC;AAChE,MAAI,QAAQ,UAAU,kBAAkB;AACtC,WAAO,EAAE,SAAS,WAAW,OAAO,eAAe,QAAQ,OAAO;AAAA,EACpE;AAEA,EAAO;AAAA,IACL;AAAA,IACO;AAAA,MACL,0BAA0B,QAAQ,OAAO,eAAe,CAAC,OAAO,iBAAiB,eAAe,CAAC;AAAA,IACnG;AAAA,EACF;AACA,SAAO;AAAA,IACL,SAAS,QAAQ,MAAM,GAAG,gBAAgB;AAAA,IAC1C,WAAW;AAAA,IACX,eAAe,QAAQ;AAAA,EACzB;AACF;AAGA,SAAS,kBAAkB,SAAuB;AAChD,QAAM,SAAS,QAAQ,KAAK,EAAE;AAE9B,MAAI,WAAW,GAAG;AAChB,UAAM,IAAI;AAAA,MACR;AAAA,IACF;AAAA,EACF;AAEA,MAAI,SAAS,kBAAkB;AAC7B,IAAO;AAAA,MACL;AAAA,MACO;AAAA,QACL,6BAA6B,MAAM,kCAAkC,gBAAgB;AAAA,MACvF;AAAA,IACF;AAAA,EACF;AACF;AAcA,eAAsB,iBAAiBD,SAAqC;AAC1E,MAAI,CAAC,MAAMA,OAAM,GAAG;AAClB,UAAM,MAAMG,MAAK,QAAQH,OAAM,EAAE,YAAY;AAC7C,QAAI,QAAQ,OAAQ,QAAO;AAC3B,QAAI,iBAAiB,IAAI,GAAG,EAAG,QAAO;AACtC,QAAI,sBAAsB,IAAI,GAAG,EAAG,QAAO;AAC3C,QAAI,QAAQ,QAAQ;AAClB,YAAM,eAAe,MAAM,uBAAuBA,OAAM;AACxD,aAAO,eAAe,eAAe;AAAA,IACvC;AACA,WAAO;AAAA,EACT;AAEA,MAAI,aAAaA,OAAM,EAAG,QAAO;AACjC,SAAO;AACT;AAGO,SAAS,cACd,OACAA,SACA,QACA,YACQ;AACR,QAAM,OAAgC;AAAA,IACpC;AAAA,IACA,QAAAA;AAAA,IACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC;AACA,MAAI,eAAe,QAAW;AAC5B,SAAK,aAAa;AAAA,EACpB;AACA,MAAI,OAAO,WAAW;AACpB,SAAK,YAAY;AACjB,SAAK,gBAAgB,OAAO;AAAA,EAC9B;AACA,QAAM,cAAc,iBAAiB,IAAI;AAEzC,SAAO,GAAG,WAAW;AAAA;AAAA,EAAO,OAAO,OAAO;AAAA;AAC5C;AAGA,eAAe,aACbA,SACA,YAC6C;AAC7C,UAAQ,YAAY;AAAA,IAClB,KAAK;AACH,aAAO,UAAUA,OAAM;AAAA,IACzB,KAAK;AACH,aAAO,UAAUA,OAAM;AAAA,IACzB,KAAK;AACH,aAAO,YAAYA,OAAM;AAAA,IAC3B,KAAK;AACH,aAAO,iBAAiBA,OAAM;AAAA,IAChC,KAAK;AACH,aAAO,WAAWA,OAAM;AAAA,EAC5B;AACF;AAUA,eAAsB,aAAaA,SAAuC;AACxE,QAAM,aAAa,MAAM,iBAAiBA,OAAM;AAChD,EAAO,OAAO,KAAY,KAAK,cAAc,UAAU,MAAMA,OAAM,EAAE,CAAC;AAEtE,QAAM,EAAE,OAAO,QAAQ,IAAI,MAAM,aAAaA,SAAQ,UAAU;AAEhE,QAAM,SAAS,iBAAiB,OAAO;AACvC,oBAAkB,OAAO,OAAO;AAChC,QAAM,WAAW,cAAc,OAAOA,SAAQ,QAAQ,UAAU;AAChE,QAAM,YAAY,MAAM,WAAW,OAAO,UAAUA,OAAM;AAE1D,SAAO;AAAA,IACL,UAAUG,MAAK,SAAS,SAAS;AAAA,IACjC,WAAW,OAAO,QAAQ;AAAA,IAC1B,WAAW,OAAO;AAAA,IAClB,QAAAH;AAAA,IACA;AAAA,EACF;AACF;AAMA,eAAO,OAA8BA,SAA+B;AAClE,QAAM,SAAS,MAAM,aAAaA,OAAM;AACxC,QAAM,YAAYG,MAAK,KAAK,aAAa,OAAO,QAAQ;AAExD,EAAO;AAAA,IACL;AAAA,IACO,QAAQ,SAAgB,KAAK,OAAO,QAAQ,CAAC,WAAa,OAAO,SAAS,CAAC,EAAE;AAAA,EACtF;AACA,EAAO,OAAO,UAAY,IAAI,uBAAuB,CAAC;AACxD;;;AaxRA,OAAOC,YAAU;AACjB,SAAS,SAAS,YAAY;;;ACI9B,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,WAAU;;;ACRjB,IAAM,kBAAkB;AAMxB,SAAS,cAAc,MAAsB;AAC3C,QAAM,UAAU,KAAK,KAAK;AAC1B,SAAO,QAAQ,SAAS,kBACpB,QAAQ,MAAM,GAAG,eAAe,EAAE,QAAQ,IAAI,WAC9C;AACN;AAWO,SAAS,oBACd,UACA,kBACA,cACQ;AACR,MAAI,YAAY,SAAS,KAAK,EAAE,SAAS,EAAG,QAAO,cAAc,QAAQ;AACzE,MAAI,kBAAkB;AACpB,UAAM,YAAY,iBAAiB,MAAM,IAAI,EAAE,CAAC;AAChD,QAAI,UAAU,KAAK,EAAE,SAAS,EAAG,QAAO,cAAc,SAAS;AAAA,EACjE;AACA,SAAO;AACT;AAOO,SAAS,iBAAiB,KAAa,UAA2B;AACvE,MAAI;AACF,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,UAAM,IAAI,MAAM,iCAAiC,QAAQ,EAAE;AAAA,EAC7D;AACF;;;ADlCA,IAAM,mBAAmB;AAGzB,IAAM,sBAAsB,oBAAI,IAAI,CAAC,QAAQ,aAAa,UAAU,YAAY,aAAa,CAAC;AAmB9F,SAAS,YAAY,SAAgD;AACnE,MAAI,OAAO,YAAY,SAAU,QAAO;AACxC,SAAO,QACJ,OAAO,CAAC,MAAM,EAAE,SAAS,UAAU,OAAO,EAAE,SAAS,QAAQ,EAC7D,IAAI,CAAC,MAAM,EAAE,IAAc,EAC3B,KAAK,IAAI;AACd;AAGA,SAAS,0BAA0B,OAA8B;AAC/D,QAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,QAAQ,KAAK,EAAE,SAAS,CAAC;AACpF,SAAO,oBAAoB,QAAW,WAAW,SAAS,gBAAgB;AAC5E;AAGA,SAAS,UAAUC,OAAkC;AACnD,MAAI;AACF,WAAO,KAAK,MAAMA,KAAI;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,YAAY,OAAwC;AAC3D,MAAI,CAAC,MAAM,WAAW,CAAC,MAAM,QAAQ,KAAM,QAAO;AAClD,QAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,SAAS,UAAU,SAAS,YAAa,QAAO;AAEpD,QAAM,UAAU,YAAY,MAAM,QAAQ,OAAO;AACjD,MAAI,QAAQ,KAAK,EAAE,WAAW,EAAG,QAAO;AAExC,SAAO,EAAE,MAAM,SAAS,WAAW,MAAM,UAAU;AACrD;AAEO,IAAM,gBAAgC;AAAA,EAC3C,MAAM;AAAA,EAEN,MAAM,OAAO,UAAoC;AAC/C,QAAIC,MAAK,QAAQ,QAAQ,EAAE,YAAY,MAAM,iBAAkB,QAAO;AACtE,UAAM,MAAM,MAAMC,UAAS,UAAU,OAAO,EAAE,MAAM,MAAM,EAAE;AAC5D,UAAM,YAAY,IAAI,MAAM,IAAI,EAAE,CAAC,EAAE,KAAK;AAC1C,QAAI,CAAC,UAAU,WAAW,GAAG,EAAG,QAAO;AACvC,QAAI;AACF,YAAM,MAAM,KAAK,MAAM,SAAS;AAChC,aAAO,OAAO,IAAI,SAAS,YAAY,oBAAoB,IAAI,IAAI,IAAI;AAAA,IACzE,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,UAA8C;AACxD,UAAM,MAAM,MAAMA,UAAS,UAAU,OAAO;AAC5C,UAAM,QAAQ,IAAI,MAAM,IAAI,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,SAAS,CAAC;AAE/D,QAAI,MAAM,WAAW,GAAG;AACtB,YAAM,IAAI,MAAM,iCAAiC,QAAQ,EAAE;AAAA,IAC7D;AAEA,UAAM,QAAuB,CAAC;AAC9B,UAAM,aAAuB,CAAC;AAE9B,eAAW,CAAC,OAAOF,KAAI,KAAK,MAAM,QAAQ,GAAG;AAC3C,YAAM,QAAQ,UAAUA,KAAI;AAC5B,UAAI,UAAU,MAAM;AAClB,cAAM,IAAI;AAAA,UACR,0BAA0B,QAAQ,CAAC,uBAAuB,QAAQ;AAAA,QACpE;AAAA,MACF;AACA,UAAI,MAAM,UAAW,YAAW,KAAK,MAAM,SAAS;AACpD,YAAM,OAAO,YAAY,KAAK;AAC9B,UAAI,KAAM,OAAM,KAAK,IAAI;AAAA,IAC3B;AAEA,UAAM,QAAQ,0BAA0B,KAAK;AAE7C,WAAO;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT,WAAW,WAAW,CAAC;AAAA,MACvB,SAAS,WAAW,WAAW,SAAS,CAAC;AAAA,MACzC,qBAAqB;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACF;;;AE3GA,SAAS,YAAAG,iBAAgB;AACzB,OAAOC,YAAU;AAIjB,IAAM,kBAAkB;AA4BxB,SAAS,UAAU,IAAoB;AACrC,SAAO,IAAI,KAAK,KAAK,GAAI,EAAE,YAAY;AACzC;AAGA,SAAS,aAAa,SAAmD;AACvE,QAAM,QAAuB,CAAC;AAC9B,aAAW,QAAQ,OAAO,OAAO,OAAO,GAAG;AACzC,UAAM,OAAO,WAAW,IAAI;AAC5B,QAAI,KAAM,OAAM,KAAK,IAAI;AAAA,EAC3B;AACA,QAAM,KAAK,uBAAuB;AAClC,SAAO;AACT;AAGA,SAAS,WAAW,MAAqC;AACvD,QAAM,MAAM,KAAK;AACjB,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,OAAO,cAAc,IAAI,QAAQ,IAAI;AAC3C,MAAI,CAAC,KAAM,QAAO;AAClB,QAAM,UAAU,iBAAiB,IAAI,SAAS,KAAK;AACnD,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,EAAE,MAAM,SAAS,WAAW,kBAAkB,IAAI,WAAW,EAAE;AACxE;AAGA,SAAS,cAAc,MAAuD;AAC5E,MAAI,SAAS,UAAU,SAAS,YAAa,QAAO;AACpD,SAAO;AACT;AAGA,SAAS,iBAAiB,OAAqC;AAC7D,UAAQ,SAAS,CAAC,GAAG,KAAK,IAAI,EAAE,KAAK;AACvC;AAGA,SAAS,kBAAkB,IAA4C;AACrE,SAAO,MAAM,OAAO,UAAU,EAAE,IAAI;AACtC;AAGA,SAAS,wBAAwB,GAAgB,GAAwB;AACvE,MAAI,CAAC,EAAE,aAAa,CAAC,EAAE,UAAW,QAAO;AACzC,SAAO,EAAE,UAAU,cAAc,EAAE,SAAS;AAC9C;AAGA,SAAS,cAAc,OAA8C;AACnE,SACE,MAAM,QAAQ,KAAK,KACnB,MAAM,SAAS,KACf,OAAQ,MAAM,CAAC,EAAwB,YAAY;AAEvD;AAEO,IAAM,eAA+B;AAAA,EAC1C,MAAM;AAAA,EAEN,MAAM,OAAO,UAAoC;AAC/C,QAAIC,OAAK,QAAQ,QAAQ,EAAE,YAAY,MAAM,gBAAiB,QAAO;AACrE,UAAM,MAAM,MAAMC,UAAS,UAAU,OAAO,EAAE,MAAM,MAAM,EAAE;AAC5D,QAAI,IAAI,UAAU,EAAE,CAAC,MAAM,IAAK,QAAO;AACvC,QAAI;AACF,aAAO,cAAc,KAAK,MAAM,GAAG,CAAC;AAAA,IACtC,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,UAA8C;AACxD,UAAM,MAAM,MAAMA,UAAS,UAAU,OAAO;AAC5C,UAAM,SAAS,iBAAiB,KAAK,QAAQ;AAE7C,QAAI,CAAC,cAAc,MAAM,GAAG;AAC1B,YAAM,IAAI;AAAA,QACR,6DAA6D,QAAQ;AAAA,MACvE;AAAA,IACF;AAEA,UAAM,OAAO,OAAO,CAAC;AACrB,UAAM,QAAQ,aAAa,KAAK,WAAW,CAAC,CAAC;AAC7C,UAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAErD,WAAO;AAAA,MACL,OAAO,oBAAoB,KAAK,OAAO,WAAW,SAAS,eAAe;AAAA,MAC1E,SAAS;AAAA,MACT,WAAW,KAAK,eAAe,OAAO,UAAU,KAAK,WAAW,IAAI;AAAA,MACpE,SAAS,KAAK,eAAe,OAAO,UAAU,KAAK,WAAW,IAAI;AAAA,MAClE,qBAAqB;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACF;;;AClIA,SAAS,YAAAC,kBAAgB;AACzB,OAAOC,YAAU;AAIjB,IAAM,mBAAmB;AAyBzB,SAAS,aAAa,OAA2C;AAC/D,SACE,OAAO,UAAU,YACjB,UAAU,QACV,UAAU,SACV,MAAM,QAAS,MAA2B,IAAI;AAElD;AAGA,SAAS,aAAa,OAA2C;AAC/D,SACE,OAAO,UAAU,YACjB,UAAU,QACV,cAAc,SACd,MAAM,QAAS,MAA2B,QAAQ;AAEtD;AAGA,SAAS,wBACP,MAC+C;AAC/C,MAAI,aAAa,IAAI,GAAG;AACtB,UAAM,MAAM,KAAK,KAAK,CAAC;AACvB,WAAO,EAAE,UAAU,KAAK,YAAY,CAAC,GAAG,OAAO,KAAK,MAAM;AAAA,EAC5D;AACA,SAAO,EAAE,UAAU,KAAK,UAAU,OAAO,KAAK,MAAM;AACtD;AAGA,SAAS,QAAQ,UAA0C;AACzD,QAAM,QAAuB,CAAC;AAC9B,aAAW,OAAO,UAAU;AAC1B,UAAM,OAAO,IAAI;AACjB,QAAI,SAAS,UAAU,SAAS,YAAa;AAC7C,UAAM,WAAW,IAAI,WAAW,IAAI,KAAK;AACzC,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,KAAK,EAAE,MAAM,SAAS,WAAW,IAAI,UAAU,CAAC;AAAA,EACxD;AACA,SAAO;AACT;AAEO,IAAM,gBAAgC;AAAA,EAC3C,MAAM;AAAA,EAEN,MAAM,OAAO,UAAoC;AAC/C,QAAIC,OAAK,QAAQ,QAAQ,EAAE,YAAY,MAAM,iBAAkB,QAAO;AACtE,UAAM,MAAM,MAAMC,WAAS,UAAU,OAAO,EAAE,MAAM,MAAM,EAAE;AAC5D,QAAI,IAAI,UAAU,EAAE,CAAC,MAAM,IAAK,QAAO;AACvC,QAAI;AACF,YAAM,SAAkB,KAAK,MAAM,GAAG;AACtC,aAAO,aAAa,MAAM,KAAK,aAAa,MAAM;AAAA,IACpD,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA,EAEA,MAAM,MAAM,UAA8C;AACxD,UAAM,MAAM,MAAMA,WAAS,UAAU,OAAO;AAC5C,UAAM,SAAS,iBAAiB,KAAK,QAAQ;AAE7C,QAAI,CAAC,aAAa,MAAM,KAAK,CAAC,aAAa,MAAM,GAAG;AAClD,YAAM,IAAI;AAAA,QACR,oEAAoE,QAAQ;AAAA,MAC9E;AAAA,IACF;AAEA,UAAM,EAAE,UAAU,OAAO,SAAS,IAAI,wBAAwB,MAAsB;AACpF,UAAM,QAAQ,QAAQ,QAAQ;AAC9B,UAAM,YAAY,MAAM,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM;AAErD,UAAM,aAAa,MAChB,OAAO,CAAC,MAAM,EAAE,aAAa,IAAI,EACjC,IAAI,CAAC,MAAM,EAAE,SAAmB;AAEnC,WAAO;AAAA,MACL,OAAO,oBAAoB,UAAU,WAAW,SAAS,gBAAgB;AAAA,MACzE,SAAS;AAAA,MACT,WAAW,WAAW,CAAC;AAAA,MACvB,SAAS,WAAW,WAAW,SAAS,CAAC;AAAA,MACzC,qBAAqB;AAAA,MACrB;AAAA,IACF;AAAA,EACF;AACF;;;ACvHO,IAAM,WAA6B,CAAC,eAAe,cAAc,aAAa;AAMrF,eAAsB,cAAc,UAAkD;AACpF,aAAW,WAAW,UAAU;AAC9B,QAAI,MAAM,QAAQ,OAAO,QAAQ,EAAG,QAAO;AAAA,EAC7C;AACA,SAAO;AACT;AAgBA,eAAsB,iBAAiB,UAA8C;AACnF,QAAM,UAAU,MAAM,cAAc,QAAQ;AAC5C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI;AAAA,MACR,2CAA2C,QAAQ;AAAA,qBAC3B,SAAS,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,IAChE;AAAA,EACF;AACA,QAAM,UAAU,MAAM,QAAQ,MAAM,QAAQ;AAC5C,8BAA4B,SAAS,QAAQ;AAC7C,SAAO;AACT;AAQA,SAAS,4BAA4B,SAA4B,UAAwB;AACvF,QAAM,gBAAgB,QAAQ,MAAM;AAAA,IAClC,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,SAAS,gBAAgB,EAAE,QAAQ,KAAK,EAAE,SAAS;AAAA,EACpF;AACA,MAAI,CAAC,eAAe;AAClB,UAAM,IAAI;AAAA,MACR,GAAG,QAAQ,OAAO,iCAAiC,QAAQ;AAAA,uBACjC,QAAQ,OAAO;AAAA,IAG3C;AAAA,EACF;AACF;AAWO,SAAS,wBAAwB,SAAoC;AAC1E,QAAM,QAAkB,CAAC;AAEzB,aAAW,QAAQ,QAAQ,OAAO;AAChC,UAAM,QAAQ,KAAK,SAAS,SAAS,SAAS,QAAQ,uBAAuB;AAC7E,UAAM,UAAU,KAAK,YACjB,OAAO,KAAK,MAAM,KAAK,SAAS,OAChC,OAAO,KAAK;AAChB,UAAM,KAAK,OAAO;AAClB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,KAAK,OAAO;AACvB,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,SAAO,MAAM,KAAK,IAAI,EAAE,QAAQ;AAClC;;;ALtEA,SAAS,wBAAwB,SAA4B,YAA4B;AACvF,QAAM,OAAgC;AAAA,IACpC,OAAO,QAAQ;AAAA,IACf,QAAQ;AAAA,IACR,SAAS,QAAQ;AAAA,IACjB,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC;AACA,MAAI,QAAQ,UAAW,MAAK,mBAAmB,QAAQ;AACvD,MAAI,QAAQ,QAAS,MAAK,iBAAiB,QAAQ;AACnD,MAAI,QAAQ,oBAAqB,MAAK,cAAc,QAAQ;AAE5D,SAAO,iBAAiB,IAAI;AAC9B;AASA,eAAe,kBAAkB,SAA4B,YAAqC;AAChG,QAAM,cAAc,wBAAwB,SAAS,UAAU;AAC/D,QAAM,OAAO,wBAAwB,OAAO;AAC5C,QAAM,WAAW,GAAG,WAAW;AAAA;AAAA,EAAO,IAAI;AAAA;AAC1C,SAAO,WAAW,QAAQ,OAAO,UAAU,UAAU;AACvD;AAMA,eAAsB,kBAAkB,UAAgD;AACtF,EAAO,OAAO,KAAY,KAAK,sBAAsB,QAAQ,EAAE,CAAC;AAEhE,QAAM,UAAU,MAAM,iBAAiB,QAAQ;AAC/C,QAAM,YAAY,MAAM,kBAAkB,SAAS,QAAQ;AAE3D,EAAO;AAAA,IACL;AAAA,IACO;AAAA,MACL,SAAgB,KAAKC,OAAK,SAAS,SAAS,CAAC,CAAC,KAAK,QAAQ,OAAO,YAAc,OAAO,SAAS,CAAC;AAAA,IACnG;AAAA,EACF;AAEA,SAAO;AAAA,IACL,UAAUA,OAAK,SAAS,SAAS;AAAA,IACjC,SAAS,QAAQ;AAAA,IACjB,OAAO,QAAQ;AAAA,IACf,QAAQ;AAAA,EACV;AACF;AAGA,eAAe,mBAAmB,SAAoC;AACpE,QAAM,UAAU,MAAM,QAAQ,OAAO;AACrC,QAAM,QAAkB,CAAC;AAEzB,aAAW,SAAS,SAAS;AAC3B,UAAM,OAAOA,OAAK,KAAK,SAAS,KAAK;AACrC,UAAMC,QAAO,MAAM,KAAK,IAAI;AAC5B,QAAIA,MAAK,OAAO,EAAG,OAAM,KAAK,IAAI;AAAA,EACpC;AAEA,SAAO;AACT;AAYA,eAAe,gBAAgB,SAAgC;AAC7D,QAAM,QAAQ,MAAM,mBAAmB,OAAO;AAE9C,MAAI,MAAM,WAAW,GAAG;AACtB,UAAM,IAAI,MAAM,gCAAgC,OAAO,EAAE;AAAA,EAC3D;AAEA,EAAO,OAAO,KAAY,KAAK,YAAY,MAAM,MAAM,gBAAgB,OAAO,EAAE,CAAC;AAEjF,MAAI,WAAW;AACf,MAAI,UAAU;AAEd,aAAW,QAAQ,OAAO;AACxB,QAAI;AACF,YAAM,kBAAkB,IAAI;AAC5B;AAAA,IACF,SAAS,KAAK;AACZ,YAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,MAAO,OAAO,KAAY,KAAK,WAAWD,OAAK,SAAS,IAAI,CAAC,KAAK,OAAO,EAAE,CAAC;AAC5E;AAAA,IACF;AAAA,EACF;AAEA,MAAI,aAAa,GAAG;AAClB,UAAM,IAAI;AAAA,MACR,6BAA6B,OAAO,KAAK,OAAO;AAAA,IAElD;AAAA,EACF;AAEA,EAAO;AAAA,IACL;AAAA,IACO,IAAI,YAAY,QAAQ,wBAAwB,OAAO,GAAG;AAAA,EACnE;AACF;AAMA,eAAO,cAAqC,YAAmC;AAC7E,QAAMC,QAAO,MAAM,KAAK,UAAU,EAAE,MAAM,MAAM;AAC9C,UAAM,IAAI,MAAM,mBAAmB,UAAU,EAAE;AAAA,EACjD,CAAC;AAED,MAAIA,MAAK,YAAY,GAAG;AACtB,UAAM,gBAAgB,UAAU;AAAA,EAClC,OAAO;AACL,UAAM,kBAAkB,UAAU;AAAA,EACpC;AAEA,EAAO,OAAO,UAAY,IAAI,uBAAuB,CAAC;AACxD;;;AM/IA,SAAS,aAAa;;;ACGtB,OAAO,UAAU;;;ACJjB,SAAS,SAAAC,QAAO,YAAAC,kBAAgB;AAChC,OAAOC,YAAU;AAkBV,IAAM,+BAA+B;AAO5C,eAAsB,eAAe,MAAc,SAAqC;AACtF,QAAMC,OAAMC,OAAK,KAAK,MAAM,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;AAC7D,QAAM,QAAwB;AAAA,IAC5B,UAAU,QAAQ;AAAA,IAClB,QAAQ,QAAQ;AAAA,IAChB,KAAI,oBAAI,KAAK,GAAE,YAAY;AAAA,EAC7B;AACA,QAAM,YAAYA,OAAK,KAAK,MAAM,cAAc,GAAG,GAAG,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAAA,CAAI;AAC1F;AAOA,eAAsB,cAAc,MAA8C;AAChF,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,WAASD,OAAK,KAAK,MAAM,cAAc,GAAG,OAAO;AAAA,EAC/D,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACJ,MAAI;AACF,aAAS,KAAK,MAAM,GAAG;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI,CAAC,aAAa,MAAM,EAAG,QAAO;AAClC,SAAO,EAAE,UAAU,OAAO,UAAU,QAAQ,OAAO,QAAQ,IAAI,OAAO,GAAG;AAC3E;AAGA,SAAS,qBAAqB,OAAiC;AAC7D,SAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,SAAS;AAC1E;AAWA,SAAS,aAAa,OAAyC;AAC7D,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,YAAY;AAClB,SACE,qBAAqB,UAAU,QAAQ,KACvC,qBAAqB,UAAU,MAAM,KACrC,OAAO,UAAU,OAAO,YACxB,6BAA6B,KAAK,UAAU,EAAE;AAElD;;;AC3DA,eAAsB,oBACpB,UAC+B;AAC/B,QAAME,QAAO,MAAM,cAAc,SAAS,IAAI;AAC9C,SAAO;AAAA,IACL,gBAAgB,SAAS,OAAO;AAAA,IAChC,SAAS,SAAS,OAAO;AAAA,IACzB,aAAa,SAAS,OAAO;AAAA,IAC7B,UAAU,SAAS,OAAO;AAAA,IAC1B,SAAS,SAAS,OAAO;AAAA,IACzB,MAAAA;AAAA,EACF;AACF;;;AClCA,IAAM,eAAe;AAGd,SAAS,gBAAgB,aAA8C;AAC5E,SAAO,OAAO,YAAY,SAAS,YAAY,YAAY,KAAK,SAAS,IACrE,YAAY,OACZ;AACN;AASO,SAAS,eAAe,OAAgC;AAC7D,QAAM,UAAU,IAAI,IAAY,MAAM,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AACtD,QAAM,QAAQ,WAAW,KAAK;AAC9B,QAAM,kBAAkB,qBAAqB,KAAK;AAClD,QAAM,cAAc,iBAAiB,KAAK;AAC1C,QAAM,YAAY,MAAM,IAAI,CAAC,MAAM,UAAU,GAAG,SAAS,WAAW,CAAC;AACrE,QAAM,aAAa,gBAAgB,OAAO,SAAS,aAAa,eAAe;AAC/E,SAAO,EAAE,OAAO,CAAC,GAAG,WAAW,GAAG,UAAU,GAAG,MAAM;AACvD;AAEA,SAAS,qBAAqB,OAA0C;AACtE,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,QAAQ,OAAO;AACxB,eAAW,EAAE,MAAM,QAAQ,KAAK,KAAK,iBAAiB,CAAC,GAAG;AACxD,YAAM,KAAK,QAAQ,IAAI;AACvB,UAAI,CAAC,IAAI,IAAI,EAAE,EAAG,KAAI,IAAI,IAAI,OAAO;AAAA,IACvC;AAAA,EACF;AACA,SAAO;AACT;AAEA,IAAM,kBAAkB;AAGxB,SAAS,QAAQ,MAAsB;AACrC,SAAO,GAAG,eAAe,IAAI,IAAI;AACnC;AAEA,SAAS,WAAW,OAAkC;AACpD,QAAM,QAAqB,CAAC;AAC5B,aAAW,QAAQ,OAAO;AACxB,eAAW,UAAU,KAAK,eAAe;AACvC,YAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,OAAO,CAAC;AAAA,IACxC;AACA,eAAW,EAAE,KAAK,KAAK,KAAK,iBAAiB,CAAC,GAAG;AAC/C,YAAM,KAAK,EAAE,QAAQ,KAAK,IAAI,QAAQ,QAAQ,IAAI,EAAE,CAAC;AAAA,IACvD;AAAA,EACF;AACA,SAAO;AACT;AAEA,SAAS,gBACP,OACA,SACA,aACA,YACa;AACb,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,SAAsB,CAAC;AAC7B,aAAW,EAAE,OAAO,KAAK,OAAO;AAC9B,QAAI,QAAQ,IAAI,MAAM,KAAK,KAAK,IAAI,MAAM,EAAG;AAC7C,SAAK,IAAI,MAAM;AACf,UAAM,CAAC,WAAW,GAAG,IAAI,IAAI,OAAO,MAAM,GAAG;AAC7C,UAAM,OAAO,KAAK,KAAK,GAAG;AAC1B,WAAO,KAAK;AAAA,MACV,IAAI;AAAA,MACJ,OAAO,WAAW,IAAI,MAAM,KAAK;AAAA,MACjC;AAAA,MACA;AAAA,MACA,MAAM;AAAA,MACN,QAAQ,YAAY,IAAI,MAAM,KAAK;AAAA,MACnC,YAAY;AAAA,IACd,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,OAAyC;AACjE,QAAM,MAAM,oBAAI,IAAoB;AACpC,aAAW,QAAQ,OAAO;AACxB,QAAI,IAAI,KAAK,SAAS,IAAI,IAAI,KAAK,MAAM,KAAK,KAAK,CAAC;AAAA,EACtD;AACA,SAAO;AACT;AAEA,SAAS,UACP,MACA,SACA,aACW;AACX,QAAM,YAAY,KAAK,cAAc,OAAO,CAAC,MAAM,QAAQ,IAAI,CAAC,CAAC,EAAE;AACnE,QAAM,WAAW,YAAY,IAAI,KAAK,EAAE,KAAK;AAC7C,QAAM,OAAO,gBAAgB,KAAK,WAAW;AAC7C,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,OAAO,KAAK;AAAA,IACZ,MAAM,KAAK;AAAA,IACX,WAAW,KAAK;AAAA,IAChB;AAAA,IACA,QAAQ,YAAY;AAAA,EACtB;AACF;;;ACtGA,SAAS,YAAAC,kBAAgB;AACzB,OAAOC,YAAU;AAIjB,IAAM,oBAAoB;AAG1B,IAAM,gBAAgB,oBAAI,IAA2B;AAmBrD,eAAsB,kBAAkB,WAA2C;AACjF,QAAM,SAAS,cAAc,IAAI,SAAS;AAC1C,MAAI,WAAW,OAAW,QAAO;AACjC,MAAI;AACJ,MAAI;AACF,YAAQ,MAAMC,WAASC,OAAK,KAAK,WAAW,YAAY,GAAG,OAAO;AAAA,EACpE,QAAQ;AACN,YAAQ;AAAA,EACV;AACA,gBAAc,IAAI,WAAW,KAAK;AAClC,SAAO;AACT;AAoBO,SAAS,oBAAoB,UAAkB,OAA6B;AACjF,QAAM,WAA2B,MAAM,IAAI,CAAC,UAAU;AAAA,IACpD,IAAI,KAAK;AAAA,IACT,eAAe,KAAK;AAAA,IACpB,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,MAAM,gBAAgB,KAAK,WAAW;AAAA,EACxC,EAAE;AACF,QAAM,OAAO,KAAK,UAAU,EAAE,OAAO,SAAS,CAAC,EAAE,QAAQ,MAAM,SAAS;AACxE,QAAM,QAAQ,mDAAmD,IAAI;AACrE,SAAO,SAAS,QAAQ,mBAAmB,KAAK;AAClD;;;ACrEA,SAAS,YAAAC,YAAU,gBAAgB;AACnC,OAAOC,YAAU;AACjB,SAAS,qBAAqB;;;ACD9B,OAAOC,YAAU;AASV,IAAM,kBAAN,cAA8B,MAAM;AAAA,EACzC,YAAY,SAAiB;AAC3B,UAAM,OAAO;AACb,SAAK,OAAO;AAAA,EACd;AACF;AAQO,SAAS,eAAe,aAA2B;AACxD,MAAI,OAAO,gBAAgB,UAAU;AACnC,UAAM,IAAI,gBAAgB,uBAAuB;AAAA,EACnD;AACA,MAAI,YAAY,WAAW,GAAG;AAC5B,UAAM,IAAI,gBAAgB,wBAAwB;AAAA,EACpD;AACA,MAAI,gBAAgB,OAAO,gBAAgB,MAAM;AAC/C,UAAM,IAAI,gBAAgB,qBAAqB,WAAW,GAAG;AAAA,EAC/D;AACA,MAAI,YAAY,SAAS,GAAG,KAAK,YAAY,SAAS,IAAI,GAAG;AAC3D,UAAM,IAAI,gBAAgB,uCAAuC;AAAA,EACnE;AACA,MAAI,YAAY,SAAS,IAAI,GAAG;AAC9B,UAAM,IAAI,gBAAgB,iCAAiC;AAAA,EAC7D;AACA,MAAIC,OAAK,QAAQ,OAAO,YAAY,SAASA,OAAK,GAAG,GAAG;AACtD,UAAM,IAAI,gBAAgB,6CAA6CA,OAAK,GAAG,GAAG;AAAA,EACpF;AACF;;;AD3BO,IAAM,aAAaC,OAAK;AAAA,EAC7BA,OAAK,QAAQ,cAAc,YAAY,GAAG,CAAC;AAAA,EAC3C;AACF;AAGA,IAAM,sBAA8C;AAAA,EAClD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,QAAQ;AACV;AAOA,eAAsB,YAAY,KAAqB,UAAiC;AACtF,QAAM,WAAW,oBAAoB,QAAQ;AAC7C,MAAI,CAAC,UAAU;AACb,oBAAgB,KAAK,KAAK,kBAAkB,iBAAiB;AAC7D;AAAA,EACF;AACA,MAAI,SAAS,WAAW,GAAG;AACzB,oBAAgB,KAAK,KAAK,mBAAmB,kBAAkB;AAC/D;AAAA,EACF;AACA,QAAM,cAAc,oBAClBA,OAAK,QAAQ,SAAS,SAAS,SAAS,CAAC,CAAC,EAAE,YAAY,CAC1D;AACA,MAAI,CAAC,aAAa;AAChB,oBAAgB,KAAK,KAAK,mBAAmB,kBAAkB;AAC/D;AAAA,EACF;AACA,QAAM,WAAW,MAAM,iBAAiB,QAAQ;AAChD,MAAI,CAAC,UAAU;AACb,oBAAgB,KAAK,KAAK,mBAAmB,kBAAkB;AAC/D;AAAA,EACF;AACA,MAAI;AACF,UAAM,OAAO,MAAMC,WAAS,QAAQ;AACpC,QAAI,aAAa;AACjB,QAAI,UAAU,gBAAgB,WAAW;AACzC,QAAI,IAAI,IAAI;AAAA,EACd,QAAQ;AACN,oBAAgB,KAAK,KAAK,mBAAmB,kBAAkB;AAAA,EACjE;AACF;AASA,SAAS,oBAAoB,UAAmC;AAC9D,QAAM,UAAU,SAAS,QAAQ,eAAe,EAAE;AAClD,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAClC,QAAM,UAAoB,CAAC;AAC3B,aAAW,OAAO,QAAQ,MAAM,GAAG,GAAG;AACpC,QAAI;AACJ,QAAI;AACF,gBAAU,mBAAmB,GAAG;AAAA,IAClC,QAAQ;AACN,aAAO;AAAA,IACT;AACA,QAAI;AACF,qBAAe,OAAO;AAAA,IACxB,SAAS,KAAK;AACZ,UAAI,eAAe,gBAAiB,QAAO;AAC3C,YAAM;AAAA,IACR;AACA,YAAQ,KAAK,OAAO;AAAA,EACtB;AACA,SAAO;AACT;AAOA,eAAe,iBAAiB,UAA4C;AAC1E,QAAM,YAAYD,OAAK,KAAK,YAAY,GAAG,QAAQ;AACnD,MAAI;AACJ,MAAI;AACF,eAAW,MAAM,SAAS,SAAS;AAAA,EACrC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,WAAW,MAAM,SAAS,UAAU,EAAE,MAAM,MAAM,UAAU;AAClE,MAAI,aAAa,SAAU,QAAO;AAClC,QAAM,SAAS,SAAS,SAASA,OAAK,GAAG,IAAI,WAAW,WAAWA,OAAK;AACxE,SAAO,SAAS,WAAW,MAAM,IAAI,WAAW;AAClD;AASA,SAAS,gBACP,KACAE,SACA,MACA,SACM;AACN,MAAI,aAAaA;AACjB,MAAI,UAAU,gBAAgB,iCAAiC;AAC/D,MAAI,IAAI,KAAK,UAAU,EAAE,OAAO,EAAE,MAAM,QAAQ,EAAE,CAAC,CAAC;AACtD;;;AE3HA,OAAO,gBAAgB;AACvB,OAAO,kBAAkB;;;ACGzB,SAAS,WAAAC,UAAS,YAAAC,YAAU,YAAAC,iBAAgB;AAC5C,OAAOC,YAAU;AAMjB,IAAM,cAAc;AA4Cb,SAAS,qBAAqB,MAAwB;AAC3D,QAAM,QAAQ,oBAAI,IAAY;AAC9B,cAAY,YAAY;AACxB,MAAI;AACJ,UAAQ,QAAQ,YAAY,KAAK,IAAI,OAAO,MAAM;AAChD,UAAM,IAAI,QAAQ,MAAM,CAAC,EAAE,KAAK,CAAC,CAAC;AAAA,EACpC;AACA,SAAO,CAAC,GAAG,KAAK;AAClB;AAOO,SAAS,uBAAuB,MAAmD;AACxF,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,UAA+C,CAAC;AACtD,cAAY,YAAY;AACxB,MAAI;AACJ,UAAQ,QAAQ,YAAY,KAAK,IAAI,OAAO,MAAM;AAChD,UAAM,SAAS,MAAM,CAAC,EAAE,KAAK;AAC7B,UAAM,QAAQ,MAAM,CAAC,GAAG,KAAK;AAC7B,UAAM,OAAO,QAAQ,MAAM;AAC3B,UAAM,UAAU,SAAS;AACzB,QAAI,CAAC,KAAK,IAAI,IAAI,GAAG;AACnB,WAAK,IAAI,IAAI;AACb,cAAQ,KAAK,EAAE,MAAM,QAAQ,CAAC;AAAA,IAChC;AAAA,EACF;AACA,SAAO;AACT;AAOA,eAAe,aAAa,GAAmC;AAC7D,MAAI;AACF,WAAO,MAAMC,UAAS,CAAC;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,YAAY,OAAe,KAAsB;AACxD,MAAI,UAAU,IAAK,QAAO;AAC1B,QAAM,SAAS,IAAI,SAASC,OAAK,GAAG,IAAI,MAAM,MAAMA,OAAK;AACzD,SAAO,MAAM,WAAW,MAAM;AAChC;AAQA,eAAe,cACb,UACA,MACA,eAC6B;AAC7B,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,WAAS,UAAU,OAAO;AAAA,EACxC,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,EAAE,MAAM,MAAM,qBAAqB,qBAAqB,IAAI,uBAAuB,GAAG;AAC5F,QAAM,QAAQ,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,SAAS,IAAI,KAAK,QAAQ;AACrF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,aAAa;AAAA,IACb;AAAA,IACA,aAAa;AAAA,MACX;AAAA,MACA;AAAA,MACA,UAAU,UAAU;AAAA,MACpB,UAAU,KAAK,aAAa;AAAA,IAC9B;AAAA,EACF;AACF;AAaA,eAAe,eACb,eACA,eACA,QACwB;AACxB,QAAM,cAAcD,OAAK,KAAK,eAAe,MAAM;AACnD,QAAM,UAAU,MAAM,aAAa,WAAW;AAC9C,MAAI,YAAY,YAAa,QAAO,CAAC;AACrC,MAAI;AACJ,MAAI;AACF,YAAQ,MAAME,SAAQ,OAAO;AAAA,EAC/B,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,QAAuB,CAAC;AAC9B,aAAW,QAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,GAAG;AACzD,UAAM,YAAYF,OAAK,KAAK,SAAS,IAAI;AACzC,UAAM,WAAW,MAAM,aAAa,SAAS;AAC7C,QAAI,CAAC,YAAY,CAAC,YAAY,UAAU,OAAO,EAAG;AAClD,UAAM,OAAO,KAAK,QAAQ,SAAS,EAAE;AACrC,UAAM,OAAO,MAAM,cAAc,UAAU,MAAM,aAAa;AAC9D,QAAI,KAAM,OAAM,KAAK,IAAI;AAAA,EAC3B;AACA,SAAO;AACT;AASA,eAAsB,oBAAoB,MAAsC;AAC9E,QAAM,gBAAgB,MAAM,aAAa,IAAI;AAC7C,MAAI,CAAC,cAAe,QAAO,CAAC;AAC5B,QAAM,CAAC,UAAU,OAAO,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC5C,eAAe,eAAe,YAAY,YAAY;AAAA,IACtD,eAAe,eAAe,WAAW,WAAW;AAAA,EACtD,CAAC;AACD,SAAO,CAAC,GAAG,UAAU,GAAG,OAAO;AACjC;;;AC9LA,eAAsB,mBAAmB,MAAqC;AAC5E,QAAM,MAAM,MAAM,oBAAoB,IAAI;AAC1C,SAAO,cAAc,GAAG;AAC1B;AASO,SAAS,gBACd,MACA,OACe;AACf,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,QAAM,UAAU,MAAM,KAAK,CAAC,MAAM,EAAE,kBAAkB,cAAc,EAAE,SAAS,IAAI;AACnF,MAAI,QAAS,QAAO,QAAQ;AAC5B,QAAM,QAAQ,MAAM,KAAK,CAAC,MAAM,EAAE,kBAAkB,aAAa,EAAE,SAAS,IAAI;AAChF,MAAI,MAAO,QAAO,MAAM;AACxB,SAAO;AACT;AAOO,SAAS,oBACd,SACA,OACU;AACV,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,UAAoB,CAAC;AAC3B,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,gBAAgB,QAAQ,KAAK;AAC9C,QAAI,YAAY,CAAC,KAAK,IAAI,QAAQ,GAAG;AACnC,WAAK,IAAI,QAAQ;AACjB,cAAQ,KAAK,QAAQ;AAAA,IACvB;AAAA,EACF;AACA,SAAO;AACT;AAQA,SAAS,cAAc,KAAkC;AACvD,QAAM,SAAS,IAAI,IAAI,cAAc;AACrC,aAAW,QAAQ,QAAQ;AACzB,UAAM,cAAc,qBAAqB,KAAK,IAAI;AAClD,UAAM,cAAc,uBAAuB,KAAK,IAAI;AACpD,SAAK,gBAAgB,oBAAoB,aAAa,MAAM;AAC5D,SAAK,gBAAgB,qBAAqB,aAAa,MAAM;AAAA,EAC/D;AACA,SAAO;AACT;AAMA,SAAS,qBACP,SACA,OACqC;AACrC,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,WAAgD,CAAC;AACvD,aAAW,KAAK,SAAS;AACvB,QAAI,gBAAgB,EAAE,MAAM,KAAK,MAAM,QAAQ,CAAC,KAAK,IAAI,EAAE,IAAI,GAAG;AAChE,WAAK,IAAI,EAAE,IAAI;AACf,eAAS,KAAK,CAAC;AAAA,IACjB;AAAA,EACF;AACA,SAAO;AACT;AAOA,SAAS,eAAe,MAA+B;AACrD,QAAM,KAAa,GAAG,KAAK,aAAa,IAAI,KAAK,IAAI;AACrD,SAAO;AAAA,IACL;AAAA,IACA,MAAM,KAAK;AAAA,IACX,eAAe,KAAK;AAAA,IACpB,OAAO,KAAK,SAAS,KAAK;AAAA,IAC1B,UAAU,KAAK;AAAA,IACf,aAAa,KAAK;AAAA,IAClB,MAAM,KAAK;AAAA,IACX,eAAe,CAAC;AAAA,IAChB,WAAW,sBAAsB,KAAK,IAAI;AAAA,IAC1C,UAAU,wBAAwB,IAAI;AAAA,EACxC;AACF;AAQA,SAAS,wBAAwB,MAAoC;AACnE,QAAM,WAA4B,CAAC;AACnC,MAAI,CAAC,KAAK,YAAY,qBAAqB;AACzC,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,SAAS,KAAK,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH,WAAW,KAAK,YAAY,sBAAsB;AAChD,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,SAAS,KAAK,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AACA,MAAI,CAAC,KAAK,YAAY,UAAU;AAC9B,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,SAAS,KAAK,IAAI;AAAA,IAC7B,CAAC;AAAA,EACH;AACA,SAAO;AACT;;;AC1IO,SAAS,WAAW,OAAuB;AAChD,SAAO,MACJ,QAAQ,MAAM,OAAO,EACrB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,MAAM,EACpB,QAAQ,MAAM,QAAQ,EACtB,QAAQ,MAAM,OAAO;AAC1B;AAQA,SAAS,iBAAiB,OAA4B;AACpD,QAAM,SAAS;AACf,SAAO,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;AACnE;AAiBO,SAAS,sBAAsB,OAAoB,QAA0B;AAClF,MAAI,iBAAiB,KAAK,IAAI,EAAG,QAAO;AACxC,MAAI,OAAQ,QAAO;AACnB,SAAO;AACT;;;AC9BA,IAAM,OAAO;AACb,IAAM,oBAAoB;AAkBnB,SAAS,iBAAiB,IAAgB,SAAgC;AAC/E,KAAG,OAAO,MAAM,MAAM,QAAQ,YAAY,YAAY,OAAO,CAAC;AAC9D,KAAG,SAAS,MAAM,WAAW,CAAC,QAAiB,QAC7C,oBAAoB,OAAO,GAAG,CAAC;AACnC;AAGA,SAAS,YAAY,SAA0B;AAC7C,SAAO,SAAS,cAAc,OAAoB,QAA0B;AAC1E,QAAI,MAAM,IAAI,WAAW,MAAM,GAAG,MAAM,kBAAmB,QAAO;AAClE,QAAI,MAAM,IAAI,WAAW,MAAM,MAAM,CAAC,MAAM,kBAAmB,QAAO;AACtE,QAAI,sBAAsB,OAAO,MAAM,EAAG,QAAO;AACjD,UAAM,UAAU,MAAM,IAAI,QAAQ,MAAM,MAAM,MAAM,CAAC;AACrD,QAAI,UAAU,EAAG,QAAO;AACxB,UAAM,QAAQ,MAAM,IAAI,MAAM,MAAM,MAAM,GAAG,OAAO;AAEpD,QAAI,MAAM,SAAS,IAAI,KAAK,MAAM,SAAS,IAAI,EAAG,QAAO;AACzD,UAAM,EAAE,WAAW,QAAQ,IAAI,oBAAoB,KAAK;AACxD,UAAM,OAAO,QAAQ,UAAU,KAAK,CAAC;AACrC,UAAM,WAAW,gBAAgB,MAAM,QAAQ,KAAK;AACpD,sBAAkB,OAAO,UAAU,MAAM,OAAO;AAChD,UAAM,MAAM,UAAU;AACtB,WAAO;AAAA,EACT;AACF;AAGA,SAAS,oBAAoB,OAAuD;AAClF,QAAM,OAAO,MAAM,QAAQ,GAAG;AAC9B,MAAI,OAAO,EAAG,QAAO,EAAE,WAAW,OAAO,SAAS,MAAM,KAAK,EAAE;AAC/D,SAAO;AAAA,IACL,WAAW,MAAM,MAAM,GAAG,IAAI;AAAA,IAC9B,SAAS,MAAM,MAAM,OAAO,CAAC,EAAE,KAAK,KAAK,MAAM,MAAM,GAAG,IAAI,EAAE,KAAK;AAAA,EACrE;AACF;AAGA,SAAS,kBACP,OACA,UACA,MACA,SACM;AACN,QAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,CAAC;AAC1C,QAAM,OAAO,EAAE,UAAU,MAAM,QAAQ;AACzC;AAGA,SAAS,oBAAoB,OAAsB;AACjD,QAAM,OAAO,MAAM;AACnB,QAAM,UAAU,WAAW,KAAK,WAAW,KAAK,IAAI;AACpD,MAAI,CAAC,KAAK,UAAU;AAClB,WAAO,+BAA+B,OAAO;AAAA,EAC/C;AACA,QAAM,OAAO,KAAK,iBAAiB,KAAK,QAAQ,CAAC;AACjD,SAAO,qCAAqC,WAAW,KAAK,QAAQ,CAAC,WAAW,WAAW,IAAI,CAAC,KAAK,OAAO;AAC9G;AAGA,SAAS,iBAAiB,IAAoB;AAC5C,QAAM,CAAC,WAAW,IAAI,IAAI,GAAG,MAAM,GAAG;AACtC,SAAO,GAAG,mBAAmB,SAAS,CAAC,IAAI,mBAAmB,IAAI,CAAC;AACrE;;;ACjFA,OAAOG,YAAU;AACjB,SAAS,qBAAqB;AAK9B,IAAM,aAAa;AACnB,IAAMC,qBAAoB;AA4BnB,SAAS,iBAAiB,IAAgB,SAAgC;AAC/E,KAAG,OAAO,MAAM,MAAM,QAAQ,YAAYC,aAAY,OAAO,CAAC;AAC9D,KAAG,SAAS,MAAM,WAAW,CAAC,QAAiB,QAC7C,oBAAoB,OAAO,GAAG,CAAC;AACnC;AAGA,SAASA,aAAY,SAA0B;AAC7C,SAAO,SAAS,cAAc,OAAoB,QAA0B;AAC1E,QAAI,MAAM,IAAI,WAAW,MAAM,GAAG,MAAM,WAAY,QAAO;AAC3D,QAAI,MAAM,IAAI,WAAW,MAAM,MAAM,CAAC,MAAMD,mBAAmB,QAAO;AACtE,QAAI,sBAAsB,OAAO,MAAM,EAAG,QAAO;AACjD,UAAM,UAAU,MAAM,IAAI,QAAQ,KAAK,MAAM,MAAM,CAAC;AACpD,QAAI,UAAU,EAAG,QAAO;AACxB,UAAM,QAAQ,MAAM,IAAI,MAAM,MAAM,MAAM,GAAG,OAAO;AACpD,QAAI,MAAM,SAAS,IAAI,EAAG,QAAO;AACjC,UAAM,YAAY,sBAAsB,KAAK,KAAK,GAAG;AACrD,mBAAe,OAAO,WAAW,OAAO;AACxC,UAAM,MAAM,UAAU;AACtB,WAAO;AAAA,EACT;AACF;AAGA,SAAS,eACP,OACA,WACA,SACM;AACN,aAAW,YAAY,WAAW;AAChC,eAAW,QAAQ,SAAS,OAAO;AACjC,YAAM,QAAQ,MAAM,KAAK,YAAY,IAAI,CAAC;AAC1C,YAAM,OAAO,cAAc,MAAM,OAAO;AAAA,IAC1C;AAAA,EACF;AACF;AAGA,SAAS,cAAc,MAAkB,SAAoC;AAC3E,QAAM,OAAiB;AAAA,IACrB,MAAM,KAAK;AAAA,IACX,WAAW,KAAK,OAAO;AAAA,IACvB,SAAS,KAAK,OAAO;AAAA,IACrB,UAAU,QAAQ,YAAY,IAAI,KAAK,IAAI;AAAA,EAC7C;AACA,MAAI,QAAQ,cAAc,KAAK,YAAY,eAAe,KAAK,IAAI,GAAG;AACpE,UAAM,eAAeE,OAAK,KAAK,QAAQ,MAAM,WAAW,KAAK,IAAI;AACjE,SAAK,eAAe;AACpB,SAAK,aAAa,gBAAgB,cAAc,KAAK,SAAS;AAAA,EAChE;AACA,SAAO;AACT;AAmBA,SAAS,gBAAgB,cAAsB,WAAuC;AACpF,QAAM,cAAc,cAAc,YAAY,EAAE;AAChD,MAAI,cAAc,OAAW,QAAO,gBAAgB,WAAW;AAC/D,SAAO,gBAAgB,WAAW,IAAI,SAAS;AACjD;AAQA,SAAS,eAAe,MAAuB;AAC7C,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,MAAI,KAAK,SAAS,GAAG,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,SAAS,IAAI,EAAG,QAAO;AAC7E,MAAI,SAAS,OAAO,SAAS,KAAM,QAAO;AAC1C,SAAO;AACT;AAGA,SAAS,oBAAoB,OAAsB;AACjD,QAAM,OAAO,MAAM;AACnB,QAAM,QAAQ,gBAAgB,IAAI;AAClC,QAAM,QAAQ,eAAe,IAAI;AACjC,SAAO,SAAS,KAAK,IAAI,WAAW,KAAK,CAAC;AAC5C;AAGA,SAAS,eAAe,MAAwB;AAC9C,QAAM,QAAQ;AAAA,IACZ;AAAA,IACA,cAAc,WAAW,KAAK,IAAI,CAAC;AAAA,IACnC,kBAAkB,KAAK,WAAW,SAAS,OAAO;AAAA,EACpD;AACA,MAAI,KAAK,cAAc,QAAW;AAChC,UAAM,KAAK,oBAAoB,KAAK,SAAS,GAAG;AAAA,EAClD;AACA,MAAI,KAAK,YAAY,QAAW;AAC9B,UAAM,KAAK,kBAAkB,KAAK,OAAO,GAAG;AAAA,EAC9C;AACA,MAAI,KAAK,iBAAiB,QAAW;AACnC,UAAM,KAAK,uBAAuB,WAAW,KAAK,YAAY,CAAC,GAAG;AAAA,EACpE;AACA,MAAI,KAAK,eAAe,QAAW;AACjC,UAAM,KAAK,qBAAqB,WAAW,KAAK,UAAU,CAAC,GAAG;AAAA,EAChE;AACA,SAAO,MAAM,KAAK,GAAG;AACvB;AAGA,SAAS,gBAAgB,MAAwB;AAC/C,MAAI,KAAK,cAAc,OAAW,QAAO,KAAK;AAC9C,MAAI,KAAK,YAAY,UAAa,KAAK,YAAY,KAAK,WAAW;AACjE,WAAO,GAAG,KAAK,IAAI,IAAI,KAAK,SAAS;AAAA,EACvC;AACA,SAAO,GAAG,KAAK,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,OAAO;AACvD;;;AL/IO,SAAS,eACd,MACA,UACA,SACkB;AAClB,QAAM,KAAK,gBAAgB,UAAU,OAAO;AAC5C,QAAM,WAAW,GAAG,OAAO,IAAI;AAC/B,QAAM,OAAO,aAAa,UAAU,qBAAqB,OAAO,CAAC;AACjE,SAAO,EAAE,KAAK;AAChB;AAGA,SAAS,gBAAgB,UAA0B,SAAoC;AACrF,QAAM,KAAK,IAAI,WAAW;AAAA,IACxB,MAAM;AAAA,IACN,SAAS;AAAA,IACT,QAAQ;AAAA,EACV,CAAC;AACD,mBAAiB,IAAI,EAAE,OAAO,SAAS,MAAM,CAAC;AAC9C,mBAAiB,IAAI;AAAA,IACnB,MAAM,SAAS;AAAA,IACf,aAAa,IAAI,IAAI,SAAS,eAAe;AAAA,IAC7C,YAAY,QAAQ;AAAA,EACtB,CAAC;AACD,SAAO;AACT;AAYO,SAAS,qBAAqB,SAAkC;AACrE,QAAM,iBAAiB,CAAC,QAAQ,SAAS,QAAQ;AACjD,QAAM,oCAAoC,CAAC,QAAQ,OAAO,MAAM;AAChE,SAAO;AAAA,IACL,aAAa;AAAA,MACX;AAAA,MAAM;AAAA,MAAM;AAAA,MAAM;AAAA,MAAM;AAAA,MAAM;AAAA,MAC9B;AAAA,MAAK;AAAA,MAAM;AAAA,MACX;AAAA,MAAM;AAAA,MAAM;AAAA,MACZ;AAAA,MACA;AAAA,MAAU;AAAA,MAAM;AAAA,MAAK;AAAA,MAAK;AAAA,MAAK;AAAA,MAC/B;AAAA,MAAQ;AAAA,MACR;AAAA,MAAS;AAAA,MAAS;AAAA,MAAS;AAAA,MAAS;AAAA,MAAM;AAAA,MAAM;AAAA,MAChD;AAAA,MAAK;AAAA,MAAO;AAAA,MAAQ;AAAA,IACtB;AAAA,IACA,oBAAoB;AAAA,IACpB,mBAAmB;AAAA,MACjB,GAAG,CAAC,QAAQ,SAAS,SAAS,MAAM,UAAU,QAAQ;AAAA,MACtD,KAAK,CAAC,OAAO,OAAO,SAAS,SAAS,IAAI;AAAA,MAC1C,MAAM,CAAC,SAAS,MAAM,UAAU,QAAQ;AAAA,MACxC,KAAK,CAAC,SAAS,MAAM,UAAU,QAAQ;AAAA,MACvC,IAAI,CAAC,SAAS,WAAW,WAAW,SAAS,IAAI;AAAA,MACjD,IAAI,CAAC,WAAW,WAAW,SAAS,IAAI;AAAA,MACxC,OAAO,CAAC,SAAS,IAAI;AAAA,MACrB,MAAM,CAAC,OAAO;AAAA,MACd,KAAK,CAAC,SAAS,IAAI;AAAA,IACrB;AAAA,IACA;AAAA,IACA,qBAAqB;AAAA,MACnB,GAAG,mBAAmB;AAAA,MACtB,KAAK,CAAC,QAAQ,SAAS,MAAM;AAAA,IAC/B;AAAA,IACA;AAAA,IACA,uBAAuB;AAAA;AAAA;AAAA;AAAA,IAIvB,eAAe,CAAC;AAAA,IAChB,wBAAwB,CAAC;AAAA,IACzB,eAAe;AAAA,MACb,GAAG,iBAAiB;AAAA,MACpB,KAAK;AAAA,MACL,MAAM,qBAAqB,OAAO;AAAA,IACpC;AAAA;AAAA;AAAA,EAGF;AACF;AAWA,SAAS,qBAAqB,SAAwB;AACpD,SAAO,SAAS,cAAc,SAAiB,SAG7C;AACA,QAAI,QAAQ,WAAY,QAAO,EAAE,SAAS,QAAQ;AAClD,QAAI,EAAE,wBAAwB,YAAY,EAAE,sBAAsB,UAAU;AAC1E,aAAO,EAAE,SAAS,QAAQ;AAAA,IAC5B;AACA,UAAM,WAAmC,CAAC;AAC1C,eAAW,CAAC,KAAK,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AAClD,UAAI,QAAQ,wBAAwB,QAAQ,mBAAoB;AAChE,eAAS,GAAG,IAAI;AAAA,IAClB;AACA,WAAO,EAAE,SAAS,SAAS,SAAS;AAAA,EACtC;AACF;AAUA,SAAS,qBAA+B;AACtC,SAAO,CAAC,QAAQ,SAAS,QAAQ;AACnC;AAQA,SAAS,mBAAmB;AAC1B,SAAO,SAAS,gBAAgB,SAAiB,SAG/C;AACA,UAAM,OAAO,QAAQ;AACrB,QAAI,OAAO,SAAS,YAAY,KAAK,WAAW,EAAG,QAAO,EAAE,SAAS,QAAQ;AAC7E,QAAI,oBAAoB,IAAI,EAAG,QAAO,EAAE,SAAS,QAAQ;AACzD,UAAM,WAAW,EAAE,GAAG,QAAQ;AAC9B,WAAO,SAAS;AAChB,WAAO,EAAE,SAAS,SAAS,SAAS;AAAA,EACtC;AACF;AAGA,SAAS,aAAa,SAAiB,SAGrC;AACA,QAAM,MAAM,QAAQ;AACpB,MAAI,OAAO,QAAQ,YAAY,IAAI,WAAW,EAAG,QAAO,EAAE,SAAS,QAAQ;AAC3E,MAAI,gBAAgB,GAAG,EAAG,QAAO,EAAE,SAAS,QAAQ;AACpD,QAAM,WAAW,EAAE,GAAG,QAAQ;AAC9B,SAAO,SAAS;AAChB,SAAO,EAAE,SAAS,SAAS,SAAS;AACtC;AAWA,SAAS,oBAAoB,MAAuB;AAClD,MAAI,KAAK,WAAW,GAAG,EAAG,QAAO;AACjC,MAAI,KAAK,WAAW,SAAS,KAAK,KAAK,WAAW,UAAU,EAAG,QAAO;AACtE,MAAI,KAAK,WAAW,SAAS,EAAG,QAAO;AACvC,SAAO;AACT;AAGA,SAAS,gBAAgB,KAAsB;AAC7C,MAAI,IAAI,WAAW,SAAS,KAAK,IAAI,WAAW,UAAU,EAAG,QAAO;AACpE,MAAI,IAAI,WAAW,aAAa,EAAG,QAAO;AAC1C,SAAO;AACT;;;AM5MA,IAAM,mBAAmB;AACzB,IAAM,cAAc;AACpB,IAAM,iBAAiB;AACvB,IAAM,mBAAmB;AAmBlB,SAAS,YACd,UACA,UAC6B;AAC7B,QAAM,SAAS,cAAc,QAAQ;AACrC,MAAI,OAAO,WAAW,EAAG,QAAO,EAAE,SAAS,CAAC,EAAE;AAC9C,QAAM,UAAU,eAAe,SAAS,OAAO,MAAM;AACrD,UAAQ,KAAK,cAAc;AAC3B,SAAO,EAAE,SAAS,QAAQ,MAAM,GAAG,WAAW,EAAE;AAClD;AAOA,SAAS,cAAc,UAA4B;AACjD,MAAI,OAAO,aAAa,SAAU,QAAO,CAAC;AAC1C,QAAM,UAAU,SAAS,KAAK;AAC9B,MAAI,QAAQ,WAAW,EAAG,QAAO,CAAC;AAClC,QAAM,SAAS,QAAQ,MAAM,GAAG,gBAAgB,EAAE,YAAY;AAC9D,SAAO,OAAO,MAAM,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AACvD;AAGA,SAAS,eAAe,OAAkC,QAAkC;AAC1F,QAAM,UAA0B,CAAC;AACjC,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,UAAU,MAAM,MAAM;AACrC,QAAI,OAAQ,SAAQ,KAAK,MAAM;AAAA,EACjC;AACA,SAAO;AACT;AAUA,SAAS,UAAU,MAAkB,QAAuC;AAC1E,QAAM,aAAa,KAAK,MAAM,YAAY;AAC1C,QAAM,YAAY,KAAK,KAAK,YAAY;AACxC,aAAW,SAAS,QAAQ;AAC1B,QAAI,CAAC,WAAW,SAAS,KAAK,KAAK,CAAC,UAAU,SAAS,KAAK,EAAG,QAAO;AAAA,EACxE;AACA,QAAM,aAAa,OAAO,MAAM,CAAC,MAAM,WAAW,SAAS,CAAC,CAAC;AAC7D,MAAI,WAAY,QAAO,YAAY,MAAM,KAAK,OAAO,OAAO;AAC5D,QAAM,UAAU,iBAAiB,KAAK,MAAM,WAAW,MAAM;AAC7D,SAAO,YAAY,MAAM,SAAS,MAAM;AAC1C;AAGA,SAAS,YAAY,MAAkB,SAAiB,WAAsC;AAC5F,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,eAAe,KAAK;AAAA,IACpB,OAAO,KAAK;AAAA,IACZ;AAAA,IACA;AAAA,EACF;AACF;AAQA,SAAS,iBAAiB,MAAc,WAAmB,QAA0B;AACnF,QAAM,WAAW,sBAAsB,WAAW,MAAM;AACxD,QAAM,QAAQ,KAAK,IAAI,GAAG,WAAW,cAAc;AACnD,QAAM,MAAM,KAAK,IAAI,KAAK,QAAQ,WAAW,cAAc;AAC3D,QAAM,UAAU,yBAAyB,KAAK,MAAM,OAAO,GAAG,CAAC,EAC5D,QAAQ,QAAQ,GAAG,EACnB,KAAK;AACR,QAAM,SAAS,QAAQ,IAAI,mBAAmB;AAC9C,QAAM,SAAS,MAAM,KAAK,SAAS,mBAAmB;AACtD,SAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM;AACrC;AAUA,SAAS,yBAAyB,MAAsB;AACtD,SAAO,KACJ,QAAQ,2BAA2B,IAAI,EACvC,QAAQ,0BAA0B,IAAI,EACtC,QAAQ,oCAAoC,IAAI,EAChD,QAAQ,uBAAuB,IAAI,EACnC,QAAQ,oBAAoB,IAAI,EAChC,QAAQ,gBAAgB,IAAI,EAC5B,QAAQ,+BAA+B,IAAI,EAC3C,QAAQ,6BAA6B,IAAI,EACzC,QAAQ,gBAAgB,IAAI,EAC5B,QAAQ,kBAAkB,IAAI;AACnC;AAGA,SAAS,sBAAsB,WAAmB,QAA0B;AAC1E,MAAI,WAAW,UAAU;AACzB,aAAW,SAAS,QAAQ;AAC1B,UAAM,MAAM,UAAU,QAAQ,KAAK;AACnC,QAAI,OAAO,KAAK,MAAM,SAAU,YAAW;AAAA,EAC7C;AACA,SAAO;AACT;AAOA,SAAS,eAAe,GAAiB,GAAyB;AAChE,MAAI,EAAE,cAAc,EAAE,WAAW;AAC/B,WAAO,EAAE,cAAc,UAAU,KAAK;AAAA,EACxC;AACA,SAAO,EAAE,MAAM,cAAc,EAAE,KAAK;AACtC;;;Ab3IA,IAAM,iBAAiB,oBAAI,IAAI,CAAC,aAAa,KAAK,CAAC;AAGnD,IAAM,0BACJ;AA8BF,eAAsB,kBACpB,UACA,QAC6B;AAC7B,QAAM,cAAkC,EAAE,GAAG,OAAO;AACpD,QAAM,SAAS,KAAK,aAAa,CAAC,KAAK,QAAQ;AAC7C,kBAAc,KAAK,KAAK,UAAU,WAAW,EAAE,MAAM,CAAC,QAAQ;AAK5D,WAAK;AACL,UAAI,CAAC,IAAI,aAAa;AACpB,uBAAe,KAAK,KAAK,kBAAkB,0BAA0B;AAAA,MACvE;AAAA,IACF,CAAC;AAAA,EACH,CAAC;AACD,QAAM,IAAI,QAAc,CAAC,SAAS,WAAW;AAC3C,UAAM,UAAU,CAAC,QAAqB;AACpC,aAAO,IAAI,aAAa,WAAW;AACnC,aAAO,GAAG;AAAA,IACZ;AACA,UAAM,cAAc,MAAY;AAC9B,aAAO,IAAI,SAAS,OAAO;AAC3B,cAAQ;AAAA,IACV;AACA,WAAO,KAAK,SAAS,OAAO;AAC5B,WAAO,KAAK,aAAa,WAAW;AACpC,WAAO,OAAO,OAAO,MAAM,OAAO,IAAI;AAAA,EACxC,CAAC;AACD,QAAM,UAAU,OAAO,QAAQ;AAC/B,MAAI,CAAC,QAAS,OAAM,IAAI,MAAM,kCAAkC;AAChE,cAAY,OAAO,QAAQ;AAC3B,SAAO;AAAA,IACL,MAAM,OAAO;AAAA,IACb,MAAM,QAAQ;AAAA,IACd,OAAO,MAAM,IAAI,QAAc,CAAC,YAAY,OAAO,MAAM,MAAM,QAAQ,CAAC,CAAC;AAAA,EAC3E;AACF;AAaA,eAAe,cACb,KACA,KACA,UACA,QACe;AACf,uBAAqB,GAAG;AACxB,MAAI,CAAC,sBAAsB,KAAK,MAAM,GAAG;AACvC,mBAAe,KAAK,KAAK,aAAa,2BAA2B;AACjE;AAAA,EACF;AACA,QAAM,MAAM,IAAI,IAAI,IAAI,OAAO,KAAK,gBAAgB,MAAM,CAAC;AAC3D,MAAI,CAAC,kBAAkB,IAAI,QAAQ,IAAI,QAAQ,GAAG;AAChD,mBAAe,KAAK,KAAK,aAAa,GAAG,IAAI,UAAU,GAAG,IAAI,IAAI,QAAQ,EAAE;AAC5E;AAAA,EACF;AACA,QAAM,gBAAgB,KAAK,KAAK,KAAK,UAAU,eAAe,IAAI,OAAO,IAAI,CAAC;AAChF;AAQA,eAAe,gBACb,KACA,KACA,WACA,UACA,YACe;AACf,MAAI,UAAU,aAAa,IAAK,QAAO,YAAY,KAAK,QAAQ;AAChE,MAAI,UAAU,SAAS,WAAW,UAAU,EAAG,QAAO,YAAY,KAAK,UAAU,QAAQ;AACzF,MAAI,UAAU,aAAa,aAAc,QAAO,eAAe,KAAK,QAAQ;AAC5E,MAAI,UAAU,aAAa,aAAc,QAAO,eAAe,KAAK,UAAU,UAAU;AACxF,MAAI,UAAU,aAAa,cAAe,QAAO,gBAAgB,KAAK,QAAQ;AAC9E,MAAI,UAAU,aAAa,cAAe,QAAO,gBAAgB,KAAK,WAAW,QAAQ;AACzF,MAAI,UAAU,aAAa,aAAc,QAAO,eAAe,KAAK,QAAQ;AAC5E,MAAI,UAAU,SAAS,WAAW,YAAY,GAAG;AAC/C,WAAO,cAAc,KAAK,UAAU,UAAU,UAAU,UAAU;AAAA,EACpE;AAIA,QAAM,IAAI,MAAM,4CAA4C,UAAU,QAAQ,EAAE;AAClF;AAMA,IAAM,yBAA8C,oBAAI,IAAI;AAAA,EAC1D;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,IAAM,2BAA8C,CAAC,YAAY,YAAY;AAG7E,SAAS,kBAAkB,QAA4B,UAA2B;AAChF,MAAI,WAAW,MAAO,QAAO;AAC7B,MAAI,uBAAuB,IAAI,QAAQ,EAAG,QAAO;AACjD,SAAO,yBAAyB,KAAK,CAAC,WAAW,SAAS,WAAW,MAAM,CAAC;AAC9E;AAOA,SAAS,qBAAqB,KAA2B;AACvD,MAAI,UAAU,2BAA2B,uBAAuB;AAChE,MAAI,UAAU,gCAAgC,aAAa;AAC3D,MAAI,UAAU,0BAA0B,SAAS;AACjD,MAAI,UAAU,mBAAmB,aAAa;AAChD;AAOA,SAAS,sBAAsB,KAAsB,QAAqC;AACxF,QAAM,OAAO,IAAI,QAAQ;AACzB,MAAI,CAAC,QAAQ,CAAC,iBAAiB,MAAM,MAAM,EAAG,QAAO;AACrD,QAAM,SAAS,IAAI,QAAQ;AAC3B,MAAI,OAAO,WAAW,YAAY,OAAO,SAAS,GAAG;AACnD,QAAI,CAAC,aAAa,QAAQ,MAAM,EAAG,QAAO;AAAA,EAC5C;AACA,QAAM,YAAY,IAAI,QAAQ,gBAAgB;AAC9C,MAAI,cAAc,aAAc,QAAO;AACvC,SAAO;AACT;AASA,SAAS,iBAAiB,YAAoB,QAAqC;AACjF,aAAW,cAAc,2BAA2B,MAAM,GAAG;AAC3D,QAAI,eAAe,WAAY,QAAO;AAAA,EACxC;AACA,SAAO;AACT;AAGA,SAAS,2BAA2B,QAAsC;AACxE,QAAM,gBAAgB,iBAAiB,OAAO,MAAM,OAAO,IAAI;AAC/D,QAAM,WAAW,CAAC,aAAa;AAC/B,MAAI,OAAO,SAAS,eAAe,OAAO,SAAS,OAAO;AACxD,aAAS,KAAK,aAAa,OAAO,IAAI,EAAE;AAAA,EAC1C;AACA,SAAO;AACT;AAGA,SAAS,aAAa,QAAgB,QAAqC;AACzE,MAAI;AACF,UAAM,SAAS,IAAI,IAAI,MAAM;AAC7B,UAAM,mBAAmB,2BAA2B,OAAO,IAAI;AAC/D,UAAM,iBAAiB,2BAA2B,OAAO,QAAQ;AACjE,WAAO,mBAAmB,oBAAoB,OAAO,OAAO,IAAI,MAAM,OAAO;AAAA,EAC/E,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,SAAS,iBAAiB,MAAc,MAAsB;AAC5D,MAAI,KAAK,SAAS,GAAG,EAAG,QAAO,IAAI,IAAI,KAAK,IAAI;AAChD,SAAO,GAAG,IAAI,IAAI,IAAI;AACxB;AAQA,SAAS,gBAAgB,QAAoC;AAC3D,MAAI,OAAO,KAAK,SAAS,GAAG,EAAG,QAAO,WAAW,OAAO,IAAI,KAAK,OAAO,IAAI;AAC5E,SAAO,UAAU,OAAO,IAAI,IAAI,OAAO,IAAI;AAC7C;AAQA,SAAS,2BAA2B,MAAsB;AACxD,MAAI,IAAI,KAAK,YAAY;AACzB,MAAI,EAAE,WAAW,GAAG,KAAK,EAAE,SAAS,GAAG,EAAG,KAAI,EAAE,MAAM,GAAG,EAAE;AAC3D,SAAO;AACT;AASA,eAAe,YAAY,KAAqB,UAAyC;AACvF,QAAM,WAAW,MAAM,kBAAkB,UAAU;AACnD,MAAI,aAAa,MAAM;AACrB,mBAAe,KAAK,KAAK,iBAAiB,0CAA0C;AACpF;AAAA,EACF;AACA,QAAM,OAAO,oBAAoB,UAAU,SAAS,KAAK;AACzD,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,0BAA0B;AACxD,MAAI,IAAI,IAAI;AACd;AAGA,SAAS,eAAe,KAAqB,UAAgC;AAC3E,YAAU,KAAK,KAAK;AAAA,IAClB,SAAS,SAAS;AAAA,IAClB,QAAQ;AAAA,MACN,UAAU,SAAS,OAAO;AAAA,MAC1B,SAAS,SAAS,OAAO;AAAA,MACzB,aAAa,SAAS,OAAO;AAAA,MAC7B,gBAAgB,SAAS,OAAO;AAAA,IAClC;AAAA,IACA,OAAO,EAAE,WAAW,SAAS,MAAM,WAAW,MAAM,SAAS,MAAM,KAAK;AAAA,IACxE,aAAa,SAAS;AAAA,IACtB,OAAO,SAAS,MAAM,IAAI,WAAW;AAAA,IACrC,WAAW,SAAS;AAAA,EACtB,CAAC;AACH;AAGA,SAAS,YAAY,MAA2C;AAC9D,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,eAAe,KAAK;AAAA,IACpB,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,MAAM,gBAAgB,KAAK,WAAW;AAAA,IACtC,SAAS,OAAO,KAAK,YAAY,YAAY,WAAW,KAAK,YAAY,UAAU;AAAA,IACnF,WACE,OAAO,KAAK,YAAY,cAAc,WAAY,KAAK,YAAY,YAAuB;AAAA,IAC5F,UAAU,KAAK;AAAA,EACjB;AACF;AAGA,SAAS,eACP,KACA,UACA,YACM;AACN,MAAI,CAAC,SAAS,MAAM,WAAW;AAC7B,mBAAe,KAAK,KAAK,qBAAqB,+BAA+B;AAC7E;AAAA,EACF;AACA,QAAM,WAAW,cAAc,SAAS,MAAM,MAAM,UAAU,UAAU;AACxE,MAAI,aAAa,MAAM;AACrB,sBAAkB,GAAG;AACrB;AAAA,EACF;AACA,YAAU,KAAK,KAAK;AAAA,IAClB,MAAM,SAAS;AAAA,IACf,eAAe,SAAS,MAAM;AAAA,IAC9B,aAAa,SAAS;AAAA,EACxB,CAAC;AACH;AAGA,SAAS,eAAe,KAAqB,UAAgC;AAC3E,YAAU,KAAK,KAAK,SAAS,KAAK;AACpC;AAGA,eAAe,gBAAgB,KAAqB,UAAyC;AAC3F,QAAM,SAAS,MAAM,oBAAoB,QAAQ;AACjD,YAAU,KAAK,KAAK,MAAM;AAC5B;AASA,SAAS,gBACP,KACA,WACA,UACM;AACN,QAAM,QAAQ,UAAU,aAAa,IAAI,GAAG,KAAK;AACjD,YAAU,KAAK,KAAK,YAAY,UAAU,KAAK,CAAC;AAClD;AAQA,SAAS,cACP,KACA,UACA,UACA,YACM;AACN,QAAM,WAAW,SAAS,QAAQ,kBAAkB,EAAE,EAAE,MAAM,GAAG;AACjE,MAAI,SAAS,WAAW,GAAG;AACzB,mBAAe,KAAK,KAAK,eAAe,qCAAqC;AAC7E;AAAA,EACF;AACA,QAAM,CAAC,kBAAkB,WAAW,IAAI;AACxC,QAAM,cAAc,eAAe,kBAAkB,WAAW;AAChE,MAAI,CAAC,aAAa;AAChB,mBAAe,KAAK,KAAK,eAAe,4BAA4B;AACpE;AAAA,EACF;AACA,QAAM,OAAO,SAAS,MAAM;AAAA,IAC1B,CAAC,MAAM,EAAE,kBAAkB,YAAY,aAAa,EAAE,SAAS,YAAY;AAAA,EAC7E;AACA,MAAI,CAAC,MAAM;AACT,mBAAe,KAAK,KAAK,kBAAkB,GAAG,YAAY,SAAS,IAAI,YAAY,IAAI,EAAE;AACzF;AAAA,EACF;AACA,QAAM,WAAW,cAAc,KAAK,MAAM,UAAU,UAAU;AAC9D,MAAI,aAAa,MAAM;AACrB,sBAAkB,GAAG;AACrB;AAAA,EACF;AACA,YAAU,KAAK,KAAK,YAAY,MAAM,UAAU,SAAS,IAAI,CAAC;AAChE;AAOA,SAAS,eACP,kBACA,aACmD;AACnD,MAAI,qBAAqB,cAAc,qBAAqB,UAAW,QAAO;AAC9E,MAAI;AACJ,MAAI;AACF,cAAU,mBAAmB,WAAW;AAAA,EAC1C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACF,mBAAe,OAAO;AAAA,EACxB,SAAS,KAAK;AACZ,QAAI,eAAe,gBAAiB,QAAO;AAC3C,UAAM;AAAA,EACR;AACA,SAAO,EAAE,WAAW,kBAAkB,MAAM,QAAQ;AACtD;AAGA,SAAS,YACP,MACA,UACA,cACyB;AACzB,SAAO;AAAA,IACL,IAAI,KAAK;AAAA,IACT,OAAO,KAAK;AAAA,IACZ,eAAe,KAAK;AAAA,IACpB,MAAM,KAAK;AAAA,IACX,MAAM;AAAA,IACN,WAAW,KAAK;AAAA,IAChB,eAAe,KAAK;AAAA,IACpB,aAAa,KAAK;AAAA,IAClB,UAAU,KAAK;AAAA,IACf,WACE,OAAO,KAAK,YAAY,cAAc,WAAY,KAAK,YAAY,YAAuB;AAAA,IAC5F,WACE,OAAO,KAAK,YAAY,cAAc,WAAY,KAAK,YAAY,YAAuB;AAAA,IAC5F,aAAa,SAAS;AAAA,EACxB;AACF;AAOA,SAAS,cACP,MACA,UACA,YACyB;AACzB,MAAI;AACF,WAAO,eAAe,MAAM,UAAU,EAAE,WAAW,CAAC;AAAA,EACtD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,kBAAkB,KAA2B;AACpD,iBAAe,KAAK,KAAK,iBAAiB,wBAAwB;AACpE;AAGA,SAAS,UAAU,KAAqBC,SAAgB,MAAqB;AAC3E,MAAI,aAAaA;AACjB,MAAI,UAAU,gBAAgB,iCAAiC;AAC/D,MAAI,IAAI,KAAK,UAAU,IAAI,CAAC;AAC9B;AAGA,SAAS,eACP,KACAA,SACA,MACA,SACM;AACN,YAAU,KAAKA,SAAQ,EAAE,OAAO,EAAE,MAAM,QAAQ,EAAE,CAAC;AACrD;;;Ac3eA,SAAS,WAAAC,UAAS,YAAAC,YAAU,YAAAC,iBAAgB;AAC5C,OAAOC,YAAU;;;ACJjB,SAAS,WAAAC,UAAS,UAAAC,SAAQ,QAAQ,aAAAC,YAAW,SAAAC,cAAa;AAC1D,SAAS,kBAAkB;AAC3B,OAAOC,YAAU;AACjB,SAAS,mBAAmB;AAW5B,IAAM,kBAAkB;AAGxB,IAAM,gBAAgB;AA8BtB,SAAS,iBAAiB,MAAsB;AAC9C,QAAM,SAAS,YAAY,eAAe,EAAE,SAAS,KAAK;AAC1D,SAAO,GAAG,IAAI,IAAI,MAAM;AAC1B;AAGA,SAAS,cAAc,MAAc,IAAoB;AACvD,SAAOC,OAAK,KAAK,MAAM,gBAAgB,GAAG,EAAE,GAAG,aAAa,EAAE;AAChE;AAGA,SAAS,YAAY,MAAc,IAAoB;AACrD,SAAOA,OAAK,KAAK,MAAM,wBAAwB,GAAG,EAAE,GAAG,aAAa,EAAE;AACxE;AASA,eAAsB,eACpB,MACA,OAC0B;AAC1B,QAAM,YAA6B;AAAA,IACjC,IAAI,iBAAiB,MAAM,IAAI;AAAA,IAC/B,OAAO,MAAM;AAAA,IACb,MAAM,MAAM;AAAA,IACZ,SAAS,MAAM;AAAA,IACf,SAAS,MAAM;AAAA,IACf,MAAM,MAAM;AAAA,IACZ,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC,GAAI,MAAM,eAAe,EAAE,cAAc,MAAM,aAAa,IAAI,CAAC;AAAA,IACjE,GAAI,MAAM,mBAAmB,EAAE,kBAAkB,MAAM,iBAAiB,IAAI,CAAC;AAAA,IAC7E,GAAI,MAAM,uBAAuB,EAAE,sBAAsB,MAAM,qBAAqB,IAAI,CAAC;AAAA,EAC3F;AAEA,QAAM,YAAY,cAAc,MAAM,UAAU,EAAE,GAAG,KAAK,UAAU,WAAW,MAAM,CAAC,CAAC;AACvF,SAAO;AACT;AAOA,SAAS,cAAc,SAAuB;AAC5C,EAAO,OAAO,KAAY,MAAM,OAAO,CAAC;AACxC,UAAQ,WAAW;AACnB,SAAO;AACT;AAUA,eAAsB,oBACpB,MACA,IACiC;AACjC,QAAM,YAAY,MAAM,cAAc,MAAM,EAAE;AAC9C,MAAI,CAAC,UAAW,QAAO,cAAc,wBAAwB,EAAE,EAAE;AACjE,SAAO;AACT;AAaA,eAAsB,6BACpB,MACA,IACiC;AACjC,QAAM,YAAY,MAAM,cAAc,MAAM,EAAE;AAC9C,MAAI,CAAC,WAAW;AACd,WAAO,cAAc,aAAa,EAAE,gDAAgD;AAAA,EACtF;AACA,SAAO;AACT;AAGA,eAAsB,cACpB,MACA,IACiC;AACjC,QAAM,MAAM,MAAM,aAAa,cAAc,MAAM,EAAE,CAAC;AACtD,MAAI,CAAC,IAAK,QAAO;AACjB,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,QAAI,CAAC,iBAAiB,MAAM,EAAG,QAAO;AACtC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,iBAAiB,OAA0C;AAClE,MAAI,CAAC,SAAS,OAAO,UAAU,SAAU,QAAO;AAChD,QAAM,YAAY;AAClB,SACE,OAAO,UAAU,OAAO,YACxB,OAAO,UAAU,UAAU,YAC3B,OAAO,UAAU,SAAS,YAC1B,OAAO,UAAU,SAAS,YAC1B,MAAM,QAAQ,UAAU,OAAO;AAEnC;AAQA,eAAsB,eAAe,MAA0C;AAC7E,QAAM,MAAMA,OAAK,KAAK,MAAM,cAAc;AAC1C,MAAI,CAAC,WAAW,GAAG,EAAG,QAAO,CAAC;AAE9B,QAAM,UAAU,MAAMC,SAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC1D,QAAM,aAAgC,CAAC;AACvC,aAAW,SAAS,SAAS;AAC3B,QAAI,CAAC,MAAM,OAAO,KAAK,CAAC,MAAM,KAAK,SAAS,aAAa,EAAG;AAC5D,UAAM,KAAK,MAAM,KAAK,MAAM,GAAG,CAAC,cAAc,MAAM;AACpD,UAAM,YAAY,MAAM,cAAc,MAAM,EAAE;AAC9C,QAAI,UAAW,YAAW,KAAK,SAAS;AAAA,EAC1C;AAEA,aAAW,KAAK,CAAC,GAAG,MAAM,EAAE,YAAY,cAAc,EAAE,WAAW,CAAC;AACpE,SAAO;AACT;AAQA,eAAsB,gBAAgB,MAA+B;AACnE,QAAM,aAAa,MAAM,eAAe,IAAI;AAC5C,SAAO,WAAW;AACpB;AAGA,eAAsB,gBAAgB,MAAc,IAA8B;AAChF,QAAM,WAAW,cAAc,MAAM,EAAE;AACvC,MAAI,CAAC,WAAW,QAAQ,EAAG,QAAO;AAClC,QAAM,OAAO,QAAQ;AACrB,SAAO;AACT;AASA,eAAsB,iBAAiB,MAAc,IAA8B;AACjF,QAAM,aAAa,cAAc,MAAM,EAAE;AACzC,MAAI,CAAC,WAAW,UAAU,EAAG,QAAO;AAEpC,QAAM,SAAS,YAAY,MAAM,EAAE;AACnC,QAAMC,OAAMF,OAAK,QAAQ,MAAM,GAAG,EAAE,WAAW,KAAK,CAAC;AAErD,MAAI;AACF,UAAMG,QAAO,YAAY,MAAM;AAAA,EACjC,QAAQ;AACN,UAAM,MAAM,MAAM,aAAa,UAAU;AACzC,UAAMC,WAAU,QAAQ,KAAK,OAAO;AACpC,UAAM,OAAO,UAAU;AAAA,EACzB;AACA,SAAO;AACT;;;AC9OA,SAAS,YAAAC,YAAU,aAAAC,YAAW,UAAAC,SAAQ,SAAAC,QAAO,gBAAgB;AAC7D,SAAS,cAAAC,mBAAkB;AAC3B,OAAOC,YAAU;AAIjB,SAAS,aAAwB;AAC/B,SAAO,EAAE,SAAS,GAAG,WAAW,IAAI,SAAS,CAAC,EAAE;AAClD;AAGA,eAAsB,UAAU,MAAkC;AAChE,QAAM,WAAWC,OAAK,KAAK,MAAM,UAAU;AAE3C,MAAI,CAACC,YAAW,QAAQ,GAAG;AACzB,WAAO,WAAW;AAAA,EACpB;AAEA,MAAI;AACF,UAAM,MAAM,MAAMC,WAAS,UAAU,OAAO;AAC5C,WAAO,KAAK,MAAM,GAAG;AAAA,EACvB,QAAQ;AACN,UAAM,UAAU,WAAW;AAC3B,YAAQ,KAAK,iDAAuC,OAAO,mBAAmB;AAC9E,UAAM,SAAS,UAAU,OAAO;AAChC,WAAO,WAAW;AAAA,EACpB;AACF;AAGA,eAAsB,WAAW,MAAc,OAAiC;AAC9E,QAAM,MAAMF,OAAK,KAAK,MAAM,WAAW;AACvC,QAAMG,OAAM,KAAK,EAAE,WAAW,KAAK,CAAC;AAEpC,QAAM,WAAWH,OAAK,KAAK,MAAM,UAAU;AAC3C,QAAM,UAAU,WAAW;AAE3B,QAAMI,WAAU,SAAS,KAAK,UAAU,OAAO,MAAM,CAAC,GAAG,OAAO;AAChE,QAAMC,QAAO,SAAS,QAAQ;AAChC;AAMA,eAAsB,kBACpB,MACA,YACA,OACe;AACf,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,QAAQ,UAAU,IAAI;AAC5B,QAAM,WAAW,MAAM,KAAK;AAC9B;AAGA,eAAsB,kBACpB,MACA,YACe;AACf,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,SAAO,MAAM,QAAQ,UAAU;AAC/B,QAAM,WAAW,MAAM,KAAK;AAC9B;;;AFpCA,IAAM,qBAAqB;AAC3B,IAAM,aAAa;AASnB,eAAsB,oBAAoB,MAAuC;AAC/E,QAAM,CAAC,OAAO,OAAO,gBAAgB,iBAAiB,KAAK,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC/E,mBAAmB,IAAI;AAAA,IACvB,UAAU,IAAI;AAAA,IACd,gBAAgB,IAAI;AAAA,IACpB,gBAAgB,IAAI;AAAA,IACpB,cAAc,IAAI;AAAA,EACpB,CAAC;AACD,QAAM,UAAU,aAAa,IAAI;AAKjC,QAAM,SAAuB;AAAA,IAC3B,UAAU,MAAM,OAAO,CAAC,MAAM,EAAE,kBAAkB,UAAU,EAAE;AAAA,IAC9D,SAAS,MAAM,OAAO,CAAC,MAAM,EAAE,kBAAkB,SAAS,EAAE;AAAA,IAC5D,aAAa,gBAAgB;AAAA,IAC7B;AAAA,IACA,iBAAiB,OAAO,KAAK,MAAM,OAAO,EAAE;AAAA,EAC9C;AACA,QAAM,YAAyB;AAAA,IAC7B,WAAW,MAAM;AAAA,IACjB,MAAM;AAAA,IACN,MAAM,MAAM;AAAA,IACZ,eAAe,oBAAoB,qBAAqB,MAAM,IAAI,GAAG,KAAK;AAAA,EAC5E;AACA,QAAM,gBAAgB,IAAI,IAAI,eAAe;AAC7C,QAAM,iBAAiB,MAAM,IAAI,CAAC,SAAS,yBAAyB,MAAM,aAAa,CAAC;AACxF,QAAM,QAAQ,eAAe,cAAc;AAC3C,SAAO;AAAA,IACL;AAAA,IACA,cAAa,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAAA,IACA;AAAA,IACA,OAAO;AAAA,IACP,aAAa,iBAAiB,cAAc;AAAA,IAC5C,OAAO;AAAA,IACP;AAAA,IACA;AAAA,EACF;AACF;AAeA,SAAS,yBAAyB,MAAkB,aAA8C;AAChG,QAAM,QAAyB,CAAC;AAChC,QAAM,gBAAgB;AACtB,MAAI;AACJ,UAAQ,QAAQ,cAAc,KAAK,KAAK,IAAI,OAAO,MAAM;AACvD,oCAAgC,MAAM,CAAC,GAAG,aAAa,KAAK;AAAA,EAC9D;AACA,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,SAAO,EAAE,GAAG,MAAM,UAAU,CAAC,GAAG,KAAK,UAAU,GAAG,KAAK,EAAE;AAC3D;AAGA,SAAS,gCACP,KACA,aACA,MACM;AACN,aAAW,SAAS,IAAI,MAAM,GAAG,GAAG;AAClC,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,QAAQ,WAAW,EAAG;AAC1B,QAAI,yBAAyB,OAAO,GAAG;AACrC,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN,SAAS,6BAA6B,OAAO;AAAA,MAC/C,CAAC;AACD;AAAA,IACF;AACA,UAAM,OAAO,QAAQ,MAAM,MAAM,EAAE,CAAC;AACpC,QAAI,KAAK,SAAS,KAAK,CAAC,YAAY,IAAI,IAAI,GAAG;AAC7C,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN,SAAS,qBAAqB,IAAI;AAAA,MACpC,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAIA,SAAS,aAAa,MAA6B;AACjD,QAAM,WAAWC,OAAK,SAAS,IAAI;AACnC,SAAO,EAAE,OAAO,UAAU,SAAS;AACrC;AAgBA,eAAe,gBAAgB,MAAiC;AAC9D,MAAI;AACJ,MAAI;AACF,oBAAgB,MAAMC,UAAS,IAAI;AAAA,EACrC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,QAAM,cAAcD,OAAK,KAAK,eAAe,WAAW;AACxD,MAAI;AACJ,MAAI;AACF,cAAU,MAAMC,UAAS,WAAW;AAAA,EACtC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACA,MAAI,YAAY,YAAa,QAAO,CAAC;AACrC,MAAI;AACF,UAAM,UAAU,MAAMC,SAAQ,SAAS,EAAE,eAAe,KAAK,CAAC;AAC9D,WAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EAC5D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AAeA,eAAe,cAAc,MAA6D;AACxF,MAAI;AACJ,MAAI;AACF,oBAAgB,MAAMD,UAAS,IAAI;AAAA,EACrC,QAAQ;AACN,WAAO,EAAE,WAAW,OAAO,MAAM,GAAG;AAAA,EACtC;AACA,QAAM,gBAAgBD,OAAK,KAAK,eAAe,QAAQ,UAAU;AACjE,MAAI;AACJ,MAAI;AACF,eAAW,MAAMC,UAAS,aAAa;AAAA,EACzC,QAAQ;AACN,WAAO,EAAE,WAAW,OAAO,MAAM,GAAG;AAAA,EACtC;AACA,MAAI,aAAa,eAAe;AAC9B,WAAO,EAAE,WAAW,OAAO,MAAM,GAAG;AAAA,EACtC;AACA,MAAI;AACF,UAAM,OAAO,MAAME,WAAS,UAAU,OAAO;AAC7C,WAAO,EAAE,WAAW,MAAM,KAAK;AAAA,EACjC,QAAQ;AACN,WAAO,EAAE,WAAW,OAAO,MAAM,GAAG;AAAA,EACtC;AACF;AAOA,SAAS,iBAAiB,OAAyC;AACjE,QAAM,OAA2B,MAAM,IAAI,CAAC,UAAU;AAAA,IACpD,IAAI,KAAK;AAAA,IACT,eAAe,KAAK;AAAA,IACpB,MAAM,KAAK;AAAA,IACX,OAAO,KAAK;AAAA,IACZ,WACE,OAAO,KAAK,YAAY,cAAc,WAAY,KAAK,YAAY,YAAuB;AAAA,EAC9F,EAAE;AACF,OAAK,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AAC1D,SAAO,KAAK,MAAM,GAAG,kBAAkB;AACzC;;;Af3NA,IAAM,gBAAgB;AAUtB,IAAM,iBAAiB,oBAAI,IAAI;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAgBD,eAAO,YAAmC,SAA4C;AACpF,QAAM,EAAE,MAAM,KAAK,IAAI,kBAAkB,OAAO;AAChD,QAAM,OAAO,QAAQ,IAAI;AACzB,QAAM,WAAW,MAAM,oBAAoB,IAAI;AAC/C,QAAM,SAAS,MAAM,kBAAkB,UAAU,EAAE,MAAM,KAAK,CAAC;AAC/D,QAAM,MAAM,cAAc,OAAO,MAAM,OAAO,IAAI;AAClD,UAAQ,OAAO,MAAM,mBAAmB,GAAG;AAAA,CAAI;AAC/C,MAAI,QAAQ,KAAM,eAAc,GAAG;AACnC,mBAAiB,OAAO,KAAK;AAC/B;AAOA,SAAS,cAAc,KAAmB;AACxC,QAAM,UACJ,QAAQ,aAAa,WAAW,SAC9B,QAAQ,aAAa,UAAU,QAC/B;AACJ,QAAM,OAAO,QAAQ,aAAa,UAAU,CAAC,MAAM,SAAS,IAAI,GAAG,IAAI,CAAC,GAAG;AAC3E,QAAM,QAAQ,MAAM,SAAS,MAAM,EAAE,OAAO,UAAU,UAAU,KAAK,CAAC;AACtE,QAAM,GAAG,SAAS,MAAM,MAAS;AACjC,QAAM,MAAM;AACd;AAMA,SAAS,kBAAkB,SAA6D;AACtF,QAAM,WAAW,OAAO,QAAQ,SAAS,YAAY,QAAQ,KAAK,SAAS;AAC3E,QAAM,WAAW,QAAQ,aAAa;AACtC,6BAA2B,UAAU,QAAQ;AAC7C,QAAM,OAAO,WAAY,QAAQ,OAAkB;AACnD,wBAAsB,IAAI;AAC1B,SAAO,EAAE,MAAM,MAAM,UAAU,QAAQ,IAAI,EAAE;AAC/C;AAGA,SAAS,2BAA2B,UAAmB,UAAyB;AAC9E,MAAI,aAAa,SAAU;AAC3B,QAAM,IAAI;AAAA,IACR;AAAA,EAEF;AACF;AAGA,SAAS,sBAAsB,MAAoB;AACjD,MAAI,CAAC,eAAe,IAAI,IAAI,EAAG;AAC/B,QAAM,IAAI;AAAA,IACR,UAAU,IAAI;AAAA,EAEhB;AACF;AASA,SAAS,cAAc,MAAc,MAAsB;AACzD,MAAI,KAAK,SAAS,GAAG,EAAG,QAAO,WAAW,IAAI,KAAK,IAAI;AACvD,SAAO,UAAU,IAAI,IAAI,IAAI;AAC/B;AAGA,SAAS,UAAU,KAA0C;AAC3D,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,QAAQ,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG;AACxD,MAAI,CAAC,YAAY,KAAK,GAAG;AACvB,UAAM,IAAI,MAAM,yBAAyB,GAAG,EAAE;AAAA,EAChD;AACA,SAAO;AACT;AAGA,SAAS,YAAY,OAAwB;AAC3C,SAAO,OAAO,UAAU,KAAK,KAAK,SAAS,KAAK,SAAS;AAC3D;AAGA,SAAS,iBAAiB,OAAkC;AAC1D,QAAM,WAAW,YAA2B;AAC1C,QAAI;AACF,YAAM,MAAM;AACZ,cAAQ,KAAK,CAAC;AAAA,IAChB,QAAQ;AACN,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACA,UAAQ,KAAK,UAAU,MAAM,KAAK,SAAS,CAAC;AAC5C,UAAQ,KAAK,WAAW,MAAM,KAAK,SAAS,CAAC;AAC/C;;;AkB5IA,SAAS,cAAAC,mBAAkB;;;ACI3B,SAAS,YAAAC,kBAAgB;AACzB,OAAOC,YAAU;;;ACKjB,OAAOC,YAAU;;;ACRjB,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,YAAAC,YAAU,WAAAC,gBAAe;AAClC,OAAOC,YAAU;AASjB,eAAsB,SAAS,UAAmC;AAChE,QAAM,UAAU,MAAMC,WAAS,UAAU,OAAO;AAChD,SAAOC,YAAW,QAAQ,EAAE,OAAO,OAAO,EAAE,OAAO,KAAK;AAC1D;AASA,eAAsB,cACpB,MACA,WACyB;AACzB,QAAM,cAAcC,OAAK,KAAK,MAAM,WAAW;AAC/C,QAAM,eAAe,MAAMC,iBAAgB,WAAW;AACtD,QAAM,UAA0B,CAAC;AAEjC,aAAW,QAAQ,cAAc;AAC/B,UAAMC,UAAS,MAAM,aAAa,MAAM,MAAM,SAAS;AACvD,YAAQ,KAAK,EAAE,MAAM,QAAAA,QAAO,CAAC;AAAA,EAC/B;AAEA,QAAM,iBAAiB,iBAAiB,cAAc,SAAS;AAC/D,UAAQ,KAAK,GAAG,cAAc;AAE9B,SAAO;AACT;AAOA,eAAeD,iBAAgB,aAAwC;AACrE,MAAI;AACF,UAAM,UAAU,MAAME,SAAQ,WAAW;AACzC,WAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AAAA,EAChD,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;AASA,eAAe,aACb,MACA,MACA,WACiC;AACjC,QAAM,WAAWH,OAAK,KAAK,MAAM,aAAa,IAAI;AAClD,QAAM,OAAO,MAAM,SAAS,QAAQ;AACpC,QAAM,OAAO,UAAU,QAAQ,IAAI;AAEnC,MAAI,CAAC,KAAM,QAAO;AAClB,MAAI,KAAK,SAAS,KAAM,QAAO;AAC/B,SAAO;AACT;AAQA,SAAS,iBACP,cACA,WACgB;AAChB,QAAM,aAAa,IAAI,IAAI,YAAY;AACvC,SAAO,OAAO,KAAK,UAAU,OAAO,EACjC,OAAO,CAAC,SAAS,CAAC,WAAW,IAAI,IAAI,CAAC,EACtC,IAAI,CAAC,UAAU,EAAE,MAAM,QAAQ,UAAmB,EAAE;AACzD;;;AD/DA,eAAsB,4BACpB,MACA,aACsC;AACtC,QAAM,WAAwC,CAAC;AAC/C,QAAM,cAAa,oBAAI,KAAK,GAAE,YAAY;AAE1C,aAAW,UAAU,aAAa;AAChC,QAAI,OAAO,SAAS,WAAW,EAAG;AAClC,aAAS,OAAO,UAAU,IAAI,MAAM,WAAW,MAAM,QAAQ,UAAU;AAAA,EACzE;AAEA,SAAO;AACT;AAGA,eAAe,WACb,MACA,QACA,YACsB;AACtB,QAAM,WAAWI,OAAK,KAAK,MAAM,aAAa,OAAO,UAAU;AAC/D,QAAM,OAAO,MAAM,SAAS,QAAQ;AACpC,SAAO;AAAA,IACL;AAAA,IACA,UAAU,OAAO,SAAS,IAAI,CAAC,YAAY,QAAQ,QAAQ,OAAO,CAAC;AAAA,IACnE;AAAA,EACF;AACF;AAWO,SAAS,qBACd,WACA,aAC6B;AAC7B,QAAM,SAAsC,CAAC;AAC7C,aAAW,QAAQ,aAAa;AAC9B,UAAM,QAAQ,UAAU,IAAI;AAC5B,QAAI,MAAO,QAAO,IAAI,IAAI;AAAA,EAC5B;AACA,SAAO;AACT;;;AE9EA,OAAO,YAAY;AAyBZ,SAAS,eAAe,MAAkC;AAC/D,QAAM,MAAM,QAAQ,IAAI,IAAI,GAAG,KAAK;AACpC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAS,OAAO,GAAG;AACzB,SAAO,OAAO,SAAS,MAAM,KAAK,SAAS,IAAI,SAAS;AAC1D;AAGA,SAAS,yBAA6C;AACpD,SAAO,eAAe,4BAA4B;AACpD;AAGO,SAAS,sBACd,MAC2B;AAC3B,SAAO;AAAA,IACL,MAAM;AAAA,IACN,UAAU;AAAA,MACR,MAAM,KAAK;AAAA,MACX,aAAa,KAAK;AAAA,MAClB,YAAY,KAAK;AAAA,IACnB;AAAA,EACF;AACF;AAGO,IAAM,iBAAN,MAA4C;AAAA,EAC9B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAEnB,YAAY,OAAe,UAAiC,CAAC,GAAG;AAC9D,SAAK,QAAQ;AACb,SAAK,2BAA2B,QAAQ;AAGxC,UAAM,cAAc,QAAQ,UAAU,QAAQ,IAAI,kBAAkB;AACpE,UAAM,UAAU,QAAQ,aAAa,uBAAuB,KAAK;AACjE,SAAK,SAAS,IAAI,OAAO;AAAA,MACvB,QAAQ;AAAA,MACR,SAAS,QAAQ,WAAW;AAAA,MAC5B;AAAA,IACF,CAAC;AACD,SAAK,mBAAmB,QAAQ,oBAC5B,IAAI,OAAO,EAAE,QAAQ,aAAa,SAAS,QAAQ,mBAAmB,QAAQ,CAAC,IAC/E,KAAK;AAAA,EACX;AAAA;AAAA,EAGA,MAAM,SAAS,QAAgB,UAAwB,WAAoC;AACzF,UAAM,WAAW,MAAM,KAAK,OAAO,KAAK,YAAY,OAAO;AAAA,MACzD,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ,UAAU,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,GAAG,GAAG,QAAQ;AAAA,IAC7D,CAAC;AAED,WAAO,SAAS,QAAQ,CAAC,GAAG,SAAS,WAAW;AAAA,EAClD;AAAA;AAAA,EAGA,MAAM,OACJ,QACA,UACA,WACA,SACiB;AACjB,UAAM,SAAS,MAAM,KAAK,OAAO,KAAK,YAAY,OAAO;AAAA,MACvD,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ,UAAU,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,GAAG,GAAG,QAAQ;AAAA,MAC3D,QAAQ;AAAA,IACV,CAAC;AAED,QAAI,WAAW;AACf,qBAAiB,SAAS,QAAQ;AAChC,YAAM,QAAQ,MAAM,QAAQ,CAAC,GAAG,OAAO;AACvC,UAAI,OAAO;AACT,oBAAY;AACZ,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA,EAGA,MAAM,SACJ,QACA,UACA,OACA,WACiB;AACjB,UAAM,cAAc,MAAM,IAAI,qBAAqB;AAEnD,UAAM,WAAW,MAAM,KAAK,OAAO,KAAK,YAAY,OAAO;AAAA,MACzD,OAAO,KAAK;AAAA,MACZ,YAAY;AAAA,MACZ,UAAU,CAAC,EAAE,MAAM,UAAU,SAAS,OAAO,GAAG,GAAG,QAAQ;AAAA,MAC3D,OAAO;AAAA,MACP,aAAa;AAAA,IACf,CAAC;AAED,UAAM,YAAY,SAAS,QAAQ,CAAC,GAAG,SAAS;AAChD,QAAI,aAAa,UAAU,SAAS,GAAG;AACrC,aAAO,UAAU,CAAC,EAAE,SAAS;AAAA,IAC/B;AAEA,WAAO,SAAS,QAAQ,CAAC,GAAG,SAAS,WAAW;AAAA,EAClD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAM,MAAM,MAAiC;AAC3C,UAAM,WAAW,MAAM,KAAK,iBAAiB,WAAW,OAAO;AAAA,MAC7D,OAAO,KAAK,eAAe;AAAA,MAC3B,OAAO;AAAA,IACT,CAAC;AAED,UAAM,SAAS,SAAS,KAAK,CAAC,GAAG;AACjC,QAAI,CAAC,MAAM,QAAQ,MAAM,GAAG;AAC1B,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AACA,WAAO;AAAA,EACT;AAAA;AAAA,EAGU,iBAAyB;AACjC,WAAO,KAAK,4BAA4B,iBAAiB;AAAA,EAC3D;AACF;;;AC5IA,SAAS,uBAAuB,UAA2B;AACzD,SACE,YACA,eAAe,mBAAmB,KAClC,eAAe,4BAA4B,KAC3C;AAEJ;AAGO,IAAM,iBAAN,cAA6B,eAAe;AAAA,EACjD,YAAY,OAAe,SAAgC;AACzD,UAAM,OAAO;AAAA,MACX,SAAS,QAAQ;AAAA,MACjB,QAAQ;AAAA,MACR,mBAAmB,QAAQ;AAAA,MAC3B,gBAAgB,QAAQ;AAAA,MACxB,WAAW,uBAAuB,QAAQ,SAAS;AAAA,IACrD,CAAC;AAAA,EACH;AAAA;AAAA,EAGmB,iBAAyB;AAC1C,WAAO,KAAK,4BAA4B,iBAAiB;AAAA,EAC3D;AACF;;;ACxCA,IAAM,mBAAmB;AAGlB,IAAM,kBAAN,cAA8B,eAAe;AAAA,EAClD,YAAY,OAAe,QAAgB;AACzC,UAAM,OAAO,EAAE,SAAS,kBAAkB,OAAO,CAAC;AAAA,EACpD;AACF;;;ACAO,IAAM,kBAAN,cAA8B,eAAe;AAAA,EAClD,YAAY,OAAe,QAAgB;AACzC,UAAM,OAAO,EAAE,SAAS,kBAAkB,OAAO,CAAC;AAAA,EACpD;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAe,MAAM,OAAkC;AACrD,UAAM,IAAI;AAAA,MACR;AAAA,IAIF;AAAA,EACF;AACF;;;ACkBA,IAAM,sBAA2C,oBAAI,IAAI,CAAC,aAAa,UAAU,UAAU,WAAW,SAAS,CAAC;AASzG,SAAS,cAA2B;AACzC,QAAM,eAAe,gBAAgB;AAErC,UAAQ,cAAc;AAAA,IACpB,KAAK;AACH,aAAO,qBAAqB;AAAA,IAC9B,KAAK;AACH,aAAO,IAAI,eAAe,oBAAoB,QAAQ,GAAG;AAAA,QACvD,SAAS,gBAAgB,iBAAiB;AAAA,QAC1C,mBAAmB,gBAAgB,4BAA4B;AAAA,QAC/D,gBAAgB,gBAAgB,yBAAyB;AAAA,MAC3D,CAAC;AAAA,IACH,KAAK;AACH,aAAO,IAAI,eAAe,oBAAoB,QAAQ,GAAG;AAAA,QACvD,SAAS,gBAAgB,aAAa,KAAK;AAAA,QAC3C,mBAAmB,gBAAgB,wBAAwB;AAAA,QAC3D,gBAAgB,gBAAgB,yBAAyB;AAAA,MAC3D,CAAC;AAAA,IACH,KAAK;AACH,aAAO,mBAAmB;AAAA,IAC5B,KAAK;AACH,aAAO,mBAAmB;AAAA,IAC5B;AACE,YAAM,IAAI,MAAM,uBAAuB,YAAY,EAAE;AAAA,EACzD;AACF;AAEA,SAAS,gBAAgB,MAAkC;AACzD,QAAM,QAAQ,QAAQ,IAAI,IAAI,GAAG,KAAK;AACtC,SAAO,QAAQ,QAAQ;AACzB;AAEA,SAAS,oBAAoB,cAAmE;AAC9F,SAAO,QAAQ,IAAI,iBAAiB,gBAAgB,YAAY;AAClE;AAEA,SAAS,qBAAsC;AAC7C,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACA,SAAO,IAAI,gBAAgB,oBAAoB,SAAS,GAAG,MAAM;AACnE;AAEA,SAAS,qBAAsC;AAC7C,QAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,CAAC,QAAQ;AACX,UAAM,IAAI;AAAA,MACR;AAAA,IAIF;AAAA,EACF;AACA,SAAO,IAAI,gBAAgB,oBAAoB,SAAS,GAAG,MAAM;AACnE;AAEA,SAAS,uBAA0C;AACjD,QAAM,QAAQ,6BAA6B,KAAK,gBAAgB;AAChE,QAAM,UAAU,+BAA+B;AAC/C,QAAM,OAAO,4BAA4B;AAEzC,SAAO,IAAI,kBAAkB,OAAO;AAAA,IAClC;AAAA,IACA,GAAG;AAAA,EACL,CAAC;AACH;AAEA,SAAS,kBAA0B;AACjC,QAAM,eAAe,QAAQ,IAAI,oBAAoB;AACrD,MAAI,CAAC,oBAAoB,IAAI,YAAY,GAAG;AAC1C,UAAM,IAAI;AAAA,MACR,qBAAqB,YAAY,iBAAiB,CAAC,GAAG,mBAAmB,EAAE,KAAK,IAAI,CAAC;AAAA,IACvF;AAAA,EACF;AACA,SAAO;AACT;AAGO,SAAS,wBAAgC;AAC9C,SAAO,gBAAgB;AACzB;;;ACpIA,SAAS,MAAM,IAA2B;AACxC,SAAO,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,EAAE,CAAC;AACzD;AAIA,IAAM,mBAAmB;AAGzB,SAAS,eAAeC,QAAyB;AAC/C,QAAM,MAAMA,kBAAiB,QAAQA,OAAM,UAAU,OAAOA,MAAK;AACjE,SAAO,iBAAiB,KAAK,GAAG;AAClC;AAgBA,eAAsB,WAAW,SAA6C;AAC5E,QAAM,EAAE,QAAQ,UAAU,OAAO,YAAY,MAAM,SAAS,OAAO,QAAQ,IAAI;AAC/E,QAAM,WAAW,YAAY;AAE7B,WAAS,UAAU,GAAG,WAAW,aAAa,WAAW;AACvD,QAAI;AACF,UAAI,QAAQ;AACV,eAAO,MAAM,SAAS,OAAO,QAAQ,UAAU,WAAW,OAAO;AAAA,MACnE;AAEA,UAAI,SAAS,MAAM,SAAS,GAAG;AAC7B,eAAO,MAAM,SAAS,SAAS,QAAQ,UAAU,OAAO,SAAS;AAAA,MACnE;AAEA,aAAO,MAAM,SAAS,SAAS,QAAQ,UAAU,SAAS;AAAA,IAC5D,SAASA,QAAO;AACd,UAAI,YAAY,eAAe,eAAeA,MAAK,EAAG,OAAMA;AAE5D,YAAM,UAAU,gBAAgB,KAAK,IAAI,kBAAkB,OAAO;AAClE,YAAM,SAASA,kBAAiB,QAAQA,OAAM,UAAU,OAAOA,MAAK;AACpE,cAAQ,KAAK,mCAA8B,UAAU,CAAC,IAAI,cAAc,CAAC,MAAM,MAAM,EAAE;AACvF,cAAQ,KAAK,iBAAiB,UAAU,GAAI,MAAM;AAClD,YAAM,MAAM,OAAO;AAAA,IACrB;AAAA,EACF;AAEA,QAAM,IAAI,MAAM,aAAa;AAC/B;;;AC5CA,SAAS,MAAM,YAAAC,YAAU,UAAAC,SAAQ,SAAAC,cAAa;AAC9C,OAAOC,YAAU;AAIjB,IAAM,iBAAiB;AACvB,IAAM,uBAAuB;AAG7B,SAAS,eAAe,KAAsB;AAC5C,MAAI;AACF,YAAQ,KAAK,KAAK,CAAC;AACnB,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,eAAsB,YAAY,MAAgC;AAChE,QAAM,WAAWC,OAAK,KAAK,MAAM,SAAS;AAC1C,QAAMC,OAAMD,OAAK,KAAK,MAAM,WAAW,GAAG,EAAE,WAAW,KAAK,CAAC;AAE7D,WAAS,UAAU,GAAG,UAAU,sBAAsB,WAAW;AAE/D,UAAM,UAAU,MAAM,cAAc,QAAQ;AAC5C,QAAI,QAAS,QAAO;AAGpB,UAAM,QAAQ,MAAM,YAAY,QAAQ;AACxC,QAAI,CAAC,OAAO;AACV,MAAO,OAAO,KAAY,KAAK,iCAAiC,CAAC;AACjE,aAAO;AAAA,IACT;AAGA,UAAM,YAAY,MAAM,iBAAiB,MAAM,QAAQ;AACvD,QAAI,UAAW,QAAO;AAAA,EAGxB;AAEA,EAAO,OAAO,KAAY,KAAK,wCAAwC,CAAC;AACxE,SAAO;AACT;AAWA,eAAe,iBAAiB,MAAc,UAAoC;AAChF,QAAM,cAAc,WAAW;AAE/B,QAAM,iBAAiB,MAAM,mBAAmB,WAAW;AAC3D,MAAI,CAAC,eAAgB,QAAO;AAE5B,MAAI;AAGF,QAAI,CAAE,MAAM,YAAY,QAAQ,GAAI;AAClC,aAAO;AAAA,IACT;AAGA,QAAI;AAAE,YAAME,QAAO,QAAQ;AAAA,IAAG,QAAQ;AAAA,IAAqB;AAE3D,UAAM,WAAW,MAAM,cAAc,QAAQ;AAC7C,QAAI,UAAU;AACZ,MAAO,OAAO,KAAY,IAAI,yCAAyC,CAAC;AAAA,IAC1E;AACA,WAAO;AAAA,EACT,UAAE;AACA,QAAI;AAAE,YAAMA,QAAO,WAAW;AAAA,IAAG,QAAQ;AAAA,IAA4B;AAAA,EACvE;AACF;AAeA,eAAe,mBAAmB,aAAuC;AACvE,MAAI,MAAM,cAAc,WAAW,EAAG,QAAO;AAG7C,MAAI,CAAE,MAAM,YAAY,WAAW,EAAI,QAAO;AAI9C,MAAI;AAAE,UAAMA,QAAO,WAAW;AAAA,EAAG,QAAQ;AAAA,EAAqB;AAC9D,SAAO;AACT;AAMA,eAAe,cAAc,UAAoC;AAC/D,MAAI;AACF,UAAM,KAAK,MAAM,KAAK,UAAU,IAAI;AACpC,UAAM,GAAG,UAAU,OAAO,QAAQ,GAAG,GAAG,OAAO;AAC/C,UAAM,GAAG,MAAM;AACf,WAAO;AAAA,EACT,SAAS,KAAc;AACrB,QAAI,eAAe,SAAS,UAAU,OAAQ,IAA8B,SAAS,UAAU;AAC7F,aAAO;AAAA,IACT;AACA,UAAM;AAAA,EACR;AACF;AAGA,eAAe,YAAY,UAAoC;AAC7D,MAAI;AACF,UAAM,UAAU,MAAMC,WAAS,UAAU,OAAO;AAChD,UAAM,MAAM,SAAS,QAAQ,KAAK,GAAG,EAAE;AACvC,QAAI,MAAM,GAAG,EAAG,QAAO;AACvB,WAAO,CAAC,eAAe,GAAG;AAAA,EAC5B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAsB,YAAY,MAA6B;AAC7D,QAAM,WAAWH,OAAK,KAAK,MAAM,SAAS;AAC1C,MAAI;AACF,UAAME,QAAO,QAAQ;AAAA,EACvB,QAAQ;AAAA,EAER;AACF;;;ACjKA,IAAM,eAAe;AAMd,SAAS,oBAAmC;AACjD,QAAM,MAAM,QAAQ,IAAI,YAAY;AACpC,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,UAAU,IAAI,KAAK;AACzB,SAAO,QAAQ,SAAS,IAAI,UAAU;AACxC;AAQO,SAAS,oBAA4B;AAC1C,QAAM,OAAO,kBAAkB;AAC/B,MAAI,CAAC,KAAM,QAAO;AAClB,SAAO,uBAAuB,IAAI;AACpC;AAYO,SAAS,oBAAoB,MAAgC;AAClE,MAAI,QAAQ,KAAK,KAAK,EAAE,SAAS,GAAG;AAClC,YAAQ,IAAI,sBAAsB,KAAK,KAAK;AAAA,EAC9C;AACF;;;AC/BA,SAAS,gBAAgB,OAA2B;AAClD,QAAM,OAAO,kBAAkB;AAC/B,SAAO,OAAO,CAAC,GAAG,OAAO,IAAI,IAAI;AACnC;AAGA,IAAM,0BAA6C;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAMO,IAAM,0BAA0B;AAAA,EACrC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,YAAY;AAAA,MACV,UAAU;AAAA,QACR,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,YAAY;AAAA,YACV,SAAS;AAAA,cACP,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,YACA,SAAS;AAAA,cACP,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,YACA,QAAQ;AAAA,cACN,MAAM;AAAA,cACN,aAAa;AAAA,YACf;AAAA,YACA,MAAM;AAAA,cACJ,MAAM;AAAA,cACN,OAAO,EAAE,MAAM,SAAS;AAAA,cACxB,aACE;AAAA,YACJ;AAAA,YACA,YAAY;AAAA,cACV,MAAM;AAAA,cACN,aACE;AAAA,YACJ;AAAA,YACA,kBAAkB;AAAA,cAChB,MAAM;AAAA,cACN,MAAM;AAAA,cACN,aACE;AAAA,YACJ;AAAA,YACA,iBAAiB;AAAA,cACf,MAAM;AAAA,cACN,OAAO;AAAA,gBACL,MAAM;AAAA,gBACN,YAAY;AAAA,kBACV,MAAM,EAAE,MAAM,UAAU,aAAa,qCAAqC;AAAA,kBAC1E,QAAQ,EAAE,MAAM,UAAU,aAAa,sCAAsC;AAAA,gBAC/E;AAAA,gBACA,UAAU,CAAC,MAAM;AAAA,cACnB;AAAA,cACA,aAAa;AAAA,YACf;AAAA,UACF;AAAA,UACA,UAAU,CAAC,WAAW,WAAW,QAAQ;AAAA,QAC3C;AAAA,MACF;AAAA,IACF;AAAA,IACA,UAAU,CAAC,UAAU;AAAA,EACvB;AACF;AASO,SAAS,sBACd,eACA,eACQ;AACR,QAAM,eAAe,gBACjB;AAAA;AAAA;AAAA;AAAA,EAAwF,aAAa,KACrG;AAEJ,SAAO;AAAA,IACL,GAAG;AAAA,MACD;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAWO,SAAS,gBACd,SACA,eACA,cACA,cACQ;AACR,QAAM,kBAAkB,eACpB;AAAA;AAAA;AAAA;AAAA,EAAmC,YAAY,KAC/C;AAEJ,QAAM,iBAAiB,eACnB;AAAA;AAAA;AAAA;AAAA,EAAoD,YAAY,KAChE;AAEJ,SAAO;AAAA,IACL,GAAG;AAAA,MACD,8EAA8E,OAAO;AAAA,MACrF;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAcA,SAAS,kBAAkB,GAAwB;AACjD,SACE,OAAO,EAAE,YAAY,YACrB,OAAO,EAAE,YAAY,YACrB,OAAO,EAAE,WAAW,cACnB,EAAE,SAAS,UAAa,MAAM,QAAQ,EAAE,IAAI;AAEjD;AAGA,SAAS,qBAAqB,KAA8C;AAC1E,MAAI,CAAC,MAAM,QAAQ,GAAG,EAAG,QAAO;AAChC,QAAM,OAA2B,CAAC;AAClC,aAAW,SAAS,KAAK;AACvB,QAAI,CAAC,SAAS,OAAO,UAAU,SAAU;AACzC,UAAM,MAAM;AACZ,QAAI,OAAO,IAAI,SAAS,YAAY,IAAI,KAAK,KAAK,EAAE,WAAW,EAAG;AAClE,UAAM,MAAwB,EAAE,MAAM,IAAI,KAAK,KAAK,EAAE;AACtD,QAAI,OAAO,IAAI,WAAW,SAAU,KAAI,SAAS,IAAI;AACrD,SAAK,KAAK,GAAG;AAAA,EACf;AACA,SAAO,KAAK,SAAS,IAAI,OAAO;AAClC;AAGA,SAAS,cAAc,GAAiC;AACtD,QAAM,aAAa,OAAO,EAAE,qBAAqB,YAC/C,wBAAwB,SAAS,EAAE,gBAAmC,IACnE,EAAE,mBACH;AACJ,SAAO;AAAA,IACL,SAAS,EAAE;AAAA,IACX,SAAS,EAAE;AAAA,IACX,QAAQ,EAAE;AAAA,IACV,MAAM,MAAM,QAAQ,EAAE,IAAI,IAAK,EAAE,OAAoB;AAAA,IACrD,YAAY,OAAO,EAAE,eAAe,WAAW,EAAE,aAAa;AAAA,IAC9D,iBAAiB;AAAA,IACjB,gBAAgB,qBAAqB,EAAE,eAAe;AAAA,EACxD;AACF;AAWO,SAAS,oBACd,MACA,MACA,qBACQ;AACR,QAAM,WAAW,KAAK;AACtB,QAAM,kBAAkB,WAAW,IAC/B,oBAAoB,QAAQ,qCAC5B;AACJ,SAAO;AAAA,IACL,GAAG;AAAA,MACD,kCAAkC,KAAK,IAAI,iBAAiB,KAAK,KAAK;AAAA,MACtE,uBAAuB,KAAK,WAAW;AAAA,MACvC,6BAA6B,KAAK,OAAO;AAAA,MACzC;AAAA,MACA;AAAA,MACA;AAAA,IACF;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAOO,SAAS,cAAc,YAAwC;AACpE,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,UAAU;AACpC,UAAM,WAAyB,OAAO,YAAY,CAAC;AACnD,WAAO,SAAS,OAAO,iBAAiB,EAAE,IAAI,aAAa;AAAA,EAC7D,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AACF;;;ACnRO,IAAM,aAAkC;AAAA,EAC7C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;ACTA,IAAM,oBAA8C;AAAA,EAClD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AACZ;AAGA,IAAM,uBAAiD;AAAA,EACrD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY;AAAA,EACZ,UAAU;AACZ;AAGA,SAAS,wBAAwD;AAC/D,SAAO;AAAA,IACL,SAAS,EAAE,cAAc,kBAAkB,SAAS,aAAa,qBAAqB,QAAQ;AAAA,IAC9F,QAAQ,EAAE,cAAc,kBAAkB,QAAQ,aAAa,qBAAqB,OAAO;AAAA,IAC3F,YAAY;AAAA,MACV,cAAc,kBAAkB;AAAA,MAChC,aAAa,qBAAqB;AAAA,IACpC;AAAA,IACA,UAAU;AAAA,MACR,cAAc,kBAAkB;AAAA,MAChC,aAAa,qBAAqB;AAAA,IACpC;AAAA,EACF;AACF;AAGO,SAAS,qBAAmC;AACjD,SAAO;AAAA,IACL,SAAS;AAAA,IACT,aAAa;AAAA,IACb,OAAO,sBAAsB;AAAA,IAC7B,WAAW,CAAC;AAAA,IACZ,YAAY;AAAA,EACd;AACF;;;AC3CA,SAAS,cAAAE,mBAAkB;AAC3B,SAAS,YAAAC,kBAAgB;AACzB,OAAOC,YAAU;AACjB,OAAOC,WAAU;AAYjB,IAAM,yBAAyB;AAAA,EAC7B;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,SAAS,eAAe,MAA6B;AACnD,aAAW,aAAa,wBAAwB;AAC9C,UAAM,WAAWC,OAAK,KAAK,MAAM,SAAS;AAC1C,QAAIC,YAAW,QAAQ,EAAG,QAAO;AAAA,EACnC;AACA,SAAO;AACT;AAGA,SAAS,gBAAgB,UAAkB,SAAoC;AAC7E,QAAM,SAAS,SAAS,SAAS,OAAO;AACxC,QAAM,SAAS,SAAS,KAAK,MAAM,OAAO,IAAIC,MAAK,KAAK,OAAO;AAC/D,MAAI,UAAU,OAAO,WAAW,SAAU,QAAO;AACjD,SAAO,CAAC;AACV;AAGA,SAAS,WAAW,OAAmC;AACrD,SAAO,OAAO,UAAU,YAAa,WAAiC,SAAS,KAAK;AACtF;AAGA,SAAS,cACP,UACA,UACc;AACd,MAAI,CAAC,SAAU,QAAO;AACtB,QAAM,eAAe,OAAO,SAAS,iBAAiB,WAClD,SAAS,eACT,SAAS;AACb,QAAM,cAAc,OAAO,SAAS,gBAAgB,WAChD,SAAS,cACT,SAAS;AACb,SAAO,EAAE,cAAc,YAAY;AACrC;AAGA,SAAS,WACP,UACA,WACgC;AAChC,QAAM,SAAS,EAAE,GAAG,SAAS;AAC7B,MAAI,CAAC,UAAW,QAAO;AAEvB,aAAW,QAAQ,YAAY;AAC7B,WAAO,IAAI,IAAI,cAAc,SAAS,IAAI,GAAG,UAAU,IAAI,CAAC;AAAA,EAC9D;AACA,SAAO;AACT;AAGA,SAAS,kBAAkB,OAA2C;AACpE,MAAI,OAAO,MAAM,UAAU,YAAY,MAAM,MAAM,KAAK,MAAM,GAAI,QAAO;AACzE,MAAI,CAAC,WAAW,MAAM,IAAI,EAAG,QAAO;AACpC,QAAM,UAAU,OAAO,MAAM,YAAY,WAAW,MAAM,UAAU;AACpE,QAAM,eAAe,MAAM,QAAQ,MAAM,YAAY,IACjD,MAAM,aAAa,OAAO,CAAC,SAAyB,OAAO,SAAS,QAAQ,IAC5E;AACJ,SAAO,EAAE,OAAO,MAAM,OAAO,MAAM,MAAM,MAAM,SAAS,aAAa;AACvE;AAGA,SAAS,mBAAmB,SAAqD;AAC/E,MAAI,CAAC,MAAM,QAAQ,OAAO,EAAG,QAAO,CAAC;AACrC,SAAO,QACJ,IAAI,iBAAiB,EACrB,OAAO,CAAC,UAA6B,UAAU,IAAI;AACxD;AAGA,SAAS,eACP,UACA,WACA,YACc;AACd,QAAM,cAAc,WAAW,UAAU,WAAW,IAChD,UAAU,cACV,SAAS;AACb,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,OAAO,WAAW,SAAS,OAAO,UAAU,KAAK;AAAA,IACjD,WAAW,mBAAmB,UAAU,SAAS;AAAA,IACjD;AAAA,EACF;AACF;AASA,eAAsB,WAAW,MAAqC;AACpE,QAAM,WAAW,mBAAmB;AACpC,QAAM,aAAa,eAAe,IAAI;AACtC,MAAI,CAAC,WAAY,QAAO;AAExB,QAAM,MAAM,MAAMC,WAAS,YAAY,OAAO;AAC9C,QAAM,SAAS,gBAAgB,YAAY,GAAG;AAC9C,SAAO,eAAe,UAAU,QAAQ,UAAU;AACpD;AAGO,SAAS,sBAAsB,MAAsB;AAC1D,SAAOH,OAAK,KAAK,MAAM,uBAAuB,CAAC,CAAC;AAClD;;;ACpIA,OAAOI,WAAU;AAKjB,IAAM,mBAAmB;AASlB,SAASC,iBAAgB,SAAkB,QAAgC;AAChF,MAAI,OAAO,YAAY,YAAa,WAAiC,SAAS,OAAO,GAAG;AACtF,WAAO;AAAA,EACT;AACA,SAAO,OAAO;AAChB;AAQO,SAAS,eAAe,MAAsB;AACnD,QAAM,UAAU,KAAK,MAAM,gBAAgB;AAC3C,SAAO,UAAU,QAAQ,SAAS;AACpC;AAQO,SAAS,sBAAsB,QAA8B;AAClE,QAAM,eAAe;AAAA,IACnB,SAAS,OAAO;AAAA,IAChB,aAAa,OAAO;AAAA,IACpB,OAAO,OAAO;AAAA,IACd,WAAW,OAAO;AAAA,EACpB;AACA,SAAOC,MAAK,KAAK,cAAc,EAAE,WAAW,IAAI,aAAa,IAAI,CAAC;AACpE;;;ACvBA,SAAS,yBACP,SACuB;AACvB,QAAM,aAAa,oBAAI,IAAsB;AAE7C,aAAW,CAAC,YAAY,KAAK,KAAK,OAAO,QAAQ,OAAO,GAAG;AACzD,eAAW,QAAQ,MAAM,UAAU;AACjC,YAAM,WAAW,WAAW,IAAI,IAAI;AACpC,UAAI,UAAU;AACZ,iBAAS,KAAK,UAAU;AAAA,MAC1B,OAAO;AACL,mBAAW,IAAI,MAAM,CAAC,UAAU,CAAC;AAAA,MACnC;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,cACP,YACG,UACU;AACb,QAAM,YAAY,IAAI,IAAI,QAAQ;AAClC,SAAO,IAAI;AAAA,IACT,QAAQ,OAAO,CAAC,MAAM,UAAU,IAAI,EAAE,MAAM,CAAC,EAAE,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EAClE;AACF;AAMA,SAAS,0BACP,YACA,OACA,YACA,aACA,KACM;AACN,QAAM,cAAc,MAAM,QAAQ,UAAU;AAC5C,MAAI,CAAC,YAAa;AAElB,aAAW,QAAQ,YAAY,UAAU;AACvC,UAAM,eAAe,WAAW,IAAI,IAAI;AACxC,QAAI,CAAC,gBAAgB,aAAa,SAAS,EAAG;AAE9C,eAAW,eAAe,cAAc;AACtC,YAAM,aAAa,YAAY,KAAK,CAAC,MAAM,EAAE,IAAI,WAAW,CAAC;AAC7D,UAAI,CAAC,WAAY,KAAI,IAAI,WAAW;AAAA,IACtC;AAAA,EACF;AACF;AAiBO,SAAS,oBACd,OACA,eACU;AACV,QAAM,eAAe,cAAc,eAAe,OAAO,SAAS;AAClE,QAAM,eAAe,cAAc,eAAe,SAAS;AAC3D,QAAM,aAAa,yBAAyB,MAAM,OAAO;AACzD,QAAM,WAAW,oBAAI,IAAY;AAEjC,aAAW,eAAe,cAAc;AACtC;AAAA,MACE;AAAA,MAAa;AAAA,MAAO;AAAA,MACpB,CAAC,cAAc,cAAc,QAAQ;AAAA,MACrC;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,QAAQ;AAC5B;AAYO,SAAS,gBACd,OACA,SACa;AAEb,QAAM,SAAS,IAAI,IAAY,MAAM,eAAe,CAAC,CAAC;AAGtD,QAAM,eAAe,QAClB,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EACpC,IAAI,CAAC,MAAM,EAAE,IAAI;AAEpB,QAAM,aAAa,yBAAyB,MAAM,OAAO;AAEzD,aAAW,QAAQ,cAAc;AAC/B,UAAM,QAAQ,MAAM,QAAQ,IAAI;AAChC,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,MAAM,UAAU;AACjC,YAAM,eAAe,WAAW,IAAI,IAAI;AACxC,UAAI,gBAAgB,aAAa,SAAS,GAAG;AAC3C,eAAO,IAAI,IAAI;AAAA,MACjB;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAQA,eAAsB,mBACpB,MACA,aACA,uBACe;AACf,QAAM,eAAe,MAAM,UAAU,IAAI;AACzC,QAAM,aAAa,yBAAyB,aAAa,OAAO;AAGhE,QAAM,cAAc,oBAAI,IAAY;AACpC,aAAW,UAAU,uBAAuB;AAC1C,QAAI,OAAO,SAAS,WAAW,EAAG;AAClC,eAAW,KAAK,OAAO,UAAU;AAC/B,kBAAY,IAAI,QAAQ,EAAE,OAAO,CAAC;AAAA,IACpC;AAAA,EACF;AACA,QAAM,gBAAgB,IAAI;AAAA,IACxB,sBACG,OAAO,CAAC,MAAM,EAAE,SAAS,SAAS,CAAC,EACnC,IAAI,CAAC,MAAM,EAAE,UAAU;AAAA,EAC5B;AAEA,QAAM,YAAY,oBAAI,IAAY;AAClC,aAAW,QAAQ,aAAa;AAC9B,UAAM,SAAS,WAAW,IAAI,IAAI,KAAK,CAAC;AAExC,UAAM,oBAAoB,OAAO,SAAS,KACrC,OAAO,MAAM,CAAC,MAAM,cAAc,IAAI,CAAC,CAAC,KACxC,YAAY,IAAI,IAAI;AAEzB,QAAI,CAAC,kBAAmB,WAAU,IAAI,IAAI;AAAA,EAC5C;AAEA,QAAM,cAAc,EAAE,GAAG,cAAc,aAAa,MAAM,KAAK,SAAS,EAAE;AAC1E,QAAM,WAAW,MAAM,WAAW;AACpC;AAOA,SAAS,kBACP,aACA,OACa;AACb,QAAM,aAAa,oBAAI,IAAY;AAEnC,aAAW,UAAU,aAAa;AAChC,UAAM,cAAc,IAAI,IAAI,MAAM,QAAQ,OAAO,UAAU,GAAG,YAAY,CAAC,CAAC;AAC5E,eAAW,KAAK,OAAO,UAAU;AAC/B,YAAM,OAAO,QAAQ,EAAE,OAAO;AAC9B,UAAI,CAAC,YAAY,IAAI,IAAI,EAAG,YAAW,IAAI,IAAI;AAAA,IACjD;AAAA,EACF;AAEA,SAAO;AACT;AAMA,SAAS,eACP,OACA,YACA,aACU;AACV,QAAM,WAAW,oBAAI,IAAY;AAEjC,aAAW,QAAQ,OAAO;AACxB,UAAM,SAAS,WAAW,IAAI,IAAI;AAClC,QAAI,CAAC,OAAQ;AACb,eAAW,SAAS,QAAQ;AAC1B,YAAM,aAAa,YAAY,KAAK,CAAC,MAAM,EAAE,IAAI,KAAK,CAAC;AACvD,UAAI,CAAC,WAAY,UAAS,IAAI,KAAK;AAAA,IACrC;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,QAAQ;AAC5B;AAaO,SAAS,wBACd,aACA,OACA,YACU;AACV,QAAM,iBAAiB,cAAc,YAAY,OAAO,SAAS;AACjE,QAAM,eAAe,cAAc,YAAY,SAAS;AACxD,QAAM,aAAa,yBAAyB,MAAM,OAAO;AACzD,QAAM,aAAa,kBAAkB,aAAa,KAAK;AAEvD,SAAO,eAAe,YAAY,YAAY,CAAC,gBAAgB,YAAY,CAAC;AAC9E;AAUO,SAAS,mBACd,YACA,OACa;AACb,QAAM,SAAS,oBAAI,IAAY;AAC/B,QAAM,cAAc,MAAM,QAAQ,UAAU;AAC5C,MAAI,CAAC,YAAa,QAAO;AAEzB,QAAM,aAAa,yBAAyB,MAAM,OAAO;AAEzD,aAAW,QAAQ,YAAY,UAAU;AACvC,UAAM,eAAe,WAAW,IAAI,IAAI;AACxC,QAAI,gBAAgB,aAAa,SAAS,GAAG;AAC3C,aAAO,IAAI,IAAI;AAAA,IACjB;AAAA,EACF;AAEA,SAAO;AACT;AAOA,eAAsB,wBACpB,MACA,SACA,aACe;AACf,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,SAAS,SAAS,EAAG;AAEhC,IAAO,OAAO,KAAY,KAAK,GAAG,OAAO,UAAU,kCAA6B,CAAC;AACjF,UAAM,eAAe,MAAM,UAAU,IAAI;AACzC,UAAM,cAAc,aAAa,QAAQ,OAAO,UAAU,GAAG,YAAY,CAAC;AAC1E,eAAW,QAAQ,YAAa,aAAY,IAAI,IAAI;AAEpD,UAAM,kBAAkB,MAAM,OAAO,YAAY;AAAA,MAC/C,MAAM;AAAA,MACN,UAAU;AAAA,MACV,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACrC,CAAC;AAAA,EACH;AACF;;;ACxTA,OAAOC,YAAU;AAiBjB,eAAsB,aACpB,MACA,YACA,OACe;AACf,QAAM,cAAc,MAAM,QAAQ,UAAU;AAC5C,MAAI,CAAC,YAAa;AAElB,QAAM,cAAc,mBAAmB,YAAY,KAAK;AAExD,aAAW,QAAQ,YAAY,UAAU;AACvC,QAAI,YAAY,IAAI,IAAI,GAAG;AACzB,MAAO,OAAO,KAAY,IAAI,SAAS,IAAI,iCAAiC,CAAC;AAC7E;AAAA,IACF;AAEA,UAAM,WAAW,MAAM,MAAM,gBAAgB;AAAA,EAC/C;AAEA,QAAM,kBAAkB,MAAM,UAAU;AAC1C;AAOA,eAAsB,yBACpB,MACA,aACe;AACf,QAAM,eAAe,MAAM,UAAU,IAAI;AACzC,QAAM,aAAa,oBAAI,IAAY;AACnC,aAAW,SAAS,OAAO,OAAO,aAAa,OAAO,GAAG;AACvD,eAAW,QAAQ,MAAM,SAAU,YAAW,IAAI,IAAI;AAAA,EACxD;AAEA,aAAW,QAAQ,aAAa;AAC9B,QAAI,WAAW,IAAI,IAAI,EAAG;AAC1B,UAAM,WAAW,MAAM,MAAM,sBAAsB;AAAA,EACrD;AACF;AAQA,eAAe,WAAW,MAAc,MAAc,QAA+B;AACnF,QAAM,WAAWC,OAAK,KAAK,MAAM,cAAc,GAAG,IAAI,KAAK;AAC3D,QAAM,UAAU,MAAM,aAAa,QAAQ;AAC3C,MAAI,CAAC,QAAS;AAEd,QAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,MAAI,KAAK,aAAa,KAAM;AAE5B,QAAM,UAAU,QAAQ,QAAQ,SAAS,uBAAuB;AAChE,QAAM,YAAY,UAAU,OAAO;AACnC,EAAO,OAAO,UAAY,KAAK,aAAa,IAAI,QAAQ,MAAM,GAAG,CAAC;AACpE;;;AC7EA,SAAS,WAAAC,UAAS,YAAAC,kBAAgB;AAClC,OAAOC,YAAU;AACjB,SAAS,cAAAC,mBAAkB;AAY3B,eAAe,gBAAgB,MAAmC;AAChE,QAAM,cAAcC,OAAK,KAAK,MAAM,YAAY;AAChD,MAAI,CAACC,YAAW,WAAW,EAAG,QAAO,CAAC;AAEtC,QAAM,QAAQ,MAAMC,SAAQ,WAAW;AACvC,QAAM,QAAoB,CAAC;AAE3B,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,SAAS,KAAK,EAAG;AAE3B,UAAM,WAAWF,OAAK,KAAK,aAAa,IAAI;AAC5C,UAAM,UAAU,MAAMG,WAAS,UAAU,OAAO;AAChD,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AAEzC,QAAI,KAAK,SAAS,OAAO,KAAK,UAAU,YAAY,CAAC,KAAK,UAAU;AAClE,YAAM,KAAK;AAAA,QACT,MAAM,KAAK,QAAQ,SAAS,EAAE;AAAA,QAC9B,OAAO,KAAK;AAAA,QACZ;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,iBAAiB,MAAc,UAA2B;AACjE,QAAM,SAAS,KAAK,YAAY,MAAM,QAAQ;AAC9C,QAAM,QAAQ,KAAK,QAAQ,MAAM,QAAQ;AACzC,MAAI,WAAW,MAAM,UAAU,GAAI,QAAO;AAE1C,QAAM,cAAc,KAAK,QAAQ,MAAM,MAAM;AAC7C,SAAO,eAAe;AACxB;AAGA,SAAS,iBAAiB,MAAc,UAA2B;AACjE,QAAM,SAAS,KAAK,YAAY,MAAM,QAAQ;AAC9C,QAAM,QAAQ,KAAK,QAAQ,KAAK,QAAQ;AACxC,MAAI,WAAW,MAAM,UAAU,GAAI,QAAO;AAE1C,QAAM,cAAc,KAAK,QAAQ,KAAK,MAAM;AAC5C,SAAO,eAAe;AACxB;AAGA,SAAS,eAAe,MAAc,OAAe,KAAsB;AACzE,QAAM,SAAS,UAAU,KAAK,wBAAwB,KAAK,KAAK,QAAQ,CAAC,CAAC;AAC1E,QAAM,QAAQ,OAAO,KAAK,UAAU,wBAAwB,KAAK,KAAK,GAAG,CAAC;AAC1E,SAAO,UAAU;AACnB;AAGA,SAAS,iBAAiB,MAAc,OAAiD;AACvF,QAAM,UAAU,MAAM,QAAQ,uBAAuB,MAAM;AAC3D,QAAM,QAAQ,IAAI,OAAO,SAAS,IAAI;AACtC,QAAM,UAA4C,CAAC;AACnD,MAAI;AAEJ,UAAQ,QAAQ,MAAM,KAAK,IAAI,OAAO,MAAM;AAC1C,YAAQ,KAAK,EAAE,OAAO,MAAM,OAAO,KAAK,MAAM,QAAQ,MAAM,CAAC,EAAE,OAAO,CAAC;AAAA,EACzE;AAEA,SAAO;AACT;AAGA,SAAS,mBAAmB,MAAc,OAAe,KAAsB;AAC7E,MAAI,iBAAiB,MAAM,KAAK,EAAG,QAAO;AAC1C,MAAI,iBAAiB,MAAM,KAAK,EAAG,QAAO;AAC1C,SAAO,eAAe,MAAM,OAAO,GAAG;AACxC;AAMA,SAAS,aAAa,MAAc,QAAoB,WAA2B;AACjF,MAAI,SAAS;AACb,QAAM,YAAY,UAAU,YAAY;AAExC,aAAW,QAAQ,QAAQ;AACzB,QAAI,KAAK,MAAM,YAAY,MAAM,UAAW;AAE5C,UAAM,UAAU,iBAAiB,QAAQ,KAAK,KAAK;AAGnD,eAAW,KAAK,QAAQ,QAAQ,GAAG;AACjC,UAAI,CAAC,mBAAmB,QAAQ,EAAE,OAAO,EAAE,GAAG,EAAG;AACjD,eAAS,OAAO,MAAM,GAAG,EAAE,KAAK,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,OAAO,OAAO,MAAM,EAAE,GAAG;AAAA,IAC3F;AAAA,EACF;AAEA,SAAO;AACT;AAYA,eAAsB,aACpB,MACA,cACA,UACiB;AACjB,QAAM,aAAa,MAAM,gBAAgB,IAAI;AAC7C,MAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,MAAI,YAAY;AAGhB,eAAa,MAAM,qBAAqB,YAAY,YAAY;AAGhE,eAAa,MAAM,oBAAoB,YAAY,QAAQ;AAE3D,MAAI,YAAY,GAAG;AACjB,IAAO,OAAO,aAAa,IAAI,qBAAqB,SAAS,UAAU,CAAC;AAAA,EAC1E;AAEA,SAAO;AACT;AAGA,eAAe,qBACb,YACA,cACiB;AACjB,MAAI,QAAQ;AAEZ,aAAW,QAAQ,YAAY;AAC7B,QAAI,CAAC,aAAa,SAAS,KAAK,IAAI,EAAG;AACvC,UAAM,UAAU,MAAM,SAAS,MAAM,UAAU;AAC/C,QAAI,QAAS;AAAA,EACf;AAEA,SAAO;AACT;AAGA,eAAe,oBACb,YACA,UACiB;AACjB,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,QAAM,YAAY,WAAW,OAAO,CAAC,MAAM,SAAS,SAAS,EAAE,IAAI,CAAC;AACpE,MAAI,UAAU,WAAW,EAAG,QAAO;AAEnC,MAAI,QAAQ;AAEZ,aAAW,QAAQ,YAAY;AAE7B,QAAI,SAAS,SAAS,KAAK,IAAI,EAAG;AAElC,UAAM,UAAU,MAAMA,WAAS,KAAK,UAAU,OAAO;AACrD,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,UAAM,SAAS,aAAa,MAAM,WAAW,KAAK,KAAK;AAEvD,QAAI,WAAW,MAAM;AACnB,YAAM,aAAa,QAAQ,QAAQ,MAAM,MAAM;AAC/C,YAAM,YAAY,KAAK,UAAU,UAAU;AAC3C;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGA,eAAe,SAAS,MAAgB,YAA0C;AAChF,QAAM,UAAU,MAAMA,WAAS,KAAK,UAAU,OAAO;AACrD,QAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,QAAM,SAAS,aAAa,MAAM,YAAY,KAAK,KAAK;AAExD,MAAI,WAAW,KAAM,QAAO;AAE5B,QAAM,aAAa,QAAQ,QAAQ,MAAM,MAAM;AAC/C,QAAM,YAAY,KAAK,UAAU,UAAU;AAC3C,SAAO;AACT;;;AC7MA,SAAS,WAAAC,gBAAe;AACxB,OAAOC,YAAU;AAUjB,eAAsB,cAAc,MAA6B;AAC/D,EAAO,OAAO,KAAY,KAAK,qBAAqB,CAAC;AAErD,QAAM,eAAeC,OAAK,KAAK,MAAM,YAAY;AACjD,QAAM,cAAcA,OAAK,KAAK,MAAM,WAAW;AAC/C,QAAM,WAAW,MAAM,qBAAqB,YAAY;AACxD,QAAM,UAAU,MAAM,qBAAqB,WAAW;AAEtD,WAAS,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AACtD,UAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AAErD,QAAM,eAAe,kBAAkB,UAAU,OAAO;AACxD,QAAM,YAAYA,OAAK,KAAK,MAAM,UAAU;AAC5C,QAAM,YAAY,WAAW,YAAY;AAEzC,QAAM,QAAQ,SAAS,SAAS,QAAQ;AACxC,EAAO,OAAO,KAAY,QAAQ,sBAAsB,KAAK,SAAS,CAAC;AACzE;AAeA,eAAsB,cAAc,SAAyC;AAC3E,MAAI;AACJ,MAAI;AACF,YAAQ,MAAMC,SAAQ,OAAO;AAAA,EAC/B,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAAyB,CAAC;AAChC,aAAW,QAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,GAAG;AACzD,UAAM,UAAU,MAAM,aAAaD,OAAK,KAAK,SAAS,IAAI,CAAC;AAC3D,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,YAAQ,KAAK,EAAE,MAAM,KAAK,QAAQ,SAAS,EAAE,GAAG,KAAK,CAAC;AAAA,EACxD;AACA,SAAO;AACT;AASA,eAAsB,qBACpB,cACwB;AACxB,QAAM,UAAU,MAAM,cAAc,YAAY;AAChD,SAAO,QACJ,OAAO,CAAC,EAAE,KAAK,MAAM,KAAK,SAAS,OAAO,KAAK,UAAU,YAAY,CAAC,KAAK,QAAQ,EACnF,IAAI,CAAC,EAAE,MAAM,KAAK,OAAO;AAAA,IACxB,OAAO,KAAK;AAAA,IACZ;AAAA,IACA,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAAA,EAC7D,EAAE;AACN;AAGA,SAAS,eAAe,MAAsB;AAC5C,SAAO,KAAK,QAAQ,qBAAqB,IAAI;AAC/C;AAOA,SAAS,kBAAkB,UAAyB,SAAgC;AAClF,QAAM,QAAQ,CAAC,oBAAoB,IAAI,eAAe,EAAE;AAExD,aAAW,QAAQ,UAAU;AAC3B,UAAM,KAAK,SAAS,KAAK,IAAI,IAAI,KAAK,KAAK,eAAU,eAAe,KAAK,OAAO,CAAC,EAAE;AAAA,EACrF;AAEA,MAAI,QAAQ,SAAS,GAAG;AACtB,UAAM,KAAK,IAAI,oBAAoB,EAAE;AACrC,eAAW,QAAQ,SAAS;AAC1B,YAAM,KAAK,SAAS,KAAK,IAAI,IAAI,KAAK,KAAK,eAAU,eAAe,KAAK,OAAO,CAAC,EAAE;AAAA,IACrF;AAAA,EACF;AAEA,QAAM,QAAQ,SAAS,SAAS,QAAQ;AACxC,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,IAAI,KAAK,uBAAsB,oBAAI,KAAK,GAAE,YAAY,CAAC,GAAG;AACrE,QAAM,KAAK,EAAE;AAEb,SAAO,MAAM,KAAK,IAAI;AACxB;;;AChGA,IAAM,oBAAoB;AAiBnB,SAAS,2BAAmC;AACjD,QAAM,MAAM,QAAQ,IAAI,qBAAqB;AAC7C,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,SAAS,OAAO,SAAS,KAAK,EAAE;AACtC,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,SAAO;AACT;AAaO,SAAS,6BACd,SACA,QACQ;AACR,QAAM,SAAS,yBAAyB;AACxC,QAAM,WAAW,OAAO,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,QAAQ,QAAQ,CAAC;AAEpE,MAAI,YAAY,QAAQ;AACtB,WAAO,aAAa,MAAM;AAAA,EAC5B;AAEA,QAAM,YAAY,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,OAAO,MAAM,CAAC;AAChE,iBAAe,SAAS,UAAU,OAAO,QAAQ,WAAW,MAAM;AAElE,QAAM,UAAU,OAAO;AAAA,IAAI,CAAC,MAC1B,EAAE,QAAQ,SAAS,YACf,EAAE,GAAG,GAAG,SAAS,EAAE,QAAQ,MAAM,GAAG,SAAS,IAAI,kBAAkB,IACnE;AAAA,EACN;AACA,SAAO,aAAa,OAAO;AAC7B;AAMA,SAAS,YAAY,SAAyB;AAC5C,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,QAAM,QAAQ,OAAO,MAAM,MAAM,EAAE;AACnC,SAAO,MACJ,IAAI,CAACE,OAAM,MAAM,GAAG,OAAO,IAAI,CAAC,EAAE,SAAS,KAAK,CAAC,MAAMA,KAAI,EAAE,EAC7D,KAAK,IAAI;AACd;AAGA,SAAS,aAAa,QAA+B;AACnD,SAAO,OACJ,IAAI,CAAC,MAAM,eAAe,EAAE,IAAI;AAAA;AAAA,EAAW,YAAY,EAAE,OAAO,CAAC,EAAE,EACnE,KAAK,MAAM;AAChB;AAGA,SAAS,eACP,SACA,UACA,aACA,WACA,QACM;AACN,EAAO;AAAA,IACL;AAAA,IACO;AAAA,MACL,gCAAgC,OAAO,MAAM,SAAS,eAAe,CAAC,iBACjE,WAAW,yBAAyB,OAAO,eAAe,CAAC,mDAChC,UAAU,eAAe,CAAC,qBAC3C,qBAAqB;AAAA,IACtC;AAAA,EACF;AACF;;;AC1GA,SAAS,WAAAC,gBAAe;AACxB,OAAOC,YAAU;AAKjB,IAAM,yBAAyB;AAG/B,IAAM,oBAAoB,CAAC,SAAS,MAAM;AASnC,SAAS,gBACd,aACA,cACA,MACM;AACN,cAAY,OAAO;AACnB,cAAY,UAAU,gBAAgB,YAAY;AACpD;AAWA,SAAS,gBAAgB,OAAyB;AAChD,QAAM,UAAoB,CAAC;AAC3B,QAAM,OAAO,QAAQ,KAAK;AAE1B,MAAI,SAAS,OAAO;AAClB,YAAQ,KAAK,IAAI;AAAA,EACnB;AAEA,QAAM,YAAY,kBAAkB,KAAK;AACzC,MAAI,WAAW;AACb,YAAQ,KAAK,SAAS;AAAA,EACxB;AAEA,QAAM,eAAe,qBAAqB,KAAK;AAC/C,MAAI,cAAc;AAChB,YAAQ,KAAK,YAAY;AAAA,EAC3B;AAEA,SAAO;AACT;AAQA,SAAS,kBAAkB,OAA8B;AACvD,aAAW,eAAe,mBAAmB;AAC3C,UAAM,QAAQ,MAAM,YAAY,EAAE,QAAQ,WAAW;AACrD,QAAI,UAAU,GAAI;AAElB,UAAM,SAAS,MAAM,MAAM,GAAG,KAAK;AACnC,UAAM,QAAQ,MAAM,MAAM,QAAQ,YAAY,MAAM;AACpD,UAAM,sBAAsB,MAAM,MAAM,OAAO,QAAQ,YAAY,MAAM;AACzE,WAAO,GAAG,KAAK,GAAG,mBAAmB,GAAG,MAAM;AAAA,EAChD;AACA,SAAO;AACT;AAQA,SAAS,qBAAqB,OAA8B;AAC1D,QAAM,QAAQ,MAAM,MAAM,KAAK;AAC/B,MAAI,MAAM,SAAS,uBAAwB,QAAO;AAElD,QAAM,eAAe,MAAM,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,YAAY,CAAC,EAAE,KAAK,EAAE;AACjE,MAAI,iBAAiB,MAAO,QAAO;AAEnC,SAAO;AACT;AAQA,eAAsB,YAAY,MAA6B;AAC7D,QAAM,eAAeC,OAAK,KAAK,MAAM,YAAY;AACjD,QAAM,QAAQ,MAAM,iBAAiB,YAAY;AAEjD,QAAM,YAAY,gBAAgB,KAAK;AACvC,QAAM,UAAU,gBAAgB,SAAS;AAEzC,QAAM,YAAYA,OAAK,KAAK,MAAM,QAAQ,GAAG,OAAO;AACtD;AAcA,eAAe,iBAAiB,cAA2C;AACzE,MAAI;AACJ,MAAI;AACF,YAAQ,MAAMC,SAAQ,YAAY;AAAA,EACpC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,QAAoB,CAAC;AAC3B,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,SAAS,KAAK,EAAG;AAE3B,UAAM,UAAU,MAAM,aAAaD,OAAK,KAAK,cAAc,IAAI,CAAC;AAChE,QAAI,CAAC,QAAS;AAEd,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,QAAI,KAAK,SAAU;AAEnB,UAAM,OAAO,KAAK,QAAQ,SAAS,EAAE;AACrC,UAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAC5D,UAAM,OAAO,MAAM,QAAQ,KAAK,IAAI,IAAK,KAAK,OAAoB,CAAC;AACnE,UAAM,KAAK,EAAE,MAAM,OAAO,KAAK,CAAC;AAAA,EAClC;AAEA,SAAO;AACT;AAOA,SAAS,gBAAgB,OAA4C;AACnE,QAAM,SAAS,oBAAI,IAAwB;AAE3C,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,KAAK,WAAW,GAAG;AAC1B,oBAAc,QAAQ,iBAAiB,IAAI;AAC3C;AAAA,IACF;AAEA,eAAW,OAAO,KAAK,MAAM;AAC3B,oBAAc,QAAQ,KAAK,IAAI;AAAA,IACjC;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,cAAc,QAAiC,KAAa,MAAsB;AACzF,QAAM,WAAW,OAAO,IAAI,GAAG;AAC/B,MAAI,UAAU;AACZ,aAAS,KAAK,IAAI;AAAA,EACpB,OAAO;AACL,WAAO,IAAI,KAAK,CAAC,IAAI,CAAC;AAAA,EACxB;AACF;AAOA,SAAS,gBAAgB,WAA4C;AACnE,QAAM,QAAkB,CAAC,oBAAoB,EAAE;AAE/C,QAAM,aAAa,CAAC,GAAG,UAAU,KAAK,CAAC,EAAE,KAAK,CAAC,GAAG,MAAM;AAEtD,QAAI,MAAM,gBAAiB,QAAO;AAClC,QAAI,MAAM,gBAAiB,QAAO;AAClC,WAAO,EAAE,cAAc,CAAC;AAAA,EAC1B,CAAC;AAED,aAAW,OAAO,YAAY;AAC5B,UAAM,QAAQ,UAAU,IAAI,GAAG,KAAK,CAAC;AACrC,UAAM,KAAK,MAAM,GAAG,IAAI,EAAE;AAC1B,eAAW,QAAQ,MAAM,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC,GAAG;AACvE,YAAM,KAAK,OAAO,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI;AAAA,IAC/C;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;;;ACpMA,SAAS,YAAAE,YAAU,WAAAC,gBAAe;AAClC,SAAS,cAAAC,mBAAkB;AAC3B,OAAOC,YAAU;;;ACLjB,SAAS,cAAAC,mBAAkB;AAQpB,SAAS,cAAc,MAAsB;AAClD,SAAOC,YAAW,QAAQ,EAAE,OAAO,MAAM,MAAM,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AAC5E;AAWO,SAAS,gBAAgB,MAAwB;AACtD,QAAM,aAAa,kBAAkB,IAAI;AACzC,MAAI,WAAW,WAAW,EAAG,QAAO,CAAC;AAErC,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS;AAEb,aAAW,aAAa,YAAY;AAClC,eAAW,SAAS,wBAAwB,SAAS,GAAG;AACtD,eAAS,gBAAgB,QAAQ,OAAO,MAAM;AAAA,IAChD;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,EAAG,QAAO,KAAK,MAAM;AACzC,SAAO,sBAAsB,MAAM;AACrC;AAGA,SAAS,gBAAgB,QAAgB,WAAmB,QAA0B;AACpF,QAAM,YAAY,SAAS,GAAG,MAAM;AAAA;AAAA,EAAO,SAAS,KAAK;AACzD,MAAI,UAAU,UAAU,mBAAoB,QAAO;AAEnD,MAAI,OAAO,SAAS,GAAG;AACrB,WAAO,KAAK,MAAM;AAClB,WAAO;AAAA,EACT;AAEA,SAAO,KAAK,SAAS;AACrB,SAAO;AACT;AAOA,SAAS,sBAAsB,QAA4B;AACzD,MAAI,OAAO,SAAS,EAAG,QAAO;AAC9B,QAAM,OAAO,OAAO,OAAO,SAAS,CAAC;AACrC,MAAI,KAAK,UAAU,gBAAiB,QAAO;AAC3C,QAAM,WAAW,OAAO,OAAO,SAAS,CAAC;AAEzC,MAAI,SAAS,SAAS,KAAK,SAAS,IAAI,gBAAiB,QAAO;AAChE,QAAM,SAAS,OAAO,MAAM,GAAG,EAAE;AACjC,SAAO,KAAK,GAAG,QAAQ;AAAA;AAAA,EAAO,IAAI,EAAE;AACpC,SAAO;AACT;AAGA,SAAS,kBAAkB,MAAwB;AACjD,SAAO,KACJ,MAAM,QAAQ,EACd,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,EACnB,OAAO,CAAC,MAAM,EAAE,SAAS,CAAC;AAC/B;AAOA,SAAS,wBAAwB,WAA6B;AAC5D,MAAI,UAAU,UAAU,gBAAiB,QAAO,CAAC,SAAS;AAE1D,QAAM,YAAY,UAAU,MAAM,eAAe;AACjD,QAAM,SAAmB,CAAC;AAC1B,MAAI,SAAS;AAEb,aAAW,YAAY,WAAW;AAChC,SAAK,SAAS,MAAM,UAAU,SAAS,mBAAmB,OAAO,SAAS,GAAG;AAC3E,aAAO,KAAK,OAAO,KAAK,CAAC;AACzB,eAAS;AAAA,IACX,OAAO;AACL,eAAS,SAAS,GAAG,MAAM,IAAI,QAAQ,KAAK;AAAA,IAC9C;AAAA,EACF;AAEA,MAAI,OAAO,SAAS,EAAG,QAAO,KAAK,OAAO,KAAK,CAAC;AAChD,SAAO,OAAO,QAAQ,OAAO;AAC/B;AAGA,SAAS,QAAQ,MAAwB;AACvC,MAAI,KAAK,UAAU,gBAAiB,QAAO,CAAC,IAAI;AAChD,QAAM,SAAmB,CAAC;AAC1B,WAAS,QAAQ,GAAG,QAAQ,KAAK,QAAQ,SAAS,iBAAiB;AACjE,WAAO,KAAK,KAAK,MAAM,OAAO,QAAQ,eAAe,CAAC;AAAA,EACxD;AACA,SAAO;AACT;AAwBO,SAAS,eACd,OACA,YAC2B;AAC3B,MAAI,WAAW,WAAW,EAAG,QAAO,CAAC;AACrC,QAAM,aAAa,SAAS,KAAK;AACjC,MAAI,WAAW,WAAW,GAAG;AAC3B,WAAO,WAAW,IAAI,CAAC,eAAe,EAAE,WAAW,OAAO,UAAU,UAAU,EAAE;AAAA,EAClF;AAEA,QAAM,OAAO,WAAW,IAAI,CAAC,MAAM,SAAS,EAAE,IAAI,CAAC;AACnD,QAAM,QAAQ,iBAAiB,IAAI;AACnC,SAAO,gBAAgB,YAAY,MAAM,YAAY,KAAK;AAC5D;AAGA,SAAS,gBACP,YACA,MACA,YACA,OAC2B;AAC3B,QAAM,SAAS,WAAW,IAAI,CAAC,WAAW,UAAU;AAClD,UAAM,UAAU,UAAU,YAAY,KAAK,KAAK,GAAG,KAAK;AACxD,WAAO,EAAE,WAAW,OAAO,UAAU,UAAU,YAAY,kBAAkB;AAAA,EAC/E,CAAC;AACD,SAAO,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AACvC,SAAO;AACT;AAGA,SAAS,SAAS,MAAwB;AACxC,SAAO,KAAK,YAAY,EAAE,MAAM,YAAY,KAAK,CAAC;AACpD;AAYA,SAAS,iBAAiB,MAA+B;AACvD,QAAM,UAAU,oBAAI,IAAoB;AACxC,MAAI,WAAW;AACf,aAAW,UAAU,MAAM;AACzB,gBAAY,OAAO;AACnB,UAAM,SAAS,IAAI,IAAI,MAAM;AAC7B,eAAW,QAAQ,OAAQ,SAAQ,IAAI,OAAO,QAAQ,IAAI,IAAI,KAAK,KAAK,CAAC;AAAA,EAC3E;AACA,QAAM,YAAY,KAAK;AACvB,QAAM,YAAY,YAAY,IAAI,WAAW,YAAY;AACzD,SAAO,EAAE,SAAS,WAAW,UAAU;AACzC;AAGA,IAAM,UAAU;AAEhB,IAAM,SAAS;AAEf,IAAM,oBAAoB;AAG1B,SAAS,UAAU,YAAsB,WAAqB,OAA4B;AACxF,MAAI,UAAU,WAAW,KAAK,MAAM,cAAc,EAAG,QAAO;AAC5D,QAAM,WAAW,WAAW,SAAS;AACrC,QAAM,cAAc,UAAU,UAAU,MAAM,aAAa;AAE3D,MAAI,QAAQ;AACZ,aAAW,QAAQ,YAAY;AAC7B,UAAM,KAAK,SAAS,IAAI,IAAI,KAAK;AACjC,QAAI,OAAO,EAAG;AACd,UAAM,MAAM,UAAU,MAAM,QAAQ,IAAI,IAAI,KAAK,GAAG,MAAM,SAAS;AACnE,UAAM,YAAY,MAAM,UAAU;AAClC,UAAM,cAAc,KAAK,WAAW,IAAI,SAAS,SAAS;AAC1D,aAAS,OAAO,YAAY;AAAA,EAC9B;AACA,SAAO;AACT;AAGA,SAAS,UAAU,cAAsB,WAA2B;AAClE,QAAM,YAAY,YAAY,eAAe;AAC7C,QAAM,cAAc,eAAe;AAEnC,SAAO,KAAK,IAAI,IAAI,YAAY,WAAW;AAC7C;AAGA,SAAS,WAAW,QAAuC;AACzD,QAAM,SAAS,oBAAI,IAAoB;AACvC,aAAW,SAAS,OAAQ,QAAO,IAAI,QAAQ,OAAO,IAAI,KAAK,KAAK,KAAK,CAAC;AAC1E,SAAO;AACT;;;ADnNA,IAAM,gBAAgB;AA4Cf,SAAS,iBAAiB,GAAa,GAAqB;AACjE,MAAI,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAG,QAAO;AAEpD,MAAI,MAAM;AACV,MAAI,OAAO;AACX,MAAI,OAAO;AACX,WAAS,IAAI,GAAG,IAAI,EAAE,QAAQ,KAAK;AACjC,WAAO,EAAE,CAAC,IAAI,EAAE,CAAC;AACjB,YAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;AAClB,YAAQ,EAAE,CAAC,IAAI,EAAE,CAAC;AAAA,EACpB;AAEA,MAAI,SAAS,KAAK,SAAS,EAAG,QAAO;AACrC,SAAO,OAAO,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,IAAI;AAChD;AAGO,SAAS,SACd,UACA,OACA,GACkB;AAClB,QAAM,SAAS,MAAM,QAAQ,IAAI,CAAC,WAAW;AAAA,IAC3C;AAAA,IACA,OAAO,iBAAiB,UAAU,MAAM,MAAM;AAAA,EAChD,EAAE;AACF,SAAO,KAAK,CAAC,MAAM,UAAU,MAAM,QAAQ,KAAK,KAAK;AACrD,SAAO,OAAO,MAAM,GAAG,CAAC,EAAE,IAAI,CAAC,SAAS,KAAK,KAAK;AACpD;AAGO,SAAS,eACd,UACA,QACA,GACsD;AACtD,QAAM,SAAS,OAAO,IAAI,CAAC,WAAW;AAAA,IACpC;AAAA,IACA,OAAO,iBAAiB,UAAU,MAAM,MAAM;AAAA,EAChD,EAAE;AACF,SAAO,KAAK,CAAC,MAAM,UAAU,MAAM,QAAQ,KAAK,KAAK;AACrD,SAAO,OAAO,MAAM,GAAG,CAAC;AAC1B;AAGA,eAAsB,mBAAmB,MAA8C;AACrF,QAAM,WAAWC,OAAK,KAAK,MAAM,eAAe;AAChD,MAAI,CAACC,YAAW,QAAQ,EAAG,QAAO;AAClC,QAAM,MAAM,MAAMC,WAAS,UAAU,OAAO;AAC5C,SAAO,KAAK,MAAM,GAAG;AACvB;AAGA,eAAsB,oBAAoB,MAAc,OAAsC;AAC5F,QAAM,WAAWF,OAAK,KAAK,MAAM,eAAe;AAChD,QAAM,YAAY,UAAU,KAAK,UAAU,OAAO,MAAM,CAAC,CAAC;AAC5D;AAMA,eAAsB,kBACpB,MACA,UACkE;AAClE,QAAM,QAAQ,MAAM,gBAAgB,MAAM,CAAC,MAAM,EAAE,QAAQ,SAAS,CAAC;AACrE,MAAI,CAAC,MAAO,QAAO,CAAC;AAEpB,QAAM,WAAW,MAAM,YAAY,EAAE,MAAM,QAAQ;AACnD,SAAO,SAAS,UAAU,OAAO,eAAe,EAAE,IAAI,CAAC,WAAW;AAAA,IAChE,MAAM,MAAM;AAAA,IACZ,OAAO,MAAM;AAAA,IACb,SAAS,MAAM;AAAA,EACjB,EAAE;AACJ;AAMA,eAAsB,mBACpB,MACA,UACA,GAC+D;AAC/D,QAAM,QAAQ,MAAM,gBAAgB,MAAM,CAAC,MAAM,QAAQ,EAAE,UAAU,EAAE,OAAO,SAAS,CAAC,CAAC;AACzF,MAAI,CAAC,MAAO,QAAO,CAAC;AACpB,QAAM,WAAW,MAAM,YAAY,EAAE,MAAM,QAAQ;AACnD,SAAO,eAAe,UAAU,MAAM,UAAU,CAAC,GAAG,CAAC;AACvD;AAOA,eAAe,gBACb,MACA,YACgC;AAChC,QAAM,QAAQ,MAAM,mBAAmB,IAAI;AAC3C,MAAI,CAAC,SAAS,CAAC,WAAW,KAAK,EAAG,QAAO;AACzC,QAAM,cAAc,sBAAsB;AAC1C,MAAI,MAAM,UAAU,aAAa;AAC/B,4BAAwB,MAAM,OAAO,WAAW;AAChD,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGA,eAAe,mBAAmB,MAAqC;AACrE,QAAM,UAAwB,CAAC;AAC/B,aAAW,OAAO,CAAC,cAAc,WAAW,GAAG;AAC7C,UAAM,SAASA,OAAK,KAAK,MAAM,GAAG;AAClC,QAAI;AACJ,QAAI;AACF,cAAQ,MAAMG,SAAQ,MAAM;AAAA,IAC9B,QAAQ;AACN;AAAA,IACF;AACA,eAAW,QAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,GAAG;AACzD,YAAM,SAAS,MAAM,eAAe,QAAQ,IAAI;AAChD,UAAI,OAAQ,SAAQ,KAAK,MAAM;AAAA,IACjC;AAAA,EACF;AACA,SAAO;AACT;AAGA,eAAe,eAAe,QAAgB,MAA0C;AACtF,QAAM,UAAU,MAAM,aAAaH,OAAK,KAAK,QAAQ,IAAI,CAAC;AAC1D,QAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB,OAAO;AAC/C,MAAI,KAAK,YAAY,OAAO,KAAK,UAAU,SAAU,QAAO;AAC5D,SAAO;AAAA,IACL,MAAM,KAAK,QAAQ,SAAS,EAAE;AAAA,IAC9B,OAAO,KAAK;AAAA,IACZ,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAAA,IAC3D;AAAA,EACF;AACF;AAGA,SAAS,mBAAmB,QAA4B;AACtD,SAAO,OAAO,UACV,GAAG,OAAO,KAAK;AAAA;AAAA,EAAO,OAAO,OAAO,KACpC,OAAO;AACb;AAMA,eAAe,WACb,SACA,cAC2B;AAC3B,QAAM,WAAW,YAAY;AAC7B,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,QAA0B,CAAC;AAEjC,aAAW,UAAU,SAAS;AAC5B,QAAI,CAAC,aAAa,IAAI,OAAO,IAAI,EAAG;AACpC,UAAM,SAAS,MAAM,SAAS,MAAM,mBAAmB,MAAM,CAAC;AAC9D,UAAM,KAAK;AAAA,MACT,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,MACd,SAAS,OAAO;AAAA,MAChB;AAAA,MACA,WAAW;AAAA,IACb,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,IAAM,oBAAoB,oBAAI,IAAY;AAG1C,SAAS,wBAAwB,aAAqB,aAA2B;AAC/E,QAAM,MAAM,GAAG,WAAW,SAAI,WAAW;AACzC,MAAI,kBAAkB,IAAI,GAAG,EAAG;AAChC,oBAAkB,IAAI,GAAG;AACzB,EAAO;AAAA,IACL;AAAA,IACO;AAAA,MACL,mCAAmC,WAAW,oCAAoC,WAAW;AAAA,IAE/F;AAAA,EACF;AACF;AAQO,SAAS,wBAAgC;AAC9C,QAAM,eAAe,sBAAsB;AAC3C,QAAM,kBAAkB,QAAQ,IAAI,yBAAyB,KAAK;AAClE,MAAI,oBAAoB,iBAAiB,YAAY,iBAAiB,WAAW;AAC/E,WAAO;AAAA,EACT;AACA,SAAO,iBAAiB,YAAY,KAAK,iBAAiB;AAC5D;AAGA,SAAS,aACP,UACA,OACA,WACkB;AAClB,QAAM,SAAS,oBAAI,IAA4B;AAC/C,aAAW,SAAS,UAAU;AAC5B,QAAI,UAAU,IAAI,MAAM,IAAI,EAAG,QAAO,IAAI,MAAM,MAAM,KAAK;AAAA,EAC7D;AACA,aAAW,SAAS,OAAO;AACzB,WAAO,IAAI,MAAM,MAAM,KAAK;AAAA,EAC9B;AACA,SAAO,MAAM,KAAK,OAAO,OAAO,CAAC;AACnC;AAMA,eAAe,uBACb,SACA,UACA,UACgC;AAChC,QAAM,YAAY,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACpD,QAAM,gBAAgB,iBAAiB,SAAS,OAAO,CAAC,MAAM,UAAU,IAAI,EAAE,IAAI,CAAC,CAAC;AACpF,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,QAA+B,CAAC;AAEtC,aAAW,UAAU,SAAS;AAC5B,UAAM,aAAa,MAAM,kBAAkB,QAAQ,eAAe,UAAU,GAAG;AAC/E,UAAM,KAAK,GAAG,UAAU;AAAA,EAC1B;AACA,SAAO;AACT;AAMA,eAAe,kBACb,QACA,eACA,UACA,KACgC;AAChC,QAAM,WAAW,YAAY;AAC7B,QAAM,aAAa,gBAAgB,OAAO,IAAI;AAC9C,QAAM,MAA6B,CAAC;AAEpC,WAAS,IAAI,GAAG,IAAI,WAAW,QAAQ,KAAK;AAC1C,UAAM,OAAO,WAAW,CAAC;AACzB,UAAM,cAAc,cAAc,IAAI;AACtC,UAAM,SAAS,kBAAkB,eAAe,OAAO,MAAM,GAAG,aAAa,QAAQ;AACrF,QAAI,QAAQ;AACV,UAAI,KAAK,EAAE,GAAG,QAAQ,OAAO,OAAO,MAAM,CAAC;AAC3C;AAAA,IACF;AACA,UAAM,SAAS,MAAM,SAAS,MAAM,IAAI;AACxC,QAAI,KAAK;AAAA,MACP,MAAM,OAAO;AAAA,MAAM,OAAO,OAAO;AAAA,MAAO,YAAY;AAAA,MACpD;AAAA,MAAa;AAAA,MAAM;AAAA,MAAQ,WAAW;AAAA,IACxC,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAGA,SAAS,iBAAiB,QAAiE;AACzF,QAAM,QAAQ,oBAAI,IAAiC;AACnD,aAAW,SAAS,OAAQ,OAAM,IAAI,SAAS,MAAM,MAAM,MAAM,UAAU,GAAG,KAAK;AACnF,SAAO;AACT;AAGA,SAAS,SAAS,MAAc,YAA4B;AAC1D,SAAO,GAAG,IAAI,IAAI,UAAU;AAC9B;AAGA,SAAS,kBACP,OACA,MACA,YACA,aACA,UAC4B;AAC5B,MAAI,SAAU,QAAO;AACrB,QAAM,WAAW,MAAM,IAAI,SAAS,MAAM,UAAU,CAAC;AACrD,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,SAAS,gBAAgB,cAAc,WAAW;AAC3D;AAMA,eAAsB,iBAAiB,MAAc,cAAuC;AAC1F,QAAM,UAAU,MAAM,mBAAmB,IAAI;AAC7C,QAAM,YAAY,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACpD,QAAM,iBAAiB,sBAAsB;AAC7C,QAAM,gBAAgB,MAAM,mBAAmB,IAAI;AACnD,QAAM,eAAe,QAAQ,iBAAiB,cAAc,UAAU,cAAc;AACpF,QAAM,UAAU,IAAI,IAAI,aAAa,OAAO,CAAC,SAAS,UAAU,IAAI,IAAI,CAAC,CAAC;AAC1E,QAAM,kBAAkB,eAAe,CAAC,IAAI,eAAe,WAAW,CAAC;AACvE,QAAM,iBAAiB,eAAe,CAAC,IAAI,eAAe,UAAU,CAAC;AAMrE,QAAM,eAAe,aAAa,aAAa;AAC/C,MAAI,CAAC,iBAAiB,gBAAiB,gBAAgB,UAAU,OAAO,GAAI;AAC1E,eAAW,UAAU,QAAS,SAAQ,IAAI,OAAO,IAAI;AAAA,EACvD;AAEA,MAAI,CAAC,mBAAmB,cAAc,SAAS,iBAAiB,gBAAgB,SAAS,GAAG;AAC1F;AAAA,EACF;AAEA,QAAM,eAAe,MAAM,WAAW,SAAS,OAAO;AACtD,QAAM,gBAAgB,aAAa,iBAAiB,cAAc,SAAS;AAC3E,QAAM,eAAe,MAAM,uBAAuB,SAAS,gBAAgB,YAAY;AAEvF,QAAM,sBAAsB,MAAM,gBAAgB,eAAe,YAAY;AAC/E;AAGA,eAAe,sBACb,MACA,gBACA,SACA,QACe;AACf,QAAM,aAAa,QAAQ,CAAC,GAAG,OAAO,UAAU,OAAO,CAAC,GAAG,OAAO,UAAU;AAC5E,QAAM,QAAwB;AAAA,IAC5B,SAAS;AAAA,IACT,OAAO;AAAA,IACP;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACA,QAAM,oBAAoB,MAAM,KAAK;AACrC,EAAO;AAAA,IACL;AAAA,IACO,IAAI,uBAAuB,QAAQ,MAAM,WAAW,OAAO,MAAM,WAAW;AAAA,EACrF;AACF;AAGA,SAAS,aAAa,OAAuC;AAC3D,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,MAAM,QAAQ,WAAW,MAAM,CAAC,MAAM,UAAU,MAAM,OAAO,WAAW;AACjF;AAGA,SAAS,mBACP,cACA,SACA,iBACA,gBACA,WACS;AACT,MAAI,aAAc,QAAO;AACzB,MAAI,QAAQ,OAAO,EAAG,QAAO;AAC7B,MAAI,CAAC,gBAAgB,MAAM,CAAC,MAAM,UAAU,IAAI,EAAE,IAAI,CAAC,EAAG,QAAO;AACjE,MAAI,CAAC,eAAe,MAAM,CAAC,MAAM,UAAU,IAAI,EAAE,IAAI,CAAC,EAAG,QAAO;AAEhE,MAAI,gBAAgB,SAAS,KAAK,eAAe,WAAW,KAAK,UAAU,OAAO,EAAG,QAAO;AAC5F,SAAO;AACT;;;AEhcA,SAAS,WAAAI,WAAS,YAAAC,kBAAgB;AAClC,SAAS,cAAAC,mBAAkB;AAC3B,OAAOC,YAAU;AAwBjB,IAAM,kBAAkB;AAGxB,IAAMC,oBAAmB;AAGzB,IAAM,mBAAmB;AAYzB,SAAS,qBAAqB,SAAiB,SAA8B;AAC3E,QAAM,UAAuB,CAAC;AAC9B,QAAM,QAAQ,QAAQ,MAAM,IAAI;AAChC,WAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,UAAM,UAAU,MAAM,CAAC,EAAE,SAAS,OAAO;AACzC,eAAW,SAAS,SAAS;AAC3B,cAAQ,KAAK,EAAE,UAAU,MAAM,CAAC,GAAG,MAAM,IAAI,EAAE,CAAC;AAAA,IAClD;AAAA,EACF;AACA,SAAO;AACT;AAMA,eAAe,kBACb,SACuD;AACvD,MAAI,CAACC,YAAW,OAAO,EAAG,QAAO,CAAC;AAElC,QAAM,UAAU,MAAMC,UAAQ,OAAO;AACrC,QAAM,UAAU,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC;AAEvD,QAAM,UAAU,MAAM,QAAQ;AAAA,IAC5B,QAAQ,IAAI,OAAO,aAAa;AAC9B,YAAM,WAAWC,OAAK,KAAK,SAAS,QAAQ;AAC5C,YAAM,UAAU,MAAMC,WAAS,UAAU,OAAO;AAChD,aAAO,EAAE,UAAU,QAAQ;AAAA,IAC7B,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAKA,eAAsB,gBACpB,MACuD;AACvD,QAAM,eAAe,MAAM,kBAAkBD,OAAK,KAAK,MAAM,YAAY,CAAC;AAC1E,QAAM,aAAa,MAAM,kBAAkBA,OAAK,KAAK,MAAM,WAAW,CAAC;AACvE,SAAO,CAAC,GAAG,cAAc,GAAG,UAAU;AACxC;AAMA,SAAS,iBACP,OACa;AACb,QAAM,QAAQ,oBAAI,IAAY;AAC9B,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAWA,OAAK,SAAS,KAAK,UAAU,KAAK;AACnD,UAAM,IAAI,SAAS,YAAY,CAAC;AAAA,EAClC;AACA,SAAO;AACT;AAGA,eAAsB,qBAAqB,MAAqC;AAC9E,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,gBAAgB,iBAAiB,KAAK;AAC5C,QAAM,UAAwB,CAAC;AAE/B,aAAW,QAAQ,OAAO;AACxB,eAAW,EAAE,UAAU,MAAAE,MAAK,KAAK,qBAAqB,KAAK,SAASL,iBAAgB,GAAG;AACrF,YAAM,aAAa,SAAS,MAAM,GAAG,EAAE,CAAC,EAAE,KAAK;AAC/C,YAAM,WAAW,QAAQ,UAAU;AACnC,UAAI,CAAC,cAAc,IAAI,QAAQ,GAAG;AAChC,gBAAQ,KAAK;AAAA,UACX,MAAM;AAAA,UACN,UAAU;AAAA,UACV,MAAM,KAAK;AAAA,UACX,SAAS,qBAAqB,QAAQ;AAAA,UACtC,MAAAK;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AACT;AAGA,eAAsB,mBAAmB,MAAqC;AAC5E,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAwB,CAAC;AAE/B,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,KAAK,IAAI,iBAAiB,KAAK,OAAO;AAC9C,QAAI,KAAK,aAAa,MAAM;AAC1B,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,UAAU;AAAA,QACV,MAAM,KAAK;AAAA,QACX,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAGA,eAAsB,sBAAsB,MAAqC;AAC/E,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAwB,CAAC;AAE/B,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,KAAK,IAAI,iBAAiB,KAAK,OAAO;AAC9C,UAAM,UAAU,KAAK;AACrB,UAAM,YAAY,CAAC,WAAY,OAAO,YAAY,YAAY,QAAQ,KAAK,MAAM;AAEjF,QAAI,WAAW;AACb,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,UAAU;AAAA,QACV,MAAM,KAAK;AAAA,QACX,SAAS;AAAA,MACX,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAGA,eAAsB,uBAAuB,MAAqC;AAChF,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,WAAW,oBAAI,IAAsB;AAE3C,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,KAAK,IAAI,iBAAiB,KAAK,OAAO;AAC9C,UAAM,QAAQ,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAC5D,QAAI,CAAC,MAAO;AAEZ,UAAM,kBAAkB,MAAM,YAAY,EAAE,KAAK;AACjD,UAAM,WAAW,SAAS,IAAI,eAAe,KAAK,CAAC;AACnD,aAAS,KAAK,KAAK,QAAQ;AAC3B,aAAS,IAAI,iBAAiB,QAAQ;AAAA,EACxC;AAEA,QAAM,UAAwB,CAAC;AAC/B,aAAW,CAAC,OAAO,KAAK,KAAK,UAAU;AACrC,QAAI,MAAM,UAAU,EAAG;AACvB,eAAW,QAAQ,OAAO;AACxB,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,UAAU;AAAA,QACV;AAAA,QACA,SAAS,oBAAoB,KAAK,oBAAe,MAAM,OAAO,CAAC,MAAM,MAAM,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,MAC7F,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAGA,eAAsB,gBAAgB,MAAqC;AACzE,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAwB,CAAC;AAE/B,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB,KAAK,OAAO;AACpD,UAAM,WAAW,OAAO,KAAK,UAAU,YAAY,KAAK,MAAM,KAAK,MAAM;AACzE,UAAM,cAAc,KAAK,KAAK,EAAE,SAAS;AAEzC,QAAI,YAAY,aAAa;AAC3B,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,UAAU;AAAA,QACV,MAAM,KAAK;AAAA,QACX,SAAS,sCAAsC,eAAe;AAAA,MAChE,CAAC;AAAA,IACH;AAAA,EACF;AAEA,SAAO;AACT;AAGA,SAAS,gBAAgB,OAAuB;AAC9C,QAAM,WAAW,MAAM,QAAQ,GAAG;AAClC,QAAM,UAAU,MAAM,QAAQ,GAAG;AACjC,QAAM,OAAO,CAAC,UAAU,OAAO,EAAE,OAAO,CAAC,MAAM,KAAK,CAAC;AACrD,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,SAAO,MAAM,MAAM,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC;AACzC;AAOA,eAAsB,wBAAwB,MAAqC;AACjF,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAwB,CAAC;AAE/B,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,KAAK,IAAI,iBAAiB,KAAK,OAAO;AAC9C,UAAM,EAAE,WAAW,IAAI,wBAAwB,IAAI;AACnD,QAAI,eAAe,UAAa,cAAc,yBAA0B;AACxE,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM,KAAK;AAAA,MACX,SAAS,mBAAmB,WAAW,QAAQ,CAAC,CAAC,aAAa,wBAAwB;AAAA,IACxF,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAGA,eAAsB,uBAAuB,MAAqC;AAChF,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAwB,CAAC;AAE/B,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,KAAK,IAAI,iBAAiB,KAAK,OAAO;AAC9C,UAAM,EAAE,eAAe,IAAI,wBAAwB,IAAI;AACvD,QAAI,CAAC,kBAAkB,eAAe,WAAW,EAAG;AACpD,UAAM,QAAQ,eAAe,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;AACzD,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM,KAAK;AAAA,MACX,SAAS,qBAAqB,KAAK;AAAA,IACrC,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAYA,eAAsB,8BAA8B,MAAqC;AACvF,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAwB,CAAC;AAE/B,aAAW,QAAQ,OAAO;AACxB,UAAM,EAAE,KAAK,IAAI,iBAAiB,KAAK,OAAO;AAC9C,UAAM,WAAW,4BAA4B,IAAI;AACjD,QAAI,YAAY,0CAA2C;AAC3D,YAAQ,KAAK;AAAA,MACX,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM,KAAK;AAAA,MACX,SAAS,YAAY,QAAQ,+CAA+C,yCAAyC;AAAA,IACvH,CAAC;AAAA,EACH;AAEA,SAAO;AACT;AAUA,IAAM,uBAAuB,WAAC,WAAO,GAAC;AAGtC,SAAS,4BAA4B,MAAsB;AACzD,QAAM,aAAa,KAAK,MAAM,SAAS;AACvC,MAAI,QAAQ;AACZ,aAAW,SAAS,YAAY;AAC9B,UAAM,UAAU,MAAM,KAAK;AAC3B,QAAI,QAAQ,WAAW,EAAG;AAC1B,QAAI,CAAC,qBAAqB,KAAK,OAAO,EAAG;AACzC,QAAI,iBAAiB,KAAK,OAAO,GAAG;AAClC,uBAAiB,YAAY;AAC7B;AAAA,IACF;AACA,qBAAiB,YAAY;AAC7B,aAAS;AAAA,EACX;AACA,SAAO;AACT;AAGA,IAAM,qBAAqB;AAG3B,IAAM,oBAAoB;AAqB1B,eAAsB,sBACpB,MACA,QACuB;AACvB,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAwB,CAAC;AAC/B,aAAW,QAAQ,OAAO;AACxB,YAAQ,KAAK,GAAG,oBAAoB,KAAK,SAAS,KAAK,UAAU,MAAM,CAAC;AAAA,EAC1E;AACA,SAAO;AACT;AAkBO,SAAS,oBACd,SACA,UACA,QACc;AACd,QAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB,OAAO;AAC/C,QAAM,OAAOC,iBAAgB,KAAK,MAAM,MAAM;AAC9C,QAAM,OAAO,OAAO,MAAM,IAAI;AAC9B,MAAI,KAAK,gBAAgB,EAAG,QAAO,CAAC;AAEpC,QAAM,YAAY,eAAe,IAAI;AACrC,MAAI,aAAa,KAAK,aAAc,QAAO,CAAC;AAE5C,SAAO;AAAA,IACL;AAAA,MACE,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SACE,cAAc,IAAI,uBAAuB,KAAK,YAAY,2BAChC,SAAS;AAAA,IACvC;AAAA,EACF;AACF;AAGA,SAAS,eAAe,OAAuC;AAC7D,QAAM,aAAa,mBAAmB,KAAK,KAAK;AAChD,MAAI,YAAY;AACd,UAAM,QAAQ,OAAO,WAAW,CAAC,CAAC;AAClC,UAAM,MAAM,WAAW,CAAC,MAAM,SAAY,OAAO,WAAW,CAAC,CAAC,IAAI;AAClE,WAAO,EAAE,OAAO,IAAI;AAAA,EACtB;AACA,QAAM,YAAY,kBAAkB,KAAK,KAAK;AAC9C,MAAI,WAAW;AACb,UAAM,QAAQ,OAAO,UAAU,CAAC,CAAC;AACjC,UAAM,MAAM,UAAU,CAAC,MAAM,SAAY,OAAO,UAAU,CAAC,CAAC,IAAI;AAChE,WAAO,EAAE,OAAO,IAAI;AAAA,EACtB;AACA,SAAO;AACT;AAGA,SAAS,WAAW,SAAyB;AAC3C,MAAI,QAAQ,WAAW,EAAG,QAAO;AACjC,SAAO,QAAQ,MAAM,IAAI,EAAE;AAC7B;AASA,eAAsB,qBAAqB,MAAqC;AAC9E,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,aAAaH,OAAK,KAAK,MAAM,WAAW;AAC9C,QAAM,UAAwB,CAAC;AAC/B,QAAM,iBAAiB,oBAAI,IAAoB;AAE/C,aAAW,QAAQ,OAAO;AACxB,UAAM,eAAe,MAAM;AAAA,MACzB,KAAK;AAAA,MACL,KAAK;AAAA,MACL;AAAA,MACA;AAAA,IACF;AACA,YAAQ,KAAK,GAAG,YAAY;AAAA,EAC9B;AAEA,SAAO;AACT;AAeA,eAAsB,yBACpB,SACA,UACA,YACA,iBAAsC,oBAAI,IAAI,GACvB;AACvB,QAAM,UAAwB,CAAC;AAC/B,aAAW,EAAE,UAAU,MAAAE,MAAK,KAAK,qBAAqB,SAAS,gBAAgB,GAAG;AAChF,UAAM,uBAAuB,UAAUA,OAAM,UAAU,YAAY,gBAAgB,OAAO;AAAA,EAC5F;AACA,SAAO;AACT;AAGA,eAAe,uBACb,UACAA,OACA,UACA,YACA,gBACA,KACe;AACf,aAAW,QAAQ,oBAAoB,QAAQ,GAAG;AAChD,UAAM,UAAU,KAAK,KAAK;AAC1B,QAAI,QAAQ,WAAW,EAAG;AAC1B,UAAM,WAAW,gBAAgB,OAAO;AACxC,UAAM,YAAYF,OAAK,KAAK,YAAY,QAAQ;AAChD,QAAI,CAACF,YAAW,SAAS,GAAG;AAC1B,UAAI,KAAK;AAAA,QACP,MAAM;AAAA,QACN,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS,qBAAqB,QAAQ;AAAA,QACtC,MAAAI;AAAA,MACF,CAAC;AACD;AAAA,IACF;AACA,UAAM,QAAQ,eAAe,OAAO;AACpC,QAAI,UAAU,KAAM;AACpB,UAAM,YAAY,MAAM,iBAAiB,WAAW,UAAU,cAAc;AAC5E,QAAI,MAAM,OAAO,UAAW;AAC5B,QAAI,KAAK;AAAA,MACP,MAAM;AAAA,MACN,UAAU;AAAA,MACV,MAAM;AAAA,MACN,SAAS,sBAAsB,OAAO,uCAAuC,SAAS;AAAA,MACtF,MAAAA;AAAA,IACF,CAAC;AAAA,EACH;AACF;AAGA,eAAe,iBACb,WACA,UACA,OACiB;AACjB,QAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,MAAI,WAAW,OAAW,QAAO;AACjC,QAAM,UAAU,MAAM,aAAa,SAAS;AAC5C,QAAM,YAAY,WAAW,OAAO;AACpC,QAAM,IAAI,UAAU,SAAS;AAC7B,SAAO;AACT;AAOA,eAAsB,6BAA6B,MAAqC;AACtF,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,UAAwB,CAAC;AAC/B,aAAW,QAAQ,OAAO;AACxB,YAAQ,KAAK,GAAG,4BAA4B,KAAK,SAAS,KAAK,QAAQ,CAAC;AAAA,EAC1E;AACA,SAAO;AACT;AAQO,SAAS,4BAA4B,SAAiB,UAAgC;AAC3F,QAAM,UAAwB,CAAC;AAC/B,aAAW,EAAE,UAAU,MAAAA,MAAK,KAAK,qBAAqB,SAAS,gBAAgB,GAAG;AAChF,eAAW,QAAQ,oBAAoB,QAAQ,GAAG;AAChD,UAAI,CAAC,yBAAyB,IAAI,EAAG;AACrC,cAAQ,KAAK;AAAA,QACX,MAAM;AAAA,QACN,UAAU;AAAA,QACV,MAAM;AAAA,QACN,SAAS,8BAA8B,QAAQ;AAAA,QAC/C,MAAAA;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;;;AClkBA,SAAS,WAAAE,iBAAe;AACxB,OAAOC,YAAU;;;ACQV,SAAS,kBACd,QACA,SACM;AACN,MAAI,OAAO,QAAQ,eAAe,UAAU;AAC1C,WAAO,aAAa,QAAQ;AAAA,EAC9B;AACA,MAAI,QAAQ,iBAAiB;AAC3B,WAAO,kBAAkB,QAAQ;AAAA,EACnC;AACA,MAAI,QAAQ,kBAAkB,QAAQ,eAAe,SAAS,GAAG;AAC/D,WAAO,iBAAiB,QAAQ;AAAA,EAClC;AACF;AAQO,SAAS,4BACd,cACA,SACM;AACN,QAAM,OAAO,QAAQ;AACrB,MAAI,CAAC,QAAQ,KAAK,WAAW,EAAG;AAChC,QAAM,QAAQ,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,KAAK,IAAI;AAC/C,EAAO;AAAA,IACL;AAAA,IACO,KAAK,8BAA8B,YAAY,4BAAuB,KAAK,EAAE;AAAA,EACtF;AACF;;;ADzBA,IAAM,6BAA6B;AAkBnC,eAAsB,wBACpB,MACA,OACA,QACiB;AACjB,QAAM,WAAWC,OAAK,KAAK,MAAM,cAAc,GAAG,MAAM,IAAI,KAAK;AACjE,QAAM,eAAe,MAAM,aAAa,QAAQ;AAChD,QAAM,eAAe,MAAM,iBAAiB,MAAM,MAAM,IAAI;AAE5D,QAAM,SAAS;AAAA,IACb,MAAM,QAAQ;AAAA,IACd,MAAM;AAAA,IACN;AAAA,IACA;AAAA,EACF;AAEA,QAAM,WAAW,MAAM,WAAW;AAAA,IAChC;AAAA,IACA,UAAU;AAAA,MACR,EAAE,MAAM,QAAQ,SAAS,4BAA4B,MAAM,QAAQ,OAAO,KAAK;AAAA,IACjF;AAAA,EACF,CAAC;AAED,QAAM,cAAc,uBAAuB,OAAO,cAAc,MAAM;AACtE,8BAA4B,MAAM,QAAQ,SAAS,MAAM,OAAO;AAChE,SAAO,GAAG,WAAW;AAAA;AAAA,EAAO,QAAQ;AAAA;AACtC;AAMA,SAAS,uBACP,OACA,cACA,QACQ;AACR,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,WAAW,eAAe,iBAAiB,YAAY,IAAI;AACjE,QAAM,YAAa,UAAU,KAAK,aAAa,OAAO,SAAS,KAAK,cAAc,WAC9E,SAAS,KAAK,YACd;AACJ,QAAM,oBAA6C;AAAA,IACjD,OAAO,MAAM,QAAQ;AAAA,IACrB,SAAS,MAAM,QAAQ;AAAA,IACvB,SAAS,MAAM;AAAA,IACf,MAAM,OAAO;AAAA,IACb;AAAA,IACA,WAAW;AAAA,EACb;AACA,kBAAgB,mBAAmB,MAAM,QAAQ,SAAS,MAAM,QAAQ,QAAQ,CAAC,CAAC;AAClF,oBAAkB,mBAAmB,MAAM,OAAO;AAClD,SAAO,iBAAiB,iBAAiB;AAC3C;AASA,eAAe,iBAAiB,MAAc,aAAsC;AAClF,QAAM,eAAeA,OAAK,KAAK,MAAM,YAAY;AACjD,MAAI;AAEJ,MAAI;AACF,YAAQ,MAAMC,UAAQ,YAAY;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AAEA,QAAM,UAAU,MACb,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,KAAK,MAAM,GAAG,WAAW,KAAK,EAC5D,MAAM,GAAG,0BAA0B;AAEtC,QAAM,WAAqB,CAAC;AAC5B,aAAW,KAAK,SAAS;AACvB,UAAM,UAAU,MAAM,aAAaD,OAAK,KAAK,cAAc,CAAC,CAAC;AAC7D,QAAI,CAAC,QAAS;AACd,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,QAAI,KAAK,SAAU;AACnB,aAAS,KAAK,OAAO;AAAA,EACvB;AAEA,SAAO,SAAS,KAAK,aAAa;AACpC;;;AzBlEA,OAAO,YAAY;AAgBnB,SAAS,qBAAoC;AAC3C,SAAO,EAAE,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,CAAC,GAAG,OAAO,CAAC,GAAG,QAAQ,CAAC,EAAE;AACpF;AASA,eAAsB,QAAQ,MAAc,UAA0B,CAAC,GAAkB;AACvF,QAAM,iBAAiB,MAAM,OAAO;AACtC;AAWA,eAAsB,iBACpB,MACA,UAA0B,CAAC,GACH;AACxB,EAAO,OAAO,iBAAiB;AAE/B,QAAM,SAAS,MAAM,YAAY,IAAI;AACrC,MAAI,CAAC,QAAQ;AACX,IAAO,OAAO,KAAY,MAAM,0CAA0C,CAAC;AAC3E,WAAO;AAAA,MACL,GAAG,mBAAmB;AAAA,MACtB,QAAQ,CAAC,wEAAmE;AAAA,IAC9E;AAAA,EACF;AAEA,MAAI;AACF,WAAO,MAAM,mBAAmB,MAAM,OAAO;AAAA,EAC/C,UAAE;AACA,UAAM,YAAY,IAAI;AAAA,EACxB;AACF;AAUA,SAAS,cAAc,SAAwC;AAC7D,SAAO;AAAA,IACL,WAAW,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS,EAAE,WAAW,SAAS;AAAA,IAC7E,SAAS,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,SAAS;AAAA,IACrD,WAAW,QAAQ,OAAO,CAAC,MAAM,EAAE,WAAW,WAAW;AAAA,EAC3D;AACF;AAmBA,eAAe,mBACb,MACA,aACA,aACA,QACA,SAC+B;AAC/B,QAAM,SAAS,iBAAiB,aAAa,WAAW;AAGxD,QAAM,eAAe,QAAQ,SACzB,MAAM,4BAA4B,MAAM,WAAW,IACnD,CAAC;AACL,QAAM,QAAQ,OAAO,mBAAmB;AACxC,QAAM,SAAmB,CAAC;AAC1B,QAAM,aAAuB,CAAC;AAC9B,QAAM,QAAQ,MAAM,QAAQ;AAAA,IAC1B,OAAO,IAAI,CAAC,UAAU,MAAM,YAAY;AACtC,YAAM,SAAS,MAAM,mBAAmB,MAAM,OAAO,QAAQ,SAAS,YAAY;AAClF,UAAI,OAAO,MAAO,QAAO,KAAK,OAAO,KAAK;AAC1C,UAAI,OAAO,YAAa,YAAW,KAAK,OAAO,WAAW;AAC1D,aAAO;AAAA,IACT,CAAC,CAAC;AAAA,EACJ;AACA,SAAO,EAAE,OAAO,QAAQ,YAAY,WAAW,CAAC,EAAE;AACpD;AAGA,eAAe,wBACb,MACA,aACe;AACf,aAAW,UAAU,aAAa;AAChC,QAAI,OAAO,SAAS,WAAW,EAAG;AAClC,UAAM,mBAAmB,MAAM,OAAO,YAAY,OAAO,YAAY,OAAO,QAAQ;AAAA,EACtF;AACF;AAGA,SAAS,iBACP,SACA,YACA,aACA,SACe;AACf,EAAO,OAAO,sBAAsB;AACpC,EAAO,OAAO,UAAY;AAAA,IACxB,GAAG,QAAQ,UAAU,MAAM,cAAc,QAAQ,UAAU,MAAM,aAAa,QAAQ,QAAQ,MAAM;AAAA,EACtG,CAAC;AACD,MAAI,QAAQ,UAAU,WAAW,WAAW,SAAS,GAAG;AACtD,IAAO,OAAO,KAAY;AAAA,MACxB,GAAG,WAAW,WAAW,MAAM;AAAA,IACjC,CAAC;AAAA,EACH,WAAW,QAAQ,UAAU,SAAS,GAAG;AACvC,IAAO,OAAO,UAAY,IAAI,0CAA0C,CAAC;AAAA,EAC3E;AAEA,QAAM,SAAS,CAAC,GAAG,WAAW,MAAM;AACpC,aAAW,UAAU,aAAa;AAChC,QAAI,OAAO,SAAS,WAAW,GAAG;AAChC,aAAO,KAAK,8BAA8B,OAAO,UAAU,EAAE;AAAA,IAC/D;AAAA,EACF;AAMA,QAAM,eAAe,WAAW,MAAM,IAAI,CAAC,UAAU,MAAM,IAAI;AAC/D,QAAM,aAA4B;AAAA,IAChC,UAAU,QAAQ,UAAU;AAAA,IAC5B,SAAS,QAAQ,UAAU;AAAA,IAC3B,SAAS,QAAQ,QAAQ;AAAA,IACzB,UAAU,WAAW,MAAM,IAAI,CAAC,UAAU,MAAM,QAAQ,OAAO;AAAA,IAC/D,OAAO,CAAC,GAAG,cAAc,GAAG,WAAW,SAAS;AAAA,IAChD;AAAA,EACF;AACA,MAAI,QAAQ,QAAQ;AAClB,eAAW,aAAa,WAAW;AAAA,EACrC;AACA,SAAO;AACT;AAGA,eAAe,mBACb,MACA,SACwB;AACxB,QAAM,SAAS,MAAM,WAAW,IAAI;AACpC,qBAAmB,MAAM;AACzB,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,UAAU,MAAM,cAAc,MAAM,KAAK;AAC/C,6BAA2B,SAAS,oBAAoB,OAAO,OAAO,CAAC;AAEvE,QAAM,UAAU,cAAc,OAAO;AACrC,MAAI,QAAQ,UAAU,WAAW,KAAK,QAAQ,QAAQ,WAAW,GAAG;AAClE,IAAO,OAAO,UAAY,QAAQ,mDAA8C,CAAC;AAIjF,QAAI,CAAC,QAAQ,QAAQ;AACnB,YAAM,kBAAwC;AAAA,QAC5C,OAAO,CAAC;AAAA,QACR,QAAQ,CAAC;AAAA,QACT,YAAY,CAAC;AAAA,QACb,WAAW,CAAC;AAAA,MACd;AACA,YAAM,kBAAkB,MAAM,QAAQ,eAAe;AAGrD,YAAM,aAAa,MAAM,gBAAgB,OAAO,gBAAgB,SAAS;AACzE,aAAO;AAAA,QACL,GAAG,mBAAmB;AAAA,QACtB,SAAS,QAAQ,UAAU;AAAA;AAAA;AAAA;AAAA,QAI3B,OAAO,CAAC,GAAG,gBAAgB,SAAS;AAAA,QACpC,QAAQ,gBAAgB;AAAA,MAC1B;AAAA,IACF;AACA,WAAO,EAAE,GAAG,mBAAmB,GAAG,SAAS,QAAQ,UAAU,OAAO;AAAA,EACtE;AAEA,sBAAoB,OAAO;AAQ3B,MAAI,CAAC,QAAQ,QAAQ;AACnB,UAAM,sBAAsB,MAAM,QAAQ,SAAS,KAAK;AAAA,EAC1D;AAEA,QAAM,cAAc,gBAAgB,OAAO,OAAO;AAClD,oBAAkB,WAAW;AAE7B,QAAM,cAAc,MAAM,oBAAoB,MAAM,QAAQ,WAAW,OAAO,OAAO;AACrF,MAAI,CAAC,QAAQ,QAAQ;AACnB,UAAM,wBAAwB,MAAM,aAAa,WAAW;AAAA,EAC9D;AAEA,QAAM,aAAa,MAAM,mBAAmB,MAAM,aAAa,aAAa,QAAQ,OAAO;AAE3F,MAAI,CAAC,QAAQ,QAAQ;AACnB,UAAM,wBAAwB,MAAM,WAAW;AAC/C,QAAI,YAAY,OAAO,GAAG;AACxB,YAAM,yBAAyB,MAAM,WAAW;AAAA,IAClD;AACA,UAAM,mBAAmB,MAAM,aAAa,WAAW;AAGvD,UAAM,kBAAkB,MAAM,QAAQ,UAAU;AAChD,UAAM,aAAa,MAAM,WAAW,OAAO,WAAW,SAAS;AAAA,EACjE;AACA,SAAO,iBAAiB,SAAS,YAAY,aAAa,OAAO;AACnE;AAGA,SAAS,mBAAmB,QAA4B;AACtD,MAAI,OAAO,YAAY;AACrB,IAAO,OAAO,KAAY,IAAI,WAAW,OAAO,UAAU,EAAE,CAAC;AAAA,EAC/D;AACF;AAGA,SAAS,2BAA2B,SAAyB,UAA0B;AACrF,aAAW,QAAQ,UAAU;AAC3B,IAAO,OAAO,KAAY,KAAK,GAAG,IAAI,+BAA+B,CAAC;AACtE,YAAQ,KAAK,EAAE,MAAM,QAAQ,UAAU,CAAC;AAAA,EAC1C;AACF;AAGA,eAAe,sBACb,MACA,SACA,OACe;AACf,aAAW,OAAO,SAAS;AACzB,UAAM,aAAa,MAAM,IAAI,MAAM,KAAK;AAAA,EAC1C;AACF;AAGA,SAAS,kBAAkB,aAAgC;AACzD,aAAW,QAAQ,aAAa;AAC9B,IAAO,OAAO,KAAY,IAAI,WAAW,IAAI,+BAA+B,CAAC;AAAA,EAC/E;AACF;AAMA,eAAe,oBACb,MACA,WACA,OACA,YAC6B;AAC7B,QAAM,cAAkC,CAAC;AACzC,aAAW,UAAU,WAAW;AAC9B,gBAAY,KAAK,MAAM,iBAAiB,MAAM,OAAO,IAAI,CAAC;AAAA,EAC5D;AAEA,QAAM,eAAe,wBAAwB,aAAa,OAAO,UAAU;AAC3E,aAAW,QAAQ,cAAc;AAC/B,IAAO,OAAO,KAAY,KAAK,GAAG,IAAI,mCAAmC,CAAC;AAC1E,gBAAY,KAAK,MAAM,iBAAiB,MAAM,IAAI,CAAC;AAAA,EACrD;AAEA,SAAO;AACT;AAUA,eAAe,aACb,MACA,OACA,YAAsB,CAAC,GACR;AACf,QAAM,sBAAsB,MAAM,IAAI,CAAC,UAAU,MAAM,IAAI;AAC3D,QAAM,kBAAkB,MACrB,OAAO,CAAC,UAAU,MAAM,QAAQ,MAAM,EACtC,IAAI,CAAC,UAAU,MAAM,IAAI;AAC5B,QAAM,kBAAkB,CAAC,GAAG,qBAAqB,GAAG,SAAS;AAC7D,QAAM,cAAc,CAAC,GAAG,iBAAiB,GAAG,SAAS;AAErD,MAAI,gBAAgB,SAAS,GAAG;AAC9B,IAAO,OAAO,aAAa,KAAK,yBAAyB,CAAC;AAC1D,UAAM,aAAa,MAAM,iBAAiB,WAAW;AAAA,EACvD;AAEA,QAAM,cAAc,IAAI;AACxB,QAAM,YAAY,IAAI;AACtB,QAAM,uBAAuB,MAAM,eAAe;AACpD;AAGA,SAAS,oBAAoB,SAA+B;AAC1D,QAAM,UAAkC;AAAA,IACtC,KAAK;AAAA,IAAK,SAAS;AAAA,IAAK,WAAW;AAAA,IAAK,SAAS;AAAA,EACnD;AACA,QAAM,SAAgD;AAAA,IACpD,KAAY;AAAA,IAAS,SAAgB;AAAA,IAAM,WAAkB;AAAA,IAAK,SAAgB;AAAA,EACpF;AAEA,aAAW,KAAK,SAAS;AACvB,UAAM,OAAO,QAAQ,EAAE,MAAM,KAAK;AAClC,UAAM,MAAM,OAAO,EAAE,MAAM,KAAY;AACvC,IAAO,OAAO,MAAM,IAAI,GAAG,EAAE,IAAI,KAAK,EAAE,MAAM,GAAG,CAAC;AAAA,EACpD;AACF;AAMA,eAAe,iBACb,MACA,YAC2B;AAC3B,EAAO,OAAO,KAAY,KAAK,eAAe,UAAU,EAAE,CAAC;AAE3D,QAAM,aAAaE,OAAK,KAAK,MAAM,aAAa,UAAU;AAC1D,QAAM,gBAAgB,MAAMC,WAAS,YAAY,OAAO;AACxD,QAAM,gBAAgB,MAAM,aAAaD,OAAK,KAAK,MAAM,UAAU,CAAC;AACpE,QAAM,WAAW,MAAM,gBAAgB,eAAe,aAAa;AAEnE,MAAI,SAAS,SAAS,GAAG;AACvB,UAAM,QAAQ,SAAS,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,IAAI;AACtD,IAAO,OAAO,KAAY,IAAI,WAAW,SAAS,MAAM,cAAc,KAAK,EAAE,CAAC;AAAA,EAChF;AACA,SAAO,EAAE,YAAY,YAAY,eAAe,SAAS;AAC3D;AAuBO,SAAS,yBACd,UACA,UACkB;AAClB,QAAM,aAAa,EAAE,GAAG,SAAS;AAGjC,MAAI,OAAO,SAAS,eAAe,UAAU;AAC3C,eAAW,aAAa,OAAO,SAAS,eAAe,WACnD,KAAK,IAAI,SAAS,YAAY,SAAS,UAAU,IACjD,SAAS;AAAA,EACf;AAGA,aAAW,kBAAkB;AAG7B,QAAM,OAAO,CAAC,GAAI,SAAS,kBAAkB,CAAC,CAAE;AAChD,QAAM,YAAY,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACjD,aAAW,OAAO,SAAS,kBAAkB,CAAC,GAAG;AAC/C,QAAI,CAAC,UAAU,IAAI,IAAI,IAAI,GAAG;AAC5B,WAAK,KAAK,GAAG;AACb,gBAAU,IAAI,IAAI,IAAI;AAAA,IACxB;AAAA,EACF;AACA,aAAW,iBAAiB,KAAK,SAAS,IAAI,OAAO;AAErD,SAAO;AACT;AAgBA,SAAS,iBACP,aACA,aACiB;AACjB,QAAM,SAAS,oBAAI,IAA2B;AAC9C,QAAM,eAAe,oBAAI,IAA2B;AAEpD,aAAW,UAAU,aAAa;AAChC,QAAI,OAAO,SAAS,WAAW,EAAG;AAElC,eAAW,WAAW,OAAO,UAAU;AACrC,YAAM,OAAO,QAAQ,QAAQ,OAAO;AACpC,UAAI,YAAY,IAAI,IAAI,EAAG;AAE3B,YAAM,WAAW,OAAO,IAAI,IAAI;AAChC,UAAI,UAAU;AACZ,iBAAS,UAAU,yBAAyB,SAAS,SAAS,OAAO;AACrE,iBAAS,YAAY,KAAK,OAAO,UAAU;AAAA,MAC7C,OAAO;AACL,eAAO,IAAI,MAAM;AAAA,UACf;AAAA,UACA;AAAA,UACA,aAAa,CAAC,OAAO,UAAU;AAAA,UAC/B,iBAAiB;AAAA,QACnB,CAAC;AACD,qBAAa,IAAI,MAAM,CAAC,CAAC;AAAA,MAC3B;AACA,mBAAa,IAAI,IAAI,EAAG,KAAK;AAAA,QAC3B,MAAM,OAAO;AAAA,QACb,SAAS,OAAO;AAAA,MAClB,CAAC;AAAA,IACH;AAAA,EACF;AAEA,aAAW,UAAU,OAAO,OAAO,GAAG;AACpC,UAAM,SAAS,aAAa,IAAI,OAAO,IAAI,KAAK,CAAC;AACjD,WAAO,kBAAkB;AAAA,MACvB,OAAO,QAAQ;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAEA,SAAO,MAAM,KAAK,OAAO,OAAO,CAAC;AACnC;AAeA,eAAe,mBACb,MACA,OACA,QACA,SACA,cAC4B;AAC5B,QAAM,WAAW,MAAM,wBAAwB,MAAM,OAAO,MAAM;AAElE,MAAI,QAAQ,QAAQ;AAClB,WAAO,MAAM,uBAAuB,MAAM,OAAO,UAAU,cAAc,MAAM;AAAA,EACjF;AAEA,QAAM,WAAWA,OAAK,KAAK,MAAM,cAAc,GAAG,MAAM,IAAI,KAAK;AACjE,QAAME,SAAQ,MAAM,iBAAiB,UAAU,UAAU,MAAM,QAAQ,OAAO;AAC9E,SAAO,EAAE,OAAOA,UAAS,OAAU;AACrC;AAGA,eAAe,uBACb,MACA,OACA,UACA,cACA,QAC4B;AAM5B,QAAM,cAAc,iBAAiB,MAAM,IAAI;AAC/C,QAAM,mBAAmB,oBAAoB,UAAU,aAAa,MAAM;AAC1E,QAAM,uBAAuB,MAAM;AAAA,IACjC;AAAA,IACA;AAAA,IACA;AAAA,EACF;AAEA,QAAM,YAA6B,MAAM,eAAe,MAAM;AAAA,IAC5D,OAAO,MAAM,QAAQ;AAAA,IACrB,MAAM,MAAM;AAAA,IACZ,SAAS,MAAM,QAAQ;AAAA,IACvB,SAAS,MAAM;AAAA,IACf,MAAM;AAAA,IACN,cAAc,qBAAqB,cAAc,MAAM,WAAW;AAAA,IAClE,kBAAkB,iBAAiB,SAAS,IAAI,mBAAmB;AAAA,IACnE,sBACE,qBAAqB,SAAS,IAAI,uBAAuB;AAAA,EAC7D,CAAC;AACD,EAAO,OAAO,KAAY,KAAK,oBAAoB,UAAU,EAAE,KAAK,MAAM,IAAI,GAAG,CAAC;AAClF,SAAO,EAAE,aAAa,UAAU,GAAG;AACrC;AAOA,eAAe,qCACb,MACA,UACA,aACuB;AACvB,QAAM,YAAY,4BAA4B,UAAU,WAAW;AACnE,QAAM,SAAS,MAAM;AAAA,IACnB;AAAA,IACA;AAAA,IACAF,OAAK,KAAK,MAAM,WAAW;AAAA,EAC7B;AACA,SAAO,CAAC,GAAG,WAAW,GAAG,MAAM;AACjC;AAYA,eAAe,kBACb,MACA,QACA,YACe;AACf,MAAI,OAAO,UAAU,WAAW,EAAG;AACnC,aAAW,QAAQ,OAAO,WAAW;AACnC,UAAM,SAAS,MAAM,uBAAuB,MAAM,QAAQ,IAAI;AAC9D,QAAI,OAAO,OAAO;AAChB,iBAAW,OAAO,KAAK,OAAO,KAAK;AACnC;AAAA,IACF;AACA,eAAW,UAAU,KAAK,OAAO,IAAI;AAAA,EACvC;AACF;AASA,eAAe,uBACb,MACA,QACA,MAC0B;AAC1B,QAAM,OAAO,QAAQ,KAAK,KAAK;AAC/B,QAAM,WAAWA,OAAK,KAAK,MAAM,cAAc,GAAG,IAAI,KAAK;AAC3D,QAAM,iBAAiB,MAAM,qBAAqB,MAAM,KAAK,gBAAgB,CAAC,CAAC;AAC/E,QAAM,OAAO,OAAO,MAAM,KAAK,IAAI;AACnC,QAAM,SAAS,oBAAoB,MAAM,MAAM,cAAc;AAC7D,QAAM,WAAW,MAAM,WAAW;AAAA,IAChC;AAAA,IACA,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,aAAa,KAAK,IAAI,iBAAiB,KAAK,KAAK,KAAK,CAAC;AAAA,EAC7F,CAAC;AAED,QAAM,OAAM,oBAAI,KAAK,GAAE,YAAY;AACnC,QAAM,WAAW,MAAM,aAAa,QAAQ;AAC5C,QAAM,eAAe,WAAW,iBAAiB,QAAQ,EAAE,OAAO;AAClE,QAAM,YAAY,OAAO,cAAc,cAAc,WAAW,aAAa,YAAY;AACzF,QAAM,cAA+B;AAAA,IACnC,OAAO,KAAK;AAAA,IACZ,SAAS,KAAK;AAAA,IACd,SAAS,CAAC;AAAA,IACV,MAAM,KAAK;AAAA,IACX;AAAA,IACA,WAAW;AAAA,EACb;AACA,QAAM,oBAA6C,EAAE,GAAG,YAAY;AACpE,kBAAgB,mBAAmB,KAAK,OAAO,CAAC,CAAC;AACjD,QAAM,cAAc,iBAAiB,iBAAiB;AACtD,QAAME,SAAQ,MAAM,iBAAiB,UAAU,GAAG,WAAW;AAAA;AAAA,EAAO,QAAQ;AAAA,GAAM,KAAK,KAAK;AAC5F,SAAOA,SAAQ,EAAE,MAAM,OAAAA,OAAM,IAAI,EAAE,KAAK;AAC1C;AAGA,eAAe,qBAAqB,MAAc,OAAkC;AAClF,MAAI,MAAM,WAAW,EAAG,QAAO;AAC/B,QAAM,WAAqB,CAAC;AAC5B,aAAW,QAAQ,OAAO;AACxB,UAAM,WAAWF,OAAK,KAAK,MAAM,cAAc,GAAG,IAAI,KAAK;AAC3D,UAAM,UAAU,MAAM,aAAa,QAAQ;AAC3C,QAAI,QAAS,UAAS,KAAK,OAAO;AAAA,EACpC;AACA,SAAO,SAAS,KAAK,aAAa;AACpC;AAQA,eAAe,gBACb,eACA,eAC6B;AAC7B,QAAM,SAAS,sBAAsB,eAAe,aAAa;AACjE,QAAM,YAAY,MAAM,WAAW;AAAA,IACjC;AAAA,IACA,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,6CAA6C,CAAC;AAAA,IAClF,OAAO,CAAC,uBAAuB;AAAA,EACjC,CAAC;AAED,SAAO,cAAc,SAAS;AAChC;AAQA,eAAe,iBACb,UACA,SACA,cACwB;AACxB,MAAI,CAAC,iBAAiB,OAAO,GAAG;AAC9B,IAAO,OAAO,KAAY,KAAK,qBAAqB,YAAY,mBAAc,CAAC;AAC/E,WAAO,qBAAqB,YAAY;AAAA,EAC1C;AAEA,QAAM,YAAY,UAAU,OAAO;AACnC,SAAO;AACT;AAOA,eAAe,uBAAuB,MAAc,cAAuC;AACzF,MAAI;AACF,UAAM,iBAAiB,MAAM,YAAY;AAAA,EAC3C,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,IAAO,OAAO,KAAY,KAAK,8BAA8B,OAAO,EAAE,CAAC;AAAA,EACzE;AACF;AASA,eAAe,mBACb,MACA,YACA,YACA,UACe;AACf,QAAM,OAAO,MAAM,SAAS,UAAU;AACtC,QAAM,QAAqB;AAAA,IACzB;AAAA,IACA,UAAU,SAAS,IAAI,CAAC,MAAM,QAAQ,EAAE,OAAO,CAAC;AAAA,IAChD,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,EACrC;AAEA,QAAM,kBAAkB,MAAM,YAAY,KAAK;AACjD;;;ADzwBA,eAAO,eAAsC,UAA0B,CAAC,GAAkB;AACxF,MAAI,CAACG,YAAW,WAAW,GAAG;AAC5B,IAAO;AAAA,MACL;AAAA,MACO,KAAK,qDAAqD;AAAA,IACnE;AACA;AAAA,EACF;AAEA,QAAM,QAAQ,QAAQ,IAAI,GAAG,OAAO;AACtC;;;A4BdA,SAAS,cAAAC,mBAAkB;AAC3B,OAAOC,YAAU;AAyBjB,IAAM,YAAY,CAAC,cAAc,WAAW;AAG5C,IAAM,sBAA+B;AAAA,EACnC,MAAM;AAAA,EACN,aAAa;AAAA,EACb,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,YAAY;AAAA,MACV,OAAO;AAAA,QACL,MAAM;AAAA,QACN,OAAO;AAAA,UACL,MAAM;AAAA,UACN,aAAa;AAAA,QACf;AAAA,QACA,UAAU;AAAA,MACZ;AAAA,MACA,WAAW;AAAA,QACT,MAAM;AAAA,QACN,aAAa;AAAA,MACf;AAAA,IACF;AAAA,IACA,UAAU,CAAC,SAAS,WAAW;AAAA,EACjC;AACF;AAaA,eAAsB,YACpB,UACA,cAC8B;AAC9B,QAAM,eACJ;AAEF,QAAM,cAAc,aAAa,QAAQ;AAAA;AAAA;AAAA,EAAoB,YAAY;AAEzE,QAAM,YAAY,MAAM,WAAW;AAAA,IACjC,QAAQ;AAAA,IACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,IACjD,OAAO,CAAC,mBAAmB;AAAA,EAC7B,CAAC;AAED,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,SAAS;AACnC,WAAO;AAAA,MACL,OAAO,MAAM,QAAQ,OAAO,KAAK,IAAI,OAAO,MAAM,OAAO,CAAC,MAAe,OAAO,MAAM,QAAQ,IAAI,CAAC;AAAA,MACnG,WAAW,OAAO,OAAO,cAAc,WAAW,OAAO,YAAY;AAAA,IACvE;AAAA,EACF,QAAQ;AACN,WAAO,EAAE,OAAO,CAAC,GAAG,WAAW,0CAA0C;AAAA,EAC3E;AACF;AAGA,SAAS,mBACP,YACQ;AACR,SAAO,WACJ,IAAI,CAAC,UAAU,OAAO,MAAM,IAAI,OAAO,MAAM,KAAK,WAAM,MAAM,OAAO,EAAE,EACvE,KAAK,IAAI;AACd;AAgBA,eAAe,oBACb,MACA,UACA,OACwB;AACxB,QAAM,iBAAiB,MAAM,mBAAmB,MAAM,UAAU,KAAK;AACrE,MAAI,eAAgB,QAAO;AAE3B,QAAM,aAAa,MAAM,qBAAqB,MAAM,QAAQ;AAE5D,MAAI,WAAW,SAAS,GAAG;AACzB,UAAM,gBAAgB,mBAAmB,UAAU;AACnD,UAAM,EAAE,OAAOC,WAAU,WAAAC,WAAU,IAAI,MAAM,YAAY,UAAU,aAAa;AAEhF,WAAO,EAAE,OAAOD,WAAU,UAAAA,WAAU,WAAAC,YAAW,QAAQ,CAAC,EAAE;AAAA,EAC5D;AAEA,QAAM,eAAe,MAAM,aAAaC,OAAK,KAAK,MAAM,UAAU,CAAC;AACnE,QAAM,EAAE,OAAO,UAAU,UAAU,IAAI,MAAM,YAAY,UAAU,YAAY;AAC/E,SAAO,EAAE,OAAO,SAAS,IAAI,CAAC,MAAM,QAAQ,CAAC,CAAC,GAAG,UAAU,WAAW,QAAQ,CAAC,EAAE;AACnF;AAMA,eAAe,mBACb,MACA,UACA,OAC+B;AAC/B,QAAM,SAAS,MAAM,sBAAsB,MAAM,QAAQ;AACzD,MAAI,OAAO,WAAW,EAAG,QAAO;AAEhC,QAAM,WAAW;AAAA,IACf;AAAA,IACA,OAAO,IAAI,CAAC,EAAE,OAAO,MAAM,OAAO,EAAE,MAAM,MAAM,MAAM,WAAW,OAAO,MAAM,EAAE;AAAA,EAClF;AACA,QAAM,OAAO,SAAS,MAAM,GAAG,iBAAiB;AAChD,QAAM,qBAAqB,aAAa,QAAQ,KAAK,IAAI,CAAC,MAAM,EAAE,UAAU,KAAK,CAAC;AAClF,QAAM,iBAAiB,iBAAiB,IAAI;AAC5C,QAAM,YAAY,gBAAgB,gBAAgB,gBAAgB;AAClE,QAAM,YAAY,oBAAoB,gBAAgB,SAAS;AAE/D,SAAO;AAAA,IACL,OAAO;AAAA,IACP,UAAU;AAAA,IACV;AAAA,IACA,QAAQ;AAAA,IACR,OAAO,QAAQ,WAAW,gBAAgB,WAAW,kBAAkB,IAAI;AAAA,EAC7E;AACF;AAGA,SAAS,aACP,QACA,OACS;AACT,QAAM,QAAQ,KAAK,IAAI,OAAO,QAAQ,MAAM,MAAM;AAClD,WAAS,IAAI,GAAG,IAAI,OAAO,KAAK;AAC9B,QAAI,OAAO,CAAC,EAAE,UAAU,MAAM,CAAC,EAAG,QAAO;AAAA,EAC3C;AACA,SAAO;AACT;AAQA,SAAS,iBAAiB,QAAwC;AAChE,SAAO,OAAO,IAAI,CAAC,EAAE,WAAW,MAAM,OAAO;AAAA,IAC3C,MAAM,UAAU,MAAM;AAAA,IACtB,OAAO,UAAU,MAAM;AAAA,IACvB,YAAY,UAAU,MAAM;AAAA,IAC5B;AAAA,IACA,MAAM,UAAU,MAAM;AAAA,EACxB,EAAE;AACJ;AAGA,SAAS,gBAAgB,QAAyB,OAAyB;AACzE,QAAM,QAAkB,CAAC;AACzB,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,SAAS,QAAQ;AAC1B,QAAI,KAAK,IAAI,MAAM,IAAI,EAAG;AAC1B,SAAK,IAAI,MAAM,IAAI;AACnB,UAAM,KAAK,MAAM,IAAI;AACrB,QAAI,MAAM,UAAU,MAAO;AAAA,EAC7B;AACA,SAAO;AACT;AAGA,SAAS,oBAAoB,QAAyB,OAAyB;AAC7E,QAAMC,OAAM,OAAO,MAAM,GAAG,MAAM,MAAM;AACxC,QAAM,UAAUA,KAAI,IAAI,CAAC,MAAM,GAAG,EAAE,IAAI,IAAI,EAAE,UAAU,KAAK,EAAE,MAAM,QAAQ,CAAC,CAAC,GAAG,EAAE,KAAK,IAAI;AAC7F,SAAO,YAAY,MAAM,MAAM,iBAAiB,OAAO,MAAM,qBAAqB,OAAO;AAC3F;AAGA,SAAS,WACP,QACA,WACA,UACgB;AAChB,QAAM,cAAc,oBAAI,IAAoB;AAC5C,aAAW,KAAK,QAAQ;AACtB,UAAM,OAAO,YAAY,IAAI,EAAE,IAAI;AACnC,QAAI,SAAS,UAAa,EAAE,QAAQ,KAAM,aAAY,IAAI,EAAE,MAAM,EAAE,KAAK;AAAA,EAC3E;AACA,SAAO;AAAA,IACL,OAAO,UAAU,IAAI,CAAC,UAAU,EAAE,MAAM,OAAO,YAAY,IAAI,IAAI,KAAK,EAAE,EAAE;AAAA,IAC5E;AAAA,IACA,YAAY;AAAA,IACZ;AAAA,EACF;AACF;AAGA,eAAe,sBACb,MACA,UAC+D;AAC/D,MAAI;AACF,WAAO,MAAM,mBAAmB,MAAM,UAAU,WAAW;AAAA,EAC7D,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,IAAO,OAAO,KAAY,IAAI,iCAAiC,OAAO,kBAAkB,CAAC;AACzF,WAAO,CAAC;AAAA,EACV;AACF;AAGA,eAAe,qBACb,MACA,UACkE;AAClE,MAAI;AACF,WAAO,MAAM,kBAAkB,MAAM,QAAQ;AAAA,EAC/C,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,IAAO,OAAO,KAAY,IAAI,oCAAoC,OAAO,sBAAsB,CAAC;AAChG,WAAO,CAAC;AAAA,EACV;AACF;AASA,eAAsB,kBAAkB,MAAc,OAAkC;AACtF,QAAM,WAAqB,CAAC;AAE5B,aAAW,QAAQ,OAAO;AACxB,QAAI,UAAU;AACd,eAAW,OAAO,WAAW;AAC3B,YAAM,YAAY,MAAM,aAAaD,OAAK,KAAK,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;AACvE,UAAI,CAAC,UAAW;AAChB,YAAM,EAAE,KAAK,IAAI,iBAAiB,SAAS;AAC3C,UAAI,KAAK,SAAU;AACnB,gBAAU;AACV;AAAA,IACF;AAEA,QAAI,CAAC,SAAS;AACZ,MAAO,OAAO,KAAY,KAAK,mBAAmB,IAAI,qBAAgB,CAAC;AACvE;AAAA,IACF;AAEA,aAAS,KAAK,aAAa,IAAI;AAAA,EAAS,OAAO,EAAE;AAAA,EACnD;AAEA,SAAO,SAAS,KAAK,MAAM;AAC7B;AAGA,IAAM,4BACJ;AAQF,SAAS,0BAAkC;AACzC,QAAM,OAAO,kBAAkB;AAC/B,SAAO,OAAO,GAAG,yBAAyB,IAAI,IAAI,KAAK;AACzD;AAQA,eAAe,cACb,UACA,cACA,QACA,SACiB;AACjB,QAAM,aAAa,OAAO,SAAS,IAAI,qBAAqB,MAAM,IAAI;AACtE,QAAM,cACJ,aAAa,QAAQ;AAAA;AAAA;AAAA,EAA6B,YAAY,GAAG,UAAU;AAC7E,SAAO,WAAW;AAAA,IAChB,QAAQ,wBAAwB;AAAA,IAChC,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,IACjD,QAAQ,QAAQ,OAAO;AAAA,IACvB;AAAA,EACF,CAAC;AACH;AAGA,SAAS,qBAAqB,QAAiC;AAC7D,QAAM,WAAW,OAAO;AAAA,IACtB,CAAC,UAAU,OAAO,MAAM,IAAI,WAAW,MAAM,UAAU;AAAA,EAAU,MAAM,IAAI;AAAA,EAC7E;AACA,SAAO;AAAA;AAAA;AAAA,EAA6D,SAAS,KAAK,MAAM,CAAC;AAC3F;AASO,SAAS,gBAAgB,QAAwB;AACtD,QAAM,YAAY,OAAO,KAAK,EAAE,MAAM,IAAI,EAAE,CAAC,KAAK;AAClD,QAAM,gBAAgB,UAAU,MAAM,cAAc,EAAE,CAAC,KAAK;AAC5D,SAAO,cAAc,MAAM,GAAG,GAAG;AACnC;AASA,eAAe,cAAc,MAAc,UAAkB,QAAiC;AAC5F,QAAM,OAAO,QAAQ,QAAQ;AAC7B,QAAM,WAAWA,OAAK,KAAK,MAAM,aAAa,GAAG,IAAI,KAAK;AAE1D,QAAM,cAAc,iBAAiB;AAAA,IACnC,OAAO;AAAA,IACP,SAAS,gBAAgB,MAAM;AAAA,IAC/B,MAAM;AAAA,IACN,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC,CAAC;AAED,QAAM,WAAW,GAAG,WAAW;AAAA;AAAA,EAAO,MAAM;AAAA;AAC5C,QAAM,YAAY,UAAU,QAAQ;AAEpC,EAAO;AAAA,IACL;AAAA,IACO,QAAQ,sBAAwB,OAAO,QAAQ,CAAC,EAAE;AAAA,EAC3D;AAIA,QAAM,cAAc,IAAI;AAIxB,MAAI;AACF,UAAM,iBAAiB,MAAM,CAAC,IAAI,CAAC;AAAA,EACrC,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,IAAO,OAAO,KAAY,KAAK,8BAA8B,OAAO,EAAE,CAAC;AAAA,EACzE;AAEA,SAAO;AACT;AAwBA,eAAsB,eACpB,MACA,UACA,UAAiC,CAAC,GACZ;AACtB,MAAI,CAACE,YAAWF,OAAK,KAAK,MAAM,UAAU,CAAC,GAAG;AAC5C,UAAM,IAAI,MAAM,oDAAoD;AAAA,EACtE;AAEA,QAAM,YAAY,MAAM,oBAAoB,MAAM,UAAU,QAAQ,QAAQ,KAAK,CAAC;AAClF,UAAQ,kBAAkB,UAAU,OAAO,UAAU,SAAS;AAE9D,QAAM,eAAe,MAAM,kBAAkB,MAAM,UAAU,KAAK;AAElE,MAAI,CAAC,cAAc;AACjB,WAAO,iBAAiB,SAAS;AAAA,EACnC;AAEA,QAAM,SAAS,MAAM,cAAc,UAAU,cAAc,UAAU,QAAQ,QAAQ,OAAO;AAC5F,QAAM,QAAQ,QAAQ,OAAO,MAAM,cAAc,MAAM,UAAU,MAAM,IAAI;AAE3E,SAAO;AAAA,IACL;AAAA,IACA,eAAe,UAAU;AAAA,IACzB,WAAW,UAAU;AAAA,IACrB;AAAA,IACA,OAAO,UAAU;AAAA,EACnB;AACF;AAGA,SAAS,iBAAiB,WAAuC;AAC/D,SAAO;AAAA,IACL,QAAQ;AAAA,IACR,eAAe,UAAU;AAAA,IACzB,WAAW,UAAU;AAAA,IACrB,OAAO,UAAU;AAAA,EACnB;AACF;AAQA,eAAO,aACL,MACA,UACA,SACe;AACf,MAAI,CAACE,YAAWF,OAAK,KAAK,MAAM,UAAU,CAAC,GAAG;AAC5C,IAAO,OAAO,KAAY,MAAM,oDAAoD,CAAC;AACrF;AAAA,EACF;AAEA,EAAO,OAAO,0BAA0B;AAExC,QAAM,SAAS,MAAM,eAAe,MAAM,UAAU;AAAA,IAClD,MAAM,QAAQ;AAAA,IACd,OAAO,QAAQ;AAAA,IACf,SAAS,CAAC,SAAS,QAAQ,OAAO,MAAM,IAAI;AAAA,IAC5C,iBAAiB,CAAC,OAAO,cAAc;AACrC,MAAO,OAAO,KAAY,IAAI,cAAc,SAAS,EAAE,CAAC;AACxD,MAAO,OAAO,KAAY,KAAK,YAAY,MAAM,MAAM,aAAa,MAAM,KAAK,IAAI,CAAC,EAAE,CAAC;AACvF,MAAO,OAAO,mBAAmB;AAAA,IACnC;AAAA,EACF,CAAC;AAGD,UAAQ,OAAO,MAAM,IAAI;AAEzB,MAAI,OAAO,MAAO,oBAAmB,OAAO,KAAK;AAEjD,MAAI,CAAC,OAAO,QAAQ;AAClB,IAAO,OAAO,KAAY,MAAM,sDAAsD,CAAC;AACvF;AAAA,EACF;AAEA,MAAI,OAAO,OAAO;AAChB,IAAO,OAAO,UAAY,IAAI,wDAAwD,CAAC;AAAA,EACzF,OAAO;AACL,IAAO,OAAO,UAAY,IAAI,iDAAiD,CAAC;AAAA,EAClF;AACF;AAGA,SAAS,mBAAmB,OAA6B;AACvD,EAAO,OAAO,iBAAiB;AAC/B,EAAO;AAAA,IACL;AAAA,IACO;AAAA,MACL,WAAW,MAAM,aAAa,gBAAgB,YAAY,eAC7C,MAAM,WAAW,QAAQ,IAAI;AAAA,IAC5C;AAAA,EACF;AACA,aAAW,QAAQ,MAAM,OAAO;AAC9B,IAAO,OAAO,UAAK,GAAG,KAAK,IAAI,sBAAsB,KAAK,MAAM,QAAQ,CAAC,CAAC,GAAG;AAAA,EAC/E;AACA,aAAW,SAAS,MAAM,QAAQ;AAChC,UAAM,UAAU,MAAM,KAAK,MAAM,GAAG,yBAAyB,EAAE,QAAQ,QAAQ,GAAG,EAAE,KAAK;AACzF,IAAO;AAAA,MACL;AAAA,MACO,IAAI,GAAG,MAAM,IAAI,IAAI,MAAM,UAAU,UAAU,MAAM,MAAM,QAAQ,CAAC,CAAC,OAAO,OAAO,QAAG;AAAA,IAC/F;AAAA,EACF;AACF;AAGA,IAAM,4BAA4B;;;AChhBlC,SAAS,SAAS,qBAAqB;AACvC,SAAS,cAAAG,mBAAkB;AAC3B,OAAOC,YAAU;AAKjB,IAAM,cAAc;AAMpB,eAAO,eAAqD;AAC1D,QAAM,cAAcC,OAAK,QAAQ,WAAW;AAE5C,MAAI,CAACC,YAAW,WAAW,GAAG;AAC5B,IAAO;AAAA,MACL;AAAA,MACO,KAAK,gEAAgE;AAAA,IAC9E;AACA;AAAA,EACF;AAEA,EAAO,OAAO,eAAe;AAC7B,EAAO,OAAO,aAAa,KAAK,YAAY,WAAW,iBAAiB,CAAC;AACzE,EAAO,OAAO,KAAY,IAAI,yBAAyB,CAAC;AAExD,MAAI,YAAY;AAChB,MAAI,mBAAmB;AACvB,MAAI,gBAAsD;AAE1D,QAAM,iBAAiB,YAA2B;AAChD,QAAI;AACF,YAAM,QAAQ,QAAQ,IAAI,CAAC;AAAA,IAC7B,SAAS,KAAK;AACZ,YAAM,MAAM,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,MAAO,OAAO,KAAY,MAAM,mBAAmB,GAAG,EAAE,CAAC;AAAA,IAC3D;AAAA,EACF;AAEA,QAAM,iBAAiB,YAA2B;AAChD,QAAI,WAAW;AACb,yBAAmB;AACnB;AAAA,IACF;AACA,gBAAY;AACZ,UAAM,eAAe;AACrB,gBAAY;AACZ,QAAI,kBAAkB;AACpB,yBAAmB;AACnB,YAAM,eAAe;AAAA,IACvB;AAAA,EACF;AAEA,QAAM,kBAAkB,CAAC,WAAmB,UAAkB;AAC5D,IAAO;AAAA,MACL;AAAA,MACO,IAAI,GAAG,KAAK,KAAKD,OAAK,SAAS,SAAS,CAAC,EAAE;AAAA,IACpD;AAEA,QAAI,cAAe,cAAa,aAAa;AAC7C,oBAAgB,WAAW,gBAAgB,WAAW;AAAA,EACxD;AAEA,QAAM,UAAU,cAAc,aAAa;AAAA,IACzC,eAAe;AAAA,IACf,kBAAkB,EAAE,oBAAoB,IAAI;AAAA,EAC9C,CAAC;AAED,UACG,GAAG,OAAO,CAAC,MAAM,gBAAgB,GAAG,OAAO,CAAC,EAC5C,GAAG,UAAU,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAC,EACjD,GAAG,UAAU,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAC;AAGpD,QAAM,IAAI,QAAc,MAAM;AAAA,EAAC,CAAC;AAClC;;;AC7DA,IAAM,uBAAmC;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAGA,IAAM,oBAA2C,CAAC,qBAAqB;AAKvE,SAAS,gBACP,SACA,UACQ;AACR,SAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,EAAE;AACxD;AASA,eAAsB,KAAK,MAAoC;AAC7D,QAAM,SAAS,MAAM,WAAW,IAAI;AACpC,QAAM,CAAC,cAAc,aAAa,IAAI,MAAM,QAAQ,IAAI;AAAA,IACtD,QAAQ,IAAI,qBAAqB,IAAI,CAAC,SAAS,KAAK,IAAI,CAAC,CAAC;AAAA,IAC1D,QAAQ,IAAI,kBAAkB,IAAI,CAAC,SAAS,KAAK,MAAM,MAAM,CAAC,CAAC;AAAA,EACjE,CAAC;AAED,QAAM,UAAU,CAAC,GAAG,aAAa,KAAK,GAAG,GAAG,cAAc,KAAK,CAAC;AAEhE,SAAO;AAAA,IACL,QAAQ,gBAAgB,SAAS,OAAO;AAAA,IACxC,UAAU,gBAAgB,SAAS,SAAS;AAAA,IAC5C,MAAM,gBAAgB,SAAS,MAAM;AAAA,IACrC;AAAA,EACF;AACF;;;AC1DA,IAAM,sBAAgF;AAAA,EACpF;AAAA,EACA,SAAgB;AAAA,EAChB;AACF;AAGA,IAAM,iBAAyD;AAAA,EAC7D,OAAO;AAAA,EACP,SAAS;AAAA,EACT,MAAM;AACR;AAGA,SAAS,YAAY,QAA0B;AAC7C,QAAM,YAAY,oBAAoB,OAAO,QAAQ;AACrD,QAAM,OAAO,eAAe,OAAO,QAAQ;AAC3C,QAAM,WAAW,OAAO,OAAO,GAAG,OAAO,IAAI,IAAI,OAAO,IAAI,KAAK,OAAO;AACxE,EAAO,OAAO,MAAM,GAAG,UAAU,OAAO,QAAQ,CAAC,IAAW,IAAI,QAAQ,CAAC,IAAI,OAAO,OAAO,EAAE;AAC/F;AAMA,eAAO,cAAoD;AACzD,EAAO,OAAO,cAAc;AAE5B,QAAM,SAAS,MAAM,WAAW,QAAQ,IAAI,CAAC;AAC7C,QAAM,eAAe,OAAO,cAAc;AAC1C,EAAO,OAAO,KAAY,IAAI,WAAW,YAAY,EAAE,CAAC;AAExD,QAAM,UAAU,MAAM,KAAK,QAAQ,IAAI,CAAC;AAExC,aAAW,UAAU,QAAQ,SAAS;AACpC,gBAAY,MAAM;AAAA,EACpB;AAEA,UAAQ,IAAI;AACZ,QAAM,cAAc;AAAA,IACX,MAAM,GAAG,QAAQ,MAAM,WAAW;AAAA,IAClC,KAAK,GAAG,QAAQ,QAAQ,aAAa;AAAA,IACrC,KAAK,GAAG,QAAQ,IAAI,OAAO;AAAA,EACpC,EAAE,KAAK,IAAI;AACX,EAAO,OAAO,KAAK,WAAW;AAE9B,QAAM,eAAe,QAAQ,IAAI,GAAG,OAAO;AAE3C,MAAI,QAAQ,SAAS,GAAG;AACtB,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;;;ACxCA,IAAM,YAAY;AAClB,IAAM,kBAAkB;AACxB,IAAM,yBAAyB;AAC/B,IAAM,oBAAoB;AAG1B,IAAM,cAAc,oBAAI,IAAI;AAAA,EAC1B;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAGD,SAAS,aAAa,QAA4B;AAChD,MAAI,YAAY,IAAI,OAAO,IAAI,EAAG,QAAO;AACzC,MAAI,OAAO,SAAS,oBAAqB,QAAO;AAChD,SAAO;AACT;AAGA,SAAS,eAAe,SAA2C;AACjE,QAAM,MAAM,oBAAI,IAA8B;AAC9C,aAAW,UAAU,SAAS;AAC5B,UAAM,WAAW,IAAI,IAAI,OAAO,IAAI;AACpC,UAAM,YAAY,aAAa,MAAM;AACrC,QAAI,UAAU;AACZ,eAAS;AACT,eAAS,aAAa;AAAA,IACxB,OAAO;AACL,UAAI,IAAI,OAAO,MAAM;AAAA,QACnB,MAAM,OAAO;AAAA,QACb,OAAO;AAAA,QACP,UAAU,OAAO;AAAA,QACjB;AAAA,MACF,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI,OAAO,CAAC;AAChC;AAOA,eAAsB,eAAe,MAAqC;AACxE,QAAM,SAAS,MAAM,WAAW,IAAI;AAEpC,QAAM,cACJ,MAAM,QAAQ,IAAI;AAAA,IAChB,qBAAqB,IAAI;AAAA,IACzB,qBAAqB,IAAI;AAAA,IACzB,6BAA6B,IAAI;AAAA,IACjC,mBAAmB,IAAI;AAAA,IACvB,sBAAsB,IAAI;AAAA,IAC1B,uBAAuB,IAAI;AAAA,IAC3B,gBAAgB,IAAI;AAAA,IACpB,wBAAwB,IAAI;AAAA,IAC5B,uBAAuB,IAAI;AAAA,IAC3B,8BAA8B,IAAI;AAAA,IAClC,sBAAsB,MAAM,MAAM;AAAA,EACpC,CAAC,GACD,KAAK;AAEP,QAAM,QAAQ,eAAe,UAAU;AACvC,QAAM,iBAAiB,MAAM,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,WAAW,CAAC;AACpE,QAAM,QAAQ,KAAK,IAAI,GAAG,YAAY,cAAc;AAEpD,SAAO,EAAE,OAAO,UAAU,WAAW,MAAM;AAC7C;;;ACpFA,OAAOE,YAAU;;;ACKjB,SAAS,YAAAC,iBAAgB;AACzB,OAAOC,YAAU;AAQjB,SAAS,sBAAsB,MAAuB;AACpD,SAAO,KAAK,MAAM,OAAO,EAAE,KAAK,CAAC,QAAQ,QAAQ,IAAI;AACvD;AAGA,SAAS,SAAS,QAAgB,WAA4B;AAC5D,MAAI,cAAc,OAAQ,QAAO;AACjC,QAAM,gBAAgB,OAAO,SAASA,OAAK,GAAG,IAAI,SAAS,SAASA,OAAK;AACzE,SAAO,UAAU,WAAW,aAAa;AAC3C;AAgBA,eAAsB,kBACpB,YACA,MACwB;AACxB,MAAI,KAAK,WAAW,KAAKA,OAAK,WAAW,IAAI,EAAG,QAAO;AACvD,MAAI,sBAAsB,IAAI,EAAG,QAAO;AACxC,QAAM,SAASA,OAAK,KAAK,YAAY,IAAI;AACzC,MAAI,CAAC,SAAS,YAAYA,OAAK,QAAQ,MAAM,CAAC,EAAG,QAAO;AACxD,MAAI;AACF,UAAM,UAAU,MAAMD,UAAS,UAAU;AACzC,UAAM,WAAW,MAAMA,UAAS,MAAM;AACtC,QAAI,CAAC,SAAS,SAAS,QAAQ,EAAG,QAAO;AACzC,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AD/CA,IAAM,gBAAgB,WAAC,WAAO,GAAC;AAW/B,eAAe,aAAa,MAAc,MAAc,YAAwC;AAC9F,QAAM,aAAa,KAAK,MAAM,SAAS,EAAE,OAAO,CAAC,MAAM,cAAc,KAAK,EAAE,KAAK,CAAC,CAAC;AACnF,MAAI,kBAAkB;AACtB,MAAI,iBAAiB;AACrB,MAAI,iBAAiB;AAErB,aAAW,QAAQ,YAAY;AAC7B,UAAM,YAAY,sBAAsB,IAAI;AAC5C,QAAI,UAAU,WAAW,EAAG;AAC5B;AACA,eAAW,EAAE,MAAM,KAAK,WAAW;AACjC,iBAAW,QAAQ,OAAO;AACxB;AACA,YAAK,MAAM,kBAAkB,YAAY,KAAK,IAAI,MAAO,KAAM;AAAA,MACjE;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,YAAY,EAAE,MAAM,iBAAiB,WAAW,QAAQ,gBAAgB;AAAA,IACxE,iBAAiB,WAAW;AAAA,IAC5B;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMA,eAAsB,yBACpB,MACiC;AACjC,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,aAAaE,OAAK,KAAK,MAAM,WAAW;AAE9C,MAAI,aAAa;AACjB,MAAI,aAAa;AACjB,MAAI,iBAAiB;AACrB,MAAI,aAAa;AACjB,QAAM,UAAgC,CAAC;AAEvC,aAAW,EAAE,UAAU,QAAQ,KAAK,OAAO;AACzC,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,UAAM,OAAOA,OAAK,SAAS,UAAU,KAAK;AAC1C,UAAM,QAAQ,MAAM,aAAa,MAAM,MAAM,UAAU;AACvD,kBAAc,MAAM;AACpB,kBAAc,MAAM;AACpB,sBAAkB,MAAM;AACxB,kBAAc,MAAM;AACpB,YAAQ,KAAK,MAAM,UAAU;AAAA,EAC/B;AAEA,QAAM,kBAAkB,eAAe,IAAI,IAAK,aAAa,aAAc;AAC3E,QAAM,mBAAmB,mBAAmB,IAAI,IAAK,aAAa,iBAAkB;AAEpF,SAAO;AAAA,IACL,sBAAsB;AAAA,IACtB,iBAAiB;AAAA,IACjB;AAAA,IACA;AAAA,IACA,gBAAgB;AAAA,IAChB;AAAA,IACA;AAAA,EACF;AACF;;;AEpFA,SAAS,cAAAC,mBAAkB;AAC3B,SAAS,YAAAC,YAAU,YAAY,SAAAC,cAAa;AAC5C,SAAS,cAAAC,oBAAkB;AAC3B,OAAOC,YAAU;AAUjB,IAAM,YAAYC,OAAK,KAAK,YAAY,MAAM;AAC9C,IAAM,aAAaA,OAAK,KAAK,WAAW,sBAAsB;AAC9D,IAAMC,iBAAgB,WAAC,WAAO,GAAC;AAa/B,IAAM,aAAsB;AAAA,EAC1B,MAAM;AAAA,EACN,aAAa;AAAA,EACb,cAAc;AAAA,IACZ,MAAM;AAAA,IACN,YAAY;AAAA,MACV,OAAO;AAAA,QACL,MAAM;AAAA,QACN,MAAM,CAAC,GAAG,GAAG,CAAC;AAAA,QACd,aACE;AAAA,MACJ;AAAA,MACA,QAAQ,EAAE,MAAM,UAAU,aAAa,sCAAsC;AAAA,IAC/E;AAAA,IACA,UAAU,CAAC,SAAS,QAAQ;AAAA,EAC9B;AACF;AAEA,IAAM,eACJ;AAKF,IAAM,oBAAoBC,YAAW,QAAQ,EAC1C,OAAO,eAAe,KAAK,UAAU,UAAU,CAAC,EAChD,OAAO,KAAK,EACZ,MAAM,GAAG,CAAC;AAGb,SAAS,SAAS,WAAmB,UAA0B;AAC7D,SAAOA,YAAW,QAAQ,EAAE,OAAO,YAAY,QAAQ,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACpF;AAGA,SAAS,aAAa,aAAqB,OAAuB;AAChE,SAAOA,YAAW,QAAQ,EACvB,OAAO,cAAc,oBAAoB,KAAK,EAC9C,OAAO,KAAK,EACZ,MAAM,GAAG,EAAE;AAChB;AAGA,eAAe,gBAAgB,UAAkB,OAAe,KAA8B;AAC5F,QAAM,UAAU,MAAMC,WAAS,UAAU,OAAO;AAChD,SAAO,QACJ,MAAM,IAAI,EACV,MAAM,QAAQ,GAAG,GAAG,EACpB,KAAK,IAAI;AACd;AAGA,SAAS,qBAAqB,WAA2B;AACvD,SAAO,UAAU,QAAQ,iBAAiB,EAAE,EAAE,KAAK;AACrD;AAGA,eAAe,cACb,MACA,WACA,MACA,YAC8B;AAC9B,MAAI,CAAC,KAAK,MAAO,QAAO;AACxB,QAAM,aAAa,MAAM,kBAAkB,YAAY,KAAK,IAAI;AAChE,MAAI,eAAe,KAAM,QAAO;AAChC,QAAM,WAAW,MAAM,gBAAgB,YAAY,KAAK,MAAM,OAAO,KAAK,MAAM,GAAG;AACnF,SAAO;AAAA,IACL,WAAW,SAAS,WAAW,QAAQ;AAAA,IACvC,UAAU;AAAA,IACV;AAAA,IACA,WAAW,KAAK;AAAA,IAChB;AAAA,IACA,WAAW,KAAK,MAAM;AAAA,IACtB,SAAS,KAAK,MAAM;AAAA,EACtB;AACF;AAGA,eAAe,sBACb,MACA,MACA,YACyB;AACzB,QAAM,YAAY,sBAAsB,IAAI;AAC5C,MAAI,UAAU,WAAW,EAAG,QAAO,CAAC;AACpC,QAAM,YAAY,qBAAqB,IAAI;AAC3C,QAAM,QAAQ,UAAU,QAAQ,CAAC,MAAM,EAAE,KAAK;AAC9C,QAAM,QAAQ,MAAM,QAAQ,IAAI,MAAM,IAAI,CAAC,MAAM,cAAc,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC;AAC/F,SAAO,MAAM,OAAO,CAAC,MAAyB,MAAM,IAAI;AAC1D;AAGA,eAAe,iBACb,MACA,MACA,YACyB;AACzB,QAAM,aAAa,KAAK,MAAM,SAAS,EAAE,OAAO,CAAC,MAAMF,eAAc,KAAK,EAAE,KAAK,CAAC,CAAC;AACnF,QAAM,UAAU,MAAM,QAAQ,IAAI,WAAW,IAAI,CAAC,MAAM,sBAAsB,MAAM,GAAG,UAAU,CAAC,CAAC;AACnG,SAAO,QAAQ,KAAK;AACtB;AAOA,eAAsB,qBAAqB,MAAuC;AAChF,QAAM,QAAQ,MAAM,gBAAgB,IAAI;AACxC,QAAM,aAAaD,OAAK,KAAK,MAAM,WAAW;AAC9C,QAAM,MAAsB,CAAC;AAC7B,aAAW,EAAE,UAAU,QAAQ,KAAK,OAAO;AACzC,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,UAAM,OAAOA,OAAK,SAAS,UAAU,KAAK;AAC1C,UAAM,QAAQ,MAAM,iBAAiB,MAAM,MAAM,UAAU;AAC3D,QAAI,KAAK,GAAG,KAAK;AAAA,EACnB;AACA,SAAO;AACT;AAcO,SAAS,0BACd,OACA,YACA,iBAA2B,CAAC,GACZ;AAChB,QAAM,aAAa,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC,CAAC,CAAC;AAC7D,QAAM,WAAW,eAAe,QAAQ,CAAC,MAAM;AAC7C,UAAM,IAAI,WAAW,IAAI,CAAC;AAC1B,WAAO,IAAI,CAAC,CAAC,IAAI,CAAC;AAAA,EACpB,CAAC;AACD,MAAI,SAAS,UAAU,WAAY,QAAO,SAAS,MAAM,GAAG,UAAU;AACtE,QAAM,cAAc,IAAI,IAAI,cAAc;AAC1C,QAAM,WAAW,MACd,OAAO,CAAC,MAAM,CAAC,YAAY,IAAI,EAAE,SAAS,CAAC,EAC3C,KAAK,CAAC,GAAG,MAAM,EAAE,UAAU,cAAc,EAAE,SAAS,CAAC;AACxD,SAAO,CAAC,GAAG,UAAU,GAAG,QAAQ,EAAE,MAAM,GAAG,UAAU;AACvD;AAGA,eAAe,qBAAqB,MAAuD;AACzF,QAAM,YAAYA,OAAK,KAAK,MAAM,UAAU;AAC5C,MAAI,CAACI,aAAW,SAAS,EAAG,QAAO,oBAAI,IAAI;AAC3C,QAAM,UAAU,MAAMD,WAAS,WAAW,OAAO;AACjD,QAAM,MAAM,oBAAI,IAA+B;AAC/C,aAAWE,SAAQ,QAAQ,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO,GAAG;AAC7D,QAAI;AACF,YAAM,QAAQ,KAAK,MAAMA,KAAI;AAC7B,UAAI,IAAI,MAAM,WAAW,KAAK;AAAA,IAChC,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAGA,eAAe,sBAAsB,MAAc,WAA6C;AAC9F,QAAMC,OAAMN,OAAK,KAAK,MAAM,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAC3D,QAAM,WAAWA,OAAK,KAAK,MAAM,UAAU,GAAG,KAAK,UAAU,SAAS,IAAI,IAAI;AAChF;AAGA,SAAS,eAAuB;AAC9B,QAAM,WAAW,QAAQ,IAAI,oBAAoB;AACjD,SAAO,QAAQ,IAAI,iBAAiB,gBAAgB,QAAQ,KAAK;AACnE;AAGA,eAAe,UAAU,MAAoB,UAAkB,OAA2C;AACxG,QAAM,cACJ,UAAU,KAAK,SAAS;AAAA;AAAA,UAAe,KAAK,SAAS,WAAW,KAAK,SAAS,SAAI,KAAK,OAAO;AAAA,EAAO,KAAK,QAAQ;AAEpH,QAAM,MAAM,MAAM,WAAW;AAAA,IAC3B,QAAQ;AAAA,IACR,UAAU,CAAC,EAAE,MAAM,QAAQ,SAAS,YAAY,CAAC;AAAA,IACjD,OAAO,CAAC,UAAU;AAAA,IAClB,WAAW;AAAA,EACb,CAAC;AAED,QAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,SAAO;AAAA,IACL,WAAW;AAAA,IACX,UAAU,KAAK;AAAA,IACf,WAAW,KAAK;AAAA,IAChB,WAAW,KAAK;AAAA,IAChB,SAAS,KAAK;AAAA,IACd,WAAW,KAAK;AAAA,IAChB,UAAU,KAAK;AAAA,IACf,OAAO,OAAO;AAAA,IACd,QAAQ,OAAO;AAAA,IACf;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,EACpC;AACF;AAGA,SAAS,oBAAoB,YAG3B;AACA,QAAM,iBAAiB,WAAW,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE;AAC/D,QAAM,qBAAqB,WAAW,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE;AACnE,QAAM,cAAc,WAAW,OAAO,CAAC,MAAM,EAAE,UAAU,CAAC,EAAE;AAC5D,QAAM,YACJ,WAAW,WAAW,IAClB,IACA,WAAW,OAAO,CAAC,KAAK,MAAM,MAAM,EAAE,OAAO,CAAC,IAAI,WAAW;AACnE,SAAO,EAAE,WAAW,gBAAgB,oBAAoB,YAAY;AACtE;AAOA,eAAe,cACb,QACA,OACA,MACmE;AACnE,QAAM,QAAQ,aAAa;AAC3B,QAAM,aAAkC,CAAC;AACzC,MAAI,cAAc;AAClB,MAAI,oBAAoB;AACxB,MAAI;AAEJ,aAAW,QAAQ,QAAQ;AACzB,UAAM,WAAW,aAAa,KAAK,WAAW,KAAK;AACnD,UAAM,SAAS,MAAM,IAAI,QAAQ;AACjC,QAAI,QAAQ;AACV,iBAAW,KAAK,MAAM;AAAA,IACxB,OAAO;AACL;AACA,UAAI;AACF,cAAM,YAAY,MAAM,UAAU,MAAM,UAAU,KAAK;AACvD,cAAM,sBAAsB,MAAM,SAAS;AAC3C,mBAAW,KAAK,SAAS;AAAA,MAC3B,SAAS,KAAK;AACZ;AACA,YAAI,eAAe,OAAW,cAAa;AAAA,MAC7C;AAAA,IACF;AAAA,EACF;AAEA,MAAI,oBAAoB,KAAK,gBAAgB,mBAAmB;AAC9D,UAAM,MAAM,sBAAsB,QAAQ,WAAW,UAAU,OAAO,UAAU;AAChF,UAAM,IAAI,MAAM,iCAAiC,WAAW,qBAAqB,GAAG,EAAE;AAAA,EACxF;AAEA,SAAO,EAAE,YAAY,YAAY;AACnC;AASA,eAAsB,wBACpB,MACA,aAAa,IACb,iBAA2B,CAAC,GACW;AACvC,QAAM,WAAW,MAAM,qBAAqB,IAAI;AAChD,MAAI,SAAS,WAAW,EAAG,QAAO;AAElC,QAAM,SAAS,0BAA0B,UAAU,YAAY,cAAc;AAC7E,QAAM,QAAQ,MAAM,qBAAqB,IAAI;AAC7C,QAAM,EAAE,YAAY,YAAY,IAAI,MAAM,cAAc,QAAQ,OAAO,IAAI;AAE3E,SAAO;AAAA,IACL,cAAc,WAAW;AAAA,IACzB,eAAe,OAAO,IAAI,CAAC,MAAM,EAAE,SAAS;AAAA,IAC5C,gBAAgB,SAAS;AAAA,IACzB;AAAA,IACA,GAAG,oBAAoB,UAAU;AAAA,IACjC;AAAA,EACF;AACF;;;ACpUA,SAAS,WAAAO,WAAS,cAAAC,aAAY,SAAAC,QAAO,YAAAC,kBAAgB;AACrD,SAAS,cAAAC,oBAAkB;AAC3B,OAAOC,YAAU;AAOjB,IAAM,cAAcC,OAAK,KAAK,YAAY,MAAM;AAChD,IAAM,eAAeA,OAAK,KAAK,aAAa,eAAe;AAG3D,eAAe,WAAW,KAA8B;AACtD,MAAI,CAACC,aAAW,GAAG,EAAG,QAAO;AAC7B,QAAM,UAAU,MAAMC,UAAQ,GAAG;AACjC,SAAO,QAAQ,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,EAAE;AAClD;AAMA,eAAsB,aAAa,MAAoC;AACrE,QAAM,CAAC,aAAa,OAAO,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC7D,WAAWF,OAAK,KAAK,MAAM,WAAW,CAAC;AAAA,IACvC,gBAAgB,IAAI;AAAA,IACpB,mBAAmB,IAAI;AAAA,EACzB,CAAC;AAED,MAAI,iBAAiB;AACrB,aAAW,EAAE,QAAQ,KAAK,OAAO;AAC/B,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,sBAAkB,KAAK;AAAA,EACzB;AAEA,QAAM,YAAY,MAAM;AACxB,QAAM,qBAAqB,cAAc,IAAI,IAAI,KAAK,MAAM,iBAAiB,SAAS;AACtF,QAAM,iBAAiB,gBAAgB,QAAQ,UAAU;AACzD,QAAM,sBAAsB,gBAAgB,QAAQ,UAAU;AAE9D,SAAO;AAAA,IACL,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAQA,eAAsB,cAAc,MAAc,QAAmC;AACnF,QAAM,aAAaA,OAAK,KAAK,MAAM,WAAW;AAC9C,QAAMG,OAAM,YAAY,EAAE,WAAW,KAAK,CAAC;AAC3C,QAAMC,YAAWJ,OAAK,KAAK,MAAM,YAAY,GAAG,KAAK,UAAU,MAAM,IAAI,IAAI;AAC/E;AAQA,eAAsB,YAAY,MAAc,IAAI,IAA2B;AAC7E,QAAM,cAAcA,OAAK,KAAK,MAAM,YAAY;AAChD,MAAI,CAACC,aAAW,WAAW,EAAG,QAAO,CAAC;AAEtC,QAAM,UAAU,MAAMI,WAAS,aAAa,OAAO;AACnD,QAAM,QAAQ,QAAQ,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AACvD,QAAM,UAAwB,CAAC;AAC/B,aAAWC,SAAQ,MAAM,MAAM,CAAC,CAAC,GAAG;AAClC,QAAI;AACF,cAAQ,KAAK,KAAK,MAAMA,KAAI,CAAe;AAAA,IAC7C,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAMA,eAAsB,mBAAmB,MAA0C;AACjF,QAAM,cAAcN,OAAK,KAAK,MAAM,YAAY;AAChD,MAAI,CAACC,aAAW,WAAW,EAAG,QAAO;AAErC,QAAM,UAAU,MAAMI,WAAS,aAAa,OAAO;AACnD,QAAM,QAAQ,QAAQ,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO;AACvD,MAAI,MAAM,WAAW,EAAG,QAAO;AAE/B,MAAI;AACF,WAAO,KAAK,MAAM,MAAM,MAAM,SAAS,CAAC,CAAC;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;AChGO,SAAS,aAAa,SAAqB,UAAiC;AACjF,QAAM,QAAmB;AAAA,IACvB,aAAa,QAAQ,OAAO,QAAQ,SAAS,OAAO;AAAA,IACpD,yBACE,QAAQ,iBAAiB,kBAAkB,SAAS,iBAAiB;AAAA,IACvE,0BACE,QAAQ,iBAAiB,mBAAmB,SAAS,iBAAiB;AAAA,EAC1E;AAEA,MAAI,QAAQ,oBAAoB,UAAa,SAAS,oBAAoB,QAAW;AACnF,UAAM,sBACJ,QAAQ,gBAAgB,YAAY,SAAS,gBAAgB;AAAA,EACjE;AAEA,SAAO;AACT;;;ACjBA,SAAS,YAAAE,kBAAgB;AACzB,SAAS,cAAAC,oBAAkB;AAC3B,OAAOC,YAAU;AACjB,OAAOC,WAAU;AAGjB,IAAM,kBAAkBD,OAAK,KAAK,YAAY,QAAQ,iBAAiB;AAYvE,eAAe,eAAe,MAAwC;AACpE,QAAM,aAAaA,OAAK,KAAK,MAAM,eAAe;AAClD,MAAI,CAACD,aAAW,UAAU,EAAG,QAAO,CAAC;AACrC,QAAM,MAAM,MAAMD,WAAS,YAAY,OAAO;AAC9C,SAAQG,MAAK,KAAK,GAAG,KAAyB,CAAC;AACjD;AAQA,eAAsB,gBACpB,QACA,MACmB;AACnB,QAAM,SAAS,MAAM,eAAe,IAAI;AACxC,QAAM,aAAuB,CAAC;AAE9B,MAAI,OAAO,iBAAiB,UAAa,OAAO,OAAO,QAAQ,OAAO,cAAc;AAClF,eAAW;AAAA,MACT,gBAAgB,OAAO,OAAO,KAAK,uBAAuB,OAAO,YAAY;AAAA,IAC/E;AAAA,EACF;AAEA,MACE,OAAO,8BAA8B,UACrC,OAAO,iBAAiB,kBAAkB,OAAO,2BACjD;AACA,eAAW;AAAA,MACT,6BAA6B,OAAO,iBAAiB,gBAAgB,QAAQ,CAAC,CAAC,wBAAwB,OAAO,yBAAyB;AAAA,IACzI;AAAA,EACF;AAEA,MACE,OAAO,+BAA+B,UACtC,OAAO,iBAAiB,mBAAmB,OAAO,4BAClD;AACA,eAAW;AAAA,MACT,8BAA8B,OAAO,iBAAiB,iBAAiB,QAAQ,CAAC,CAAC,wBAAwB,OAAO,0BAA0B;AAAA,IAC5I;AAAA,EACF;AAEA,MACE,OAAO,0BAA0B,UACjC,OAAO,oBAAoB,UAC3B,OAAO,gBAAgB,YAAY,OAAO,uBAC1C;AACA,eAAW;AAAA,MACT,yBAAyB,OAAO,gBAAgB,UAAU,QAAQ,CAAC,CAAC,uBAAuB,OAAO,qBAAqB;AAAA,IACzH;AAAA,EACF;AAEA,MACE,OAAO,6BAA6B,UACpC,OAAO,oBAAoB,UAC3B,OAAO,gBAAgB,cAAc,OAAO,0BAC5C;AACA,eAAW;AAAA,MACT,yBAAyB,OAAO,gBAAgB,WAAW,gBAAgB,OAAO,wBAAwB;AAAA,IAC5G;AAAA,EACF;AAEA,SAAO;AACT;;;ACrFA,IAAM,YAAY;AAClB,IAAM,aAAa,SAAI,OAAO,SAAS;AAEvC,SAAS,KAAK,UAAU,IAAY;AAClC,SAAO,UAAK,QAAQ,OAAO,YAAY,CAAC,CAAC;AAC3C;AAEA,SAAS,MAAc;AACrB,SAAO,SAAI,UAAU;AACvB;AAEA,SAAS,UAAkB;AACzB,SAAO,SAAI,UAAU;AACvB;AAEA,SAAS,SAAiB;AACxB,SAAO,SAAI,UAAU;AACvB;AAGA,SAAS,SAAS,OAAmC;AACnD,MAAI,UAAU,UAAa,UAAU,EAAG,QAAO;AAC/C,QAAM,MAAM,KAAK,IAAI,KAAK,EAAE,QAAQ,CAAC,EAAE,QAAQ,QAAQ,EAAE;AACzD,SAAO,QAAQ,IAAI,IAAI,WAAM,GAAG,GAAG,IAAI,IAAI,WAAM,GAAG,GAAG;AACzD;AAGA,SAAS,QAAQ,MAAgC;AAC/C,MAAI,KAAK,UAAU,EAAG,QAAO;AAC7B,QAAM,QAAQ,OAAO,KAAK,IAAI;AAC9B,QAAM,QAAQ,GAAG,KAAK,KAAK,YAAO,KAAK,SAAS;AAChD,QAAM,MAAM,YAAY,IAAI,MAAM,SAAS,MAAM;AACjD,SAAO,KAAK,GAAG,KAAK,GAAG,IAAI,OAAO,KAAK,IAAI,GAAG,GAAG,CAAC,CAAC,GAAG,KAAK,EAAE;AAC/D;AAEA,SAAS,aAAa,QAAoB,OAAwC;AAChF,QAAM,aAAa,SAAS,OAAO,WAAW;AAC9C,QAAM,OAAO;AAAA,IACX,KAAK;AAAA,IACL,KAAK,KAAK,uBAAuB,OAAO,OAAO,KAAK,SAAS,UAAU,EAAE,CAAC;AAAA,EAC5E;AACA,aAAW,QAAQ,OAAO,OAAO,OAAO;AACtC,UAAM,MAAM,QAAQ,IAAI;AACxB,QAAI,IAAK,MAAK,KAAK,GAAG;AAAA,EACxB;AACA,SAAO;AACT;AAEA,SAAS,eAAe,QAAoB,OAAwC;AAClF,QAAM,MAAM,OAAO;AACnB,QAAM,WAAW,SAAS,OAAO,uBAAuB;AACxD,QAAM,YAAY,SAAS,OAAO,wBAAwB;AAC1D,SAAO;AAAA,IACL,KAAK;AAAA,IACL,KAAK,KAAK,uBAAuB,IAAI,gBAAgB,QAAQ,CAAC,CAAC,IAAI,QAAQ,EAAE,CAAC;AAAA,IAC9E,KAAK,KAAK,IAAI,eAAe,MAAM,IAAI,oBAAoB,yBAAyB;AAAA,IACpF;AAAA,MACE,gBAAgB,IAAI,iBAAiB,QAAQ,CAAC,CAAC,IAAI,SAAS,KAAK,IAAI,cAAc,IAAI,IAAI,cAAc;AAAA,IAC3G;AAAA,EACF;AACF;AAEA,SAAS,cAAc,QAAoB,OAAwC;AACjF,QAAM,IAAI,OAAO;AACjB,MAAI,CAAC,EAAG,QAAO,CAAC;AAChB,QAAM,YAAY,SAAS,OAAO,mBAAmB;AACrD,QAAM,QAAQ,CAAC,MACb,EAAE,iBAAiB,IAAI,WAAM,IAAK,IAAI,EAAE,eAAgB,KAAK,QAAQ,CAAC,CAAC;AACzE,QAAM,OAAO;AAAA,IACX,KAAK;AAAA,IACL,KAAK,KAAK,qBAAqB,EAAE,YAAY,YAAY,CAAC;AAAA,IAC1D,KAAK,iBAAiB,EAAE,UAAU,QAAQ,CAAC,CAAC,SAAS,SAAS,EAAE;AAAA,IAChE,KAAK,0BAA0B,EAAE,cAAc,MAAM,MAAM,EAAE,cAAc,CAAC,GAAG;AAAA,IAC/E,KAAK,0BAA0B,EAAE,kBAAkB,MAAM,MAAM,EAAE,kBAAkB,CAAC,GAAG;AAAA,IACvF,KAAK,0BAA0B,EAAE,WAAW,MAAM,MAAM,EAAE,WAAW,CAAC,GAAG;AAAA,EAC3E;AACA,MAAI,EAAE,cAAc,GAAG;AACrB,SAAK,KAAK,KAAK,MAAW,0BAA0B,EAAE,WAAW,EAAE,CAAC,CAAC;AAAA,EACvE;AACA,SAAO;AACT;AAEA,SAAS,YAAY,QAA8B;AACjD,QAAM,IAAI,OAAO;AACjB,SAAO;AAAA,IACL,KAAK;AAAA,IACL,KAAK,KAAK,QAAQ,CAAC;AAAA,IACnB;AAAA,MACE,cAAc,EAAE,WAAW,YAAY,EAAE,SAAS,aAAa,EAAE,mBAAmB;AAAA,IACtF;AAAA,IACA,KAAK,gBAAgB,EAAE,eAAe,eAAe,CAAC,QAAQ;AAAA,EAChE;AACF;AAEA,SAAS,iBAAiB,YAAgC;AACxD,MAAI,WAAW,WAAW,EAAG,QAAO,CAAC;AACrC,SAAO,CAAC,KAAK,GAAG,GAAG,WAAW,IAAI,CAAC,MAAM,KAAK,MAAW,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC;AAC3E;AAMO,SAAS,qBAAqB,QAA4B;AAC/D,QAAM,QAAQ,OAAO;AACrB,QAAM,OAAO;AAAA,IACX,IAAI;AAAA,IACJ,KAAK,KAAK,yCAAoC,CAAC;AAAA,IAC/C,QAAQ;AAAA,IACR,GAAG,aAAa,QAAQ,KAAK;AAAA,IAC7B,GAAG,eAAe,QAAQ,KAAK;AAAA,IAC/B,GAAG,cAAc,QAAQ,KAAK;AAAA,IAC9B,GAAG,YAAY,MAAM;AAAA,IACrB,GAAG,iBAAiB,OAAO,mBAAmB;AAAA,IAC9C,KAAK;AAAA,IACL,OAAO;AAAA,EACT;AACA,SAAO,KAAK,KAAK,IAAI;AACvB;AAMO,SAAS,iBAAiB,QAA4B;AAC3D,SAAO,KAAK,UAAU,QAAQ,MAAM,CAAC;AACvC;AAGA,SAAS,aAAa,KAAqB;AACzC,SAAO,IAAI,MAAM,GAAG,EAAE,EAAE,QAAQ,KAAK,GAAG;AAC1C;AAOO,SAAS,mBAAmB,SAA+B;AAChE,MAAI,QAAQ,WAAW,EAAG,QAAO;AAEjC,QAAMC,UAAS,GAAG,OAAO,OAAO,EAAE,CAAC,GAAG,QAAQ,OAAO,CAAC,CAAC,GAAG,SAAS,OAAO,CAAC,CAAC,GAAG,WAAW,OAAO,EAAE,CAAC;AACpG,QAAMC,WAAU,SAAI,OAAOD,QAAO,MAAM;AACxC,QAAM,OAAO,QAAQ,IAAI,CAAC,MAAM;AAC9B,UAAM,UAAU,EAAE,kBACd,EAAE,gBAAgB,UAAU,QAAQ,CAAC,IACrC;AACJ,WAAO;AAAA,MACL,aAAa,EAAE,SAAS,EAAE,OAAO,EAAE;AAAA,MACnC,EAAE,MAAM,OAAO,CAAC;AAAA,MAChB,OAAO,EAAE,OAAO,KAAK,EAAE,OAAO,CAAC;AAAA,MAC/B,GAAG,EAAE,iBAAiB,gBAAgB,QAAQ,CAAC,CAAC,IAAI,OAAO,EAAE;AAAA,MAC7D;AAAA,IACF,EAAE,KAAK,EAAE;AAAA,EACX,CAAC;AAED,SAAO,CAAC,kBAAkB,QAAQ,MAAM,OAAO,QAAQ,WAAW,IAAI,KAAK,GAAG,KAAKC,UAASD,SAAQC,UAAS,GAAG,IAAI,EAAE,KAAK,IAAI;AACjI;AAEA,IAAM,eAAuC;AAAA,EAC3C,GAAG;AAAA,EACH,GAAG;AAAA,EACH,GAAG;AACL;AAEA,SAAS,IAAI,GAAW,OAAuB;AAC7C,SAAO,UAAU,IAAI,OAAO,IAAK,IAAI,QAAS,KAAK,QAAQ,CAAC,CAAC;AAC/D;AAOO,SAAS,gBAAgB,YAAiC,SAA+B;AAC9F,QAAM,QAAkB,CAAC,KAAK,yBAAsB,QAAQ,KAAK,aAAa,CAAC;AAE/E,MAAI,QAAQ,UAAU,EAAG,QAAO,MAAM,KAAK,IAAI;AAE/C,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,oCAAoC,QAAQ,cAAc,MAAM,IAAI,QAAQ,gBAAgB,QAAQ,KAAK,CAAC,GAAG;AACxH,QAAM,KAAK,oCAAoC,QAAQ,kBAAkB,MAAM,IAAI,QAAQ,oBAAoB,QAAQ,KAAK,CAAC,GAAG;AAChI,QAAM,KAAK,oCAAoC,QAAQ,WAAW,MAAM,IAAI,QAAQ,aAAa,QAAQ,KAAK,CAAC,GAAG;AAElH,MAAI,QAAQ,OAAO,SAAS,GAAG;AAC7B,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,cAAc;AACzB,eAAW,EAAE,MAAM,MAAM,KAAK,QAAQ,OAAO,MAAM,GAAG,EAAE,GAAG;AACzD,YAAM,KAAK,OAAO,IAAI,MAAM,KAAK,aAAa,UAAU,IAAI,KAAK,GAAG,EAAE;AAAA,IACxE;AAAA,EACF;AAGA,OAAK;AACL,SAAO,MAAM,KAAK,IAAI;AACxB;AAEA,IAAM,oBAAoB,SAAI,OAAO,EAAE;AAOhC,SAAS,wBAAwB,YAAyC;AAC/E,MAAI,WAAW,WAAW,EAAG,QAAO;AAEpC,QAAM,SAAS,WAAW,IAAI,CAAC,GAAG,MAAM;AACtC,UAAM,aAAa,aAAa,EAAE,KAAK,KAAK;AAC5C,UAAMD,UAAS,IAAI,IAAI,CAAC,IAAI,WAAW,MAAM,WAAW,EAAE,QAAQ,YAAY,EAAE,KAAK,KAAK,UAAU;AACpG,WAAO;AAAA,MACL;AAAA,MACAA;AAAA,MACA,WAAW,EAAE,SAAS,YAAY,EAAE,SAAS,SAAI,EAAE,OAAO;AAAA,MAC1D,aAAa,EAAE,SAAS;AAAA,MACxB,aAAa,EAAE,QAAQ;AAAA,MACvB,aAAa,EAAE,MAAM;AAAA,IACvB,EAAE,KAAK,IAAI;AAAA,EACb,CAAC;AAED,SAAO,CAAC,GAAG,QAAQ,iBAAiB,EAAE,KAAK,IAAI;AACjD;;;AChOA,SAAS,UAAAE,SAAQ,YAAAC,kBAAgB;AACjC,SAAS,cAAAC,oBAAkB;AAC3B,OAAOC,YAAU;AAGjB,IAAMC,cAAaD,OAAK,KAAK,YAAY,QAAQ,sBAAsB;AAevE,eAAsB,mBAAmB,MAAgC;AACvE,QAAM,YAAYA,OAAK,KAAK,MAAMC,WAAU;AAC5C,MAAI,CAACF,aAAW,SAAS,EAAG,QAAO;AACnC,QAAMF,QAAO,SAAS;AACtB,SAAO;AACT;AAQA,eAAsB,kBAAkB,MAA4C;AAClF,QAAM,YAAYG,OAAK,KAAK,MAAMC,WAAU;AAC5C,MAAI,CAACF,aAAW,SAAS,EAAG,QAAO,CAAC;AAEpC,QAAM,UAAU,MAAMD,WAAS,WAAW,OAAO;AACjD,QAAM,aAAkC,CAAC;AACzC,aAAWI,SAAQ,QAAQ,KAAK,EAAE,MAAM,IAAI,EAAE,OAAO,OAAO,GAAG;AAC7D,QAAI;AACF,iBAAW,KAAK,KAAK,MAAMA,KAAI,CAAsB;AAAA,IACvD,QAAQ;AAAA,IAER;AAAA,EACF;AACA,SAAO;AACT;AAMO,SAAS,uBAAuB,YAA+C;AACpF,MAAI,iBAAiB;AACrB,MAAI,qBAAqB;AACzB,MAAI,cAAc;AAClB,QAAM,aAAa,oBAAI,IAAoB;AAE3C,aAAW,KAAK,YAAY;AAC1B,QAAI,EAAE,UAAU,EAAG;AAAA,aACV,EAAE,UAAU,EAAG;AAAA,QACnB;AACL,eAAW,IAAI,EAAE,WAAW,WAAW,IAAI,EAAE,QAAQ,KAAK,KAAK,CAAC;AAAA,EAClE;AAEA,QAAM,SAAS,CAAC,GAAG,WAAW,QAAQ,CAAC,EACpC,IAAI,CAAC,CAAC,MAAM,KAAK,OAAO,EAAE,MAAM,MAAM,EAAE,EACxC,KAAK,CAAC,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK;AAEnC,SAAO,EAAE,OAAO,WAAW,QAAQ,gBAAgB,oBAAoB,aAAa,OAAO;AAC7F;;;AC5CA,IAAM,sBAAsB;AAYrB,SAAS,gBAAgB,KAAqB;AACnD,QAAM,IAAI,OAAO,GAAG;AACpB,MAAI,CAAC,OAAO,UAAU,CAAC,KAAK,KAAK,GAAG;AAClC,UAAM,IAAI,MAAM,6CAA6C,GAAG,IAAI;AAAA,EACtE;AACA,SAAO;AACT;AAEA,SAAS,mBAAmB,SAA2C;AACrE,SAAO;AAAA,IACL,OAAO,QAAQ,UAAU,SAAS,SAAS;AAAA,IAC3C,YAAY,gBAAgB,QAAQ,UAAU,OAAO,mBAAmB,CAAC;AAAA,IACzE,WAAW,QAAQ,QAAQ,SAAS,SAAS;AAAA,EAC/C;AACF;AAEA,eAAe,kBAAkB,MAAc,OAAwB,YAAoB;AACzF,QAAM,CAAC,QAAQ,kBAAkB,OAAO,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC1E,eAAe,IAAI;AAAA,IACnB,yBAAyB,IAAI;AAAA,IAC7B,aAAa,IAAI;AAAA,IACjB,mBAAmB,IAAI;AAAA,EACzB,CAAC;AACD,QAAM,kBAAkB,UAAU,SAC9B,MAAM,wBAAwB,MAAM,YAAY,gBAAgB,iBAAiB,iBAAiB,CAAC,CAAC,IACpG;AACJ,SAAO,EAAE,QAAQ,kBAAkB,OAAO,gBAAgB,gBAAgB;AAC5E;AAEA,eAAe,YAAY,MAAc,YAA2D,OAA6C;AAC/I,QAAM,EAAE,QAAQ,kBAAkB,OAAO,gBAAgB,gBAAgB,IAAI;AAC7E,QAAM,UAAU;AAAA,IACd;AAAA,IACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA,GAAI,kBAAkB,EAAE,gBAAgB,IAAI,CAAC;AAAA,EAC/C;AACA,QAAM,QAAQ,iBAAiB,aAAa,SAAuB,cAAc,IAAI;AACrF,QAAM,sBAAsB,MAAM,gBAAgB,SAAuB,IAAI;AAC7E,SAAO,EAAE,GAAG,SAAS,GAAI,QAAQ,EAAE,MAAM,IAAI,CAAC,GAAI,oBAAoB;AACxE;AAMA,eAAO,YAAmC,UAAuB,CAAC,GAAkB;AAClF,QAAM,OAAO,QAAQ,IAAI;AACzB,QAAM,EAAE,OAAO,YAAY,UAAU,IAAI,mBAAmB,OAAO;AAEnE,QAAM,aAAa,MAAM,kBAAkB,MAAM,OAAO,UAAU;AAClE,QAAM,SAAS,MAAM,YAAY,MAAM,YAAY,KAAK;AACxD,QAAM,cAAc,MAAM,MAAM;AAEhC,QAAM,SAAS,cAAc,SAAS,iBAAiB,MAAM,IAAI,qBAAqB,MAAM;AAC5F,UAAQ,IAAI,MAAM;AAElB,MAAI,OAAO,oBAAoB,SAAS,GAAG;AACzC,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAGA,eAAsB,wBAAuC;AAC3D,QAAM,UAAU,MAAM,mBAAmB,QAAQ,IAAI,CAAC;AACtD,UAAQ,IAAI,UAAU,4BAA4B,0BAA0B;AAC9E;AAGA,eAAsB,uBAAsC;AAC1D,QAAM,aAAa,MAAM,kBAAkB,QAAQ,IAAI,CAAC;AACxD,QAAM,UAAU,uBAAuB,UAAU;AACjD,UAAQ,IAAI,gBAAgB,YAAY,OAAO,CAAC;AAClD;AAKA,eAAsB,kBAAkB,UAAyB,CAAC,GAAkB;AAClF,QAAM,SAAS,MAAM,mBAAmB,QAAQ,IAAI,CAAC;AACrD,MAAI,CAAC,QAAQ;AACX,YAAQ,IAAI,oEAAoE;AAChF;AAAA,EACF;AACA,QAAM,SAAS,QAAQ,QAAQ,SAAS,iBAAiB,MAAM,IAAI,qBAAqB,MAAM;AAC9F,UAAQ,IAAI,MAAM;AACpB;AAKA,eAAsB,mBAAmB,UAA0B,CAAC,GAAkB;AACpF,QAAM,IAAI,SAAS,QAAQ,KAAK,MAAM,EAAE;AACxC,QAAM,UAAU,MAAM,YAAY,QAAQ,IAAI,GAAG,CAAC;AAClD,MAAI,QAAQ,QAAQ,QAAQ;AAC1B,YAAQ,IAAI,KAAK,UAAU,SAAS,MAAM,CAAC,CAAC;AAC5C;AAAA,EACF;AACA,UAAQ,IAAI,mBAAmB,OAAO,CAAC;AACzC;AAIA,SAAS,iBAAiB,YAA2D,SAA4B;AAC/G,MAAI,SAAS;AACb,MAAI,QAAQ,UAAU,OAAW,UAAS,OAAO,OAAO,CAAC,MAAM,EAAE,UAAU,SAAS,QAAQ,OAAQ,EAAE,CAAC;AACvG,MAAI,QAAQ,KAAM,UAAS,OAAO,OAAO,CAAC,MAAM,EAAE,aAAa,QAAQ,IAAI;AAC3E,MAAI,QAAQ,MAAM,OAAW,UAAS,OAAO,MAAM,GAAG,SAAS,QAAQ,GAAG,EAAE,CAAC;AAC7E,SAAO;AACT;AAGA,eAAsB,sBAAsB,UAA6B,CAAC,GAAkB;AAC1F,QAAM,aAAa,iBAAiB,MAAM,kBAAkB,QAAQ,IAAI,CAAC,GAAG,OAAO;AACnF,MAAI,QAAQ,QAAQ,QAAQ;AAC1B,YAAQ,IAAI,KAAK,UAAU,YAAY,MAAM,CAAC,CAAC;AAC/C;AAAA,EACF;AACA,UAAQ,IAAI,wBAAwB,UAAU,CAAC;AACjD;;;ACzJA,OAAOC,YAAU;AACjB,SAAS,qBAAqB;;;ACK9B,SAAS,aAAa,KAA8B;AAClD,QAAM,OAAO,IAAI;AACjB,SAAO;AAAA,IACL,OAAO,IAAI;AAAA,IACX,MAAM,IAAI;AAAA,IACV,eAAe,IAAI;AAAA,IACnB,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAAA,IAC3D,SAAS,MAAM,QAAQ,KAAK,OAAO,IAC9B,KAAK,QAAsB,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IAC5E,CAAC;AAAA,IACL,MAAM,MAAM,QAAQ,KAAK,IAAI,IACxB,KAAK,KAAmB,OAAO,CAAC,MAAmB,OAAO,MAAM,QAAQ,IACzE,CAAC;AAAA,IACL,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACxF,WAAW,OAAO,KAAK,cAAc,WAAW,KAAK,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACxF,OAAO,qBAAqB,IAAI,IAAI;AAAA,IACpC,MAAM,IAAI;AAAA,EACZ;AACF;AAOA,eAAsB,mBAAmB,MAAqC;AAC5E,QAAM,MAAM,MAAM,oBAAoB,IAAI;AAC1C,QAAM,OAAO,IAAI,OAAO,CAAC,SAAS,KAAK,YAAY,YAAY,CAAC,KAAK,YAAY,QAAQ;AACzF,QAAM,QAAQ,KAAK,IAAI,YAAY;AACnC,QAAM,KAAK,CAAC,GAAG,MAAM,EAAE,MAAM,cAAc,EAAE,KAAK,CAAC;AACnD,SAAO;AACT;;;AChCA,SAAS,iBAAiB,MAA0B;AAClD,SAAO,QAAQ,KAAK,aAAa,IAAI,KAAK,IAAI;AAChD;AAMA,SAAS,eAAe,MAA0B;AAChD,QAAM,QAAkB,CAAC;AACzB,MAAI,KAAK,QAAS,OAAM,KAAK,KAAK,OAAO;AACzC,MAAI,KAAK,KAAK,SAAS,EAAG,OAAM,KAAK,SAAS,KAAK,KAAK,KAAK,IAAI,CAAC,EAAE;AACpE,MAAI,KAAK,QAAQ,SAAS,EAAG,OAAM,KAAK,YAAY,KAAK,QAAQ,KAAK,IAAI,CAAC,EAAE;AAC7E,QAAM,KAAK,YAAY,KAAK,SAAS,EAAE;AACvC,QAAM,KAAK,YAAY,KAAK,SAAS,EAAE;AACvC,SAAO,MAAM,KAAK,KAAK;AACzB;AAGA,SAAS,gBAAgB,MAA0B;AACjD,QAAM,OAAO,eAAe,IAAI;AAChC,SAAO,MAAM,KAAK,KAAK,KAAK,iBAAiB,IAAI,CAAC,MAAM,IAAI;AAC9D;AAGA,SAAS,aAAa,SAAiB,OAA+B;AACpE,MAAI,MAAM,WAAW,EAAG,QAAO,CAAC;AAChC,SAAO,CAAC,MAAM,OAAO,IAAI,IAAI,GAAG,MAAM,IAAI,eAAe,GAAG,EAAE;AAChE;AASO,SAAS,aAAa,OAAqB,cAA8B;AAC9E,QAAM,WAAW,MAAM,OAAO,CAAC,MAAM,EAAE,kBAAkB,UAAU;AACnE,QAAM,UAAU,MAAM,OAAO,CAAC,MAAM,EAAE,kBAAkB,SAAS;AAEjE,QAAM,QAAkB;AAAA,IACtB,KAAK,YAAY;AAAA,IACjB;AAAA,IACA,KAAK,MAAM,MAAM,2BAAqB,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,IAC9D;AAAA,IACA,GAAG,aAAa,YAAY,QAAQ;AAAA,IACpC,GAAG,aAAa,iBAAiB,OAAO;AAAA,EAC1C;AAEA,SAAO,MAAM,KAAK,IAAI;AACxB;AASO,SAAS,iBAAiB,OAAqB,cAA8B;AAClF,QAAM,WAAqB,CAAC,aAAa,OAAO,YAAY,CAAC;AAE7D,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,KAAK,KAAK,SAAS,IAAI;AAAA,QAAW,KAAK,KAAK,KAAK,IAAI,CAAC,KAAK;AACxE,UAAM,UAAU,KAAK,QAAQ,SAAS,IAAI;AAAA,WAAc,KAAK,QAAQ,KAAK,IAAI,CAAC,KAAK;AACpF,UAAMC,UAAS;AAAA,MACb;AAAA,MACA,MAAM,KAAK,KAAK;AAAA,MAChB,KAAK,KAAK,OAAO,GAAG,IAAI,GAAG,OAAO;AAAA,MAClC,YAAY,KAAK,SAAS,eAAe,KAAK,SAAS;AAAA,MACvD;AAAA,IACF,EAAE,KAAK,IAAI;AACX,aAAS,KAAK,GAAGA,OAAM;AAAA,EAAK,KAAK,KAAK,KAAK,CAAC;AAAA,CAAI;AAAA,EAClD;AAEA,SAAO,SAAS,KAAK,IAAI;AAC3B;;;ACxEO,SAAS,gBAAgB,OAA6B;AAC3D,QAAM,MAA0B;AAAA,IAC9B,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,WAAW,MAAM;AAAA,IACjB;AAAA,EACF;AACA,SAAO,KAAK,UAAU,KAAK,MAAM,CAAC;AACpC;;;ACnBA,IAAM,aAAa;AAGnB,SAAS,QAAQ,MAAsB;AACrC,SAAO,GAAG,UAAU,GAAG,IAAI;AAC7B;AAGA,SAAS,aAAa,MAA2C;AAC/D,QAAM,OAAgC;AAAA,IACpC,OAAO,QAAQ,KAAK,IAAI;AAAA,IACxB,SAAS;AAAA,IACT,MAAM,KAAK;AAAA,IACX,aAAa,KAAK;AAAA,IAClB,aAAa,KAAK;AAAA,IAClB,cAAc,KAAK;AAAA,EACrB;AAEA,MAAI,KAAK,KAAK,SAAS,GAAG;AACxB,SAAK,UAAU,IAAI,KAAK;AAAA,EAC1B;AAGA,MAAI,KAAK,QAAQ,SAAS,GAAG;AAC3B,SAAK,WAAW,IAAI,KAAK;AAAA,EAC3B;AAEA,MAAI,KAAK,MAAM,SAAS,GAAG;AACzB,SAAK,UAAU,IAAI,KAAK,MAAM,IAAI,CAAC,UAAU,EAAE,OAAO,QAAQ,IAAI,EAAE,EAAE;AAAA,EACxE;AAEA,SAAO;AACT;AAOO,SAAS,YAAY,OAA6B;AACvD,QAAM,MAAM;AAAA,IACV,YAAY;AAAA,IACZ,UAAU,MAAM,IAAI,YAAY;AAAA,EAClC;AACA,SAAO,KAAK,UAAU,KAAK,MAAM,CAAC;AACpC;;;AC7CA,IAAM,cAAsC;AAAA,EAC1C,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AAAA,EACL,KAAK;AACP;AAGA,SAAS,UAAU,OAAuB;AACxC,SAAO,MAAM,QAAQ,YAAY,CAAC,OAAO,YAAY,EAAE,KAAK,EAAE;AAChE;AAGA,IAAM,WAAW;AAAA,EACf;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,EAAE,KAAK,MAAM;AAGb,SAAS,WAAW,MAA0B;AAC5C,QAAM,OAAO,KAAK,KAAK,KAAK,IAAI;AAChC,QAAM,UAAU,KAAK,QAAQ,KAAK,IAAI;AACtC,SAAO;AAAA,IACL,eAAe,UAAU,KAAK,IAAI,CAAC;AAAA,IACnC,yBAAyB,UAAU,KAAK,KAAK,CAAC;AAAA,IAC9C,2BAA2B,UAAU,KAAK,OAAO,CAAC;AAAA,IAClD,wBAAwB,UAAU,IAAI,CAAC;AAAA,IACvC,2BAA2B,UAAU,OAAO,CAAC;AAAA,IAC7C,6BAA6B,UAAU,KAAK,SAAS,CAAC;AAAA,IACtD,6BAA6B,UAAU,KAAK,SAAS,CAAC;AAAA,IACtD;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAGA,SAAS,YAAY,MAAkB,YAAmC;AACxE,SAAO,KAAK,MACT,OAAO,CAAC,SAAS,WAAW,IAAI,IAAI,CAAC,EACrC;AAAA,IACC,CAAC,SACC,mBAAmB,UAAU,KAAK,IAAI,CAAC,aAAa,UAAU,IAAI,CAAC;AAAA,EACvE;AACJ;AASO,SAAS,aAAa,OAA6B;AACxD,QAAM,aAAa,IAAI,IAAI,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC;AACnD,QAAM,QAAQ,MAAM,IAAI,UAAU,EAAE,KAAK,IAAI;AAC7C,QAAM,QAAQ,MAAM,QAAQ,CAAC,MAAM,YAAY,GAAG,UAAU,CAAC,EAAE,KAAK,IAAI;AAExE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,KAAK,QAAQ;AAAA,IACb;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;;;ACxEA,IAAM,uBAAuB;AAG7B,SAAS,sBAAsB,MAAsB;AACnD,QAAM,UAAU,KAAK,KAAK;AAE1B,QAAM,aAAa,QAAQ,MAAM,SAAS,EAAE,CAAC,KAAK;AAElD,QAAM,WAAW,WACd,QAAQ,gBAAgB,EAAE,EAC1B,QAAQ,eAAe,EAAE,EACzB,KAAK;AACR,MAAI,SAAS,UAAU,qBAAsB,QAAO;AACpD,SAAO,GAAG,SAAS,MAAM,GAAG,oBAAoB,CAAC;AACnD;AAGA,SAAS,kBAAkB,MAA0B;AACnD,QAAM,QAAkB,CAAC,YAAY,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,EAAE;AACnF,MAAI,KAAK,QAAQ,SAAS,EAAG,OAAM,KAAK,YAAY,KAAK,QAAQ,KAAK,IAAI,CAAC,EAAE;AAC7E,SAAO,QAAQ,MAAM,KAAK,KAAK,CAAC;AAClC;AAGA,SAAS,YAAY,MAA0B;AAC7C,QAAM,UAAU,KAAK,KAAK,SAAS,IAAI;AAAA,SAAY,KAAK,KAAK,KAAK,IAAI,CAAC,MAAM;AAC7E,QAAM,UAAU,sBAAsB,KAAK,IAAI;AAC/C,QAAM,QAAQ,kBAAkB,IAAI;AACpC,SAAO;AAAA,IACL,MAAM,KAAK,KAAK;AAAA,IAChB;AAAA,IACA,KAAK,KAAK,OAAO,GAAG,OAAO;AAAA,IAC3B;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF,EAAE,KAAK,IAAI;AACb;AAMA,SAAS,eAAe,OAAqBC,SAAkC;AAC7E,MAAIA,YAAW,MAAO,QAAO;AAC7B,SAAO,MAAM,OAAO,CAAC,MAAM,EAAE,kBAAkBA,OAAM;AACvD;AASO,SAAS,UACd,OACA,cACAA,UAAqB,OACb;AACR,QAAM,WAAW,eAAe,OAAOA,OAAM;AAE7C,QAAM,cAAc;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,WAAW,YAAY;AAAA,IACvB;AAAA,EACF,EAAE,KAAK,IAAI;AAEX,QAAM,aAAa;AAAA,IACjB;AAAA,IACA,KAAK,YAAY;AAAA,IACjB;AAAA,IACA,GAAG,SAAS,MAAM,aAAY,oBAAI,KAAK,GAAE,YAAY,CAAC;AAAA,EACxD,EAAE,KAAK,IAAI;AAEX,QAAM,SAAS,SAAS,IAAI,CAAC,MAAM;AAAA;AAAA,EAAU,YAAY,CAAC,CAAC,EAAE;AAE7D,SAAO,CAAC,aAAa,YAAY,GAAG,QAAQ,EAAE,EAAE,KAAK,MAAM;AAC7D;;;ACjDO,IAAM,eAAsC,CAAC,YAAY,WAAW,KAAK;AAYzE,IAAM,iBAA0C;AAAA,EACrD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;;;APvCA,IAAMC,WAAU,cAAc,YAAY,GAAG;AAG7C,IAAM,aAAa;AAGnB,IAAM,mBAAiD;AAAA,EACrD,YAAY;AAAA,EACZ,iBAAiB;AAAA,EACjB,MAAM;AAAA,EACN,WAAW;AAAA,EACX,SAAS;AAAA,EACT,MAAM;AACR;AAsBA,SAAS,oBAAoB,MAAsB;AACjD,MAAI;AACF,UAAM,MAAMA,SAAQC,OAAK,KAAK,MAAM,cAAc,CAAC;AACnD,WAAO,OAAO,IAAI,SAAS,WAAW,IAAI,OAAO;AAAA,EACnD,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,cAAc,OAAsC;AAC3D,SAAQ,eAAqC,SAAS,KAAK;AAC7D;AAGA,SAAS,kBAAkB,OAAoC;AAC7D,SAAQ,aAAmC,SAAS,KAAK;AAC3D;AAGA,SAAS,kBAAkB,WAA2C;AACpE,MAAI,CAAC,UAAW,QAAO;AACvB,MAAI,CAAC,kBAAkB,SAAS,GAAG;AACjC,UAAM,IAAI;AAAA,MACR,2BAA2B,SAAS,oBAAoB,aAAa,KAAK,IAAI,CAAC;AAAA,IACjF;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,aACP,QACA,OACA,cACA,YACQ;AACR,UAAQ,QAAQ;AAAA,IACd,KAAK;AACH,aAAO,aAAa,OAAO,YAAY;AAAA,IACzC,KAAK;AACH,aAAO,iBAAiB,OAAO,YAAY;AAAA,IAC7C,KAAK;AACH,aAAO,gBAAgB,KAAK;AAAA,IAC9B,KAAK;AACH,aAAO,YAAY,KAAK;AAAA,IAC1B,KAAK;AACH,aAAO,aAAa,KAAK;AAAA,IAC3B,KAAK;AACH,aAAO,UAAU,OAAO,cAAc,UAAU;AAAA,EACpD;AACF;AAQA,SAAS,yBACP,OACA,SACA,YACQ;AACR,QAAM,iBAAiB,QAAQ,WAAW,KAAK,QAAQ,CAAC,MAAM;AAC9D,MAAI,kBAAkB,eAAe,OAAO;AAC1C,WAAO,MAAM,OAAO,CAAC,MAAM,EAAE,kBAAkB,UAAU,EAAE;AAAA,EAC7D;AACA,SAAO,MAAM;AACf;AAQA,eAAsB,UAAU,MAAc,UAAyB,CAAC,GAA0B;AAChG,QAAM,QAAQ,MAAM,mBAAmB,IAAI;AAC3C,QAAM,eAAe,oBAAoB,IAAI;AAE7C,QAAM,UAAU,eAAe,QAAQ,MAAM;AAC7C,QAAM,aAAa,kBAAkB,QAAQ,MAAM;AACnD,QAAM,UAAoB,CAAC;AAE3B,aAAW,UAAU,SAAS;AAC5B,UAAM,UAAU,aAAa,QAAQ,OAAO,cAAc,UAAU;AACpE,UAAM,UAAUA,OAAK,KAAK,MAAM,YAAY,iBAAiB,MAAM,CAAC;AACpE,UAAM,YAAY,SAAS,OAAO;AAClC,YAAQ,KAAK,OAAO;AACpB,IAAO,OAAO,KAAY,QAAQ,YAAY,MAAM,WAAa,OAAO,OAAO,CAAC,EAAE,CAAC;AAAA,EACrF;AAEA,SAAO,EAAE,SAAS,WAAW,yBAAyB,OAAO,SAAS,UAAU,EAAE;AACpF;AAOA,SAAS,eAAe,WAA+C;AACrE,MAAI,CAAC,UAAW,QAAO,CAAC,GAAG,cAAc;AAEzC,MAAI,CAAC,cAAc,SAAS,GAAG;AAC7B,UAAM,IAAI;AAAA,MACR,0BAA0B,SAAS,qBAAqB,eAAe,KAAK,IAAI,CAAC;AAAA,IACnF;AAAA,EACF;AAEA,SAAO,CAAC,SAAS;AACnB;AAOA,eAAO,cACL,MACA,SACe;AACf,EAAO,OAAO,gBAAgB;AAC9B,QAAM,EAAE,SAAS,UAAU,IAAI,MAAM,UAAU,MAAM,OAAO;AAC5D,EAAO;AAAA,IACL;AAAA,IACO,QAAQ,eAAU,SAAS,sBAAsB,QAAQ,MAAM,WAAW;AAAA,EACnF;AACF;;;AQrLA,SAAS,cAAAC,oBAAkB;AAC3B,SAAS,SAAAC,QAAO,aAAAC,kBAAiB;AACjC,OAAOC,YAAU;AAajB,eAAsB,oBAAmC;AACvD,QAAM,OAAO,QAAQ,IAAI;AACzB,QAAM,WAAW,mBAAmB;AACpC,QAAM,aAAa,sBAAsB,IAAI;AAE7C,MAAIC,aAAW,UAAU,GAAG;AAC1B,IAAO,OAAO,KAAY,KAAK,iCAAiC,UAAU,EAAE,CAAC;AAC7E;AAAA,EACF;AAEA,QAAMC,OAAMC,OAAK,QAAQ,UAAU,GAAG,EAAE,WAAW,KAAK,CAAC;AACzD,QAAM,eAAe;AAAA,IACnB,SAAS,SAAS;AAAA,IAClB,aAAa,SAAS;AAAA,IACtB,OAAO,SAAS;AAAA,IAChB,WAAW,SAAS;AAAA,EACtB;AACA,QAAMC,WAAU,YAAY,GAAG,KAAK,UAAU,cAAc,MAAM,CAAC,CAAC;AAAA,GAAM,OAAO;AACjF,EAAO,OAAO,KAAY,QAAQ,mBAAmB,UAAU,EAAE,CAAC;AACpE;AAMA,eAAsB,oBAAmC;AACvD,QAAM,SAAS,MAAM,WAAW,QAAQ,IAAI,CAAC;AAC7C,QAAM,aAAa,OAAO,cAAc;AACxC,EAAO,OAAO,WAAW,UAAU,GAAG;AACtC,UAAQ,IAAI,sBAAsB,MAAM,CAAC;AAC3C;;;AC7CA,eAAO,oBAA0D;AAC/D,EAAO,OAAO,2BAA2B;AAEzC,QAAM,aAAa,MAAM,eAAe,QAAQ,IAAI,CAAC;AACrD,MAAI,WAAW,WAAW,GAAG;AAC3B,IAAO,OAAO,UAAY,QAAQ,wBAAwB,CAAC;AAC3D;AAAA,EACF;AAEA,aAAW,aAAa,YAAY;AAClC,UAAM,UAAU,UAAU,QAAQ,KAAK,IAAI;AAC3C,UAAM,OAAc,IAAI,GAAG,UAAU,WAAW,eAAe,OAAO,EAAE;AACxE,IAAO,OAAO,KAAK,GAAU,KAAK,UAAU,EAAE,CAAC,WAAM,UAAU,IAAI,IAAI,IAAI,EAAE;AAAA,EAC/E;AAEA,EAAO;AAAA,IACL;AAAA,IACO,IAAI,0DAA0D;AAAA,EACvE;AACF;;;ACnBA,eAAO,kBAAyC,IAA2B;AACzE,QAAM,YAAY,MAAM,oBAAoB,QAAQ,IAAI,GAAG,EAAE;AAC7D,MAAI,CAAC,UAAW;AAEhB,EAAO,OAAO,aAAa,UAAU,EAAE,EAAE;AACzC,EAAO,OAAO,KAAY,IAAI,eAAe,UAAU,KAAK,EAAE,CAAC;AAC/D,EAAO,OAAO,KAAY,IAAI,eAAe,UAAU,IAAI,EAAE,CAAC;AAC9D,EAAO,OAAO,KAAY,IAAI,eAAe,UAAU,OAAO,EAAE,CAAC;AACjE,EAAO,OAAO,KAAY,IAAI,eAAe,UAAU,QAAQ,KAAK,IAAI,CAAC,EAAE,CAAC;AAC5E,EAAO,OAAO,KAAY,IAAI,eAAe,UAAU,WAAW,EAAE,CAAC;AAErE,UAAQ,IAAI;AACZ,UAAQ,IAAI,UAAU,IAAI;AAE1B,MAAI,UAAU,oBAAoB,UAAU,iBAAiB,SAAS,GAAG;AACvE,YAAQ,IAAI;AACZ,IAAO,OAAO,mBAAmB;AACjC,eAAW,KAAK,UAAU,kBAAkB;AAC1C,MAAO,OAAO,KAAY,KAAK,IAAI,EAAE,QAAQ,KAAK,EAAE,OAAO,EAAE,CAAC;AAAA,IAChE;AAAA,EACF;AAEA,MAAI,UAAU,wBAAwB,UAAU,qBAAqB,SAAS,GAAG;AAC/E,YAAQ,IAAI;AACZ,IAAO,OAAO,uBAAuB;AACrC,eAAW,KAAK,UAAU,sBAAsB;AAC9C,MAAO,OAAO,KAAY,KAAK,IAAI,EAAE,QAAQ,KAAK,EAAE,OAAO,EAAE,CAAC;AAAA,IAChE;AAAA,EACF;AACF;;;ACzBA,OAAOC,YAAU;;;ACmBjB,eAAsB,mBACpB,IACA,WACe;AACf,QAAM,OAAO,QAAQ,IAAI;AAIzB,QAAM,WAAW,MAAM,oBAAoB,MAAM,EAAE;AACnD,MAAI,CAAC,SAAU;AAEf,QAAM,SAAS,MAAM,YAAY,IAAI;AACrC,MAAI,CAAC,QAAQ;AACX,IAAO,OAAO,KAAY,MAAM,0CAA0C,CAAC;AAC3E,YAAQ,WAAW;AACnB;AAAA,EACF;AAEA,MAAI;AACF,UAAM,UAAU,MAAM,EAAE;AAAA,EAC1B,UAAE;AACA,UAAM,YAAY,IAAI;AAAA,EACxB;AACF;;;ADtBA,eAAO,qBAA4C,IAA2B;AAC5E,QAAM,mBAAmB,IAAI,gBAAgB;AAC/C;AASA,eAAe,iBAAiB,MAAc,IAA2B;AACvE,QAAM,YAAY,MAAM,6BAAuB,MAAM,EAAE;AACvD,MAAI,CAAC,UAAW;AAEhB,MAAI,CAAC,iBAAiB,UAAU,IAAI,GAAG;AACrC,IAAO,OAAO,KAAY,MAAM,aAAa,EAAE,wCAAwC,CAAC;AACxF,YAAQ,WAAW;AACnB;AAAA,EACF;AAEA,QAAM,WAAWC,OAAK,KAAK,MAAM,cAAc,GAAG,UAAU,IAAI,KAAK;AACrE,QAAM,YAAY,UAAU,UAAU,IAAI;AAC1C,EAAO,OAAO,KAAY,QAAQ,mBAAqB,OAAO,QAAQ,CAAC,EAAE,CAAC;AAE1E,QAAM,6BAA6B,MAAM,SAAS;AAClD,QAAM,yBAAyB,MAAM,UAAU,IAAI;AACnD,QAAM,gBAAgB,MAAM,EAAE;AAC9B,EAAO,OAAO,UAAY,IAAI,aAAa,EAAE,WAAW,CAAC;AAC3D;AAgBA,eAAe,6BACb,MACA,WACe;AACf,QAAM,SAAS,UAAU;AACzB,MAAI,CAAC,OAAQ;AACb,QAAM,eAAe,MAAM,6BAA6B,MAAM,UAAU,EAAE;AAC1E,aAAW,CAAC,YAAY,KAAK,KAAK,OAAO,QAAQ,MAAM,GAAG;AACxD,QAAI,aAAa,IAAI,UAAU,EAAG;AAClC,UAAM,kBAAkB,MAAM,YAAY,KAAK;AAAA,EACjD;AACF;AAOA,eAAe,6BACb,MACA,aACsB;AACtB,QAAM,UAAU,MAAM,eAAe,IAAI;AACzC,QAAM,UAAU,oBAAI,IAAY;AAChC,aAAW,aAAa,SAAS;AAC/B,QAAI,UAAU,OAAO,YAAa;AAClC,eAAWC,WAAU,UAAU,QAAS,SAAQ,IAAIA,OAAM;AAAA,EAC5D;AACA,SAAO;AACT;AAGA,eAAe,yBAAyB,MAAc,MAA6B;AACjF,QAAM,aAAa,MAAM,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC;AACvC,QAAM,cAAc,IAAI;AACxB,QAAM,YAAY,IAAI;AACtB,QAAMC,wBAAuB,MAAM,CAAC,IAAI,CAAC;AAC3C;AAOA,eAAeA,wBAAuB,MAAc,OAAgC;AAClF,MAAI;AACF,UAAM,iBAAiB,MAAM,KAAK;AAAA,EACpC,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,IAAO,OAAO,KAAY,KAAK,8BAA8B,OAAO,EAAE,CAAC;AAAA,EACzE;AACF;;;AE9GA,eAAO,oBAA2C,IAA2B;AAC3E,QAAM,mBAAmB,IAAI,eAAe;AAC9C;AASA,eAAe,gBAAgB,MAAc,IAA2B;AACtE,QAAM,YAAY,MAAM,6BAAuB,MAAM,EAAE;AACvD,MAAI,CAAC,UAAW;AAEhB,QAAM,iBAAiB,MAAM,EAAE;AAC/B,EAAO;AAAA,IACL;AAAA,IACO,KAAK,sBAAsB,EAAE,KAAK,UAAU,IAAI,oCAA+B;AAAA,EACxF;AACF;;;ACtBA,SAAS,QAAAC,OAAM,WAAAC,WAAS,YAAAC,kBAAgB;AACxC,OAAOC,YAAU;AAcjB,IAAM,eAAe;AAgDrB,eAAsB,oBAAoB,MAAqC;AAC7E,QAAM,eAAe,MAAM,YAAY,IAAI;AAC3C,MAAI,CAAC,aAAc,QAAO,mBAAmB,IAAI;AACjD,QAAM,OAAO,MAAM,mBAAmB,IAAI;AAC1C,QAAM,SAAS,MAAM,kBAAkB,MAAM,IAAI;AACjD,QAAMC,QAAO,MAAM,uBAAuB,IAAI;AAC9C,QAAM,SAAS,MAAM,cAAc,MAAM,IAAI;AAC7C,SAAO,cAAc,EAAE,MAAM,MAAM,QAAQ,MAAAA,OAAM,OAAO,CAAC;AAC3D;AAGA,SAAS,mBAAmB,MAA4B;AACtD,SAAO;AAAA,IACL;AAAA,IACA,eAAe;AAAA,IACf,YAAY;AAAA,IACZ,gBAAgB;AAAA,IAChB,aAAa;AAAA,IACb,cAAc;AAAA,IACd,YAAY;AAAA,IACZ,mBAAmB;AAAA,IACnB,UAAU;AAAA,IACV,MAAM,EAAE,SAAS,OAAO,OAAO,KAAK;AAAA,IACpC,mBAAmB;AAAA,IACnB,qBAAqB;AAAA,IACrB,UAAU;AAAA,MACR;AAAA,QACE,MAAM;AAAA,QACN,SAAS,yDAAyD,IAAI;AAAA,MACxE;AAAA,IACF;AAAA,EACF;AACF;AAyBA,eAAe,mBAAmB,MAAoC;AACpE,QAAM,CAAC,eAAe,YAAY,cAAc,IAAI,MAAM,QAAQ,IAAI;AAAA,IACpE,YAAYC,OAAK,KAAK,MAAM,WAAW,CAAC;AAAA,IACxC,YAAYA,OAAK,KAAK,MAAM,MAAM,CAAC;AAAA,IACnC,YAAYA,OAAK,KAAK,MAAM,WAAW,CAAC;AAAA,EAC1C,CAAC;AACD,SAAO,EAAE,eAAe,YAAY,eAAe;AACrD;AAGA,eAAe,kBAAkB,MAAc,MAAwC;AACrF,QAAM,CAAC,aAAa,cAAc,YAAY,mBAAmB,QAAQ,IAAI,MAAM,QAAQ,IAAI;AAAA,IAC7F,KAAK,gBAAgB,mBAAmBA,OAAK,KAAK,MAAM,WAAW,CAAC,IAAI;AAAA,IACxE,KAAK,aAAa,mBAAmBA,OAAK,KAAK,MAAM,YAAY,CAAC,IAAI;AAAA,IACtE,KAAK,aAAa,mBAAmBA,OAAK,KAAK,MAAM,WAAW,CAAC,IAAI;AAAA,IACrE,KAAK,iBAAiB,oBAAoB,IAAI,IAAI;AAAA,IAClD,KAAK,aAAa,OAAOA,OAAK,KAAK,MAAM,UAAU,CAAC,IAAI;AAAA,EAC1D,CAAC;AACD,SAAO,EAAE,aAAa,cAAc,YAAY,mBAAmB,SAAS;AAC9E;AAGA,eAAe,cAAc,MAAc,MAAuC;AAChF,QAAM,CAAC,mBAAmB,mBAAmB,IAAI,MAAM,QAAQ,IAAI;AAAA,IACjE,KAAK,aAAa,UAAUA,OAAK,KAAK,MAAM,MAAM,CAAC,IAAI,QAAQ,QAAQ,IAAI;AAAA,IAC3E,KAAK,gBAAgB,UAAUA,OAAK,KAAK,MAAM,WAAW,CAAC,IAAI,QAAQ,QAAQ,IAAI;AAAA,EACrF,CAAC;AACD,SAAO,EAAE,mBAAmB,oBAAoB;AAClD;AAGA,eAAe,uBAAuB,MAAwC;AAC5E,QAAM,YAAYA,OAAK,KAAK,MAAM,cAAc;AAChD,QAAM,SAAS,MAAM,OAAO,SAAS;AACrC,MAAI,CAAC,OAAQ,QAAO,EAAE,SAAS,OAAO,OAAO,KAAK;AAClD,QAAM,QAAQ,MAAM,mBAAmB,SAAS;AAChD,SAAO,EAAE,SAAS,MAAM,MAAM;AAChC;AAGA,eAAe,mBAAmB,WAAmD;AACnF,MAAI;AACJ,MAAI;AACF,UAAM,MAAMC,WAAS,WAAW,OAAO;AAAA,EACzC,QAAQ;AACN,WAAO;AAAA,EACT;AACA,MAAI;AACF,UAAM,SAAS,KAAK,MAAM,GAAG;AAC7B,WAAO,mBAAmB,MAAM;AAAA,EAClC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,mBAAmB,OAAuC;AACjE,MAAI,OAAO,UAAU,YAAY,UAAU,KAAM,QAAO;AACxD,QAAM,YAAY;AAClB,QAAM,EAAE,UAAU,QAAQ,GAAG,IAAI;AACjC,MAAI,CAACC,sBAAqB,QAAQ,EAAG,QAAO;AAC5C,MAAI,CAACA,sBAAqB,MAAM,EAAG,QAAO;AAC1C,MAAI,OAAO,OAAO,YAAY,CAAC,6BAA6B,KAAK,EAAE,EAAG,QAAO;AAC7E,SAAO,EAAE,UAAU,QAAQ,GAAG;AAChC;AAGA,SAASA,sBAAqB,OAAiC;AAC7D,SAAO,OAAO,UAAU,YAAY,OAAO,UAAU,KAAK,KAAK,SAAS;AAC1E;AAYA,SAAS,cAAc,OAAyC;AAC9D,QAAM,EAAE,MAAM,MAAM,QAAQ,MAAAH,OAAM,OAAO,IAAI;AAC7C,QAAM,WAAW,cAAc,EAAE,MAAM,QAAQ,MAAAA,OAAM,OAAO,CAAC;AAC7D,SAAO,EAAE,MAAM,GAAG,MAAM,GAAG,QAAQ,MAAAA,OAAM,GAAG,QAAQ,SAAS;AAC/D;AAWA,SAAS,cAAc,OAA4C;AACjE,QAAM,WAAkC,CAAC;AACzC,qBAAmB,UAAU,MAAM,MAAM,MAAM,OAAO,iBAAiB;AACvE,2BAAyB,UAAU,MAAM,MAAM,MAAM,MAAM;AAC3D,SAAO;AACT;AAGA,SAAS,mBACP,UACAA,OACA,mBACM;AACN,MAAIA,MAAK,WAAWA,MAAK,UAAU,MAAM;AACvC,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS;AAAA,IACX,CAAC;AACD;AAAA,EACF;AACA,MAAIA,MAAK,SAAS,iBAAiBA,MAAK,OAAO,iBAAiB,GAAG;AACjE,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAGA,SAAS,yBACP,UACA,MACA,QACM;AACN,QAAM,WAAW,OAAO,eAAe,KAAK,OAAO,aAAa;AAChE,MAAI,YAAY,CAAC,OAAO,UAAU;AAChC,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACA,MAAI,OAAO,oBAAoB,GAAG;AAChC,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,GAAG,OAAO,iBAAiB,uBAAuB,OAAO,sBAAsB,IAAI,KAAK,GAAG;AAAA,IACtG,CAAC;AAAA,EACH;AACA,MAAI,KAAK,iBAAiB,OAAO,cAAc,KAAK,CAAC,UAAU;AAC7D,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS;AAAA,IACX,CAAC;AAAA,EACH;AACF;AAGA,SAAS,iBAAiB,OAAuB,mBAA2C;AAC1F,MAAI,sBAAsB,KAAM,QAAO;AACvC,QAAM,SAAS,KAAK,MAAM,MAAM,EAAE;AAClC,MAAI,OAAO,MAAM,MAAM,EAAG,QAAO;AACjC,SAAO,oBAAoB;AAC7B;AAGA,eAAe,YAAY,QAAkC;AAC3D,MAAI;AACF,UAAM,QAAQ,MAAMI,MAAK,MAAM;AAC/B,WAAO,MAAM,YAAY;AAAA,EAC3B,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,OAAO,QAAkC;AACtD,MAAI;AACF,UAAM,QAAQ,MAAMA,MAAK,MAAM;AAC/B,WAAO,MAAM,OAAO;AAAA,EACtB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,UAAU,QAAwC;AAC/D,MAAI;AACF,UAAM,QAAQ,MAAMA,MAAK,MAAM;AAC/B,WAAO,MAAM;AAAA,EACf,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,mBAAmB,KAA8B;AAC9D,MAAI;AACF,UAAM,UAAU,MAAMC,UAAQ,KAAK,EAAE,eAAe,KAAK,CAAC;AAC1D,QAAI,QAAQ;AACZ,eAAW,SAAS,SAAS;AAC3B,UAAI,MAAM,OAAO,KAAK,MAAM,KAAK,SAAS,YAAY,EAAG,UAAS;AAAA,IACpE;AACA,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,eAAe,oBAAoB,MAA+B;AAChE,MAAI;AACF,WAAO,MAAM,gBAAgB,IAAI;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;;;ACzSO,SAAS,oBAAoB,OAAqC;AACvE,QAAM,OAAO,cAAc,KAAK;AAChC,SAAO,EAAE,OAAO,MAAM,aAAa,cAAc,IAAI,GAAG,cAAc,gBAAgB,IAAI,EAAE;AAC9F;AAGA,SAAS,cAAc,OAAuC;AAC5D,MAAI,gBAAgB,KAAK,EAAG,QAAO;AACnC,MAAI,cAAc,KAAK,EAAG,QAAO;AACjC,MAAI,MAAM,oBAAoB,EAAG,QAAO;AACxC,MAAI,cAAc,KAAK,EAAG,QAAO;AACjC,MAAI,aAAa,KAAK,EAAG,QAAO;AAChC,MAAI,YAAY,KAAK,EAAG,QAAO;AAC/B,SAAO;AACT;AAGA,SAAS,gBAAgB,OAA8B;AACrD,SAAO,MAAM,SAAS,KAAK,CAAC,MAAM,EAAE,SAAS,oBAAoB;AACnE;AAOA,SAAS,YAAY,OAA8B;AACjD,SAAO,MAAM,cAAc,CAAC,aAAa,KAAK;AAChD;AAGA,SAAS,cAAc,OAA8B;AACnD,SAAO,MAAM,cAAc,KAAK,CAAC,aAAa,KAAK;AACrD;AAGA,SAAS,cAAc,OAA8B;AACnD,SAAO,MAAM,KAAK,UAAU,QAAQ,MAAM,KAAK,MAAM,SAAS;AAChE;AAGA,SAAS,aAAa,OAA8B;AAClD,SAAO,MAAM,eAAe,KAAK,MAAM,aAAa;AACtD;AAGA,SAAS,cAAc,MAA2C;AAChE,SAAO,gBAAgB,IAAI;AAC7B;AAGA,SAAS,gBAAgB,MAA6C;AACpE,SAAO,cAAc,IAAI,EAAE,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,EAAE;AAClD;AAGA,IAAM,oBAAuC;AAAA,EAC3C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,YAAY,GAAG,cAAc,CAAC,QAAQ,EAAE;AAClF;AAGA,IAAM,gBAAmC;AAAA,EACvC,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE;AAC9E;AAGA,IAAM,iBAAoC;AAAA,EACxC,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,SAAS,EAAE;AACrD;AAGA,IAAM,qBAAwC;AAAA,EAC5C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,UAAU,MAAM,EAAE;AAC5D;AAGA,IAAM,wBAA2C;AAAA,EAC/C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,UAAU,SAAS,GAAG,cAAc,CAAC,IAAI,EAAE;AACrF;AAGA,IAAM,cAAiC;AAAA,EACrC,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,MAAM,EAAE;AAClD;AAGA,IAAM,mBAAsC;AAAA,EAC1C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,QAAQ,QAAQ,EAAE;AAC5D;AAGA,IAAM,eAAkC;AAAA,EACtC,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,OAAO,GAAG,cAAc,CAAC,UAAU,EAAE;AAC/E;AAGA,IAAM,wBAA2C;AAAA,EAC/C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY;AACd;AAGA,IAAM,kBAA+D;AAAA,EACnE,kBAAkB;AAAA,EAClB,OAAO,EAAE,GAAG,mBAAmB,QAAQ,uCAAuC;AAAA,EAC9E,gBAAgB,EAAE,GAAG,gBAAgB,QAAQ,sDAAsD;AAAA,EACnG,kBAAkB,EAAE,GAAG,oBAAoB,QAAQ,+CAA+C;AAAA,EAClG,kBAAkB,EAAE,GAAG,aAAa,QAAQ,wDAAwD;AAAA,EACpG,cAAc,EAAE,GAAG,kBAAkB,QAAQ,kCAAkC;AAAA,EAC/E,cAAc;AAAA,IACZ,GAAG;AAAA,IACH,QAAQ;AAAA,EACV;AACF;AAGA,IAAM,gBAA+D;AAAA,EACnE,OAAO,CAAC,eAAe,iBAAiB;AAAA,EACxC,gBAAgB,CAAC,gBAAgB,iBAAiB;AAAA,EAClD,cAAc,CAAC,gBAAgB,aAAa;AAAA,EAC5C,kBAAkB,CAAC,oBAAoB,qBAAqB;AAAA,EAC5D,kBAAkB,CAAC,aAAa,gBAAgB;AAAA,EAChD,cAAc,CAAC,kBAAkB,YAAY;AAAA,EAC7C,kBAAkB,CAAC;AACrB;;;ACnKA,IAAM,oBAAoB;AAE1B,IAAM,uBAAoE;AAAA,EACxE,kBAAkB,MAAM;AAAA,EACxB,OAAO,MAAM;AAAA,EACb,gBAAgB,CAAC,UACf,GAAG,MAAM,WAAW,UAAU,OAAO,MAAM,WAAW,CAAC;AAAA,EACzD,kBAAkB,CAAC,UACjB,mBAAmB,MAAM,iBAAiB,aAAa,OAAO,MAAM,iBAAiB,CAAC;AAAA,EACxF,kBAAkB;AAAA,EAClB,cAAc,MAAM;AAAA,EACpB,cAAc;AAChB;AAOA,eAAO,YAAmC,UAA8B,CAAC,GAAoB;AAC3F,QAAM,QAAQ,MAAM,oBAAoB,QAAQ,IAAI,CAAC;AACrD,QAAM,iBAAiB,oBAAoB,KAAK;AAChD,MAAI,QAAQ,MAAM;AAChB,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,iBAAiB,OAAO,cAAc,GAAG,MAAM,CAAC,CAAC;AAAA,CAAI;AAAA,EAC9F,OAAO;AACL,YAAQ,OAAO,MAAM,GAAG,YAAY,OAAO,cAAc,CAAC;AAAA,CAAI;AAAA,EAChE;AACA,SAAO;AACT;AAGA,SAAS,iBAAiB,OAAqB,gBAA6C;AAC1F,SAAO;AAAA,IACL,SAAS;AAAA,IACT,aAAa,MAAM;AAAA,IACnB,OAAO,eAAe;AAAA,IACtB,SAAS,aAAa,KAAK;AAAA,IAC3B,aAAa,eAAe;AAAA,IAC5B,cAAc,eAAe;AAAA,IAC7B,UAAU,MAAM;AAAA,EAClB;AACF;AAyBA,SAAS,aAAa,OAAkC;AACtD,SAAO;AAAA,IACL,aAAa,MAAM;AAAA,IACnB,cAAc,MAAM;AAAA,IACpB,YAAY,MAAM;AAAA,IAClB,mBAAmB,MAAM;AAAA,IACzB,UAAU,MAAM;AAAA,IAChB,cAAc,MAAM,KAAK;AAAA,IACzB,MAAM,MAAM,KAAK;AAAA,EACnB;AACF;AAGA,SAAS,YAAY,OAAqB,gBAAwC;AAChF,QAAM,QAAkB,CAAC;AACzB,QAAM,KAAK,cAAc;AACzB,QAAM,KAAK,cAAc;AACzB,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,YAAY,MAAM,IAAI,EAAE;AACnC,QAAM,KAAK,UAAU,kBAAkB,OAAO,eAAe,KAAK,CAAC,EAAE;AACrE,4BAA0B,OAAO,eAAe,WAAW;AAC3D,0BAAwB,OAAO,eAAe,YAAY;AAC1D,sBAAoB,OAAO,MAAM,QAAQ;AACzC,SAAO,MAAM,KAAK,IAAI;AACxB;AAGA,SAAS,0BAA0B,OAAiB,QAAiC;AACnF,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,0BAA0B;AACrC,QAAM,KAAK,KAAK,OAAO,WAAW,OAAO,MAAM,EAAE;AACnD;AAGA,SAAS,wBAAwB,OAAiB,SAAoC;AACpF,MAAI,QAAQ,WAAW,EAAG;AAC1B,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,uBAAuB;AAClC,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,QAAS,OAAM,KAAK,KAAK,OAAO,OAAO,EAAE;AAAA,EACtD;AACF;AAGA,SAAS,oBAAoB,OAAiB,UAAuC;AACnF,MAAI,SAAS,WAAW,EAAG;AAC3B,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,QAAQ;AACnB,aAAW,WAAW,SAAU,OAAM,KAAK,OAAO,QAAQ,OAAO,EAAE;AACrE;AAGA,SAAS,kBAAkB,OAAqB,MAAgC;AAC9E,SAAO,qBAAqB,IAAI,EAAE,KAAK;AACzC;AAGA,SAAS,oBAAoB,OAA6B;AACxD,SAAO,eAAe,UAAU,KAAK,CAAC,QAAQ,OAAO,UAAU,KAAK,CAAC,CAAC,KAAK,MAAM,iBAAiB,qBAAqB,OAAO,MAAM,iBAAiB,CAAC;AACxJ;AAGA,SAAS,wBAAwB,OAA6B;AAC5D,QAAM,QAAQ,MAAM,KAAK;AACzB,MAAI,CAAC,MAAO,QAAO;AACnB,SAAO,gBAAgB,MAAM,MAAM,SAAS,OAAO,MAAM,MAAM,CAAC,KAAK,MAAM,QAAQ,WAAW,OAAO,MAAM,QAAQ,CAAC;AACtH;AAGA,SAAS,UAAU,OAA6B;AAC9C,SAAO,MAAM,eAAe,MAAM;AACpC;AAGA,SAAS,OAAO,OAAuB;AACrC,SAAO,UAAU,IAAI,KAAK;AAC5B;;;AC9IA,OAAOC,YAAU;;;ACPjB,IAAM,oBAAmD;AAAA,EACvD,WAAW;AAAA,EACX,QAAQ;AAAA,EACR,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AACX;AAOO,SAAS,0BAAgC;AAC9C,QAAM,WAAW,QAAQ,IAAI,oBAAoB;AAEjD,MAAI,aAAa,aAAa;AAC5B,UAAM,OAAO,4BAA4B;AACzC,QAAI,CAAC,KAAK,UAAU,CAAC,KAAK,WAAW;AACnC,YAAM,IAAI;AAAA,QACR;AAAA;AAAA,MAEF;AAAA,IACF;AACA;AAAA,EACF;AAEA,QAAM,SAAS,kBAAkB,QAAQ;AACzC,MAAI,WAAW,QAAW;AACxB,UAAM,IAAI;AAAA,MACR,qBAAqB,QAAQ;AAAA,eACX,OAAO,KAAK,iBAAiB,EAAE,KAAK,IAAI,CAAC;AAAA,IAC7D;AAAA,EACF;AAEA,MAAI,UAAU,CAAC,QAAQ,IAAI,MAAM,GAAG;AAClC,UAAM,IAAI;AAAA,MACR,GAAG,MAAM,8CAA8C,QAAQ;AAAA,wBACpC,MAAM;AAAA,IACnC;AAAA,EACF;AACF;;;ADJA,IAAM,0BAA0B;AAChC,IAAM,qBAAqB;AAI3B,IAAM,eAA4B,MAAM;AAAC;AA0EzC,eAAO,kBACLC,SACA,UAA6B,CAAC,GACb;AACjB,QAAM,WAAW,QAAQ,SAAS;AAClC,EAAO,SAAS,QAAQ;AACxB,QAAM,aAAa,kBAAkB,OAAO;AAC5C,MAAI;AACF,WAAO,MAAM,cAAcA,SAAQ,SAAS,QAAQ;AAAA,EACtD,UAAE;AACA,eAAW;AACX,IAAO,SAAS,KAAK;AAAA,EACvB;AACF;AAGA,eAAe,cACbA,SACA,SACA,UACiB;AACjB,QAAM,OAAO,QAAQ,IAAI;AACzB,QAAMC,UAAS,MAAM,cAAcD,OAAM;AACzC,MAAI,CAACC,QAAO,IAAI;AACd,WAAO,gBAAgB,EAAE,QAAAD,SAAQ,QAAAC,SAAQ,SAAS,CAAC;AAAA,EACrD;AAEA,QAAMC,WAAU,MAAM,eAAe,MAAM,QAAQ,WAAW,IAAI;AAClE,QAAM,SAAS,oBAAoB;AACnC,QAAM,MAAqB,EAAE,QAAAF,SAAQ,QAAAC,SAAQ,SAAAC,UAAS,OAAO;AAC7D,SAAO,MAAM,gBAAgB,KAAK,SAAS,UAAU,IAAI;AAC3D;AASA,SAAS,kBAAkB,SAAyC;AAClE,SAAO,iBAAiB;AAAA,IACtB,sBAAsB,QAAQ,QAAQ;AAAA,IACtC,sBAAsB,QAAQ,IAAI;AAAA,EACpC,CAAC;AACH;AAGA,SAAS,iBAAiB,WAAuC;AAC/D,SAAO,MAAM;AACX,eAAW,WAAW,UAAW,SAAQ;AAAA,EAC3C;AACF;AAGA,SAAS,sBAAsB,UAA2C;AACxE,QAAM,UAAU,UAAU,KAAK;AAC/B,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,UAAU,YAAY,kBAAkB;AAC9C,UAAQ,IAAI,mBAAmB;AAC/B,SAAO;AACT;AAGA,SAAS,sBAAsB,MAAuC;AACpE,QAAM,UAAU,MAAM,KAAK;AAC3B,MAAI,CAAC,QAAS,QAAO;AACrB,QAAM,UAAU,YAAY,qBAAqB;AACjD,sBAAoB,OAAO;AAC3B,SAAO;AACT;AAGA,SAAS,YAAY,MAA2B;AAC9C,QAAM,WAAW,QAAQ,IAAI,IAAI;AACjC,SAAO,MAAM;AACX,QAAI,aAAa,QAAW;AAC1B,aAAO,QAAQ,IAAI,IAAI;AAAA,IACzB,OAAO;AACL,cAAQ,IAAI,IAAI,IAAI;AAAA,IACtB;AAAA,EACF;AACF;AAGA,eAAe,cAAcF,SAAyC;AACpE,EAAO,OAAO,oBAAoB;AAClC,EAAO,OAAO,KAAY,KAAK,aAAaA,OAAM,EAAE,CAAC;AACrD,MAAI;AACF,UAAM,SAAS,MAAM,aAAaA,OAAM;AACxC,UAAM,UAAUG,OAAK,KAAK,aAAa,OAAO,QAAQ;AACtD,IAAO,OAAO,KAAY,QAAQ,mBAAc,OAAO,EAAE,CAAC;AAC1D,WAAO,mBAAmB,QAAQ,OAAO;AAAA,EAC3C,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,IAAO,OAAO,KAAY,MAAM,kBAAkB,OAAO,EAAE,CAAC;AAC5D,WAAO,mBAAmB,OAAO;AAAA,EACnC;AACF;AAGA,SAAS,mBAAmB,QAAsB,SAAiC;AACjF,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,YAAY,OAAO,cAAc;AAAA,IACjC,OAAO;AAAA,EACT;AACF;AAGA,SAAS,mBAAmB,SAAiC;AAC3D,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,MAAM;AAAA,IACN,YAAY;AAAA,IACZ,OAAO,EAAE,MAAM,iBAAiB,SAAS,aAAa,MAAM;AAAA,EAC9D;AACF;AAGA,SAAS,uBAAwC;AAC/C,SAAO;AAAA,IACL,IAAI;AAAA,IACJ,UAAU;AAAA,IACV,SAAS;AAAA,IACT,SAAS;AAAA,IACT,mBAAmB;AAAA,IACnB,QAAQ;AAAA,IACR,OAAO;AAAA,EACT;AACF;AAMA,IAAM,sBAAwD;AAAA,EAC5D,sBAAsB;AAAA,EACtB,gBAAgB;AAClB;AAOA,eAAe,4BACb,MACA,MACA,KAC0B;AAC1B,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,EAAO,OAAO,KAAY,MAAM,GAAG,oBAAoB,IAAI,CAAC,YAAY,OAAO,EAAE,CAAC;AAClF,SAAO;AAAA,IACL,GAAG,qBAAqB;AAAA,IACxB,mBAAmB,MAAMC,qBAAoB,IAAI;AAAA,IACjD,OAAO,EAAE,MAAM,SAAS,aAAa,KAAK;AAAA,EAC5C;AACF;AAGA,eAAe,eAAe,MAAc,QAA2C;AACrF,MAAI;AACF,4BAAwB;AAAA,EAC1B,SAAS,KAAK;AACZ,WAAO,MAAM,4BAA4B,MAAM,wBAAwB,GAAG;AAAA,EAC5E;AACA,MAAI;AACF,UAAM,SAAS,MAAM,iBAAiB,MAAM,EAAE,OAAO,CAAC;AACtD,WAAO,MAAM,+BAA+B,MAAM,MAAM;AAAA,EAC1D,SAAS,KAAK;AACZ,WAAO,MAAM,4BAA4B,MAAM,kBAAkB,GAAG;AAAA,EACtE;AACF;AAOA,eAAe,+BACb,MACA,QAC0B;AAC1B,QAAM,oBAAoB,MAAMA,qBAAoB,IAAI;AACxD,QAAM,YAAY,OAAO,OAAO,SAAS;AACzC,SAAO;AAAA,IACL,IAAI,CAAC;AAAA,IACL,UAAU,OAAO;AAAA,IACjB,SAAS,OAAO;AAAA,IAChB,SAAS,OAAO;AAAA,IAChB;AAAA,IACA,QAAQ,YAAY,CAAC,GAAG,OAAO,MAAM,IAAI,CAAC;AAAA,IAC1C,OAAO;AAAA,EACT;AACF;AAGA,eAAeA,qBAAoB,MAA+B;AAChE,MAAI;AACF,WAAO,MAAM,gBAAgB,IAAI;AAAA,EACnC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAOA,SAAS,sBAAsC;AAC7C,SAAO,EAAE,QAAQ,OAAO,KAAK,KAAK;AACpC;AAkBA,SAAS,gBAAgB,KAA6B;AACpD,QAAMF,WAA2B,qBAAqB;AACtD,QAAM,OAAqB;AAAA,IACzB,SAAS,kBAAkB,IAAI,MAAM;AAAA,IACrC,QAAQ;AAAA,IACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,QAAQ,GAAG,cAAc,CAAC,QAAQ,EAAE;AAAA,EAC9E;AACA,QAAM,WAA+B;AAAA,IACnC,SAAS;AAAA,IACT,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ,SAAAA;AAAA,IACA,QAAQ,oBAAoB;AAAA,IAC5B;AAAA,EACF;AACA,eAAa,UAAU,IAAI,QAAQ;AACnC,SAAO;AACT;AAWA,eAAe,gBACb,KACA,SACA,UACA,MACiB;AACjB,QAAM,eAAe,MAAM,iBAAiB,IAAI;AAChD,QAAM,OAAO,iBAAiB,KAAK,SAAS,YAAY;AACxD,QAAM,UAAU,kBAAkB,EAAE,SAAS,UAAU,SAAS,IAAI,SAAS,aAAa,CAAC;AAC3F,QAAM,WAAW,qBAAqB,KAAK,4BAA4B,MAAM,OAAO,CAAC;AACrF,eAAa,UAAU,QAAQ;AAC/B,MAAI,QAAS,OAAM,gBAAgB;AACnC,SAAO;AACT;AAGA,SAAS,qBAAqB,KAAoB,MAAwC;AACxF,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ,IAAI;AAAA,IACZ,QAAQ,IAAI;AAAA,IACZ,SAAS,IAAI;AAAA,IACb,QAAQ,IAAI;AAAA,IACZ;AAAA,EACF;AACF;AAOA,eAAe,iBAAiB,MAA4C;AAC1E,MAAI;AACF,WAAO,MAAM,oBAAoB,IAAI;AAAA,EACvC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,iBACP,KACA,SACA,cACc;AACd,MAAI,QAAQ,WAAW,QAAQ,IAAI,QAAQ,GAAI,QAAO,iBAAiB;AACvE,MAAI,CAAC,IAAI,QAAQ,GAAI,QAAO,oBAAoB,IAAI,OAAO;AAC3D,SAAO,0BAA0B,YAAY;AAC/C;AAGA,SAAS,mBAAiC;AACxC,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ;AAAA,IACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,UAAU,MAAM,EAAE;AAAA,EAC5D;AACF;AAMA,SAAS,oBAAoBA,UAAwC;AACnE,QAAM,SAASA,SAAQ,QACnB,uDACA;AACJ,SAAO;AAAA,IACL,SAAS;AAAA,IACT;AAAA,IACA,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,SAAS,EAAE;AAAA,EACrD;AACF;AAQA,SAAS,0BAA0B,cAAiD;AAClF,MAAI,CAAC,cAAc;AACjB,WAAO;AAAA,MACL,SAAS;AAAA,MACT,QAAQ;AAAA,MACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,QAAQ,QAAQ,EAAE;AAAA,IAC5D;AAAA,EACF;AACA,QAAM,EAAE,YAAY,IAAI,oBAAoB,YAAY;AACxD,SAAO;AAAA,IACL,SAAS,YAAY;AAAA,IACrB,QAAQ,YAAY;AAAA,IACpB,YAAY,YAAY;AAAA,EAC1B;AACF;AAYA,IAAM,0BAA4C;AAAA,EAChD,CAAC,SAAS,KAAK;AAAA,EACf,CAAC,SAAS,KAAK,QAAQ,SAAS;AAAA,EAChC,CAAC,SAAS,KAAK,QAAQ,WAAW;AAAA,EAClC,CAAC,SAAS,CAAC,KAAK,QAAQ;AAAA,EACxB,CAAC,SAAS,CAAC,mBAAmB,KAAK,YAAY;AACjD;AASA,SAAS,kBAAkB,MAA4B;AACrD,SAAO,wBAAwB,MAAM,CAAC,cAAc,CAAC,UAAU,IAAI,CAAC;AACtE;AAGA,SAAS,mBAAmB,OAAqC;AAC/D,SAAO,eAAe,KAAK,IAAI,aAAa,KAAK,IAAI;AACvD;AAGA,SAAS,eAAe,OAAoC;AAC1D,SAAO,UAAU,OAAO,IAAI,MAAM;AACpC;AAGA,SAAS,aAAa,OAAoC;AACxD,SAAO,UAAU,OAAO,IAAI,MAAM;AACpC;AAaA,SAAS,4BAA4B,MAAoB,SAAgC;AACvF,MAAI,CAAC,QAAS,QAAO;AACrB,MAAI,CAAC,iBAAiB,IAAI,EAAG,QAAO;AACpC,SAAO,EAAE,SAAS,MAAM,QAAQ,KAAK,QAAQ,YAAY,KAAK;AAChE;AAQA,SAAS,iBAAiB,MAA6B;AACrD,SAAO,KAAK,YAAY,KAAK,KAAK,IAAQ,MAAM;AAClD;AASA,eAAe,kBAAiC;AAC9C,UAAQ,OAAO,MAAM,4CAA4C;AACjE,QAAM,YAAY,EAAE,MAAM,KAAK,CAAC;AAClC;AAGA,SAAS,aAAa,UAA8B,UAAyB;AAC3E,MAAI,UAAU;AACZ,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,UAAU,MAAM,CAAC,CAAC;AAAA,CAAI;AAC7D;AAAA,EACF;AACA,EAAAG,aAAY,QAAQ;AACtB;AAGA,SAASA,aAAY,UAAoC;AACvD,QAAM,QAAkB,CAAC;AACzB,mBAAiB,OAAO,SAAS,MAAM;AACvC,qBAAmB,OAAO,SAAS,OAAO;AAC1C,kBAAgB,OAAO,SAAS,IAAI;AACpC,UAAQ,OAAO,MAAM;AAAA,EAAK,MAAM,KAAK,IAAI,CAAC;AAAA,CAAI;AAChD;AAGA,SAAS,iBAAiB,OAAiBJ,SAA8B;AACvE,MAAIA,QAAO,MAAMA,QAAO,MAAM;AAC5B,UAAM,KAAK,6BAAwBA,QAAO,IAAI,EAAE;AAChD;AAAA,EACF;AACA,QAAM,KAAK,0CAAqC;AAClD;AAGA,SAAS,mBAAmB,OAAiBC,UAAgC;AAC3E,QAAM,OAAO,mBAAmB,KAAK,CAAC,cAAc,UAAU,QAAQA,QAAO,CAAC;AAC9E,MAAI,KAAM,OAAM,KAAK,KAAK,OAAOA,QAAO,CAAC;AAC3C;AAOA,IAAM,qBAAwC;AAAA,EAC5C;AAAA,IACE,SAAS,CAACA,aAAYA,SAAQ,UAAU;AAAA,IACxC,QAAQ,MAAM;AAAA,EAChB;AAAA,EACA;AAAA,IACE,SAAS,CAACA,cAAaA,SAAQ,QAAQ,UAAU,KAAK;AAAA,IACtD,QAAQ,CAACA,aAAY,uBAAuBA,SAAQ,QAAQ,UAAU,CAAC;AAAA,EACzE;AAAA,EACA;AAAA,IACE,SAAS,CAACA,aAAYA,SAAQ,MAAMA,SAAQ,oBAAoB;AAAA,IAChE,QAAQ,CAACA,aAAY,wCAAmCA,SAAQ,iBAAiB;AAAA,EACnF;AAAA,EACA;AAAA,IACE,SAAS,CAACA,aAAYA,SAAQ;AAAA,IAC9B,QAAQ,CAACA,aAAY,2BAAsBA,SAAQ,QAAQ,SAASA,SAAQ,OAAO;AAAA,EACrF;AACF;AAGA,SAAS,gBAAgB,OAAiB,MAA0B;AAClE,MAAI,CAAC,KAAK,QAAS;AACnB,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,OAAO;AAClB,QAAM,KAAK,KAAK,KAAK,OAAO,EAAE;AAChC;;;AEnnBA,OAAOI,YAAU;;;ACQjB,SAAS,YAAY,UAAU;AAC/B,OAAOC,YAAU;AAKV,IAAM,qBAAqB;AAE3B,IAAM,uBAAuB;AAQpC,IAAM,yBAAyB;AAgCxB,SAAS,iBAAiB,WAA4C;AAC3E,QAAM,MAAsB,CAAC;AAC7B,QAAM,OAAO,oBAAI,IAAY;AAC7B,aAAW,YAAY,WAAW;AAChC,eAAW,QAAQ,SAAS,OAAO;AACjC,YAAM,OAAO,eAAe,IAAI;AAChC,YAAM,MAAM,YAAY,IAAI;AAC5B,UAAI,KAAK,IAAI,GAAG,EAAG;AACnB,WAAK,IAAI,GAAG;AACZ,UAAI,KAAK,IAAI;AAAA,IACf;AAAA,EACF;AACA,SAAO;AACT;AAGA,SAAS,eAAe,MAAgC;AACtD,MAAI,CAAC,KAAK,MAAO,QAAO,EAAE,MAAM,KAAK,KAAK;AAC1C,SAAO,EAAE,MAAM,KAAK,MAAM,OAAO,KAAK,MAAM,OAAO,KAAK,KAAK,MAAM,IAAI;AACzE;AAGA,SAAS,YAAY,UAAgC;AACnD,QAAM,QAAQ,SAAS,SAAS;AAChC,QAAM,MAAM,SAAS,OAAO;AAC5B,SAAO,CAAC,SAAS,MAAM,OAAO,KAAK,GAAG,OAAO,GAAG,CAAC,EAAE,KAAK,sBAAsB;AAChF;AAcO,SAAS,2BAA+C;AAC7D,SAAO,EAAE,WAAW,mBAAmB;AACzC;AAOA,eAAsB,yBACpB,MACA,WACA,QACyB;AACzB,MAAI,OAAO,aAAa,EAAG,QAAO,CAAC;AACnC,QAAM,UAA0B,CAAC;AACjC,aAAW,YAAY,WAAW;AAChC,QAAI,OAAO,aAAa,EAAG;AAC3B,QAAI,SAAS,UAAU,UAAa,SAAS,QAAQ,OAAW;AAChE,UAAM,SAAS,MAAM,iBAAiB,MAAM,QAAQ;AACpD,QAAI,CAAC,OAAQ;AACb,YAAQ,KAAK,MAAM;AACnB,WAAO,aAAa;AAAA,EACtB;AACA,SAAO;AACT;AAGA,eAAe,iBACb,MACA,UAC8B;AAC9B,MAAI,SAAS,UAAU,UAAa,SAAS,QAAQ,OAAW,QAAO;AACvE,QAAM,cAAc,MAAM,mBAAmB,IAAI;AACjD,MAAI,CAAC,YAAa,QAAO;AACzB,QAAM,WAAW,MAAM,gBAAgB,aAAa,SAAS,IAAI;AACjE,MAAI,CAAC,SAAU,QAAO;AACtB,SAAO,kBAAkB,UAAU,QAAQ;AAC7C;AAWA,eAAe,mBAAmB,MAAsC;AACtE,QAAM,YAAYC,OAAK,KAAK,MAAM,WAAW;AAC7C,MAAI;AACF,WAAO,MAAM,GAAG,SAAS,SAAS;AAAA,EACpC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAaA,eAAe,gBAAgB,aAAqB,MAAsC;AACxF,MAAI,KAAK,WAAW,EAAG,QAAO;AAC9B,MAAIA,OAAK,WAAW,IAAI,EAAG,QAAO;AAClC,MAAIC,uBAAsB,IAAI,EAAG,QAAO;AACxC,QAAM,SAASD,OAAK,KAAK,aAAa,IAAI;AAC1C,QAAM,WAAWA,OAAK,QAAQ,MAAM;AAIpC,MAAI,CAACE,UAAS,aAAa,QAAQ,EAAG,QAAO;AAC7C,MAAI;AACF,UAAM,WAAW,MAAM,GAAG,SAAS,QAAQ;AAC3C,QAAI,CAACA,UAAS,aAAa,QAAQ,EAAG,QAAO;AAC7C,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQA,SAASD,uBAAsB,MAAuB;AACpD,QAAM,WAAW,KAAK,MAAM,OAAO;AACnC,SAAO,SAAS,KAAK,CAAC,YAAY,YAAY,IAAI;AACpD;AAOA,SAASC,UAAS,QAAgB,WAA4B;AAC5D,MAAI,cAAc,OAAQ,QAAO;AACjC,QAAM,mBAAmB,OAAO,SAASF,OAAK,GAAG,IAAI,SAAS,GAAG,MAAM,GAAGA,OAAK,GAAG;AAClF,SAAO,UAAU,WAAW,gBAAgB;AAC9C;AAQA,eAAe,kBACb,UACA,UAC8B;AAC9B,MAAI,SAAS,UAAU,UAAa,SAAS,QAAQ,OAAW,QAAO;AACvE,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,GAAG,SAAS,UAAU,OAAO;AAAA,EAC3C,QAAQ;AACN,WAAO;AAAA,EACT;AACA,QAAM,QAAQ,IAAI,MAAM,OAAO;AAC/B,QAAM,aAAa,KAAK,IAAI,GAAG,SAAS,QAAQ,CAAC;AACjD,QAAM,eAAe,KAAK,IAAI,MAAM,QAAQ,SAAS,GAAG;AACxD,MAAI,cAAc,aAAc,QAAO;AACvC,QAAM,aAAa,KAAK,IAAI,cAAc,aAAa,oBAAoB;AAC3E,QAAM,OAAO,MAAM,MAAM,YAAY,UAAU,EAAE,KAAK,IAAI;AAC1D,SAAO,EAAE,MAAM,SAAS,MAAM,OAAO,SAAS,OAAO,KAAK,cAAc,aAAa,aAAa,KAAK;AACzG;;;ACrNA,IAAM,qBAAqB;AAC3B,IAAM,oBAAoB;AAC1B,IAAM,oBAAoB;AAC1B,IAAM,qBAAqB;AAG3B,IAAM,wBAAwB;AAE9B,IAAM,8BAA8B;AAEpC,IAAM,4BAA4B;AAGlC,IAAM,uBAAuB;AAgCtB,SAAS,UACd,UACA,QACA,MACA,eAAmC,CAAC,GAClB;AAClB,QAAM,OAAO,oBAAI,IAAwB;AACzC,sBAAoB,MAAM,UAAU,MAAM;AAC1C,oBAAkB,MAAM,UAAU,MAAM;AACxC,uBAAqB,MAAM,UAAU,YAAY;AACjD,QAAM,SAAS,MAAM,KAAK,KAAK,OAAO,CAAC,EAAE,KAAK,WAAW;AACzD,SAAO,OAAO,MAAM,GAAG,KAAK,IAAI,GAAG,IAAI,CAAC,EAAE,IAAI,YAAY;AAC5D;AAGA,SAAS,oBACP,MACA,UACA,QACM;AACN,QAAM,EAAE,QAAQ,IAAI,YAAY,UAAU,MAAM;AAChD,aAAW,UAAU,SAAS;AAC5B,UAAM,OAAO,SAAS,MAAM,KAAK,CAAC,MAAM,EAAE,OAAO,OAAO,EAAE;AAC1D,QAAI,CAAC,KAAM;AACX,UAAM,MAAM,UAAU,MAAM,IAAI;AAChC,QAAI,UAAU,IAAI,WAAW,OAAO;AACpC,QAAI,OAAO,cAAc,SAAS;AAChC,gBAAU,KAAK,eAAe,kBAAkB;AAAA,IAClD,OAAO;AACL,gBAAU,KAAK,cAAc,iBAAiB;AAAA,IAChD;AAAA,EACF;AACF;AAGA,SAAS,kBACP,MACA,UACA,QACM;AACN,QAAM,aAAa,OAAO,KAAK,EAAE,YAAY;AAC7C,MAAI,WAAW,WAAW,EAAG;AAC7B,aAAW,QAAQ,SAAS,OAAO;AACjC,QAAI,KAAK,KAAK,YAAY,MAAM,YAAY;AAC1C,gBAAU,UAAU,MAAM,IAAI,GAAG,cAAc,iBAAiB;AAAA,IAClE;AACA,QAAI,KAAK,MAAM,KAAK,EAAE,YAAY,MAAM,YAAY;AAClD,gBAAU,UAAU,MAAM,IAAI,GAAG,eAAe,kBAAkB;AAAA,IACpE;AAAA,EACF;AACF;AAaA,SAAS,qBACP,MACA,UACA,MACM;AACN,MAAI,KAAK,WAAW,EAAG;AACvB,QAAM,SAAS,gBAAgB,IAAI;AACnC,aAAW,CAAC,MAAM,QAAQ,KAAK,QAAQ;AACrC,UAAM,OAAO,eAAe,UAAU,IAAI;AAC1C,QAAI,CAAC,KAAM;AACX,UAAM,MAAM,UAAU,MAAM,IAAI;AAChC,cAAU,KAAK,kBAAkB,qBAAqB;AACtD,QAAI,UAAU,wBAAwB,SAAS,MAAM;AACrD,eAAW,OAAO,UAAU;AAC1B,UAAI,OAAO,KAAK;AAAA,QACd,MAAM,IAAI;AAAA,QACV,OAAO,IAAI;AAAA,QACX,aAAa,IAAI;AAAA,MACnB,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAGA,SAAS,gBAAgB,MAA2D;AAClF,QAAM,SAAS,oBAAI,IAAgC;AACnD,aAAW,OAAO,MAAM;AACtB,UAAM,WAAW,OAAO,IAAI,IAAI,IAAI;AACpC,QAAI,SAAU,UAAS,KAAK,GAAG;AAAA,QAC1B,QAAO,IAAI,IAAI,MAAM,CAAC,GAAG,CAAC;AAAA,EACjC;AACA,SAAO;AACT;AAGA,SAAS,wBAAwB,YAA4B;AAC3D,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,aAAa,GAAG,yBAAyB,CAAC;AAC7E,SAAO,QAAQ;AACjB;AAOA,SAAS,eAAe,UAA0B,MAAiC;AACjF,QAAM,UAAU,SAAS,MAAM;AAAA,IAC7B,CAAC,MAAM,EAAE,kBAAkB,cAAc,EAAE,SAAS;AAAA,EACtD;AACA,MAAI,QAAS,QAAO;AACpB,QAAM,QAAQ,SAAS,MAAM;AAAA,IAC3B,CAAC,MAAM,EAAE,kBAAkB,aAAa,EAAE,SAAS;AAAA,EACrD;AACA,SAAO,SAAS;AAClB;AAGA,SAAS,UAAU,MAA+B,MAA8B;AAC9E,QAAM,WAAW,KAAK,IAAI,KAAK,EAAE;AACjC,MAAI,SAAU,QAAO;AACrB,QAAM,UAAsB;AAAA,IAC1B;AAAA,IACA,SAAS,oBAAI,IAAI;AAAA,IACjB,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,QAAQ,CAAC;AAAA,EACX;AACA,OAAK,IAAI,KAAK,IAAI,OAAO;AACzB,SAAO;AACT;AAGA,SAAS,UAAU,KAAiB,QAAuB,QAAsB;AAC/E,MAAI,QAAQ,IAAI,MAAM;AACtB,MAAI,UAAU;AAChB;AAGA,SAAS,YAAY,GAAe,GAAuB;AACzD,MAAI,EAAE,WAAW,EAAE,OAAQ,QAAO,EAAE,SAAS,EAAE;AAC/C,QAAM,UAAU,EAAE,KAAK,MAAM,cAAc,EAAE,KAAK,KAAK;AACvD,MAAI,YAAY,EAAG,QAAO;AAC1B,SAAO,EAAE,KAAK,GAAG,cAAc,EAAE,KAAK,EAAE;AAC1C;AAoBA,SAAS,aAAa,KAAiC;AACrD,SAAO;AAAA,IACL,IAAI,IAAI,KAAK;AAAA,IACb,OAAO,IAAI,KAAK;AAAA,IAChB,eAAe,IAAI,KAAK;AAAA,IACxB,OAAO,gBAAgB,IAAI,MAAM;AAAA,IACjC,SAAS,MAAM,KAAK,IAAI,OAAO,EAAE,KAAK;AAAA,IACtC,SAAS,IAAI;AAAA,IACb,QAAQ,IAAI;AAAA,IACZ,WAAW,iBAAiB,IAAI,KAAK,SAAS;AAAA,IAC9C,eAAe,CAAC;AAAA,IAChB,UAAU,IAAI,KAAK,SAAS,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,QAAQ,EAAE;AAAA,EAC/E;AACF;AAGA,SAAS,gBAAgB,QAAwB;AAC/C,MAAI,UAAU,EAAG,QAAO;AACxB,MAAI,UAAU,qBAAsB,QAAO;AAC3C,SAAO,KAAK,MAAM,SAAS,GAAG,IAAI;AACpC;;;ACvLA,eAAsB,uBACpB,MACA,QACA,WACmC;AACnC,MAAI,aAAa,EAAG,QAAO,aAAa,IAAI;AAC5C,MAAI,MAAM,gBAAgB,IAAI,EAAG,QAAO,aAAa,yBAAyB;AAE9E,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,mBAAmB,MAAM,QAAQ,SAAS;AAAA,EACxD,SAAS,KAAK;AAIZ,WAAO,aAAa,uBAAuB,GAAG,CAAC;AAAA,EACjD;AAEA,MAAI,IAAI,WAAW,GAAG;AAKpB,WAAO,aAAa,yBAAyB;AAAA,EAC/C;AAEA,SAAO,EAAE,MAAM,IAAI,IAAI,kBAAkB,GAAG,SAAS,KAAK;AAC5D;AAGA,SAAS,aAAa,SAAoE;AACxF,SAAO,EAAE,MAAM,CAAC,GAAG,QAAQ;AAC7B;AAcA,eAAe,gBAAgB,MAAgC;AAC7D,QAAM,QAAQ,MAAM,sBAAsB,IAAI;AAC9C,MAAI,CAAC,MAAO,QAAO;AACnB,MAAI,CAAC,MAAM,UAAU,MAAM,OAAO,WAAW,EAAG,QAAO;AACvD,MAAI,aAAa,KAAK,EAAG,QAAO;AAChC,SAAO;AACT;AAQA,eAAe,sBAAsB,MAA8C;AACjF,MAAI;AACF,WAAO,MAAM,mBAAmB,IAAI;AAAA,EACtC,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AASA,SAAS,aAAa,OAAgC;AACpD,MAAI;AACF,WAAO,MAAM,UAAU,sBAAsB;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAGA,SAAS,uBAAuB,KAAwC;AACtE,QAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,SAAO,yBAAyB,OAAO,IACnC,gCACA;AACN;AAGA,SAAS,yBAAyB,SAA0B;AAC1D,SAAO,kGACJ,KAAK,OAAO;AACjB;AAGA,SAAS,mBACP,KACkB;AAClB,SAAO;AAAA,IACL,MAAM,IAAI,MAAM;AAAA,IAChB,MAAM,IAAI,MAAM;AAAA,IAChB,OAAO,IAAI;AAAA,IACX,aAAa,IAAI,MAAM;AAAA,EACzB;AACF;;;ACpJA,IAAM,2BAA2B;AASjC,IAAM,2BAA2B;AASjC,IAAM,sBAAsB;AAG5B,IAAM,yBAAyB;AAG/B,IAAM,6BAA6B;AAOnC,IAAM,kCAAkC;AACxC,IAAM,oCAAoC;AAG1C,IAAM,yBAAyB;AAC/B,IAAM,oBAAoB;AAG1B,IAAMG,wBAAuB;AA6CtB,SAAS,wBACd,OACsB;AAKtB,MAAI,MAAM,SAAS,KAAK,MAAM,WAAW,SAAS,GAAG;AACnD,WAAO,EAAE,WAAW,CAAC,GAAG,MAAM,CAAC,EAAE;AAAA,EACnC;AACA,QAAM,YAAY,eAAe,MAAM,KAAK;AAC5C,QAAM,WAAW,gBAAgB,MAAM,MAAM,KAAK;AAClD,QAAM,YAAY,iBAAiB,MAAM,KAAK;AAK9C,QAAM,YAAY,eAAe;AAAA,IAC/B,YAAY,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,EACF,CAAC;AACD,QAAM,SAAS,UAAU,KAAK,gBAAgB,EAAE,MAAM,GAAG,mBAAmB;AAC5E,QAAM,SAAS,MAAM,SAAS,IAC1B,eAAe;AAAA,IACb,YAAY,MAAM;AAAA,IAClB;AAAA,IACA;AAAA,IACA;AAAA,IACA,iBAAiB,uBAAuB,MAAM;AAAA,EAChD,CAAC,IACD,CAAC;AAIL,QAAM,YAAY,CAAC,GAAG,QAAQ,GAAG,MAAM,EACpC,KAAK,gBAAgB,EACrB,MAAM,GAAG,mBAAmB;AAC/B,SAAO,EAAE,WAAW,MAAM,oBAAoB,KAAK,EAAE;AACvD;AAGA,SAAS,oBAAoB,OAAwC;AACnE,QAAM,OAAmB,CAAC;AAC1B,aAAW,QAAQ,MAAM,OAAO;AAC9B,QAAI,CAAC,MAAM,WAAW,IAAI,KAAK,EAAE,EAAG;AACpC,eAAW,YAAY,KAAK,iBAAiB,CAAC,GAAG;AAC/C,WAAK,KAAK;AAAA,QACR,MAAM;AAAA,QACN,SAAS,mBAAmB,SAAS,OAAO;AAAA,QAC5C,QAAQ,KAAK;AAAA,MACf,CAAC;AAAA,IACH;AAAA,EACF;AACA,SAAO;AACT;AASA,SAAS,eAAe,OAA6B;AACnD,QAAM,WAAW,oBAAI,IAAyB;AAC9C,QAAM,WAAW,oBAAI,IAAyB;AAC9C,aAAW,QAAQ,MAAM,OAAO;AAC9B,gBAAY,UAAU,KAAK,QAAQ,KAAK,MAAM;AAC9C,gBAAY,UAAU,KAAK,QAAQ,KAAK,MAAM;AAAA,EAChD;AACA,SAAO,EAAE,UAAU,SAAS;AAC9B;AAGA,SAAS,YAAkB,KAAqB,KAAQ,OAAgB;AACtE,QAAM,WAAW,IAAI,IAAI,GAAG;AAC5B,MAAI,SAAU,UAAS,IAAI,KAAK;AAAA,MAC3B,KAAI,IAAI,KAAK,oBAAI,IAAI,CAAC,KAAK,CAAC,CAAC;AACpC;AAGA,SAAS,gBAAgB,OAAiC;AACxD,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,QAAQ,MAAO,KAAI,KAAK,WAAY,QAAO,IAAI,KAAK,EAAE;AACjE,SAAO;AACT;AAeA,SAAS,eAAe,OAAuC;AAC7D,QAAM,UAAU,oBAAI,IAA2B;AAC/C,QAAM,kBAAkB,oBAAI,IAAoB;AAChD,aAAW,WAAW,MAAM,YAAY;AACtC,2BAAuB,EAAE,GAAG,OAAO,SAAS,SAAS,gBAAgB,CAAC;AAAA,EACxE;AACA,8BAA4B,SAAS,eAAe;AACpD,SAAO,MAAM,KAAK,QAAQ,OAAO,CAAC;AACpC;AAUA,SAAS,uBAAuB,KAA+B;AAC7D,oBAAkB,IAAI,WAAW,IAAI,SAAS,CAAC,OAAO,cAAc;AAClE,kBAAc,EAAE,KAAK,OAAO,UAAU,CAAC;AAAA,EACzC,CAAC;AACH;AAeA,SAAS,cAAc,OAA8B;AACnD,QAAM,EAAE,KAAK,OAAO,UAAU,IAAI;AAClC,MAAI,IAAI,SAAS,IAAI,KAAK,EAAG;AAC7B,MAAI,IAAI,WAAW,IAAI,KAAK,EAAG;AAC/B,iBAAe,IAAI,iBAAiB,KAAK;AACzC,wBAAsB,IAAI,SAAS;AAAA,IACjC,MAAM,IAAI;AAAA,IACV,IAAI;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,MACL;AAAA,MACA,IAAI;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,IACN;AAAA,EACF,CAAC;AACH;AAGA,SAAS,eAAe,SAA8B,QAAsB;AAC1E,UAAQ,IAAI,SAAS,QAAQ,IAAI,MAAM,KAAK,KAAK,CAAC;AACpD;AAGA,IAAM,qBAA0C,oBAAI,IAAY;AAQhE,SAAS,kBACP,WACA,MACA,QACM;AACN,QAAM,WAAW,UAAU,SAAS,IAAI,IAAI,KAAK;AACjD,QAAM,WAAW,UAAU,SAAS,IAAI,IAAI,KAAK;AACjD,aAAW,UAAU,SAAU,QAAO,QAAQ,UAAU;AACxD,aAAWC,WAAU,SAAU,QAAOA,SAAQ,UAAU;AAC1D;AAkBA,SAAS,sBACP,SACA,WACM;AACN,QAAM,MAAM,iBAAiB,UAAU,MAAM,UAAU,EAAE;AACzD,QAAM,WAAW,QAAQ,IAAI,GAAG;AAChC,MAAI,UAAU;AACZ,QAAI,SAAS,cAAc,cAAc,UAAU,cAAc,YAAY;AAC3E,eAAS,YAAY;AAAA,IACvB;AACA;AAAA,EACF;AACA,UAAQ,IAAI,KAAK,EAAE,GAAG,WAAW,QAAQ,yBAAyB,CAAC;AACrE;AAOA,SAAS,4BACP,SACA,iBACM;AACN,aAAW,YAAY,QAAQ,OAAO,GAAG;AACvC,UAAM,OAAO,gBAAgB,IAAI,SAAS,EAAE,KAAK;AACjD,UAAM,YAAY,KAAK;AAAA,MACrB,KAAK,IAAI,GAAG,OAAO,CAAC;AAAA,MACpB;AAAA,IACF;AACA,aAAS,QAAQ;AAAA,MACf,SAAS,QAAQ,YAAY;AAAA,IAC/B;AAAA,EACF;AACF;AAkBA,SAAS,eAAe,OAAuC;AAC7D,QAAM,UAAU,oBAAI,IAA2B;AAC/C,aAAW,UAAU,MAAM,iBAAiB;AAC1C,2BAAuB,EAAE,GAAG,OAAO,QAAQ,QAAQ,CAAC;AAAA,EACtD;AACA,SAAO,MAAM,KAAK,QAAQ,OAAO,CAAC;AACpC;AASA,SAAS,uBAAuB,KAA8B;AAC5D,oBAAkB,IAAI,WAAW,IAAI,QAAQ,CAAC,OAAO,cAAc;AACjE,qBAAiB,EAAE,KAAK,OAAO,UAAU,CAAC;AAAA,EAC5C,CAAC;AACH;AAeA,SAAS,iBAAiB,OAAiC;AACzD,QAAM,EAAE,KAAK,OAAO,UAAU,IAAI;AAClC,MAAI,IAAI,SAAS,IAAI,KAAK,EAAG;AAC7B,MAAI,IAAI,WAAW,IAAI,KAAK,EAAG;AAC/B,MAAI,IAAI,gBAAgB,IAAI,KAAK,EAAG;AACpC,wBAAsB,IAAI,SAAS;AAAA,IACjC,MAAM,IAAI;AAAA,IACV,IAAI;AAAA,IACJ;AAAA,IACA,UAAU;AAAA,IACV,OAAO;AAAA,MACL;AAAA,MACA,IAAI;AAAA,MACJ;AAAA,MACA,IAAI;AAAA,IACN;AAAA,EACF,CAAC;AACH;AAGA,SAAS,iBAAiB,OAA0C;AAClE,QAAM,QAAQ,oBAAI,IAAoB;AACtC,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,KAAK,YAAY;AAC9B,UAAM,IAAI,KAAK,IAAI,OAAO,SAAS,YAAY,KAAK,SAAS,IAAI,OAAO,iBAAiB;AAAA,EAC3F;AACA,SAAO;AACT;AAGA,SAAS,uBACP,MACA,MACA,IACA,WACQ;AACR,SAAO,aAAa,MAAM,IAAI,SAAS,IACnC,WAAW,OAAO,sBAAsB,IACxC;AACN;AAGA,SAAS,aACP,MACA,IACA,WACS;AACT,QAAM,WAAW,UAAU,IAAI,IAAI;AACnC,QAAM,SAAS,UAAU,IAAI,EAAE;AAC/B,SAAO,aAAa,UAAa,WAAW,UAAa,aAAa;AACxE;AAGA,SAAS,uBAAuB,WAAyC;AACvE,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,KAAK,UAAW,KAAI,IAAI,EAAE,EAAE;AACvC,SAAO;AACT;AAOA,SAAS,iBAAiB,GAAW,GAAmB;AACtD,SAAO,IAAI,IACP,GAAG,CAAC,GAAG,wBAAwB,GAAG,CAAC,KACnC,GAAG,CAAC,GAAG,wBAAwB,GAAG,CAAC;AACzC;AAGA,SAAS,WAAW,QAAwB;AAC1C,MAAI,UAAU,EAAG,QAAO;AACxB,MAAI,UAAUD,sBAAsB,QAAOA;AAC3C,SAAO,KAAK,MAAM,SAAS,GAAG,IAAI;AACpC;AAOA,SAAS,iBAAiB,GAAkB,GAA0B;AACpE,MAAI,EAAE,UAAU,EAAE,MAAO,QAAO,EAAE,QAAQ,EAAE;AAC5C,SAAO,EAAE,GAAG,cAAc,EAAE,EAAE;AAChC;;;ACjdA,IAAM,yBAAyB;AAUxB,SAAS,eAAe,MAAuB;AACpD,MAAI,SAAS,QAAQ,SAAS,OAAW,QAAO;AAChD,QAAM,cAAc,OAAO,SAAS,WAAW,OAAO,OAAO,IAAI;AACjE,MAAI,YAAY,WAAW,EAAG,QAAO;AACrC,SAAO,KAAK,KAAK,YAAY,SAAS,sBAAsB;AAC9D;AAQO,SAAS,mBAAmB,MAA2B;AAC5D,SAAO,eAAe,KAAK,UAAU,IAAI,CAAC;AAC5C;AAQO,SAAS,YAAY,iBAAyB,iBAAwC;AAC3F,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA,WAAW;AAAA,IACX,iBAAiB,CAAC;AAAA,EACpB;AACF;AAuBO,SAAS,aAAa,MAAmB,iBAAqC;AACnF,QAAM,QAAQ,UAAU,IAAI;AAC5B,QAAM,UAAU,oBAAI,IAAoB;AACxC,MAAI,mBAAmB,KAAK,KAAK,iBAAiB;AAChD,WAAO,EAAE,MAAM,OAAO,iBAAiB,CAAC,EAAE;AAAA,EAC5C;AACA,gBAAc,OAAO,iBAAiB,OAAO;AAC7C,oBAAkB,OAAO,iBAAiB,OAAO;AACjD,aAAW,OAAO,iBAAiB,OAAO;AAC1C,cAAY,OAAO,iBAAiB,OAAO;AAC3C,SAAO,EAAE,MAAM,OAAO,iBAAiB,gBAAgB,OAAO,EAAE;AAClE;AAGA,SAAS,UAAU,MAAgC;AACjD,SAAO,gBAAgB,IAAI;AAC7B;AAGA,SAAS,gBAAgB,SAAgD;AACvE,QAAM,QAA0B,CAAC,aAAa,iBAAiB,UAAU,SAAS;AAClF,SAAO,MAAM,OAAO,CAAC,YAAY,QAAQ,IAAI,OAAO,CAAC;AACvD;AAGA,SAAS,cACP,MACA,QACA,SACM;AACN,SAAO,KAAK,UAAU,SAAS,KAAK,mBAAmB,IAAI,IAAI,QAAQ;AACrE,SAAK,UAAU,IAAI;AACnB,YAAQ,IAAI,WAAW;AAAA,EACzB;AACF;AAQA,SAAS,kBACP,MACA,QACA,SACM;AACN,WAAS,IAAI,KAAK,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AACjD,WAAO,KAAK,QAAQ,CAAC,EAAE,cAAc,SAAS,KAAK,mBAAmB,IAAI,IAAI,QAAQ;AACpF,WAAK,QAAQ,CAAC,EAAE,cAAc,IAAI;AAClC,cAAQ,IAAI,eAAe;AAAA,IAC7B;AACA,QAAI,mBAAmB,IAAI,KAAK,OAAQ;AAAA,EAC1C;AACF;AAGA,SAAS,WACP,MACA,QACA,SACM;AACN,WAAS,IAAI,KAAK,QAAQ,SAAS,GAAG,KAAK,GAAG,KAAK;AACjD,WAAO,KAAK,QAAQ,CAAC,EAAE,OAAO,SAAS,KAAK,mBAAmB,IAAI,IAAI,QAAQ;AAC7E,WAAK,QAAQ,CAAC,EAAE,OAAO,IAAI;AAC3B,cAAQ,IAAI,QAAQ;AAAA,IACtB;AACA,QAAI,mBAAmB,IAAI,KAAK,OAAQ;AAAA,EAC1C;AACF;AAGA,SAAS,YACP,MACA,QACA,SACM;AACN,SAAO,KAAK,QAAQ,SAAS,KAAK,mBAAmB,IAAI,IAAI,QAAQ;AACnE,SAAK,QAAQ,IAAI;AACjB,YAAQ,IAAI,SAAS;AAAA,EACvB;AACF;;;ACTO,IAAM,yBAAyB;AAG/B,IAAM,wBAAwB;AAG9B,IAAM,gBAAgB;AAGtB,IAAM,YAAY;AAGlB,IAAM,oBAAoB;AAG1B,IAAM,gBAAgB;AAGtB,IAAM,qBAAqB;AAG3B,IAAM,iBAAiB;;;ACtG9B,eAAsB,iBAAiB,SAAwD;AAC7F,QAAM,aAAa,iBAAiB,OAAO;AAC3C,QAAM,WAAW,MAAM,oBAAoB,QAAQ,IAAI;AACvD,QAAM,QAAQ,MAAM,oBAAoB,QAAQ,IAAI;AACpD,QAAM,iBAAiB,oBAAoB,KAAK;AAKhD,QAAM,WAAW,MAAM;AAAA,IACrB,QAAQ;AAAA,IACR,WAAW;AAAA,IACX,WAAW;AAAA,EACb;AACA,QAAM,QAAQ,cAAc;AAAA,IAC1B;AAAA,IACA;AAAA,IACA;AAAA,IACA,SAAS;AAAA,IACT;AAAA,EACF,CAAC;AAID,QAAM,cAAc,WAAW,iBAC3B,MAAM,oBAAoB,OAAO,QAAQ,IAAI,IAC7C;AAMJ,QAAM,sBAAsB,sBAAsB,aAAa,OAAO,UAAU;AAChF,QAAM,QAAQ,WAAW,oBAAoB,WAAW,SAAS,IAC7D,SAAS,QACT;AACJ,SAAO,eAAe,qBAAqB,WAAW,QAAQ,KAAK;AACrE;AAUA,eAAe,oBAAoB,MAAmB,MAAoC;AACxF,QAAM,SAAS,yBAAyB;AAIxC,QAAM,UAAkC,CAAC;AACzC,aAAW,SAAS,KAAK,SAAS;AAChC,UAAM,UAAU,MAAM,yBAAyB,MAAM,MAAM,WAAW,MAAM;AAC5E,YAAQ,KAAK,EAAE,GAAG,OAAO,eAAe,QAAQ,CAAC;AAAA,EACnD;AACA,SAAO,EAAE,GAAG,MAAM,QAAQ;AAC5B;AAmCA,SAAS,iBAAiB,SAAqD;AAC7E,QAAM,gBAAgB,QAAQ,UAAU;AACxC,QAAM,EAAE,SAAS,UAAU,IAAI,eAAe,aAAa;AAC3D,SAAO;AAAA,IACL,eAAe;AAAA,IACf;AAAA,IACA,QAAQ,cAAc,QAAQ,QAAQ,qBAAqB;AAAA,IAC3D,OAAO,WAAW,QAAQ,KAAK;AAAA,IAC/B,UAAU,aAAa,QAAQ,UAAU,mBAAmB,aAAa;AAAA,IACzE,WAAW,aAAa,QAAQ,WAAW,oBAAoB,cAAc;AAAA,IAC7E,UAAU,QAAQ,aAAa;AAAA;AAAA;AAAA,IAG/B,kBAAkB,QAAQ,cAAc;AAAA,IACxC,gBAAgB,QAAQ,mBAAmB;AAAA,IAC3C,iBAAiB;AAAA,EACnB;AACF;AAGA,SAAS,eAAe,KAAsD;AAC5E,MAAI,IAAI,UAAU,uBAAwB,QAAO,EAAE,SAAS,KAAK,WAAW,MAAM;AAClF,SAAO,EAAE,SAAS,IAAI,MAAM,GAAG,sBAAsB,GAAG,WAAW,KAAK;AAC1E;AAGA,SAAS,cAAc,OAA2B,UAA0B;AAC1E,MAAI,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AAC3D,SAAO,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC;AACtC;AAGA,SAAS,aAAa,OAA2B,UAAkB,KAAqB;AACtF,SAAO,KAAK,IAAI,KAAK,cAAc,OAAO,QAAQ,CAAC;AACrD;AAGA,SAAS,WAAW,OAAmC;AACrD,MAAI,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AAC3D,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,WAAW,KAAK,MAAM,KAAK,CAAC,CAAC;AAC3D;AAYA,SAAS,cAAc,OAAmC;AACxD,QAAM,EAAE,UAAU,OAAO,gBAAgB,SAAS,SAAS,IAAI;AAC/D,QAAM,UAAUE,cAAa,UAAU,OAAO,QAAQ,QAAQ;AAG9D,QAAM,UAAU,UAAU,UAAU,QAAQ,eAAe,QAAQ,UAAU,SAAS,IAAI;AAC1F,QAAM,eAAe,QAAQ,oBAAoB,QAAQ,SAAS;AAClE,QAAM,YAAY,eACd,wBAAwB;AAAA,IACtB,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,YAAY,kBAAkB,OAAO;AAAA,IACrC,OAAO,QAAQ;AAAA,EACjB,CAAC,IACD,eAAe;AAMnB,QAAM,mBAAmB,eACrB,uBAAuB,SAAS,SAAS,KAAK,IAC9C;AACJ,SAAO;AAAA,IACL,SAAS;AAAA,IACT,QAAQ,QAAQ;AAAA,IAChB,QAAQ,YAAY,QAAQ,QAAQ,CAAC;AAAA,IACrC;AAAA,IACA,SAAS;AAAA,IACT,WAAW,UAAU;AAAA,IACrB,UAAU,sBAAsB,QAAQ,iBAAiB,SAAS,OAAO;AAAA,IACzE,MAAM,UAAU;AAAA,IAChB,kBAAkB,wBAAwB,gBAAgB;AAAA,MACxD,UAAU,SAAS,MAAM,SAAS;AAAA,MAClC,iBAAiB,SAAS;AAAA,IAC5B,CAAC;AAAA,EACH;AACF;AAUA,SAAS,uBACP,SACA,OACwB;AACxB,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,QAAM,aAAa,kBAAkB,OAAO;AAC5C,QAAM,YAAY,oBAAI,IAAY;AAClC,aAAW,QAAQ,MAAM,OAAO;AAC9B,QAAI,WAAW,IAAI,KAAK,MAAM,KAAK,WAAW,IAAI,KAAK,MAAM,GAAG;AAC9D,gBAAU,IAAI,KAAK,MAAM;AACzB,gBAAU,IAAI,KAAK,MAAM;AAAA,IAC3B;AAAA,EACF;AACA,MAAI,UAAU,SAAS,EAAG,QAAO;AACjC,SAAO,QAAQ,IAAI,CAAC,UAAU;AAC5B,QAAI,CAAC,UAAU,IAAI,MAAM,EAAE,EAAG,QAAO;AACrC,QAAI,MAAM,QAAQ,SAAS,gBAAgB,EAAG,QAAO;AACrD,UAAM,UAAU,MAAM,KAAK,oBAAI,IAAI,CAAC,GAAG,MAAM,SAAS,gBAAyB,CAAC,CAAC,EAAE,KAAK;AACxF,WAAO,EAAE,GAAG,OAAO,SAAS,QAAQ;AAAA,EACtC,CAAC;AACH;AAGA,SAAS,kBAAkB,SAA8C;AACvE,QAAM,MAAM,oBAAI,IAAY;AAC5B,aAAW,SAAS,QAAS,KAAI,IAAI,MAAM,EAAE;AAC7C,SAAO;AACT;AAGA,SAAS,yBACP,SACwB;AACxB,SAAO,QAAQ,IAAI,CAAC,WAAW;AAAA,IAC7B,GAAG;AAAA,IACH,SAAS,MAAM,QAAQ,OAAO,CAAC,WAAW,WAAW,gBAAgB;AAAA,EACvE,EAAE;AACJ;AAGA,SAAS,8BACP,MACA,OACa;AACb,QAAM,WAAW,yBAAyB,KAAK,OAAO;AACtD,QAAM,UAAU,QAAQ,uBAAuB,UAAU,KAAK,IAAI;AAClE,SAAO,YAAY,KAAK,UAAU,OAAO,EAAE,GAAG,MAAM,QAAQ;AAC9D;AAGA,SAAS,iBAAuC;AAC9C,SAAO,EAAE,WAAW,CAAC,GAAG,MAAM,CAAC,EAAE;AACnC;AAGA,SAASA,cACP,UACA,OACA,UACgB;AAChB,SAAO;AAAA,IACL,MAAM,WAAW,OAAO,SAAS;AAAA,IACjC,OAAO,SAAS,MAAM;AAAA,IACtB,mBAAmB,MAAM;AAAA,IACzB,MAAM,MAAM,KAAK;AAAA,EACnB;AACF;AAQA,SAAS,sBACP,iBACA,kBACkB;AAClB,QAAM,WAA6B,CAAC;AACpC,MAAI,iBAAiB;AACnB,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,mBAAmB,sBAAsB;AAAA,IACpD,CAAC;AAAA,EACH;AACA,MAAI,qBAAqB,2BAA2B;AAClD,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SACE;AAAA,IAEJ,CAAC;AAAA,EACH,WAAW,qBAAqB,+BAA+B;AAC7D,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SACE;AAAA,IAEJ,CAAC;AAAA,EACH,WAAW,qBAAqB,4BAA4B;AAC1D,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SACE;AAAA,IAEJ,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAgBA,IAAM,yBAA4C;AAAA,EAChD,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,SAAS,EAAE;AACrD;AAGA,IAAM,sBAAyC;AAAA,EAC7C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,QAAQ,QAAQ,EAAE;AAC5D;AAGA,IAAM,uBAA0C;AAAA,EAC9C,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,YAAY,EAAE,QAAQ,WAAW,MAAM,CAAC,OAAO,GAAG,cAAc,CAAC,QAAQ,EAAE;AAC7E;AAEA,SAAS,wBACP,gBACA,OACqB;AACrB,QAAM,UAA+B,CAAC;AACtC,aAAW,UAAU,CAAC,eAAe,aAAa,GAAG,eAAe,YAAY,GAAG;AACjF,uBAAmB,SAAS,MAAM;AAAA,EACpC;AACA,MAAI,MAAM,YAAY,MAAM,oBAAoB,2BAA2B;AACzE,uBAAmB,SAAS,sBAAsB;AAAA,EACpD;AACA,MAAI,MAAM,UAAU;AAClB,uBAAmB,SAAS,mBAAmB;AAC/C,uBAAmB,SAAS,oBAAoB;AAAA,EAClD;AACA,SAAO;AACT;AAGA,SAAS,mBAAmB,SAA8B,WAAoC;AAC5F,MAAI,QAAQ,KAAK,CAAC,WAAW,UAAU,MAAM,MAAM,UAAU,SAAS,CAAC,EAAG;AAC1E,UAAQ,KAAK,SAAS;AACxB;AAGA,SAAS,UAAU,QAAmC;AACpD,MAAI,CAAC,OAAO,WAAY,QAAO,OAAO,WAAW,OAAO;AACxD,SAAO,GAAG,OAAO,WAAW,MAAM,IAAI,OAAO,WAAW,KAAK,KAAK,GAAG,CAAC;AACxE;AAwBA,SAAS,eACP,OACA,iBACA,QAA0B,MACb;AACb,QAAM,kBAAkB,mBAAmB,KAAK;AAChD,QAAM,OAAO,mBAAmB,kBAC5B,EAAE,MAAM,OAAO,iBAAiB,CAAC,EAAc,IAC/C,aAAa,OAAO,eAAe;AACvC,QAAM,aAAa,KAAK,gBAAgB,SAAS;AACjD,QAAM,iBAAiB,8BAA8B,KAAK,MAAM,KAAK;AAMrE,QAAM,KAAK;AAAA,IACT,YAAY,gBAAgB;AAAA,MAC1B;AAAA,MAAiB,iBAAiB;AAAA,MAClC,WAAW;AAAA,MAAY,iBAAiB,KAAK;AAAA,IAC/C,CAAC;AAAA,EACH;AACA,QAAM,KAAK;AAAA,IACT,YAAY,gBAAgB;AAAA,MAC1B;AAAA,MAAiB,iBAAiB;AAAA,MAClC,WAAW;AAAA,MAAY,iBAAiB,KAAK;AAAA,IAC/C,CAAC;AAAA,EACH;AACA,QAAM,YAAY,cAAc,KAAK;AACrC,SAAO,YAAY,gBAAgB;AAAA,IACjC;AAAA,IACA,iBAAiB;AAAA,IACjB;AAAA,IACA,iBAAiB,KAAK;AAAA,EACxB,CAAC;AACH;AAGA,SAAS,YAAY,MAAmB,QAA4C;AAClF,SAAO,EAAE,GAAG,MAAM,OAAO;AAC3B;AAaA,SAAS,sBACP,MACA,OACA,SACa;AACb,QAAM,WAAW,CAAC,GAAG,KAAK,QAAQ;AAClC,MAAI,MAAM,oBAAoB,GAAG;AAC/B,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SACE,GAAG,MAAM,iBAAiB,oBAAoB,MAAM,sBAAsB,IAAI,KAAK,GAAG;AAAA,IAE1F,CAAC;AAAA,EACH;AACA,QAAM,aAAa,MAAM,KAAK,OAAO,UAAU;AAC/C,MAAI,aAAa,GAAG;AAClB,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SAAS,0BAA0B,UAAU,SAAS,eAAe,IAAI,KAAK,GAAG;AAAA,IACnF,CAAC;AAAA,EACH;AACA,MAAI,QAAQ,kBAAkB,uBAAuB,IAAI,GAAG;AAC1D,aAAS,KAAK;AAAA,MACZ,MAAM;AAAA,MACN,SACE;AAAA,IAEJ,CAAC;AAAA,EACH;AACA,SAAO,EAAE,GAAG,MAAM,SAAS;AAC7B;AAGA,SAAS,uBAAuB,MAA4B;AAC1D,aAAW,SAAS,KAAK,SAAS;AAChC,UAAM,iBAAiB,MAAM,UAAU;AAAA,MACrC,CAAC,MAAM,EAAE,UAAU,UAAa,EAAE,QAAQ;AAAA,IAC5C,EAAE;AACF,QAAI,iBAAiB,MAAM,cAAc,OAAQ,QAAO;AAAA,EAC1D;AACA,SAAO;AACT;;;APjfA,eAAO,eACL,QACA,UAAiC,CAAC,GACjB;AACjB,QAAM,OAAO,MAAM,iBAAiB;AAAA,IAClC,MAAM,QAAQ,IAAI;AAAA,IAClB;AAAA,IACA,QAAQ,aAAa,QAAQ,QAAQ,qBAAqB;AAAA,IAC1D,OAAO,aAAa,QAAQ,OAAO,aAAa;AAAA,IAChD,UAAU,aAAa,QAAQ,UAAU,iBAAiB;AAAA,IAC1D,WAAW,aAAa,QAAQ,WAAW,kBAAkB;AAAA,IAC7D,UAAU,QAAQ,aAAa;AAAA,IAC/B,WAAW,QAAQ;AAAA,IACnB,gBAAgB,QAAQ,mBAAmB;AAAA,EAC7C,CAAC;AACD,OAAK,MAAM,cAAc,OAAO,CAAC;AACjC,SAAO;AACT;AAGA,SAAS,aAAa,KAAkC,UAA0B;AAChF,MAAI,QAAQ,OAAW,QAAO;AAC9B,QAAM,QAAQ,OAAO,QAAQ,WAAW,MAAM,OAAO,GAAG;AACxD,SAAO,OAAO,SAAS,KAAK,IAAI,QAAQ;AAC1C;AAGA,SAAS,cAAc,SAAqD;AAC1E,MAAI,QAAQ,SAAS,KAAM,QAAO;AAClC,MAAI,QAAQ,WAAW,OAAQ,QAAO;AACtC,SAAO;AACT;AAGA,SAAS,KAAK,MAAmB,QAAmC;AAClE,MAAI,WAAW,QAAQ;AACrB,YAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,MAAM,MAAM,CAAC,CAAC;AAAA,CAAI;AACzD;AAAA,EACF;AACA,UAAQ,OAAO,MAAM,GAAG,eAAe,IAAI,CAAC;AAAA,CAAI;AAClD;AAGA,SAAS,eAAe,MAA2B;AACjD,QAAM,QAAkB,CAAC;AACzB,eAAa,OAAO,IAAI;AACxB,qBAAmB,OAAO,KAAK,OAAO;AACtC,0BAAwB,OAAO,KAAK,SAAS;AAC7C,iBAAe,OAAO,KAAK,QAAQ;AACnC,yBAAuB,OAAO,KAAK,gBAAgB;AACnD,SAAO,MAAM,KAAK,IAAI;AACxB;AAQA,SAAS,wBACP,OACA,WACM;AACN,MAAI,UAAU,WAAW,EAAG;AAC5B,QAAM,KAAK,uBAAuB;AAClC,QAAM,KAAK,EAAE;AACb,aAAW,YAAY,WAAW;AAChC,UAAM,QAAQ,SAAS,cAAc,aAAa,OAAO;AACzD,UAAM;AAAA,MACJ,OAAO,SAAS,IAAI,MAAM,KAAK,MAAM,SAAS,EAAE,OACzC,SAAS,MAAM,cAAc,SAAS,QAAQ;AAAA,IACvD;AAAA,EACF;AACA,QAAM,KAAK,EAAE;AACf;AAGA,SAAS,aAAa,OAAiB,MAAyB;AAC9D,QAAM,KAAK,gBAAgB;AAC3B,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,WAAW,KAAK,MAAM,EAAE;AACnC,QAAM,KAAK,WAAW,KAAK,OAAO,eAAe,MAAM,KAAK,OAAO,eAAe,mBAAmB;AACvG;AAGA,SAAS,mBAAmB,OAAiB,SAAiC;AAC5E,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,kBAAkB;AAC7B,QAAM,KAAK,EAAE;AACb,MAAI,QAAQ,WAAW,GAAG;AACxB,UAAM,KAAK,wCAAwC;AACnD;AAAA,EACF;AACA,aAAW,QAAQ,QAAS,mBAAkB,OAAO,IAAI;AAC3D;AAGA,SAAS,kBAAkB,OAAiB,MAA4B;AACtE,QAAM,WAAWC,OAAK,KAAK,QAAQ,KAAK,eAAe,GAAG,WAAW,KAAK,EAAE,CAAC,KAAK;AAClF,QAAM,KAAK,OAAO,KAAK,KAAK,OAAO,QAAQ,KAAK;AAChD,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,iBAAiB,KAAK,QAAQ,KAAK,IAAI,KAAK,cAAc,EAAE;AACvE,MAAI,KAAK,SAAS;AAChB,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,KAAK,OAAO;AAAA,EACzB;AACA,kBAAgB,OAAO,IAAI;AAC3B,sBAAoB,OAAO,IAAI;AAC/B,QAAM,KAAK,EAAE;AACf;AAGA,SAAS,gBAAgB,OAAiB,MAA4B;AACpE,MAAI,KAAK,UAAU,WAAW,EAAG;AACjC,QAAM,OAAO,KAAK,UAAU,IAAI,cAAc,EAAE,KAAK,IAAI;AACzD,QAAM,KAAK,EAAE;AACb,QAAM,KAAK,YAAY,IAAI,EAAE;AAC/B;AAGA,SAAS,eAAe,UAAuD;AAC7E,MAAI,SAAS,UAAU,UAAa,SAAS,QAAQ,QAAW;AAC9D,WAAO,KAAK,SAAS,IAAI,IAAI,SAAS,KAAK,IAAI,SAAS,GAAG;AAAA,EAC7D;AACA,SAAO,KAAK,SAAS,IAAI;AAC3B;AAGA,SAAS,oBAAoB,OAAiB,MAA4B;AACxE,MAAI,KAAK,cAAc,WAAW,EAAG;AACrC,aAAW,UAAU,KAAK,eAAe;AACvC,UAAM,KAAK,EAAE;AACb,UAAM,KAAK,UAAU,OAAO,IAAI,IAAI,OAAO,KAAK,IAAI,OAAO,GAAG,KAAK;AACnE,UAAM,KAAK,EAAE;AACb,eAAWC,SAAQ,OAAO,KAAK,MAAM,OAAO,EAAG,OAAM,KAAK,KAAKA,KAAI,EAAE;AAAA,EACvE;AACF;AAGA,SAAS,WAAW,IAAoB;AACtC,QAAM,MAAM,GAAG,QAAQ,GAAG;AAC1B,SAAO,QAAQ,KAAK,KAAK,GAAG,MAAM,MAAM,CAAC;AAC3C;AAGA,SAAS,eAAe,OAAiB,UAAkC;AACzE,MAAI,SAAS,WAAW,EAAG;AAC3B,QAAM,KAAK,aAAa;AACxB,QAAM,KAAK,EAAE;AACb,aAAW,WAAW,SAAU,OAAM,KAAK,KAAK,QAAQ,OAAO,EAAE;AACjE,QAAM,KAAK,EAAE;AACf;AAGA,SAAS,uBAAuB,OAAiB,SAAoC;AACnF,MAAI,QAAQ,WAAW,EAAG;AAC1B,QAAM,KAAK,2BAA2B;AACtC,QAAM,KAAK,EAAE;AACb,aAAW,UAAU,SAAS;AAC5B,QAAI,OAAO,QAAS,OAAM,KAAK,OAAO,OAAO,OAAO,IAAI;AAAA,EAC1D;AACF;;;AQvNA,SAAS,aAAAC,kBAAiB;AAC1B,SAAS,4BAA4B;;;ACJrC,OAAOC,YAAU;AACjB,SAAS,SAAS;AAsBlB,IAAMC,aAAY,CAAC,cAAc,WAAW;AAe5C,SAAS,WAAW,SAGlB;AACA,SAAO;AAAA,IACL,SAAS,CAAC,EAAE,MAAM,QAAiB,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC,EAAE,CAAC;AAAA,IAC3E,mBAAmB,EAAE,QAAQ,QAAQ;AAAA,EACvC;AACF;AAGO,SAAS,kBAAkB,QAAmB,MAAoB;AACvE,qBAAmB,QAAQ,IAAI;AAC/B,sBAAoB,QAAQ,IAAI;AAChC,oBAAkB,QAAQ,IAAI;AAC9B,qBAAmB,QAAQ,IAAI;AAC/B,mBAAiB,QAAQ,IAAI;AAC7B,mBAAiB,QAAQ,IAAI;AAC7B,qBAAmB,QAAQ,IAAI;AAC/B,0BAAwB,QAAQ,IAAI;AACtC;AAEA,SAAS,mBAAmB,QAAmB,MAAoB;AACjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MAEF,aAAa;AAAA,QACX,QAAQ,EACL,OAAO,EACP,SAAS,sDAAsD;AAAA,MACpE;AAAA,IACF;AAAA,IACA,OAAO,EAAE,QAAAC,QAAO,MAAM;AACpB,YAAM,cAAc,QAAQ,IAAI;AAChC,UAAI;AACF,gBAAQ,MAAM,IAAI;AAClB,cAAM,SAAS,MAAM,aAAaA,OAAM;AACxC,eAAO,WAAW,MAAM;AAAA,MAC1B,UAAE;AACA,gBAAQ,MAAM,WAAW;AAAA,MAC3B;AAAA,IACF;AAAA,EACF;AACF;AAEA,SAAS,oBAAoB,QAAmB,MAAoB;AAClE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MAGF,aAAa,CAAC;AAAA,IAChB;AAAA,IACA,YAAY;AACV,8BAAwB;AACxB,YAAM,SAAS,MAAM,iBAAiB,IAAI;AAC1C,aAAO,WAAW,MAAM;AAAA,IAC1B;AAAA,EACF;AACF;AAEA,SAAS,kBAAkB,QAAmB,MAAoB;AAChE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MAIF,aAAa;AAAA,QACX,UAAU,EAAE,OAAO,EAAE,SAAS,0CAA0C;AAAA,QACxE,MAAM,EACH,QAAQ,EACR,SAAS,EACT,SAAS,uDAAuD;AAAA,QACnE,OAAO,EACJ,QAAQ,EACR,SAAS,EACT,SAAS,gEAAgE;AAAA,MAC9E;AAAA,IACF;AAAA,IACA,OAAO,EAAE,UAAU,MAAM,MAAM,MAAM;AACnC,8BAAwB;AACxB,YAAM,SAAS,MAAM,eAAe,MAAM,UAAU,EAAE,MAAM,MAAM,CAAC;AACnE,aAAO,WAAW,MAAM;AAAA,IAC1B;AAAA,EACF;AACF;AAEA,SAAS,mBAAmB,QAAmB,MAAoB;AACjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MAGF,aAAa;AAAA,QACX,UAAU,EAAE,OAAO,EAAE,SAAS,+BAA+B;AAAA,MAC/D;AAAA,IACF;AAAA,IACA,OAAO,EAAE,SAAS,MAAM;AACtB,8BAAwB;AACxB,YAAM,QAAQ,MAAM,gBAAgB,MAAM,QAAQ;AAClD,YAAM,UAAU,MAAM,gBAAgB,MAAM,KAAK;AACjD,aAAO,WAAW,EAAE,OAAO,QAAQ,CAAC;AAAA,IACtC;AAAA,EACF;AACF;AAOA,eAAe,gBAAgB,MAAc,UAAqC;AAChF,MAAI;AACF,UAAM,SAAS,MAAM,mBAAmB,MAAM,UAAU,WAAW;AACnE,QAAI,OAAO,SAAS,EAAG,QAAO,sBAAsB,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,IAAI,CAAC;AAAA,EACrF,QAAQ;AAAA,EAER;AAEA,MAAI;AACF,UAAM,aAAa,MAAM,kBAAkB,MAAM,QAAQ;AACzD,QAAI,WAAW,SAAS,EAAG,QAAO,WAAW,IAAI,CAAC,MAAM,EAAE,IAAI;AAAA,EAChE,QAAQ;AAAA,EAER;AAEA,QAAM,eAAe,MAAM,aAAaC,OAAK,KAAK,MAAM,UAAU,CAAC;AACnE,QAAM,EAAE,MAAM,IAAI,MAAM,YAAY,UAAU,YAAY;AAC1D,SAAO;AACT;AAGA,SAAS,sBAAsB,OAA2B;AACxD,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,MAAgB,CAAC;AACvB,aAAW,QAAQ,OAAO;AACxB,QAAI,KAAK,IAAI,IAAI,EAAG;AACpB,SAAK,IAAI,IAAI;AACb,QAAI,KAAK,IAAI;AAAA,EACf;AACA,SAAO;AACT;AAEA,SAAS,iBAAiB,QAAmB,MAAoB;AAC/D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MAEF,aAAa;AAAA,QACX,MAAM,EAAE,OAAO,EAAE,SAAS,mCAAmC;AAAA,MAC/D;AAAA,IACF;AAAA,IACA,OAAO,EAAE,KAAK,MAAM;AAClB,YAAM,OAAO,MAAM,SAAS,MAAM,IAAI;AACtC,UAAI,CAAC,MAAM;AACT,cAAM,IAAI,MAAM,mBAAmB,IAAI,EAAE;AAAA,MAC3C;AACA,aAAO,WAAW,IAAI;AAAA,IACxB;AAAA,EACF;AACF;AAEA,SAAS,iBAAiB,QAAmB,MAAoB;AAC/D,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MAEF,aAAa,CAAC;AAAA,IAChB;AAAA,IACA,YAAY;AACV,YAAM,UAAU,MAAM,KAAK,IAAI;AAC/B,aAAO,WAAW,OAAO;AAAA,IAC3B;AAAA,EACF;AACF;AAEA,SAAS,mBAAmB,QAAmB,MAAoB;AACjE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aACE;AAAA,MAGF,aAAa,CAAC;AAAA,IAChB;AAAA,IACA,YAAY,WAAW,MAAM,cAAc,IAAI,CAAC;AAAA,EAClD;AACF;AAgBA,SAAS,wBAAwB,QAAmB,MAAoB;AACtE,SAAO;AAAA,IACL;AAAA,IACA,sBAAsB;AAAA,IACtB,OAAO,SAAS,WAAW,MAAM,yBAAyB,MAAM,IAAI,CAAC;AAAA,EACvE;AACF;AAcA,SAAS,wBAIP;AACA,SAAO;AAAA,IACL,OAAO;AAAA,IACP,aACE;AAAA,IAMF,aAAa,uBAAuB;AAAA,EACtC;AACF;AAQA,SAAS,yBAQP;AACA,SAAO;AAAA,IACL,QAAQ,EAAE,OAAO,EAAE,SAAS,kDAAkD;AAAA,IAC9E,QAAQ,EACL,OAAO,EACP,SAAS,EACT,SAAS,iDAAiD;AAAA,IAC7D,OAAO,EACJ,OAAO,EACP,SAAS,EACT,SAAS,mEAAmE;AAAA,IAC/E,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,SAAS,wCAAwC;AAAA,IACjF,WAAW,EACR,OAAO,EACP,SAAS,EACT,SAAS,qDAAqD;AAAA,IACjE,UAAU,EACP,QAAQ,EACR,SAAS,EACT,SAAS,2DAA2D;AAAA,IACvE,gBAAgB,EACb,QAAQ,EACR,SAAS,EACT;AAAA,MACC;AAAA,IAEF;AAAA,EACJ;AACF;AAGA,eAAe,yBACb,MACA,MACuD;AACvD,SAAO,iBAAiB;AAAA,IACtB;AAAA,IACA,QAAQ,KAAK;AAAA,IACb,QAAQ,KAAK;AAAA,IACb,OAAO,KAAK;AAAA,IACZ,UAAU,KAAK;AAAA,IACf,WAAW,KAAK;AAAA,IAChB,UAAU,KAAK;AAAA,IACf,gBAAgB,KAAK;AAAA,EACvB,CAAC;AACH;AAGA,eAAe,cAAc,MAAmC;AAC9D,QAAM,WAAW,MAAM,qBAAqBA,OAAK,KAAK,MAAM,YAAY,CAAC;AACzE,QAAM,UAAU,MAAM,qBAAqBA,OAAK,KAAK,MAAM,WAAW,CAAC;AACvE,QAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,QAAM,UAAU,MAAM,cAAc,MAAM,KAAK;AAC/C,QAAM,UAAU,MAAM,kBAAkB,IAAI;AAC5C,QAAM,oBAAoB,MAAM,gBAAgB,IAAI;AACpD,QAAM,eAAe,OAAO,OAAO,MAAM,OAAO,EAAE,IAAI,CAAC,MAAM,EAAE,UAAU;AACzE,QAAM,cAAc,aAAa,SAAS,IACtC,aAAa,KAAK,EAAE,MAAM,EAAE,EAAE,CAAC,IAC/B;AAEJ,SAAO;AAAA,IACL,OAAO,EAAE,UAAU,SAAS,QAAQ,SAAS,QAAQ,QAAQ,OAAO,SAAS,SAAS,QAAQ,OAAO;AAAA,IACrG,SAAS,OAAO,KAAK,MAAM,OAAO,EAAE;AAAA,IACpC,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf;AAAA,IACA,gBAAgB,QACb,OAAO,CAAC,MAAM,EAAE,WAAW,WAAW,EACtC,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,EAAE,OAAO,EAAE;AAAA,EACpD;AACF;AAaA,eAAe,kBAAkB,MAAiC;AAChE,QAAM,UAAU,MAAM,cAAcA,OAAK,KAAK,MAAM,YAAY,CAAC;AACjE,SAAO,QAAQ,OAAO,CAAC,EAAE,KAAK,MAAM,KAAK,QAAQ,EAAE,IAAI,CAAC,EAAE,KAAK,MAAM,IAAI;AAC3E;AAGA,eAAe,gBAAgB,MAAc,OAAwC;AACnF,QAAM,UAAwB,CAAC;AAC/B,aAAW,QAAQ,OAAO;AACxB,UAAM,OAAO,MAAM,SAAS,MAAM,IAAI;AACtC,QAAI,KAAM,SAAQ,KAAK,IAAI;AAAA,EAC7B;AACA,SAAO;AACT;AAMA,eAAsB,SAAS,MAAc,MAA0C;AACrF,aAAW,OAAOF,YAAW;AAC3B,UAAM,UAAU,MAAM,aAAaE,OAAK,KAAK,MAAM,KAAK,GAAG,IAAI,KAAK,CAAC;AACrE,QAAI,CAAC,QAAS;AAEd,UAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB,OAAO;AAC/C,QAAI,KAAK,SAAU;AAEnB,WAAO;AAAA,MACL;AAAA,MACA,OAAO,OAAO,KAAK,UAAU,WAAW,KAAK,QAAQ;AAAA,MACrD,SAAS,OAAO,KAAK,YAAY,WAAW,KAAK,UAAU;AAAA,MAC3D,MAAM,KAAK,KAAK;AAAA,IAClB;AAAA,EACF;AACA,SAAO;AACT;;;ACnbA,OAAOC,YAAU;AACjB,SAAS,WAAAC,iBAAe;AACxB,SAAoB,wBAAwB;AAY5C,SAAS,YAAY,KAAU,SAI7B;AACA,SAAO;AAAA,IACL,KAAK,IAAI;AAAA,IACT,UAAU;AAAA,IACV,MAAM,KAAK,UAAU,SAAS,MAAM,CAAC;AAAA,EACvC;AACF;AAGA,SAAS,gBAAgB,KAAU,MAIjC;AACA,SAAO;AAAA,IACL,KAAK,IAAI;AAAA,IACT,UAAU;AAAA,IACV;AAAA,EACF;AACF;AAGO,SAAS,sBAAsB,QAAmB,MAAoB;AAC3E,wBAAsB,QAAQ,IAAI;AAClC,0BAAwB,QAAQ,IAAI;AACpC,wBAAsB,QAAQ,IAAI;AAClC,0BAAwB,QAAQ,IAAI;AACpC,wBAAsB,QAAQ,IAAI;AACpC;AAEA,SAAS,sBAAsB,QAAmB,MAAoB;AACpE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO,QAAQ;AACb,YAAM,UAAU,MAAM,aAAaC,OAAK,KAAK,MAAM,UAAU,CAAC;AAC9D,aAAO,EAAE,UAAU,CAAC,gBAAgB,KAAK,OAAO,CAAC,EAAE;AAAA,IACrD;AAAA,EACF;AACF;AAEA,SAAS,wBAAwB,QAAmB,MAAoB;AACtE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO,SAAS;AAAA,MACd,UAAU,CAAC,YAAY,KAAK,MAAM,YAAY,IAAI,CAAC,CAAC;AAAA,IACtD;AAAA,EACF;AACF;AAEA,SAAS,sBAAsB,QAAmB,MAAoB;AACpE,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO,QAAQ;AACb,YAAM,QAAQ,MAAM,UAAU,IAAI;AAClC,aAAO,EAAE,UAAU,CAAC,YAAY,KAAK,KAAK,CAAC,EAAE;AAAA,IAC/C;AAAA,EACF;AACF;AAEA,SAAS,wBAAwB,QAAmB,MAAoB;AACtE,SAAO;AAAA,IACL;AAAA,IACA,IAAI,iBAAiB,4BAA4B;AAAA,MAC/C,MAAM,YAAY,eAAe,MAAM,cAAc,SAAS;AAAA,IAChE,CAAC;AAAA,IACD;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO,KAAK,EAAE,KAAK,OAAO;AAAA,MACxB,UAAU,CAAC,YAAY,KAAK,MAAM,iBAAiB,MAAM,cAAc,OAAO,IAAI,CAAC,CAAC,CAAC;AAAA,IACvF;AAAA,EACF;AACF;AAEA,SAAS,sBAAsB,QAAmB,MAAoB;AACpE,SAAO;AAAA,IACL;AAAA,IACA,IAAI,iBAAiB,0BAA0B;AAAA,MAC7C,MAAM,YAAY,eAAe,MAAM,aAAa,OAAO;AAAA,IAC7D,CAAC;AAAA,IACD;AAAA,MACE,OAAO;AAAA,MACP,aAAa;AAAA,MACb,UAAU;AAAA,IACZ;AAAA,IACA,OAAO,KAAK,EAAE,KAAK,OAAO;AAAA,MACxB,UAAU,CAAC,YAAY,KAAK,MAAM,iBAAiB,MAAM,aAAa,OAAO,IAAI,CAAC,CAAC,CAAC;AAAA,IACtF;AAAA,EACF;AACF;AAGA,eAAe,YAAY,MAAuD;AAChF,QAAM,cAAcA,OAAK,KAAK,MAAM,WAAW;AAC/C,MAAI;AACJ,MAAI;AACF,YAAQ,MAAMC,UAAQ,WAAW;AAAA,EACnC,QAAQ;AACN,WAAO,CAAC;AAAA,EACV;AAEA,QAAM,UAA0C,CAAC;AACjD,aAAW,QAAQ,MAAM,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,GAAG;AACzD,UAAM,UAAU,MAAM,aAAaD,OAAK,KAAK,aAAa,IAAI,CAAC;AAC/D,UAAM,EAAE,KAAK,IAAI,iBAAiB,OAAO;AACzC,YAAQ,KAAK,EAAE,UAAU,MAAM,GAAG,KAAK,CAAC;AAAA,EAC1C;AACA,SAAO;AACT;AAGA,eAAe,iBACb,MACA,KACA,MACwE;AACxE,QAAM,WAAWA,OAAK,KAAK,MAAM,KAAK,GAAG,IAAI,KAAK;AAClD,QAAM,UAAU,MAAM,aAAa,QAAQ;AAC3C,MAAI,CAAC,SAAS;AACZ,UAAM,IAAI,MAAM,mBAAmB,GAAG,IAAI,IAAI,KAAK;AAAA,EACrD;AAEA,QAAM,EAAE,MAAM,KAAK,IAAI,iBAAiB,OAAO;AAC/C,SAAO,EAAE,MAAM,MAAM,MAAM,KAAK,KAAK,EAAE;AACzC;AAGA,eAAe,eACb,MACA,KACA,QAC8D;AAC9D,QAAM,YAAYA,OAAK,KAAK,MAAM,GAAG;AACrC,MAAI;AACJ,MAAI;AACF,YAAQ,MAAMC,UAAQ,SAAS;AAAA,EACjC,QAAQ;AACN,WAAO,EAAE,WAAW,CAAC,EAAE;AAAA,EACzB;AAEA,QAAM,YAAY,MACf,OAAO,CAAC,MAAM,EAAE,SAAS,KAAK,CAAC,EAC/B,IAAI,CAAC,MAAM;AACV,UAAM,OAAO,EAAE,QAAQ,SAAS,EAAE;AAClC,WAAO,EAAE,KAAK,aAAa,MAAM,IAAI,IAAI,IAAI,MAAM,KAAK;AAAA,EAC1D,CAAC;AAEH,SAAO,EAAE,UAAU;AACrB;;;AFlKA,eAAsB,eAAe,SAAuC;AAC1E,QAAM,EAAE,MAAM,SAAAC,SAAQ,IAAI;AAC1B,QAAM,SAAS,IAAIC,WAAU,EAAE,MAAM,WAAW,SAAAD,SAAQ,GAAG;AAAA,IACzD,cACE;AAAA,EAIJ,CAAC;AAED,oBAAkB,QAAQ,IAAI;AAC9B,wBAAsB,QAAQ,IAAI;AAElC,QAAM,YAAY,IAAI,qBAAqB;AAC3C,QAAM,OAAO,QAAQ,SAAS;AAChC;;;A3GTA,IAAME,WAAUC,eAAc,YAAY,GAAG;AAC7C,IAAM,EAAE,QAAQ,IAAID,SAAQ,iBAAiB;AAE7C,IAAM,UAAU,IAAI,QAAQ;AAE5B,QACG,KAAK,SAAS,EACd,YAAY,oEAA+D,EAC3E,QAAQ,OAAO;AAElB,QACG,QAAQ,iBAAiB,EACzB,YAAY,0CAA0C,EACtD,OAAO,OAAOE,YAAmB;AAChC,MAAI;AACF,UAAM,OAAcA,OAAM;AAAA,EAC5B,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,uBAAuB,EAC/B,YAAY,4EAA4E,EACxF,OAAO,OAAO,eAAuB;AACpC,MAAI;AACF,UAAM,cAAqB,UAAU;AAAA,EACvC,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,iEAAiE,EAC7E,OAAO,iBAAiB,6CAAwC,EAChE,OAAO,iBAAiB,wDAAwD,EAChF,OAAO,eAAe,oEAAoE,EAC1F,OAAO,UAAU,sDAAsD,EACvE,OAAO,OAAO,YAAkF;AAC/F,MAAI;AACF,UAAM,YAAY,OAAO;AAAA,EAC3B,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,SAAS,EACjB,YAAY,2CAA2C,EACvD;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,OAAO,YAAiD;AAC9D,MAAI;AACF,wBAAoB,QAAQ,IAAI;AAChC,oBAAgB;AAChB,UAAM,eAAe,EAAE,QAAQ,QAAQ,OAAO,CAAC;AAAA,EACjD,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,IAAM,gBAAgB,QACnB,QAAQ,QAAQ,EAChB,YAAY,sDAAsD;AAErE,cACG,QAAQ,MAAM,EACd,YAAY,gCAAgC,EAC5C,OAAO,YAAY;AAClB,MAAI;AACF,UAAM,kBAAkB;AAAA,EAC1B,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,cACG,QAAQ,WAAW,EACnB,YAAY,8CAA8C,EAC1D,OAAO,OAAO,OAAe;AAC5B,MAAI;AACF,UAAM,kBAAkB,EAAE;AAAA,EAC5B,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,cACG,QAAQ,cAAc,EACtB,YAAY,wDAAwD,EACpE,OAAO,OAAO,OAAe;AAC5B,MAAI;AACF,UAAM,qBAAqB,EAAE;AAAA,EAC/B,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,cACG,QAAQ,aAAa,EACrB,YAAY,0DAA0D,EACtE,OAAO,OAAO,OAAe;AAC5B,MAAI;AACF,UAAM,oBAAoB,EAAE;AAAA,EAC9B,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,kBAAkB,EAC1B,YAAY,iCAAiC,EAC7C,OAAO,UAAU,gCAAgC,EACjD,OAAO,WAAW,6DAA6D,EAC/E;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC,OACE,UACA,YACG;AACH,QAAI;AACF,0BAAoB,QAAQ,IAAI;AAChC,sBAAgB;AAChB,YAAM,aAAa,QAAQ,IAAI,GAAG,UAAU,OAAO;AAAA,IACrD,SAAS,KAAK;AACZ,cAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,cAAQ,KAAK,CAAC;AAAA,IAChB;AAAA,EACF;AACF;AAEF,QACG,QAAQ,OAAO,EACf,YAAY,8CAA8C,EAC1D,OAAO,YAAY;AAClB,MAAI;AACF,oBAAgB;AAChB,UAAM,aAAa;AAAA,EACrB,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,gDAAgD,EAC5D,OAAO,YAAY;AAClB,MAAI;AACF,UAAM,YAAY;AAAA,EACpB,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,IAAM,UAAU,QACb,QAAQ,MAAM,EACd,YAAY,8DAA8D,EAC1E,OAAO,mBAAmB,8CAA8C,MAAM,EAC9E,OAAO,kBAAkB,oBAAoB,UAAU,EACvD,OAAO,gBAAgB,8CAA8C,IAAI,EACzE,OAAO,OAAO,SAAS;AACtB,MAAI;AACF,UAAM,YAAY,IAAI;AAAA,EACxB,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,IAAM,eAAe,QAClB,QAAQ,OAAO,EACf,YAAY,qCAAqC;AAEpD,aACG,QAAQ,OAAO,EACf,YAAY,iEAAiE,EAC7E,OAAO,YAAY;AAClB,MAAI;AAAE,UAAM,sBAAsB;AAAA,EAAG,SAC9B,KAAK;AAAE,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AACrH,CAAC;AAEH,aACG,QAAQ,MAAM,EACd,YAAY,kEAAkE,EAC9E,OAAO,YAAY;AAClB,MAAI;AAAE,UAAM,qBAAqB;AAAA,EAAG,SAC7B,KAAK;AAAE,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AACrH,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,mEAAmE,EAC/E,OAAO,kBAAkB,oBAAoB,UAAU,EACvD,OAAO,OAAO,SAAS;AACtB,MAAI;AAAE,UAAM,kBAAkB,IAAI;AAAA,EAAG,SAC9B,KAAK;AAAE,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AACrH,CAAC;AAEH,QACG,QAAQ,SAAS,EACjB,YAAY,sCAAsC,EAClD,OAAO,eAAe,0BAA0B,IAAI,EACpD,OAAO,kBAAkB,oBAAoB,UAAU,EACvD,OAAO,OAAO,SAAS;AACtB,MAAI;AAAE,UAAM,mBAAmB,IAAI;AAAA,EAAG,SAC/B,KAAK;AAAE,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AACrH,CAAC;AAEH,QACG,QAAQ,YAAY,EACpB,YAAY,mCAAmC,EAC/C,OAAO,mBAAmB,4DAA4D,EACtF,OAAO,iBAAiB,0BAA0B,EAClD,OAAO,eAAe,kCAAkC,EACxD,OAAO,kBAAkB,oBAAoB,UAAU,EACvD,OAAO,OAAO,SAAS;AACtB,MAAI;AAAE,UAAM,sBAAsB,IAAI;AAAA,EAAG,SAClC,KAAK;AAAE,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AAAG,YAAQ,KAAK,CAAC;AAAA,EAAG;AACrH,CAAC;AAEH,IAAM,YAAY,QACf,QAAQ,QAAQ,EAChB,YAAY,wDAAwD;AAEvE,UACG,QAAQ,MAAM,EACd,YAAY,qDAAqD,EACjE,OAAO,YAAY;AAClB,MAAI;AACF,UAAM,kBAAkB;AAAA,EAC1B,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,UACG,QAAQ,MAAM,EACd,YAAY,4CAA4C,EACxD,OAAO,YAAY;AAClB,MAAI;AACF,UAAM,kBAAkB;AAAA,EAC1B,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,QAAQ,EAChB,YAAY,iFAA4E,EACxF,OAAO,mBAAmB,wCAAwC,EAClE;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,OAAO,YAAkD;AAC/D,MAAI;AACF,UAAM,cAAc,QAAQ,IAAI,GAAG,OAAO;AAAA,EAC5C,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AAEH,QACG,QAAQ,MAAM,EACd,YAAY,uEAAuE,EACnF,OAAO,UAAU,mDAAmD,EACpE;AAAA,EAAO,OAAO,YACb,mBAAmB,MAAM,YAAY,EAAE,MAAM,QAAQ,KAAK,CAAC,CAAC;AAC9D;AAEF,QACG,QAAQ,kBAAkB,EAC1B;AAAA,EACC;AAGF,EACC,OAAO,qBAAqB,gDAAgD,EAC5E,OAAO,qBAAqB,mDAAmD,EAC/E,OAAO,UAAU,uDAAuD,EACxE,OAAO,eAAe,kEAAkE,EACxF,OAAO,mBAAmB,uCAAuC,EACjE,OAAO,oBAAoB,yCAAyC,EACpE,OAAO,eAAe,uCAAuC,EAC7D,OAAO,kBAAkB,iEAAiE,EAC1F;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EAAO,OAAO,QAAgB,YAC7B,mBAAmB,MAAM,eAAe,QAAQ,OAAO,CAAC;AAC1D;AAEF,QACG,QAAQ,qBAAqB,EAC7B;AAAA,EACC;AACF,EACC,OAAO,YAAY,sDAAsD,EACzE,OAAO,aAAa,oDAAoD,EACxE;AAAA,EACC;AAAA,EACA;AACF,EACC;AAAA,EACC;AAAA,EACA;AACF,EACC,OAAO,UAAU,+EAA+E,EAChG;AAAA,EAAO,OAAOA,SAAgB,YAC7B,mBAAmB,MAAM,kBAAkBA,SAAQ,OAAO,CAAC;AAC7D;AAEF,QACG,QAAQ,OAAO,EACf,YAAY,kEAAkE,EAC9E,OAAO,gBAAgB,0BAA0B,QAAQ,IAAI,CAAC,EAC9D,OAAO,OAAO,YAA8B;AAC3C,MAAI;AAGF,UAAM,eAAe,EAAE,MAAM,QAAQ,MAAM,QAAQ,CAAC;AAAA,EACtD,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF,CAAC;AASH,SAAS,kBAAwB;AAC/B,MAAI;AACF,4BAAwB;AAAA,EAC1B,SAAS,KAAK;AACZ,UAAM,UAAU,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC/D,YAAQ,MAAM,yBAAyB,OAAO,EAAE;AAChD,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAWA,eAAe,mBAAmB,MAA4C;AAC5E,MAAI;AACF,UAAM,OAAO,MAAM,KAAK;AACxB,QAAI,SAAS,EAAG,SAAQ,WAAW;AAAA,EACrC,SAAS,KAAK;AACZ,YAAQ,MAAM,yBAAyB,eAAe,QAAQ,IAAI,UAAU,GAAG,EAAE;AACjF,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,QAAQ,MAAM;","names":["createRequire","path","readFile","mkdir","readFile","writeFile","path","source","candidatePath","path","readFile","mkdir","writeFile","readFile","path","path","path","readFile","readFile","info","readFile","readFile","path","Anthropic","path","Anthropic","path","readFile","readFile","path","source","line","path","readFile","source","TIMESTAMP_PATTERN","readFile","path","path","readFile","path","line","path","readFile","readFile","path","path","readFile","readFile","path","path","readFile","path","info","mkdir","readFile","path","mkdir","path","readFile","lint","readFile","path","readFile","path","readFile","path","path","path","path","readFile","status","readdir","readFile","realpath","path","realpath","path","readFile","readdir","path","CHAR_OPEN_BRACKET","buildParser","path","status","readdir","readFile","realpath","path","readdir","rename","writeFile","mkdir","path","path","readdir","mkdir","rename","writeFile","readFile","writeFile","rename","mkdir","existsSync","path","path","existsSync","readFile","mkdir","writeFile","rename","path","realpath","readdir","readFile","existsSync","readFile","path","path","createHash","readFile","readdir","path","readFile","createHash","path","listSourceFiles","status","readdir","path","error","readFile","unlink","mkdir","path","path","mkdir","unlink","readFile","existsSync","readFile","path","yaml","path","existsSync","yaml","readFile","yaml","resolvePageKind","yaml","path","path","readdir","readFile","path","existsSync","path","existsSync","readdir","readFile","readdir","path","path","readdir","line","readdir","path","path","readdir","readFile","readdir","existsSync","path","createHash","createHash","path","existsSync","readFile","readdir","readdir","readFile","existsSync","path","WIKILINK_PATTERN","existsSync","readdir","path","readFile","line","resolvePageKind","readdir","path","path","readdir","path","readFile","error","existsSync","existsSync","path","rawPages","reasoning","path","top","existsSync","existsSync","path","path","existsSync","path","realpath","path","path","createHash","readFile","mkdir","existsSync","path","path","PROSE_LEAD_RE","createHash","readFile","existsSync","line","mkdir","readdir","appendFile","mkdir","readFile","existsSync","path","path","existsSync","readdir","mkdir","appendFile","readFile","line","readFile","existsSync","path","yaml","header","divider","unlink","readFile","existsSync","path","CACHE_FILE","line","path","header","source","require","path","existsSync","mkdir","writeFile","path","existsSync","mkdir","path","writeFile","path","path","source","safelyUpdateEmbeddings","stat","readdir","readFile","path","lint","path","readFile","isNonNegativeInteger","stat","readdir","path","source","ingest","compile","path","safeCountCandidates","renderHuman","path","path","path","containsParentSegment","isInside","MAX_NORMALIZED_SCORE","source","buildProject","path","line","McpServer","path","PAGE_DIRS","source","path","path","readdir","path","readdir","version","McpServer","require","createRequire","source"]}
|