llm-wiki-compiler 0.1.0 → 0.1.1
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/README.md +4 -2
- package/dist/cli.js +11 -24
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,6 +4,8 @@ Compile raw sources into an interlinked markdown wiki.
|
|
|
4
4
|
|
|
5
5
|
Inspired by Karpathy's [LLM Wiki](https://gist.github.com/karpathy/442a6bf555914893e9891c11519de94f) pattern: instead of re-discovering knowledge at query time, compile it once into a persistent, browsable artifact that compounds over time.
|
|
6
6
|
|
|
7
|
+

|
|
8
|
+
|
|
7
9
|
## Who this is for
|
|
8
10
|
|
|
9
11
|
- **AI researchers and engineers** building persistent knowledge from papers, docs, and notes
|
|
@@ -97,9 +99,9 @@ Try it on any article or document:
|
|
|
97
99
|
|
|
98
100
|
```bash
|
|
99
101
|
mkdir my-wiki && cd my-wiki
|
|
100
|
-
llmwiki ingest https://en.wikipedia.org/wiki/
|
|
102
|
+
llmwiki ingest https://en.wikipedia.org/wiki/Andrej_Karpathy
|
|
101
103
|
llmwiki compile
|
|
102
|
-
llmwiki query "
|
|
104
|
+
llmwiki query "What terms did Andrej coin?"
|
|
103
105
|
```
|
|
104
106
|
|
|
105
107
|
See `examples/basic/` in the repo for pre-generated output you can browse without an API key.
|
package/dist/cli.js
CHANGED
|
@@ -82,7 +82,6 @@ var DIM = "\x1B[2m";
|
|
|
82
82
|
var GREEN = "\x1B[32m";
|
|
83
83
|
var YELLOW = "\x1B[33m";
|
|
84
84
|
var BLUE = "\x1B[34m";
|
|
85
|
-
var MAGENTA = "\x1B[35m";
|
|
86
85
|
var CYAN = "\x1B[36m";
|
|
87
86
|
var RED = "\x1B[31m";
|
|
88
87
|
function bold(text) {
|
|
@@ -103,9 +102,6 @@ function info(text) {
|
|
|
103
102
|
function error(text) {
|
|
104
103
|
return `${RED}${text}${RESET}`;
|
|
105
104
|
}
|
|
106
|
-
function concept(text) {
|
|
107
|
-
return `${MAGENTA}${BOLD}${text}${RESET}`;
|
|
108
|
-
}
|
|
109
105
|
function source(text) {
|
|
110
106
|
return `${CYAN}${text}${RESET}`;
|
|
111
107
|
}
|
|
@@ -526,7 +522,7 @@ ${existingIndex}` : "\n\nNo existing wiki pages yet.";
|
|
|
526
522
|
sourceContent
|
|
527
523
|
].join("\n");
|
|
528
524
|
}
|
|
529
|
-
function buildPagePrompt(
|
|
525
|
+
function buildPagePrompt(concept, sourceContent, existingPage, relatedPages) {
|
|
530
526
|
const existingSection = existingPage ? `
|
|
531
527
|
|
|
532
528
|
Existing page to update:
|
|
@@ -538,7 +534,7 @@ Related wiki pages for cross-referencing:
|
|
|
538
534
|
|
|
539
535
|
${relatedPages}` : "";
|
|
540
536
|
return [
|
|
541
|
-
`You are a wiki author. Write a clear, well-structured markdown page about "${
|
|
537
|
+
`You are a wiki author. Write a clear, well-structured markdown page about "${concept}".`,
|
|
542
538
|
"Draw facts only from the provided source material.",
|
|
543
539
|
"Include a ## Sources section at the end listing the source document.",
|
|
544
540
|
"Suggest [[wikilinks]] to related concepts where appropriate.",
|
|
@@ -1036,20 +1032,23 @@ function printChangesSummary(changes) {
|
|
|
1036
1032
|
}
|
|
1037
1033
|
}
|
|
1038
1034
|
async function extractForSource(root, sourceFile) {
|
|
1039
|
-
|
|
1035
|
+
status("*", info(`Extracting: ${sourceFile}`));
|
|
1040
1036
|
const sourcePath = path10.join(root, SOURCES_DIR, sourceFile);
|
|
1041
1037
|
const sourceContent = await readFile7(sourcePath, "utf-8");
|
|
1042
1038
|
const existingIndex = await safeReadFile(path10.join(root, INDEX_FILE));
|
|
1043
1039
|
const concepts = await extractConcepts(sourceContent, existingIndex);
|
|
1044
|
-
if (concepts.length > 0)
|
|
1040
|
+
if (concepts.length > 0) {
|
|
1041
|
+
const names = concepts.map((c) => c.concept).join(", ");
|
|
1042
|
+
status("*", dim(` Found ${concepts.length} concepts: ${names}`));
|
|
1043
|
+
}
|
|
1045
1044
|
return { sourceFile, sourcePath, sourceContent, concepts };
|
|
1046
1045
|
}
|
|
1047
1046
|
function mergeExtractions(extractions, frozenSlugs) {
|
|
1048
1047
|
const bySlug = /* @__PURE__ */ new Map();
|
|
1049
1048
|
for (const result of extractions) {
|
|
1050
1049
|
if (result.concepts.length === 0) continue;
|
|
1051
|
-
for (const
|
|
1052
|
-
const slug = slugify(
|
|
1050
|
+
for (const concept of result.concepts) {
|
|
1051
|
+
const slug = slugify(concept.concept);
|
|
1053
1052
|
if (frozenSlugs.has(slug)) continue;
|
|
1054
1053
|
const existing = bySlug.get(slug);
|
|
1055
1054
|
if (existing) {
|
|
@@ -1062,7 +1061,7 @@ ${result.sourceContent}`;
|
|
|
1062
1061
|
} else {
|
|
1063
1062
|
bySlug.set(slug, {
|
|
1064
1063
|
slug,
|
|
1065
|
-
concept
|
|
1064
|
+
concept,
|
|
1066
1065
|
sourceFiles: [result.sourceFile],
|
|
1067
1066
|
combinedContent: `--- SOURCE: ${result.sourceFile} ---
|
|
1068
1067
|
|
|
@@ -1077,7 +1076,6 @@ async function generateMergedPage(root, entry) {
|
|
|
1077
1076
|
const pagePath = path10.join(root, CONCEPTS_DIR, `${entry.slug}.md`);
|
|
1078
1077
|
const existingPage = await safeReadFile(pagePath);
|
|
1079
1078
|
const relatedPages = await loadRelatedPages(root, entry.slug);
|
|
1080
|
-
status(">", info(`Generating: ${entry.concept.concept}`));
|
|
1081
1079
|
const system = buildPagePrompt(
|
|
1082
1080
|
entry.concept.concept,
|
|
1083
1081
|
entry.combinedContent,
|
|
@@ -1088,11 +1086,8 @@ async function generateMergedPage(root, entry) {
|
|
|
1088
1086
|
system,
|
|
1089
1087
|
messages: [
|
|
1090
1088
|
{ role: "user", content: `Write the wiki page for "${entry.concept.concept}".` }
|
|
1091
|
-
]
|
|
1092
|
-
stream: true,
|
|
1093
|
-
onToken: (token) => process.stdout.write(dim(token))
|
|
1089
|
+
]
|
|
1094
1090
|
});
|
|
1095
|
-
process.stdout.write("\n");
|
|
1096
1091
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1097
1092
|
const existing = existingPage ? parseFrontmatter(existingPage) : null;
|
|
1098
1093
|
const createdAt = existing?.meta.createdAt && typeof existing.meta.createdAt === "string" ? existing.meta.createdAt : now;
|
|
@@ -1110,7 +1105,6 @@ ${pageBody}
|
|
|
1110
1105
|
await writePageIfValid(pagePath, fullPage, entry.concept.concept);
|
|
1111
1106
|
}
|
|
1112
1107
|
async function extractConcepts(sourceContent, existingIndex) {
|
|
1113
|
-
status("*", info("Extracting concepts..."));
|
|
1114
1108
|
const system = buildExtractionPrompt(sourceContent, existingIndex);
|
|
1115
1109
|
const rawOutput = await callClaude({
|
|
1116
1110
|
system,
|
|
@@ -1119,12 +1113,6 @@ async function extractConcepts(sourceContent, existingIndex) {
|
|
|
1119
1113
|
});
|
|
1120
1114
|
return parseConcepts(rawOutput);
|
|
1121
1115
|
}
|
|
1122
|
-
function logExtractedConcepts(concepts) {
|
|
1123
|
-
for (const c of concepts) {
|
|
1124
|
-
const tag = c.is_new ? success("NEW") : dim("update");
|
|
1125
|
-
status("*", `${concept(c.concept)} [${tag}] \u2014 ${c.summary}`);
|
|
1126
|
-
}
|
|
1127
|
-
}
|
|
1128
1116
|
async function loadRelatedPages(root, excludeSlug) {
|
|
1129
1117
|
const conceptsPath = path10.join(root, CONCEPTS_DIR);
|
|
1130
1118
|
let files;
|
|
@@ -1150,7 +1138,6 @@ async function writePageIfValid(pagePath, content, conceptTitle) {
|
|
|
1150
1138
|
return;
|
|
1151
1139
|
}
|
|
1152
1140
|
await atomicWrite(pagePath, content);
|
|
1153
|
-
status("+", success(`Wrote: ${conceptTitle}`));
|
|
1154
1141
|
}
|
|
1155
1142
|
async function persistSourceState(root, sourcePath, sourceFile, concepts) {
|
|
1156
1143
|
const hash = await hashFile(sourcePath);
|
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/constants.ts","../src/utils/output.ts","../src/ingest/web.ts","../src/ingest/file.ts","../src/commands/compile.ts","../src/compiler/index.ts","../src/utils/state.ts","../src/utils/llm.ts","../src/utils/lock.ts","../src/compiler/prompts.ts","../src/compiler/hasher.ts","../src/compiler/deps.ts","../src/compiler/orphan.ts","../src/compiler/resolver.ts","../src/compiler/indexgen.ts","../src/commands/query.ts","../src/commands/watch.ts"],"sourcesContent":["/**\n * CLI entry point for llmwiki — the knowledge compiler.\n *\n * Registers all commands (ingest, compile, query, watch) via Commander.\n * Validates ANTHROPIC_API_KEY for commands that need LLM access.\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 compileCommand from \"./commands/compile.js\";\nimport queryCommand from \"./commands/query.js\";\nimport watchCommand from \"./commands/watch.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(\"compile\")\n .description(\"Compile sources/ into an interlinked wiki\")\n .action(async () => {\n requireApiKey();\n try {\n await compileCommand();\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 .action(async (question: string, options: { save?: boolean }) => {\n requireApiKey();\n try {\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\nprogram\n .command(\"watch\")\n .description(\"Watch sources/ and auto-recompile on changes\")\n .action(async () => {\n requireApiKey();\n try {\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.parse();\n\n/** Exit with a helpful message if ANTHROPIC_API_KEY is missing. */\nfunction requireApiKey(): void {\n if (!process.env.ANTHROPIC_API_KEY) {\n console.error(\n \"\\x1b[31mError:\\x1b[0m ANTHROPIC_API_KEY environment variable is required.\\n\" +\n \" Set it with: export ANTHROPIC_API_KEY=sk-ant-...\\n\" +\n \" Get a key at: https://console.anthropic.com/settings/keys\",\n );\n process.exit(1);\n }\n}\n","/**\n * Commander action for `llmwiki ingest <source>`.\n * Detects whether the source is a URL or local file, delegates to the\n * appropriate ingestion module, and saves the result as a markdown file\n * with YAML frontmatter in the sources/ directory.\n */\n\nimport path from \"path\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { slugify, buildFrontmatter } from \"../utils/markdown.js\";\nimport { MAX_SOURCE_CHARS, MIN_SOURCE_CHARS, SOURCES_DIR } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\nimport ingestWeb from \"../ingest/web.js\";\nimport ingestFile from \"../ingest/file.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/** 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/** Build the full markdown document with frontmatter. */\nexport function buildDocument(\n title: string,\n source: string,\n result: TruncateResult,\n): string {\n const meta: Record<string, unknown> = {\n title,\n source,\n ingestedAt: new Date().toISOString(),\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/** Write the ingested document to the sources/ directory. */\nasync function saveSource(title: string, document: string): Promise<string> {\n const filename = `${slugify(title)}.md`;\n const destPath = path.join(SOURCES_DIR, filename);\n\n await mkdir(SOURCES_DIR, { recursive: true });\n await writeFile(destPath, document, \"utf-8\");\n\n return destPath;\n}\n\n/**\n * Ingest a source (URL or local file) and save it to the sources/ directory.\n * @param source - A URL (http/https) or a local file path (.md or .txt).\n */\nexport default async function ingest(source: string): Promise<void> {\n output.status(\"*\", output.info(`Ingesting: ${source}`));\n\n const { title, content } = isUrl(source)\n ? await ingestWeb(source)\n : await ingestFile(source);\n\n const result = enforceCharLimit(content);\n enforceMinContent(result.content);\n const document = buildDocument(title, source, result);\n const savedPath = await saveSource(title, document);\n\n output.status(\n \"+\",\n output.success(`Saved ${output.bold(title)} → ${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\";\n\n/** Convert a human-readable concept title to a filename slug. */\nexport function slugify(title: string): string {\n return title\n .toLowerCase()\n .replace(/['']/g, \"\")\n .replace(/[^\\w\\s-]/g, \"\")\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 match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n if (!match) {\n return { meta: {}, body: content };\n }\n\n let meta: Record<string, unknown> = {};\n try {\n const parsed = yaml.load(match[1]);\n if (parsed && typeof parsed === \"object\") {\n meta = parsed as Record<string, unknown>;\n }\n } catch {\n // Malformed YAML — return empty meta so callers degrade gracefully.\n }\n return { meta, body: match[2] };\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/** 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/**\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 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/** 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/** Maximum related pages fed as context during page generation. */\nexport const MAX_RELATED_PAGES = 5;\n\n/** API retry configuration. */\nexport const RETRY_COUNT = 3;\nexport const RETRY_BASE_MS = 1000;\nexport const RETRY_MULTIPLIER = 4;\n\n/** Claude model to use for all LLM calls. */\nexport const MODEL = \"claude-sonnet-4-20250514\";\n\n/** Directory names relative to the project root. */\nexport const SOURCES_DIR = \"sources\";\nexport const WIKI_DIR = \"wiki\";\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\";\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 concept(text: string): string {\n return `${MAGENTA}${BOLD}${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\";\n\nconst SUPPORTED_EXTENSIONS = new Set([\".md\", \".txt\"]);\n\ninterface FileIngestResult {\n title: string;\n content: string;\n}\n\n/** Derive a human-readable title from a filename (without extension). */\nfunction titleFromFilename(filePath: string): string {\n const basename = path.basename(filePath, path.extname(filePath));\n return basename.replace(/[-_]+/g, \" \").trim();\n}\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<FileIngestResult> {\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 * 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\";\n\n/**\n * Run the compile command from the current working directory.\n * Exits early if no sources directory exists yet.\n */\nexport default async function compileCommand(): 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());\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, readdir } from \"fs/promises\";\nimport path from \"path\";\nimport { readState, updateSourceState } from \"../utils/state.js\";\nimport {\n atomicWrite,\n safeReadFile,\n validateWikiPage,\n slugify,\n buildFrontmatter,\n parseFrontmatter,\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 buildPagePrompt,\n parseConcepts,\n} from \"./prompts.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 * 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 { ExtractedConcept, SourceState, SourceChange } from \"../utils/types.js\";\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 */\nexport async function compile(root: string): Promise<void> {\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 }\n\n try {\n await runCompilePipeline(root);\n } finally {\n await releaseLock(root);\n }\n}\n\n/** Inner pipeline, runs under lock protection. */\nasync function runCompilePipeline(root: string): Promise<void> {\n const state = await readState(root);\n const changes = await detectChanges(root, state);\n\n // Semantic dependency tracking: find unchanged sources that share concepts\n // with changed sources and need recompilation to preserve cross-source content\n const affectedFiles = findAffectedSources(state, changes);\n for (const file of affectedFiles) {\n output.status(\"~\", output.info(`${file} [affected by shared concept]`));\n changes.push({ file, status: \"changed\" });\n }\n\n const toCompile = changes.filter((c) => c.status === \"new\" || c.status === \"changed\");\n const deleted = changes.filter((c) => c.status === \"deleted\");\n const unchanged = changes.filter((c) => c.status === \"unchanged\");\n\n if (toCompile.length === 0 && deleted.length === 0) {\n output.status(\"✓\", output.success(\"Nothing to compile — all sources up to date.\"));\n return;\n }\n\n printChangesSummary(changes);\n\n // Handle deleted sources: mark their wiki pages as orphaned\n for (const del of deleted) {\n await markOrphaned(root, del.file, state);\n }\n\n // Frozen slugs: shared concepts that lost a contributor (deleted source).\n const frozenSlugs = findFrozenSlugs(state, changes);\n for (const slug of frozenSlugs) {\n output.status(\"i\", output.dim(`Frozen: ${slug} (shared with deleted source)`));\n }\n\n // Phase 1: Extract concepts for ALL sources before generating any pages.\n // This eliminates order-dependence: we know which extractions failed\n // before committing any page writes.\n const extractions: ExtractionResult[] = [];\n for (const change of toCompile) {\n extractions.push(await extractForSource(root, change.file));\n }\n\n // Post-extraction dependency check: new sources may extract concepts\n // that existing unchanged sources already own. findAffectedSources\n // couldn't detect this earlier because new sources had no state entry.\n const lateAffected = findLateAffectedSources(extractions, state, changes);\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 // Freeze concepts from failed extractions before page generation.\n await freezeFailedExtractions(root, extractions, frozenSlugs);\n\n // Phase 2: Merge shared concepts across sources, then generate pages.\n // When multiple sources extract the same concept, combine their content\n // so the LLM sees all contributing material in a single generation call.\n const merged = mergeExtractions(extractions, frozenSlugs);\n const limit = pLimit(COMPILE_CONCURRENCY);\n const pageResults = await Promise.all(\n merged.map((entry) => limit(async () => {\n await generateMergedPage(root, entry);\n return entry;\n })),\n );\n const allChangedSlugs = pageResults.map((e) => e.slug);\n const allNewSlugs = pageResults\n .filter((e) => e.concept.is_new)\n .map((e) => e.slug);\n\n // Persist state for each successfully extracted source.\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 // Orphan frozen pages that lost all owners after recompilation.\n if (frozenSlugs.size > 0) {\n await orphanUnownedFrozenPages(root, frozenSlugs);\n }\n\n // Persist frozen slugs: unfreeze any that are now safe to regenerate\n // (all current owners compiled and extracted them), keep the rest.\n await persistFrozenSlugs(root, frozenSlugs, extractions);\n\n // Interlink resolution: outbound on changed, inbound for new titles\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\n output.header(\"Compilation complete\");\n output.status(\"✓\", output.success(\n `${toCompile.length} compiled, ${unchanged.length} skipped, ${deleted.length} deleted`,\n ));\n if (toCompile.length > 0) {\n output.status(\"→\", output.dim('Next: llmwiki query \"your question here\"'));\n }\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.header(`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) logExtractedConcepts(concepts);\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 * 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 */\nfunction mergeExtractions(\n extractions: ExtractionResult[],\n frozenSlugs: Set<string>,\n): MergedConcept[] {\n const bySlug = new Map<string, MergedConcept>();\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.sourceFiles.push(result.sourceFile);\n existing.combinedContent += `\\n\\n--- SOURCE: ${result.sourceFile} ---\\n\\n${result.sourceContent}`;\n } else {\n bySlug.set(slug, {\n slug,\n concept,\n sourceFiles: [result.sourceFile],\n combinedContent: `--- SOURCE: ${result.sourceFile} ---\\n\\n${result.sourceContent}`,\n });\n }\n }\n }\n\n return Array.from(bySlug.values());\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.\n */\nasync function generateMergedPage(\n root: string,\n entry: MergedConcept,\n): Promise<void> {\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 output.status(\">\", output.info(`Generating: ${entry.concept.concept}`));\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 stream: true,\n onToken: (token) => process.stdout.write(output.dim(token)),\n });\n\n process.stdout.write(\"\\n\");\n\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 frontmatter = buildFrontmatter({\n title: entry.concept.concept,\n summary: entry.concept.summary,\n sources: entry.sourceFiles,\n createdAt,\n updatedAt: now,\n });\n const fullPage = `${frontmatter}\\n\\n${pageBody}\\n`;\n await writePageIfValid(pagePath, fullPage, entry.concept.concept);\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 output.status(\"*\", output.info(\"Extracting concepts...\"));\n\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/** Log the list of extracted concepts to the terminal. */\nfunction logExtractedConcepts(\n concepts: ReturnType<typeof parseConcepts>,\n): void {\n for (const c of concepts) {\n const tag = c.is_new ? output.success(\"NEW\") : output.dim(\"update\");\n output.status(\"*\", `${output.concept(c.concept)} [${tag}] — ${c.summary}`);\n }\n}\n\n\n/**\n * Load related wiki pages to provide cross-referencing context.\n * Returns concatenated content of up to 5 existing concept pages.\n * @param root - Project root directory.\n * @param excludeSlug - Slug of the current page to exclude.\n * @returns Concatenated related page contents.\n */\nasync function loadRelatedPages(\n root: string,\n excludeSlug: string,\n): 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, 5);\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/**\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<void> {\n if (!validateWikiPage(content)) {\n output.status(\"!\", output.warn(`Invalid page for \"${conceptTitle}\" — skipped.`));\n return;\n }\n\n await atomicWrite(pagePath, content);\n output.status(\"+\", output.success(`Wrote: ${conceptTitle}`));\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 * 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 * Shared LLM helper wrapping the Anthropic SDK.\n * Provides callClaude() for both streaming and tool_use calls,\n * with retry logic and exponential backoff.\n */\n\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { MODEL, RETRY_COUNT, RETRY_BASE_MS, RETRY_MULTIPLIER } from \"./constants.js\";\n\nlet client: Anthropic | null = null;\n\n/** Get or create the Anthropic client singleton. */\nexport function getClient(): Anthropic {\n if (!client) {\n client = new Anthropic();\n }\n return client;\n}\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: Anthropic.MessageParam[];\n tools?: Anthropic.Tool[];\n maxTokens?: number;\n stream?: boolean;\n onToken?: (text: string) => void;\n}\n\n/**\n * Call Claude with retry logic. Supports both streaming and non-streaming modes.\n * For tool_use calls, returns the parsed tool input. For streaming calls, invokes\n * onToken for each text chunk and returns the full assembled text.\n */\nexport async function callClaude(options: CallClaudeOptions): Promise<string> {\n const { system, messages, tools, maxTokens = 4096, stream = false, onToken } = options;\n const anthropic = getClient();\n\n for (let attempt = 0; attempt <= RETRY_COUNT; attempt++) {\n try {\n if (stream) {\n return await callClaudeStreaming(anthropic, system, messages, maxTokens, onToken);\n }\n\n if (tools && tools.length > 0) {\n return await callClaudeToolUse(anthropic, system, messages, tools, maxTokens);\n }\n\n return await callClaudeBasic(anthropic, 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\nasync function callClaudeStreaming(\n anthropic: Anthropic,\n system: string,\n messages: Anthropic.MessageParam[],\n maxTokens: number,\n onToken?: (text: string) => void,\n): Promise<string> {\n const stream = anthropic.messages.stream({\n model: MODEL,\n max_tokens: maxTokens,\n system,\n messages,\n });\n\n let fullText = \"\";\n\n for await (const event of stream) {\n if (\n event.type === \"content_block_delta\" &&\n event.delta.type === \"text_delta\"\n ) {\n fullText += event.delta.text;\n onToken?.(event.delta.text);\n }\n }\n\n return fullText;\n}\n\nasync function callClaudeToolUse(\n anthropic: Anthropic,\n system: string,\n messages: Anthropic.MessageParam[],\n tools: Anthropic.Tool[],\n maxTokens: number,\n): Promise<string> {\n const response = await anthropic.messages.create({\n model: MODEL,\n max_tokens: maxTokens,\n system,\n messages,\n tools,\n });\n\n const toolBlock = response.content.find((block) => block.type === \"tool_use\");\n if (toolBlock && toolBlock.type === \"tool_use\") {\n return JSON.stringify(toolBlock.input);\n }\n\n // Fallback: return text content if no tool use\n const textBlock = response.content.find((block) => block.type === \"text\");\n if (textBlock && textBlock.type === \"text\") {\n return textBlock.text;\n }\n\n return \"\";\n}\n\nasync function callClaudeBasic(\n anthropic: Anthropic,\n system: string,\n messages: Anthropic.MessageParam[],\n maxTokens: number,\n): Promise<string> {\n const response = await anthropic.messages.create({\n model: MODEL,\n max_tokens: maxTokens,\n system,\n messages,\n });\n\n const textBlock = response.content.find((block) => block.type === \"text\");\n if (textBlock && textBlock.type === \"text\") {\n return textBlock.text;\n }\n\n return \"\";\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 * 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 { ExtractedConcept } from \"../utils/types.js\";\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 },\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 \"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 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 `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 existingSection,\n relatedSection,\n \"\\n\\n--- SOURCE MATERIAL ---\\n\\n\",\n sourceContent,\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: ExtractedConcept[] = parsed.concepts ?? [];\n return concepts.filter(\n (c) =>\n typeof c.concept === \"string\" &&\n typeof c.summary === \"string\" &&\n typeof c.is_new === \"boolean\",\n );\n } catch {\n return [];\n }\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 * 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/**\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 = new Set(\n directChanges\n .filter((c) => c.status === \"new\" || c.status === \"changed\")\n .map((c) => c.file),\n );\n\n // Deleted files must never be enqueued for recompilation — their source\n // no longer exists on disk, so compileSource would fail reading it.\n const deletedFiles = new Set(\n directChanges\n .filter((c) => c.status === \"deleted\")\n .map((c) => c.file),\n );\n\n const conceptMap = buildConceptToSourcesMap(state.sources);\n const affected = new Set<string>();\n\n for (const changedFile of changedFiles) {\n const sourceEntry = state.sources[changedFile];\n if (!sourceEntry) continue;\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 skip = changedFiles.has(contributor)\n || deletedFiles.has(contributor)\n || affected.has(contributor);\n if (!skip) {\n affected.add(contributor);\n }\n }\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 * 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 = new Set(\n allChanges\n .filter((c) => c.status === \"new\" || c.status === \"changed\")\n .map((c) => c.file),\n );\n const deletedFiles = new Set(\n allChanges.filter((c) => c.status === \"deleted\").map((c) => c.file),\n );\n const conceptMap = buildConceptToSourcesMap(state.sources);\n\n // Collect concept slugs from ALL extractions that weren't in the source's\n // previous concept list. These are \"newly gained\" concepts that\n // findAffectedSources couldn't have matched pre-extraction.\n const freshSlugs = new Set<string>();\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)) {\n freshSlugs.add(slug);\n }\n }\n }\n\n // Find unchanged sources that own any of those freshly gained slugs.\n const affected = new Set<string>();\n for (const slug of freshSlugs) {\n const owners = conceptMap.get(slug);\n if (!owners) continue;\n for (const owner of owners) {\n if (!compilingFiles.has(owner) && !deletedFiles.has(owner)) {\n affected.add(owner);\n }\n }\n }\n\n return Array.from(affected);\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 [[wikilinks]]. Obsidian-compatible format using\n * display titles, not slugs.\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 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/**\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\n for (const page of titles) {\n if (page.title.toLowerCase() === selfTitle.toLowerCase()) continue;\n\n const escaped = page.title.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regex = new RegExp(escaped, \"gi\");\n let match;\n\n // Process matches in reverse to preserve positions\n const matches: { start: number; end: number }[] = [];\n while ((match = regex.exec(result)) !== null) {\n matches.push({ start: match.index, end: match.index + match[0].length });\n }\n\n for (const m of matches.reverse()) {\n if (isInsideWikilink(result, m.start)) continue;\n if (!isWordBoundary(result, m.start, m.end)) continue;\n\n result =\n result.slice(0, m.start) +\n `[[${page.title}]]` +\n 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/**\n * Scan the concepts directory and extract page summaries from frontmatter.\n * @param conceptsPath - Absolute path to wiki/concepts/.\n * @returns Array of page summary objects.\n */\nasync function collectPageSummaries(\n conceptsPath: string,\n): Promise<PageSummary[]> {\n let files: string[];\n\n try {\n files = await readdir(conceptsPath);\n } catch {\n return [];\n }\n\n const pages: PageSummary[] = [];\n\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const content = await safeReadFile(path.join(conceptsPath, file));\n const { meta } = parseFrontmatter(content);\n if (meta.title && typeof meta.title === \"string\" && !meta.orphaned) {\n pages.push({\n title: meta.title,\n slug: file.replace(/\\.md$/, \"\"),\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n });\n }\n }\n\n return pages;\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.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.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 * 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 Anthropic from \"@anthropic-ai/sdk\";\nimport { callClaude } from \"../utils/llm.js\";\nimport { atomicWrite, safeReadFile, slugify, buildFrontmatter, parseFrontmatter } from \"../utils/markdown.js\";\nimport { generateIndex } from \"../compiler/indexgen.js\";\nimport * as output from \"../utils/output.js\";\nimport { QUERY_PAGE_LIMIT, INDEX_FILE, CONCEPTS_DIR, QUERIES_DIR } from \"../utils/constants.js\";\n\n/** Directories to search when loading selected pages, in priority order. */\nconst PAGE_DIRS = [CONCEPTS_DIR, QUERIES_DIR];\n\n/** Anthropic tool schema for page selection. */\nconst PAGE_SELECTION_TOOL: Anthropic.Tool = {\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 */\nasync 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/**\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/**\n * Stream an answer from Claude using the loaded wiki pages as context.\n * @param question - The user's natural language question.\n * @param pagesContent - Combined content of the selected wiki pages.\n * @returns The full answer text after streaming completes.\n */\nasync function streamAnswer(question: string, pagesContent: string): Promise<string> {\n const systemPrompt =\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 const userMessage = `Question: ${question}\\n\\nRelevant wiki pages:\\n${pagesContent}`;\n\n const answer = await callClaude({\n system: systemPrompt,\n messages: [{ role: \"user\", content: userMessage }],\n stream: true,\n onToken: (text: string) => process.stdout.write(text),\n });\n\n // Ensure terminal output ends on a new line after streaming\n process.stdout.write(\"\\n\");\n return answer;\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<void> {\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\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 },\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 // Step 1: Select relevant pages\n output.header(\"Selecting relevant pages\");\n\n const indexContent = await safeReadFile(path.join(root, INDEX_FILE));\n const { pages: rawPages, reasoning } = await selectPages(question, indexContent);\n const pages = rawPages.map((p) => slugify(p));\n\n output.status(\"i\", output.dim(`Reasoning: ${reasoning}`));\n output.status(\"*\", output.info(`Selected ${pages.length} page(s): ${rawPages.join(\", \")}`));\n\n // Step 2: Load pages and stream the answer\n output.header(\"Generating answer\");\n\n const pagesContent = await loadSelectedPages(root, pages);\n\n if (!pagesContent) {\n output.status(\"!\", output.error(\"No matching pages found. Try refining your question.\"));\n return;\n }\n\n const answer = await streamAnswer(question, pagesContent);\n\n // Optional: save the answer as a query page\n if (options.save) {\n await saveQueryPage(root, question, answer);\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 * 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"],"mappings":"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","names":["path","mkdir","writeFile","readFile","path","source","path","mkdir","writeFile","existsSync","readFile","readdir","path","readFile","writeFile","rename","mkdir","path","path","readFile","mkdir","writeFile","rename","error","readFile","mkdir","path","path","mkdir","readFile","concept","readFile","path","readFile","path","status","path","path","readdir","readFile","path","existsSync","path","existsSync","readdir","readFile","readdir","path","path","readdir","path","readFile","concept","readdir","existsSync","existsSync","path","path","existsSync","existsSync","path","path","existsSync","require","source"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli.ts","../src/commands/ingest.ts","../src/utils/markdown.ts","../src/utils/constants.ts","../src/utils/output.ts","../src/ingest/web.ts","../src/ingest/file.ts","../src/commands/compile.ts","../src/compiler/index.ts","../src/utils/state.ts","../src/utils/llm.ts","../src/utils/lock.ts","../src/compiler/prompts.ts","../src/compiler/hasher.ts","../src/compiler/deps.ts","../src/compiler/orphan.ts","../src/compiler/resolver.ts","../src/compiler/indexgen.ts","../src/commands/query.ts","../src/commands/watch.ts"],"sourcesContent":["/**\n * CLI entry point for llmwiki — the knowledge compiler.\n *\n * Registers all commands (ingest, compile, query, watch) via Commander.\n * Validates ANTHROPIC_API_KEY for commands that need LLM access.\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 compileCommand from \"./commands/compile.js\";\nimport queryCommand from \"./commands/query.js\";\nimport watchCommand from \"./commands/watch.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(\"compile\")\n .description(\"Compile sources/ into an interlinked wiki\")\n .action(async () => {\n requireApiKey();\n try {\n await compileCommand();\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 .action(async (question: string, options: { save?: boolean }) => {\n requireApiKey();\n try {\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\nprogram\n .command(\"watch\")\n .description(\"Watch sources/ and auto-recompile on changes\")\n .action(async () => {\n requireApiKey();\n try {\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.parse();\n\n/** Exit with a helpful message if ANTHROPIC_API_KEY is missing. */\nfunction requireApiKey(): void {\n if (!process.env.ANTHROPIC_API_KEY) {\n console.error(\n \"\\x1b[31mError:\\x1b[0m ANTHROPIC_API_KEY environment variable is required.\\n\" +\n \" Set it with: export ANTHROPIC_API_KEY=sk-ant-...\\n\" +\n \" Get a key at: https://console.anthropic.com/settings/keys\",\n );\n process.exit(1);\n }\n}\n","/**\n * Commander action for `llmwiki ingest <source>`.\n * Detects whether the source is a URL or local file, delegates to the\n * appropriate ingestion module, and saves the result as a markdown file\n * with YAML frontmatter in the sources/ directory.\n */\n\nimport path from \"path\";\nimport { mkdir, writeFile } from \"fs/promises\";\nimport { slugify, buildFrontmatter } from \"../utils/markdown.js\";\nimport { MAX_SOURCE_CHARS, MIN_SOURCE_CHARS, SOURCES_DIR } from \"../utils/constants.js\";\nimport * as output from \"../utils/output.js\";\nimport ingestWeb from \"../ingest/web.js\";\nimport ingestFile from \"../ingest/file.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/** 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/** Build the full markdown document with frontmatter. */\nexport function buildDocument(\n title: string,\n source: string,\n result: TruncateResult,\n): string {\n const meta: Record<string, unknown> = {\n title,\n source,\n ingestedAt: new Date().toISOString(),\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/** Write the ingested document to the sources/ directory. */\nasync function saveSource(title: string, document: string): Promise<string> {\n const filename = `${slugify(title)}.md`;\n const destPath = path.join(SOURCES_DIR, filename);\n\n await mkdir(SOURCES_DIR, { recursive: true });\n await writeFile(destPath, document, \"utf-8\");\n\n return destPath;\n}\n\n/**\n * Ingest a source (URL or local file) and save it to the sources/ directory.\n * @param source - A URL (http/https) or a local file path (.md or .txt).\n */\nexport default async function ingest(source: string): Promise<void> {\n output.status(\"*\", output.info(`Ingesting: ${source}`));\n\n const { title, content } = isUrl(source)\n ? await ingestWeb(source)\n : await ingestFile(source);\n\n const result = enforceCharLimit(content);\n enforceMinContent(result.content);\n const document = buildDocument(title, source, result);\n const savedPath = await saveSource(title, document);\n\n output.status(\n \"+\",\n output.success(`Saved ${output.bold(title)} → ${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\";\n\n/** Convert a human-readable concept title to a filename slug. */\nexport function slugify(title: string): string {\n return title\n .toLowerCase()\n .replace(/['']/g, \"\")\n .replace(/[^\\w\\s-]/g, \"\")\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 match = content.match(/^---\\n([\\s\\S]*?)\\n---\\n?([\\s\\S]*)$/);\n if (!match) {\n return { meta: {}, body: content };\n }\n\n let meta: Record<string, unknown> = {};\n try {\n const parsed = yaml.load(match[1]);\n if (parsed && typeof parsed === \"object\") {\n meta = parsed as Record<string, unknown>;\n }\n } catch {\n // Malformed YAML — return empty meta so callers degrade gracefully.\n }\n return { meta, body: match[2] };\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/** 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/**\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 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/** 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/** Maximum related pages fed as context during page generation. */\nexport const MAX_RELATED_PAGES = 5;\n\n/** API retry configuration. */\nexport const RETRY_COUNT = 3;\nexport const RETRY_BASE_MS = 1000;\nexport const RETRY_MULTIPLIER = 4;\n\n/** Claude model to use for all LLM calls. */\nexport const MODEL = \"claude-sonnet-4-20250514\";\n\n/** Directory names relative to the project root. */\nexport const SOURCES_DIR = \"sources\";\nexport const WIKI_DIR = \"wiki\";\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\";\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 concept(text: string): string {\n return `${MAGENTA}${BOLD}${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\";\n\nconst SUPPORTED_EXTENSIONS = new Set([\".md\", \".txt\"]);\n\ninterface FileIngestResult {\n title: string;\n content: string;\n}\n\n/** Derive a human-readable title from a filename (without extension). */\nfunction titleFromFilename(filePath: string): string {\n const basename = path.basename(filePath, path.extname(filePath));\n return basename.replace(/[-_]+/g, \" \").trim();\n}\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<FileIngestResult> {\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 * 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\";\n\n/**\n * Run the compile command from the current working directory.\n * Exits early if no sources directory exists yet.\n */\nexport default async function compileCommand(): 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());\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, readdir } from \"fs/promises\";\nimport path from \"path\";\nimport { readState, updateSourceState } from \"../utils/state.js\";\nimport {\n atomicWrite,\n safeReadFile,\n validateWikiPage,\n slugify,\n buildFrontmatter,\n parseFrontmatter,\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 buildPagePrompt,\n parseConcepts,\n} from \"./prompts.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 * 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 { ExtractedConcept, SourceState, SourceChange } from \"../utils/types.js\";\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 */\nexport async function compile(root: string): Promise<void> {\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 }\n\n try {\n await runCompilePipeline(root);\n } finally {\n await releaseLock(root);\n }\n}\n\n/** Inner pipeline, runs under lock protection. */\nasync function runCompilePipeline(root: string): Promise<void> {\n const state = await readState(root);\n const changes = await detectChanges(root, state);\n\n // Semantic dependency tracking: find unchanged sources that share concepts\n // with changed sources and need recompilation to preserve cross-source content\n const affectedFiles = findAffectedSources(state, changes);\n for (const file of affectedFiles) {\n output.status(\"~\", output.info(`${file} [affected by shared concept]`));\n changes.push({ file, status: \"changed\" });\n }\n\n const toCompile = changes.filter((c) => c.status === \"new\" || c.status === \"changed\");\n const deleted = changes.filter((c) => c.status === \"deleted\");\n const unchanged = changes.filter((c) => c.status === \"unchanged\");\n\n if (toCompile.length === 0 && deleted.length === 0) {\n output.status(\"✓\", output.success(\"Nothing to compile — all sources up to date.\"));\n return;\n }\n\n printChangesSummary(changes);\n\n // Handle deleted sources: mark their wiki pages as orphaned\n for (const del of deleted) {\n await markOrphaned(root, del.file, state);\n }\n\n // Frozen slugs: shared concepts that lost a contributor (deleted source).\n const frozenSlugs = findFrozenSlugs(state, changes);\n for (const slug of frozenSlugs) {\n output.status(\"i\", output.dim(`Frozen: ${slug} (shared with deleted source)`));\n }\n\n // Phase 1: Extract concepts for ALL sources before generating any pages.\n // This eliminates order-dependence: we know which extractions failed\n // before committing any page writes.\n const extractions: ExtractionResult[] = [];\n for (const change of toCompile) {\n extractions.push(await extractForSource(root, change.file));\n }\n\n // Post-extraction dependency check: new sources may extract concepts\n // that existing unchanged sources already own. findAffectedSources\n // couldn't detect this earlier because new sources had no state entry.\n const lateAffected = findLateAffectedSources(extractions, state, changes);\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 // Freeze concepts from failed extractions before page generation.\n await freezeFailedExtractions(root, extractions, frozenSlugs);\n\n // Phase 2: Merge shared concepts across sources, then generate pages.\n // When multiple sources extract the same concept, combine their content\n // so the LLM sees all contributing material in a single generation call.\n const merged = mergeExtractions(extractions, frozenSlugs);\n const limit = pLimit(COMPILE_CONCURRENCY);\n const pageResults = await Promise.all(\n merged.map((entry) => limit(async () => {\n await generateMergedPage(root, entry);\n return entry;\n })),\n );\n const allChangedSlugs = pageResults.map((e) => e.slug);\n const allNewSlugs = pageResults\n .filter((e) => e.concept.is_new)\n .map((e) => e.slug);\n\n // Persist state for each successfully extracted source.\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 // Orphan frozen pages that lost all owners after recompilation.\n if (frozenSlugs.size > 0) {\n await orphanUnownedFrozenPages(root, frozenSlugs);\n }\n\n // Persist frozen slugs: unfreeze any that are now safe to regenerate\n // (all current owners compiled and extracted them), keep the rest.\n await persistFrozenSlugs(root, frozenSlugs, extractions);\n\n // Interlink resolution: outbound on changed, inbound for new titles\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\n output.header(\"Compilation complete\");\n output.status(\"✓\", output.success(\n `${toCompile.length} compiled, ${unchanged.length} skipped, ${deleted.length} deleted`,\n ));\n if (toCompile.length > 0) {\n output.status(\"→\", output.dim('Next: llmwiki query \"your question here\"'));\n }\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 * 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 */\nfunction mergeExtractions(\n extractions: ExtractionResult[],\n frozenSlugs: Set<string>,\n): MergedConcept[] {\n const bySlug = new Map<string, MergedConcept>();\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.sourceFiles.push(result.sourceFile);\n existing.combinedContent += `\\n\\n--- SOURCE: ${result.sourceFile} ---\\n\\n${result.sourceContent}`;\n } else {\n bySlug.set(slug, {\n slug,\n concept,\n sourceFiles: [result.sourceFile],\n combinedContent: `--- SOURCE: ${result.sourceFile} ---\\n\\n${result.sourceContent}`,\n });\n }\n }\n }\n\n return Array.from(bySlug.values());\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.\n */\nasync function generateMergedPage(\n root: string,\n entry: MergedConcept,\n): Promise<void> {\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 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 frontmatter = buildFrontmatter({\n title: entry.concept.concept,\n summary: entry.concept.summary,\n sources: entry.sourceFiles,\n createdAt,\n updatedAt: now,\n });\n const fullPage = `${frontmatter}\\n\\n${pageBody}\\n`;\n await writePageIfValid(pagePath, fullPage, entry.concept.concept);\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/**\n * Load related wiki pages to provide cross-referencing context.\n * Returns concatenated content of up to 5 existing concept pages.\n * @param root - Project root directory.\n * @param excludeSlug - Slug of the current page to exclude.\n * @returns Concatenated related page contents.\n */\nasync function loadRelatedPages(\n root: string,\n excludeSlug: string,\n): 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, 5);\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/**\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<void> {\n if (!validateWikiPage(content)) {\n output.status(\"!\", output.warn(`Invalid page for \"${conceptTitle}\" — skipped.`));\n return;\n }\n\n await atomicWrite(pagePath, content);\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 * 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 * Shared LLM helper wrapping the Anthropic SDK.\n * Provides callClaude() for both streaming and tool_use calls,\n * with retry logic and exponential backoff.\n */\n\nimport Anthropic from \"@anthropic-ai/sdk\";\nimport { MODEL, RETRY_COUNT, RETRY_BASE_MS, RETRY_MULTIPLIER } from \"./constants.js\";\n\nlet client: Anthropic | null = null;\n\n/** Get or create the Anthropic client singleton. */\nexport function getClient(): Anthropic {\n if (!client) {\n client = new Anthropic();\n }\n return client;\n}\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: Anthropic.MessageParam[];\n tools?: Anthropic.Tool[];\n maxTokens?: number;\n stream?: boolean;\n onToken?: (text: string) => void;\n}\n\n/**\n * Call Claude with retry logic. Supports both streaming and non-streaming modes.\n * For tool_use calls, returns the parsed tool input. For streaming calls, invokes\n * onToken for each text chunk and returns the full assembled text.\n */\nexport async function callClaude(options: CallClaudeOptions): Promise<string> {\n const { system, messages, tools, maxTokens = 4096, stream = false, onToken } = options;\n const anthropic = getClient();\n\n for (let attempt = 0; attempt <= RETRY_COUNT; attempt++) {\n try {\n if (stream) {\n return await callClaudeStreaming(anthropic, system, messages, maxTokens, onToken);\n }\n\n if (tools && tools.length > 0) {\n return await callClaudeToolUse(anthropic, system, messages, tools, maxTokens);\n }\n\n return await callClaudeBasic(anthropic, 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\nasync function callClaudeStreaming(\n anthropic: Anthropic,\n system: string,\n messages: Anthropic.MessageParam[],\n maxTokens: number,\n onToken?: (text: string) => void,\n): Promise<string> {\n const stream = anthropic.messages.stream({\n model: MODEL,\n max_tokens: maxTokens,\n system,\n messages,\n });\n\n let fullText = \"\";\n\n for await (const event of stream) {\n if (\n event.type === \"content_block_delta\" &&\n event.delta.type === \"text_delta\"\n ) {\n fullText += event.delta.text;\n onToken?.(event.delta.text);\n }\n }\n\n return fullText;\n}\n\nasync function callClaudeToolUse(\n anthropic: Anthropic,\n system: string,\n messages: Anthropic.MessageParam[],\n tools: Anthropic.Tool[],\n maxTokens: number,\n): Promise<string> {\n const response = await anthropic.messages.create({\n model: MODEL,\n max_tokens: maxTokens,\n system,\n messages,\n tools,\n });\n\n const toolBlock = response.content.find((block) => block.type === \"tool_use\");\n if (toolBlock && toolBlock.type === \"tool_use\") {\n return JSON.stringify(toolBlock.input);\n }\n\n // Fallback: return text content if no tool use\n const textBlock = response.content.find((block) => block.type === \"text\");\n if (textBlock && textBlock.type === \"text\") {\n return textBlock.text;\n }\n\n return \"\";\n}\n\nasync function callClaudeBasic(\n anthropic: Anthropic,\n system: string,\n messages: Anthropic.MessageParam[],\n maxTokens: number,\n): Promise<string> {\n const response = await anthropic.messages.create({\n model: MODEL,\n max_tokens: maxTokens,\n system,\n messages,\n });\n\n const textBlock = response.content.find((block) => block.type === \"text\");\n if (textBlock && textBlock.type === \"text\") {\n return textBlock.text;\n }\n\n return \"\";\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 * 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 { ExtractedConcept } from \"../utils/types.js\";\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 },\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 \"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 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 `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 existingSection,\n relatedSection,\n \"\\n\\n--- SOURCE MATERIAL ---\\n\\n\",\n sourceContent,\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: ExtractedConcept[] = parsed.concepts ?? [];\n return concepts.filter(\n (c) =>\n typeof c.concept === \"string\" &&\n typeof c.summary === \"string\" &&\n typeof c.is_new === \"boolean\",\n );\n } catch {\n return [];\n }\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 * 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/**\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 = new Set(\n directChanges\n .filter((c) => c.status === \"new\" || c.status === \"changed\")\n .map((c) => c.file),\n );\n\n // Deleted files must never be enqueued for recompilation — their source\n // no longer exists on disk, so compileSource would fail reading it.\n const deletedFiles = new Set(\n directChanges\n .filter((c) => c.status === \"deleted\")\n .map((c) => c.file),\n );\n\n const conceptMap = buildConceptToSourcesMap(state.sources);\n const affected = new Set<string>();\n\n for (const changedFile of changedFiles) {\n const sourceEntry = state.sources[changedFile];\n if (!sourceEntry) continue;\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 skip = changedFiles.has(contributor)\n || deletedFiles.has(contributor)\n || affected.has(contributor);\n if (!skip) {\n affected.add(contributor);\n }\n }\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 * 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 = new Set(\n allChanges\n .filter((c) => c.status === \"new\" || c.status === \"changed\")\n .map((c) => c.file),\n );\n const deletedFiles = new Set(\n allChanges.filter((c) => c.status === \"deleted\").map((c) => c.file),\n );\n const conceptMap = buildConceptToSourcesMap(state.sources);\n\n // Collect concept slugs from ALL extractions that weren't in the source's\n // previous concept list. These are \"newly gained\" concepts that\n // findAffectedSources couldn't have matched pre-extraction.\n const freshSlugs = new Set<string>();\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)) {\n freshSlugs.add(slug);\n }\n }\n }\n\n // Find unchanged sources that own any of those freshly gained slugs.\n const affected = new Set<string>();\n for (const slug of freshSlugs) {\n const owners = conceptMap.get(slug);\n if (!owners) continue;\n for (const owner of owners) {\n if (!compilingFiles.has(owner) && !deletedFiles.has(owner)) {\n affected.add(owner);\n }\n }\n }\n\n return Array.from(affected);\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 [[wikilinks]]. Obsidian-compatible format using\n * display titles, not slugs.\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 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/**\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\n for (const page of titles) {\n if (page.title.toLowerCase() === selfTitle.toLowerCase()) continue;\n\n const escaped = page.title.replace(/[.*+?^${}()|[\\]\\\\]/g, \"\\\\$&\");\n const regex = new RegExp(escaped, \"gi\");\n let match;\n\n // Process matches in reverse to preserve positions\n const matches: { start: number; end: number }[] = [];\n while ((match = regex.exec(result)) !== null) {\n matches.push({ start: match.index, end: match.index + match[0].length });\n }\n\n for (const m of matches.reverse()) {\n if (isInsideWikilink(result, m.start)) continue;\n if (!isWordBoundary(result, m.start, m.end)) continue;\n\n result =\n result.slice(0, m.start) +\n `[[${page.title}]]` +\n 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/**\n * Scan the concepts directory and extract page summaries from frontmatter.\n * @param conceptsPath - Absolute path to wiki/concepts/.\n * @returns Array of page summary objects.\n */\nasync function collectPageSummaries(\n conceptsPath: string,\n): Promise<PageSummary[]> {\n let files: string[];\n\n try {\n files = await readdir(conceptsPath);\n } catch {\n return [];\n }\n\n const pages: PageSummary[] = [];\n\n for (const file of files.filter((f) => f.endsWith(\".md\"))) {\n const content = await safeReadFile(path.join(conceptsPath, file));\n const { meta } = parseFrontmatter(content);\n if (meta.title && typeof meta.title === \"string\" && !meta.orphaned) {\n pages.push({\n title: meta.title,\n slug: file.replace(/\\.md$/, \"\"),\n summary: typeof meta.summary === \"string\" ? meta.summary : \"\",\n });\n }\n }\n\n return pages;\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.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.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 * 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 Anthropic from \"@anthropic-ai/sdk\";\nimport { callClaude } from \"../utils/llm.js\";\nimport { atomicWrite, safeReadFile, slugify, buildFrontmatter, parseFrontmatter } from \"../utils/markdown.js\";\nimport { generateIndex } from \"../compiler/indexgen.js\";\nimport * as output from \"../utils/output.js\";\nimport { QUERY_PAGE_LIMIT, INDEX_FILE, CONCEPTS_DIR, QUERIES_DIR } from \"../utils/constants.js\";\n\n/** Directories to search when loading selected pages, in priority order. */\nconst PAGE_DIRS = [CONCEPTS_DIR, QUERIES_DIR];\n\n/** Anthropic tool schema for page selection. */\nconst PAGE_SELECTION_TOOL: Anthropic.Tool = {\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 */\nasync 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/**\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/**\n * Stream an answer from Claude using the loaded wiki pages as context.\n * @param question - The user's natural language question.\n * @param pagesContent - Combined content of the selected wiki pages.\n * @returns The full answer text after streaming completes.\n */\nasync function streamAnswer(question: string, pagesContent: string): Promise<string> {\n const systemPrompt =\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 const userMessage = `Question: ${question}\\n\\nRelevant wiki pages:\\n${pagesContent}`;\n\n const answer = await callClaude({\n system: systemPrompt,\n messages: [{ role: \"user\", content: userMessage }],\n stream: true,\n onToken: (text: string) => process.stdout.write(text),\n });\n\n // Ensure terminal output ends on a new line after streaming\n process.stdout.write(\"\\n\");\n return answer;\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<void> {\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\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 },\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 // Step 1: Select relevant pages\n output.header(\"Selecting relevant pages\");\n\n const indexContent = await safeReadFile(path.join(root, INDEX_FILE));\n const { pages: rawPages, reasoning } = await selectPages(question, indexContent);\n const pages = rawPages.map((p) => slugify(p));\n\n output.status(\"i\", output.dim(`Reasoning: ${reasoning}`));\n output.status(\"*\", output.info(`Selected ${pages.length} page(s): ${rawPages.join(\", \")}`));\n\n // Step 2: Load pages and stream the answer\n output.header(\"Generating answer\");\n\n const pagesContent = await loadSelectedPages(root, pages);\n\n if (!pagesContent) {\n output.status(\"!\", output.error(\"No matching pages found. Try refining your question.\"));\n return;\n }\n\n const answer = await streamAnswer(question, pagesContent);\n\n // Optional: save the answer as a query page\n if (options.save) {\n await saveQueryPage(root, question, answer);\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 * 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"],"mappings":"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","names":["path","mkdir","writeFile","readFile","path","source","path","mkdir","writeFile","existsSync","readFile","readdir","path","readFile","writeFile","rename","mkdir","path","path","readFile","mkdir","writeFile","rename","error","readFile","mkdir","path","path","mkdir","readFile","readFile","path","readFile","path","status","path","path","readdir","readFile","path","existsSync","path","existsSync","readdir","readFile","readdir","path","path","readdir","path","readFile","readdir","existsSync","existsSync","path","path","existsSync","existsSync","path","path","existsSync","require","source"]}
|