kb-core 0.1.0 → 0.1.2

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.
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/init.ts","../src/config.ts","../src/project.ts","../src/db.ts","../src/markdown.ts","../src/indexer.ts","../src/search.ts","../src/source-reader.ts","../src/llm.ts","../src/ingest.ts","../src/query.ts","../src/lint.ts","../src/log-parser.ts"],"sourcesContent":["// Core package — business logic\n\nexport const VERSION = \"0.1.0\";\n\nexport { initProject } from \"./init.js\";\nexport type { InitOptions } from \"./init.js\";\n\nexport { parseConfig } from \"./config.js\";\nexport type { KbConfig } from \"./config.js\";\n\nexport { loadProject, tryLoadProject } from \"./project.js\";\nexport type { Project } from \"./project.js\";\n\nexport { openDb, closeDb } from \"./db.js\";\n\nexport { parsePage } from \"./markdown.js\";\nexport type { ParsedPage } from \"./markdown.js\";\n\nexport { indexProject } from \"./indexer.js\";\nexport type { IndexStats } from \"./indexer.js\";\n\nexport { searchWiki } from \"./search.js\";\nexport type { SearchResult, SearchOptions } from \"./search.js\";\n\nexport { readSource } from \"./source-reader.js\";\nexport type { SourceContent, SourceType } from \"./source-reader.js\";\n\nexport { createLlmAdapter } from \"./llm.js\";\nexport type { LlmAdapter, LlmMessage } from \"./llm.js\";\n\nexport type { IngestResult } from \"./ingest-types.js\";\n\nexport { ingestSource } from \"./ingest.js\";\nexport type { IngestOptions, IngestPlan } from \"./ingest.js\";\n\nexport { queryWiki } from \"./query.js\";\nexport type { QueryResult, QueryOptions } from \"./query.js\";\n\nexport { lintProject } from \"./lint.js\";\nexport type { LintIssue, LintResult, LintSeverity } from \"./lint.js\";\n\nexport { parseLogEntries } from \"./log-parser.js\";\nexport type { ParsedLogEntry } from \"./log-parser.js\";\n","import { mkdir, writeFile, access, rm } from \"node:fs/promises\";\nimport { join, basename } from \"node:path\";\nimport TOML from \"@iarna/toml\";\n\nexport interface InitOptions {\n name: string;\n directory: string; // absolute path where to init\n}\n\nfunction resolveProjectName(options: InitOptions): string {\n return options.name || basename(options.directory);\n}\n\nfunction buildConfigToml(projectName: string): string {\n const config = {\n project: {\n name: projectName,\n version: \"0.1.0\",\n },\n directories: {\n sources: \"sources\",\n wiki: \"wiki\",\n },\n llm: {\n provider: \"anthropic\",\n model: \"claude-sonnet-4-20250514\",\n },\n };\n\n const tomlStr = TOML.stringify(config as TOML.JsonMap);\n return (\n tomlStr +\n '\\n[dependencies]\\n# shared-glossary = { path = \"../shared-glossary\" }\\n'\n );\n}\n\nfunction buildSchemaMd(): string {\n return `# KB Schema — LLM Instructions\n\nThis file defines the conventions for this knowledge base. The \\`kb\\` CLI and any\nLLM operating on this wiki MUST follow these rules.\n\n---\n\n## Wiki Structure Conventions\n\n- All pages live under the \\`wiki/\\` directory.\n- \\`wiki/_index.md\\` is the wiki root and serves as a table of contents.\n- Sub-topics may be organised into sub-directories: \\`wiki/<topic>/_index.md\\`.\n- File names use kebab-case, e.g. \\`wiki/authentication-flow.md\\`.\n- Every page must have a valid YAML frontmatter block.\n\n---\n\n## Frontmatter Schema\n\nEvery wiki page must begin with a YAML frontmatter block:\n\n\\`\\`\\`yaml\n---\ntitle: <Human-readable page title>\ntags: [tag1, tag2] # optional; array of lowercase strings\ncreated: <ISO 8601 date> # e.g. 2026-04-05\nupdated: <ISO 8601 date> # updated whenever content changes\nsource: <path or URL> # optional; original source material\n---\n\\`\\`\\`\n\nRequired fields: \\`title\\`, \\`created\\`.\n\n---\n\n## Page Templates\n\n### Entity Page\nUse for: people, systems, services, tools.\n\n\\`\\`\\`markdown\n---\ntitle: <Entity Name>\ntags: [entity]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# <Entity Name>\n\n**Type**: <system | person | service | tool>\n\n## Overview\n\n<One-paragraph description.>\n\n## Key Attributes\n\n- **Attribute**: value\n\n## Related\n\n- [[related-page]]\n\\`\\`\\`\n\n### Concept Page\nUse for: ideas, patterns, terminology.\n\n\\`\\`\\`markdown\n---\ntitle: <Concept Name>\ntags: [concept]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# <Concept Name>\n\n## Definition\n\n<Clear definition in 1-3 sentences.>\n\n## Context\n\n<When and why this concept matters in the project.>\n\n## See Also\n\n- [[related-concept]]\n\\`\\`\\`\n\n### Source Summary Page\nUse for: summarised source material (docs, papers, meetings).\n\n\\`\\`\\`markdown\n---\ntitle: Summary — <Source Title>\ntags: [source-summary]\ncreated: <ISO date>\nsource: <path or URL>\n---\n\n# Summary — <Source Title>\n\n## Key Points\n\n- Point one\n- Point two\n\n## Decisions / Implications\n\n<What this source means for the project.>\n\n## Raw Source\n\nSee \\`sources/<filename>\\`.\n\\`\\`\\`\n\n### Comparison Page\nUse for: side-by-side evaluation of options.\n\n\\`\\`\\`markdown\n---\ntitle: Comparison — <Topic>\ntags: [comparison]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# Comparison — <Topic>\n\n| Criterion | Option A | Option B |\n|-----------|----------|----------|\n| ... | ... | ... |\n\n## Recommendation\n\n<Which option and why.>\n\\`\\`\\`\n\n---\n\n## Wikilink Conventions\n\n- Basic link: \\`[[page-name]]\\` — links to \\`wiki/page-name.md\\`.\n- Display text: \\`[[page-name|display text]]\\` — renders as \"display text\".\n- Cross-directory: \\`[[topic/sub-page]]\\`.\n- All wikilink targets must be lowercase kebab-case matching the file name without \\`.md\\`.\n\n---\n\n## Ingest Workflow\n\n1. Place the source file in \\`sources/\\` (PDF, Markdown, plain text, etc.).\n2. Run \\`kb ingest sources/<filename>\\`.\n3. The CLI reads the file, calls the configured LLM, and generates a source-summary\n page in \\`wiki/\\`.\n4. The summary page is linked from \\`wiki/_index.md\\` under **Sources**.\n5. An entry is appended to \\`log.md\\`.\n\n---\n\n## Query Workflow\n\n1. Run \\`kb query \"<natural-language question>\"\\`.\n2. The CLI searches the wiki index for relevant pages.\n3. Relevant page content is assembled into a prompt context.\n4. The LLM answers the question, citing wikilinks.\n5. The answer is printed to stdout. Nothing is written to disk unless \\`--save\\` is passed.\n\n---\n\n## Lint Workflow\n\nRun \\`kb lint\\` to check for:\n\n- Pages missing required frontmatter fields (\\`title\\`, \\`created\\`).\n- Broken wikilinks (targets that don't resolve to an existing page).\n- Pages not reachable from \\`wiki/_index.md\\`.\n- Duplicate page titles across the wiki.\n- Frontmatter fields with invalid types or formats.\n\nLint exits with code 0 on success, 1 if errors are found.\n`;\n}\n\nfunction buildIndexMd(projectName: string, isoDate: string): string {\n return `---\ntitle: ${projectName} Knowledge Base\ncreated: ${isoDate}\n---\n\n# ${projectName} Knowledge Base\n\n> This wiki is maintained by the \\`kb\\` CLI tool.\n\n## Pages\n\n(No pages yet. Use \\`kb ingest <source>\\` to add content.)\n\n## Sources\n\n(No sources yet.)\n`;\n}\n\nfunction buildLogMd(projectName: string, isoDate: string): string {\n return `# Activity Log\n\n## ${isoDate} — Project initialized\n\nProject \\`${projectName}\\` initialized.\n`;\n}\n\nasync function kbDirExists(directory: string): Promise<boolean> {\n try {\n await access(join(directory, \".kb\"));\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function initProject(options: InitOptions): Promise<void> {\n const projectName = resolveProjectName(options);\n const { directory } = options;\n\n if (await kbDirExists(directory)) {\n throw new Error(\n `Knowledge base already initialized: .kb/ already exists in ${directory}`,\n );\n }\n\n const isoDate = new Date().toISOString().split(\"T\")[0]!;\n\n try {\n await Promise.all([\n mkdir(join(directory, \".kb\"), { recursive: true }),\n mkdir(join(directory, \"sources\"), { recursive: true }),\n mkdir(join(directory, \"wiki\"), { recursive: true }),\n ]);\n\n await Promise.all([\n writeFile(\n join(directory, \".kb\", \"config.toml\"),\n buildConfigToml(projectName),\n \"utf8\",\n ),\n writeFile(join(directory, \".kb\", \"schema.md\"), buildSchemaMd(), \"utf8\"),\n writeFile(join(directory, \"sources\", \".gitkeep\"), \"\", \"utf8\"),\n writeFile(\n join(directory, \"wiki\", \"_index.md\"),\n buildIndexMd(projectName, isoDate),\n \"utf8\",\n ),\n writeFile(\n join(directory, \"log.md\"),\n buildLogMd(projectName, isoDate),\n \"utf8\",\n ),\n ]);\n } catch (error) {\n // Rollback: remove .kb/ if it was created\n await rm(join(directory, \".kb\"), { recursive: true, force: true });\n throw error;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport TOML from \"@iarna/toml\";\n\nexport interface KbConfig {\n project: {\n name: string;\n version: string;\n };\n directories: {\n sources: string;\n wiki: string;\n };\n llm: {\n provider: \"anthropic\" | \"openai\" | \"ollama\";\n model: string;\n };\n dependencies: Record<\n string,\n { path?: string; git?: string; branch?: string; mode?: string }\n >;\n}\n\nconst VALID_PROVIDERS = [\"anthropic\", \"openai\", \"ollama\"] as const;\n\nfunction requireSafeRelativePath(val: string, field: string): void {\n if (val.startsWith(\"/\") || val.split(\"/\").includes(\"..\")) {\n throw new Error(\n `Invalid config: ${field} must be a safe relative path, got \"${val}\"`,\n );\n }\n}\n\nfunction requireString(\n obj: Record<string, unknown>,\n key: string,\n context: string,\n): string {\n const val = obj[key];\n if (typeof val !== \"string\" || val.trim() === \"\") {\n throw new Error(\n `Invalid config: missing required field \"${context}.${key}\"`,\n );\n }\n return val;\n}\n\nfunction requireSection(\n obj: Record<string, unknown>,\n key: string,\n): Record<string, unknown> {\n const val = obj[key];\n if (\n val === undefined ||\n val === null ||\n typeof val !== \"object\" ||\n Array.isArray(val)\n ) {\n throw new Error(`Invalid config: missing required section \"[${key}]\"`);\n }\n return val as Record<string, unknown>;\n}\n\nexport async function parseConfig(configPath: string): Promise<KbConfig> {\n let raw: string;\n try {\n raw = await readFile(configPath, \"utf8\");\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Config file not found: ${configPath}\\n${message}`);\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = TOML.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid TOML in config file ${configPath}: ${message}`);\n }\n\n const project = requireSection(parsed, \"project\");\n const name = requireString(project, \"name\", \"project\");\n const version = requireString(project, \"version\", \"project\");\n\n const directories = requireSection(parsed, \"directories\");\n const sources = requireString(directories, \"sources\", \"directories\");\n requireSafeRelativePath(sources, \"directories.sources\");\n const wiki = requireString(directories, \"wiki\", \"directories\");\n requireSafeRelativePath(wiki, \"directories.wiki\");\n\n const llm = requireSection(parsed, \"llm\");\n const providerRaw = requireString(llm, \"provider\", \"llm\");\n if (!(VALID_PROVIDERS as readonly string[]).includes(providerRaw)) {\n throw new Error(\n `Invalid config: llm.provider must be one of ${VALID_PROVIDERS.join(\", \")}, got \"${providerRaw}\"`,\n );\n }\n const provider = providerRaw as KbConfig[\"llm\"][\"provider\"];\n const model = requireString(llm, \"model\", \"llm\");\n\n const rawDeps = parsed[\"dependencies\"];\n const dependencies: KbConfig[\"dependencies\"] = {};\n if (\n rawDeps !== undefined &&\n rawDeps !== null &&\n typeof rawDeps === \"object\" &&\n !Array.isArray(rawDeps)\n ) {\n for (const [depKey, depVal] of Object.entries(\n rawDeps as Record<string, unknown>,\n )) {\n if (\n typeof depVal === \"object\" &&\n depVal !== null &&\n !Array.isArray(depVal)\n ) {\n const dep = depVal as Record<string, unknown>;\n dependencies[depKey] = {\n ...(typeof dep[\"path\"] === \"string\" ? { path: dep[\"path\"] } : {}),\n ...(typeof dep[\"git\"] === \"string\" ? { git: dep[\"git\"] } : {}),\n ...(typeof dep[\"branch\"] === \"string\"\n ? { branch: dep[\"branch\"] }\n : {}),\n ...(typeof dep[\"mode\"] === \"string\" ? { mode: dep[\"mode\"] } : {}),\n };\n }\n }\n }\n\n return {\n project: { name, version },\n directories: { sources, wiki },\n llm: { provider, model },\n dependencies,\n };\n}\n","import { access } from \"node:fs/promises\";\nimport { join, dirname, resolve } from \"node:path\";\nimport { parseConfig, type KbConfig } from \"./config.js\";\n\nexport interface Project {\n name: string;\n root: string;\n kbDir: string;\n sourcesDir: string;\n wikiDir: string;\n config: KbConfig;\n}\n\nasync function hasKbDir(dir: string): Promise<boolean> {\n try {\n await access(join(dir, \".kb\", \"config.toml\"));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function findProjectRoot(startDir: string): Promise<string | null> {\n let current = resolve(startDir);\n\n while (true) {\n if (await hasKbDir(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root\n return null;\n }\n current = parent;\n }\n}\n\nexport async function loadProject(startDir: string): Promise<Project> {\n const root = await findProjectRoot(startDir);\n if (root === null) {\n throw new Error(\n `No kb project found. Run \"kb init\" to initialize a knowledge base in the current directory.`,\n );\n }\n\n const kbDir = join(root, \".kb\");\n const configPath = join(kbDir, \"config.toml\");\n const config = await parseConfig(configPath);\n\n return {\n name: config.project.name,\n root,\n kbDir,\n sourcesDir: join(root, config.directories.sources),\n wikiDir: join(root, config.directories.wiki),\n config,\n };\n}\n\nexport async function tryLoadProject(\n startDir: string,\n): Promise<Project | null> {\n try {\n return await loadProject(startDir);\n } catch (err: unknown) {\n if (err instanceof Error && /no kb project found/i.test(err.message)) {\n return null;\n }\n throw err;\n }\n}\n","import Database from \"better-sqlite3\";\nimport { join } from \"node:path\";\nimport type { Project } from \"./project.js\";\n\nconst SCHEMA_SQL = `\nCREATE VIRTUAL TABLE IF NOT EXISTS pages USING fts5(\n path,\n title,\n content,\n tags,\n project,\n tokenize='porter unicode61'\n);\n\nCREATE TABLE IF NOT EXISTS page_meta (\n path TEXT PRIMARY KEY,\n sha256 TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n word_count INTEGER NOT NULL DEFAULT 0,\n frontmatter TEXT NOT NULL DEFAULT '{}',\n outgoing_links TEXT NOT NULL DEFAULT '[]',\n updated_at INTEGER NOT NULL\n);\n`;\n\nexport function openDb(project: Project): Database.Database {\n const dbPath = join(project.kbDir, \"index.db\");\n const db = new Database(dbPath);\n db.pragma(\"journal_mode = WAL\");\n db.exec(SCHEMA_SQL);\n return db;\n}\n\nexport function closeDb(db: Database.Database): void {\n db.close();\n}\n","import { readFile } from \"node:fs/promises\";\nimport matter from \"gray-matter\";\n\nexport interface ParsedPage {\n path: string;\n title: string;\n content: string;\n tags: string;\n frontmatter: Record<string, unknown>;\n outgoingLinks: string[];\n wordCount: number;\n}\n\nconst WIKILINK_RE = /\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]/g;\nconst H1_RE = /^#\\s+(.+)$/m;\n\nfunction extractTitle(\n fm: Record<string, unknown>,\n content: string,\n relativePath: string,\n): string {\n if (typeof fm[\"title\"] === \"string\" && fm[\"title\"].trim() !== \"\") {\n return fm[\"title\"].trim();\n }\n const h1Match = H1_RE.exec(content);\n if (h1Match) {\n return h1Match[1]!.trim();\n }\n // Fallback: use filename without extension\n const filename = relativePath.split(\"/\").pop() ?? relativePath;\n return filename.replace(/\\.md$/i, \"\");\n}\n\nfunction extractTags(fm: Record<string, unknown>): string {\n const tags = fm[\"tags\"];\n if (!Array.isArray(tags)) return \"\";\n return tags.filter((t): t is string => typeof t === \"string\").join(\",\");\n}\n\nfunction extractWikiLinks(content: string): string[] {\n const links: string[] = [];\n let match: RegExpExecArray | null;\n const re = new RegExp(WIKILINK_RE.source, \"g\");\n while ((match = re.exec(content)) !== null) {\n links.push(match[1]!.trim());\n }\n return links;\n}\n\nfunction countWords(text: string): number {\n const trimmed = text.trim();\n if (trimmed === \"\") return 0;\n return trimmed.split(/\\s+/).length;\n}\n\nexport async function parsePage(\n filePath: string,\n relativePath: string,\n rawContent?: string,\n): Promise<ParsedPage> {\n const raw = rawContent ?? (await readFile(filePath, \"utf8\"));\n const parsed = matter(raw);\n const fm = parsed.data as Record<string, unknown>;\n const content = parsed.content;\n\n const title = extractTitle(fm, content, relativePath);\n const tags = extractTags(fm);\n const outgoingLinks = extractWikiLinks(content);\n const wordCount = countWords(content);\n\n return {\n path: relativePath,\n title,\n content,\n tags,\n frontmatter: fm,\n outgoingLinks,\n wordCount,\n };\n}\n","import { createHash } from \"node:crypto\";\nimport { readFile, stat, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport Database from \"better-sqlite3\";\nimport type { Project } from \"./project.js\";\nimport { parsePage } from \"./markdown.js\";\nimport { openDb, closeDb } from \"./db.js\";\n\nexport interface IndexStats {\n indexed: number;\n skipped: number;\n deleted: number;\n errors: number;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\nfunction sha256(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\ninterface PageMetaRow {\n sha256: string;\n}\n\ninterface UpsertStmts {\n deletePages: Database.Statement;\n insertPage: Database.Statement;\n upsertMeta: Database.Statement;\n}\n\nfunction upsertParsedPage(\n stmts: UpsertStmts,\n project: Project,\n page: Awaited<ReturnType<typeof parsePage>>,\n hash: string,\n mtime: number,\n): void {\n stmts.deletePages.run(page.path);\n stmts.insertPage.run(\n page.path,\n page.title,\n page.content,\n page.tags,\n project.name,\n );\n stmts.upsertMeta.run(\n page.path,\n hash,\n mtime,\n page.wordCount,\n JSON.stringify(page.frontmatter),\n JSON.stringify(page.outgoingLinks),\n Date.now(),\n );\n}\n\nexport async function indexProject(\n project: Project,\n rebuild = false,\n): Promise<IndexStats> {\n const db = openDb(project);\n try {\n if (rebuild) {\n db.exec(\"DELETE FROM pages; DELETE FROM page_meta;\");\n }\n\n const files = await collectMdFiles(project.wikiDir);\n const stats: IndexStats = { indexed: 0, skipped: 0, deleted: 0, errors: 0 };\n\n const getMetaStmt = db.prepare<[string], PageMetaRow>(\n \"SELECT sha256 FROM page_meta WHERE path = ?\",\n );\n\n const upsertStmts: UpsertStmts = {\n deletePages: db.prepare(\"DELETE FROM pages WHERE path = ?\"),\n insertPage: db.prepare(\n \"INSERT INTO pages(path, title, content, tags, project) VALUES (?, ?, ?, ?, ?)\",\n ),\n upsertMeta: db.prepare(`\n INSERT INTO page_meta(path, sha256, mtime, word_count, frontmatter, outgoing_links, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(path) DO UPDATE SET\n sha256 = excluded.sha256,\n mtime = excluded.mtime,\n word_count = excluded.word_count,\n frontmatter = excluded.frontmatter,\n outgoing_links = excluded.outgoing_links,\n updated_at = excluded.updated_at\n `),\n };\n\n const deleteStalePages = db.prepare(\"DELETE FROM pages WHERE path = ?\");\n const deleteStaleMeta = db.prepare(\"DELETE FROM page_meta WHERE path = ?\");\n const listMetaStmt = db.prepare<[], { path: string }>(\n \"SELECT path FROM page_meta\",\n );\n\n const processFile = db.transaction(\n (\n page: Awaited<ReturnType<typeof parsePage>>,\n hash: string,\n mtime: number,\n ) => {\n upsertParsedPage(upsertStmts, project, page, hash, mtime);\n },\n );\n\n const onDiskPaths = new Set<string>();\n\n for (const absPath of files) {\n const relPath = relative(project.root, absPath);\n onDiskPaths.add(relPath);\n\n let raw: string;\n try {\n raw = await readFile(absPath, \"utf8\");\n } catch (err) {\n stats.errors++;\n continue;\n }\n\n const hash = sha256(raw);\n const existing = getMetaStmt.get(relPath);\n\n if (existing && existing.sha256 === hash) {\n stats.skipped++;\n continue;\n }\n\n let fileStat: Awaited<ReturnType<typeof stat>>;\n try {\n fileStat = await stat(absPath);\n } catch {\n stats.errors++;\n continue;\n }\n\n let page: Awaited<ReturnType<typeof parsePage>>;\n try {\n page = await parsePage(absPath, relPath, raw);\n } catch {\n stats.errors++;\n continue;\n }\n\n try {\n processFile(page, hash, Math.floor(fileStat.mtimeMs));\n stats.indexed++;\n } catch {\n stats.errors++;\n }\n }\n\n // Remove entries for deleted files\n const allMetaPaths = listMetaStmt.all().map((r) => r.path);\n\n const stalePaths = allMetaPaths.filter((p) => !onDiskPaths.has(p));\n\n const deleteStale = db.transaction((paths: string[]) => {\n for (const p of paths) {\n deleteStalePages.run(p);\n deleteStaleMeta.run(p);\n }\n });\n\n deleteStale(stalePaths);\n stats.deleted += stalePaths.length;\n\n return stats;\n } finally {\n closeDb(db);\n }\n}\n","import Database from \"better-sqlite3\";\n\nexport interface SearchResult {\n rank: number;\n path: string;\n title: string;\n snippet: string;\n tags: string[];\n}\n\nexport interface SearchOptions {\n limit?: number;\n tags?: string[];\n}\n\ninterface FtsRow {\n path: string;\n title: string;\n tags: string;\n rank: number;\n snippet: string;\n}\n\nfunction sanitizeFtsQuery(query: string): string {\n // Split into tokens, quote each one to escape special FTS5 chars.\n // Using individual quoted tokens (AND logic) instead of a single phrase\n // so non-adjacent words still match.\n const tokens = query\n .trim()\n .split(/\\s+/)\n .filter((t) => t.length > 0);\n if (tokens.length === 0) return '\"\"';\n return tokens.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" \");\n}\n\nfunction parseTags(raw: string): string[] {\n if (!raw || raw.trim() === \"\") return [];\n return raw\n .split(\",\")\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n}\n\nexport function searchWiki(\n db: Database.Database,\n query: string,\n projectName: string,\n options?: SearchOptions,\n): SearchResult[] {\n if (!query || query.trim() === \"\") {\n return [];\n }\n\n const limit = options?.limit ?? 10;\n const ftsQuery = sanitizeFtsQuery(query.trim());\n\n // Build dynamic tag WHERE clauses so filtering happens in SQL\n const filterTags = options?.tags?.length\n ? options.tags\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0)\n : [];\n\n const tagClauses = filterTags.map(() => \"AND lower(tags) LIKE ?\").join(\" \");\n const tagParams = filterTags.map((t) => `%${t}%`);\n\n const stmt = db.prepare<[string, string, ...string[], number], FtsRow>(`\n SELECT path, title, tags, bm25(pages) as rank,\n snippet(pages, 2, '', '', '...', 8) as snippet\n FROM pages\n WHERE pages MATCH ? AND project = ?\n ${tagClauses}\n ORDER BY rank\n LIMIT ?\n `);\n\n const rows = stmt.all(ftsQuery, projectName, ...tagParams, limit);\n\n const results: SearchResult[] = rows.map((row) => ({\n rank: row.rank,\n path: row.path,\n title: row.title,\n snippet: row.snippet,\n tags: parseTags(row.tags),\n }));\n\n return results;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\n\nexport type SourceType = \"markdown\" | \"text\" | \"pdf\" | \"url\";\n\nexport interface SourceContent {\n type: SourceType;\n originalPath: string;\n content: string;\n filename: string;\n}\n\nfunction sanitizeFilename(name: string): string {\n return name.toLowerCase().replace(/\\s/g, \"-\");\n}\n\nfunction detectType(sourcePath: string): SourceType {\n if (sourcePath.startsWith(\"http://\") || sourcePath.startsWith(\"https://\")) {\n return \"url\";\n }\n const ext = extname(sourcePath).toLowerCase();\n if (ext === \".pdf\") return \"pdf\";\n if (ext === \".md\") return \"markdown\";\n return \"text\";\n}\n\nfunction filenameFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const parts = parsed.pathname.split(\"/\").filter(Boolean);\n const last = parts[parts.length - 1];\n const pagePart = last\n ? last.includes(\".\")\n ? last\n : `${last}.html`\n : \"index.html\";\n return sanitizeFilename(`${parsed.hostname}-${pagePart}`);\n } catch {\n return \"url-content.html\";\n }\n}\n\nfunction stripHtml(html: string): string {\n // Remove script and style blocks entirely\n let text = html.replace(/<script[\\s\\S]*?<\\/script>/gi, \" \");\n text = text.replace(/<style[\\s\\S]*?<\\/style>/gi, \" \");\n // Strip remaining tags\n text = text.replace(/<[^>]+>/g, \" \");\n // Decode common HTML entities\n text = text\n .replace(/&amp;/gi, \"&\")\n .replace(/&lt;/gi, \"<\")\n .replace(/&gt;/gi, \">\")\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;/gi, \"'\")\n .replace(/&nbsp;/gi, \" \");\n // Collapse whitespace\n text = text.replace(/\\s+/g, \" \").trim();\n return text;\n}\n\nasync function readPdf(filePath: string): Promise<string> {\n // Dynamic import to avoid issues if pdf-parse not available in test env\n const pdfParse = await import(\"pdf-parse\").then((m) => m.default ?? m);\n const buffer = await readFile(filePath);\n const data = await pdfParse(buffer);\n return data.text;\n}\n\nfunction isPrivateUrl(url: string): boolean {\n try {\n const { hostname } = new URL(url);\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname.startsWith(\"169.254.\") || // link-local\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"192.168.\") ||\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(hostname)\n );\n } catch {\n return false;\n }\n}\n\nasync function fetchUrl(url: string): Promise<string> {\n if (isPrivateUrl(url)) {\n throw new Error(`Fetching private/localhost URLs is not allowed: ${url}`);\n }\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch URL ${url}: HTTP ${response.status} ${response.statusText}`,\n );\n }\n const html = await response.text();\n return stripHtml(html);\n}\n\nexport async function readSource(sourcePath: string): Promise<SourceContent> {\n const type = detectType(sourcePath);\n\n if (type === \"url\") {\n const content = await fetchUrl(sourcePath);\n const filename = filenameFromUrl(sourcePath);\n return { type, originalPath: sourcePath, content, filename };\n }\n\n if (type === \"pdf\") {\n const content = await readPdf(sourcePath);\n const raw = basename(sourcePath);\n const filename = sanitizeFilename(raw);\n return { type, originalPath: sourcePath, content, filename };\n }\n\n // markdown or text\n const content = await readFile(sourcePath, \"utf8\");\n const raw = basename(sourcePath);\n const filename = sanitizeFilename(raw);\n return { type, originalPath: sourcePath, content, filename };\n}\n","import type { KbConfig } from \"./config.js\";\n\nexport interface LlmMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\nexport interface LlmAdapter {\n complete(messages: LlmMessage[], systemPrompt: string): Promise<string>;\n}\n\nfunction createAnthropicAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"ANTHROPIC_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"ANTHROPIC_API_KEY environment variable is not set\");\n }\n const Anthropic = await import(\"@anthropic-ai/sdk\").then(\n (m) => m.default ?? m,\n );\n const client = new Anthropic({ apiKey });\n const response = await client.messages.create({\n model,\n max_tokens: 8192,\n system: systemPrompt,\n messages: messages.map((m) => ({\n role: m.role,\n content: m.content,\n })),\n });\n const block = response.content[0];\n if (!block || block.type !== \"text\") {\n throw new Error(\"Anthropic returned no text content\");\n }\n return block.text;\n },\n };\n}\n\nfunction createOpenAiAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"OPENAI_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"OPENAI_API_KEY environment variable is not set\");\n }\n const body = {\n model,\n messages: [\n { role: \"system\", content: systemPrompt },\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n };\n const response = await fetch(\n \"https://api.openai.com/v1/chat/completions\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n },\n );\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OpenAI API error: HTTP ${response.status} — ${text}`);\n }\n const data = (await response.json()) as {\n choices: Array<{ message: { content: string } }>;\n };\n const content = data.choices[0]?.message?.content;\n if (!content) {\n throw new Error(\"OpenAI returned no content\");\n }\n return content;\n },\n };\n}\n\nfunction createOllamaAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const baseUrl =\n process.env[\"OLLAMA_BASE_URL\"] ?? \"http://localhost:11434\";\n const body = {\n model,\n messages: [\n { role: \"system\", content: systemPrompt },\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n stream: false,\n };\n const response = await fetch(`${baseUrl}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Ollama API error: HTTP ${response.status} — ${text}`);\n }\n const data = (await response.json()) as {\n message?: { content: string };\n };\n const content = data.message?.content;\n if (!content) {\n throw new Error(\"Ollama returned no content\");\n }\n return content;\n },\n };\n}\n\nexport function createLlmAdapter(config: KbConfig): LlmAdapter {\n const { provider, model } = config.llm;\n switch (provider) {\n case \"anthropic\":\n return createAnthropicAdapter(model);\n case \"openai\":\n return createOpenAiAdapter(model);\n case \"ollama\":\n return createOllamaAdapter(model);\n default: {\n const _exhaustive: never = provider;\n throw new Error(`Unsupported LLM provider: ${String(_exhaustive)}`);\n }\n }\n}\n","import { readFile, writeFile, mkdir, appendFile } from \"node:fs/promises\";\nimport { join, dirname, resolve } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport type { LlmAdapter } from \"./llm.js\";\nimport type { IngestResult } from \"./ingest-types.js\";\nimport { readSource } from \"./source-reader.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport interface IngestOptions {\n apply?: boolean;\n batch?: boolean;\n}\n\nexport interface IngestPlan {\n result: IngestResult;\n sourceFile: string;\n dryRun: boolean;\n}\n\nconst SYSTEM_PROMPT = `You are an AI assistant maintaining a knowledge base wiki.\nYou will be given a new source document and the current state of the wiki.\nYour task is to integrate the new knowledge into the wiki.\n\nReturn ONLY a JSON object matching this exact schema (no markdown fences):\n{\n \"summary\": { \"path\": \"wiki/sources/<filename>-summary.md\", \"content\": \"...\" },\n \"updates\": [{ \"path\": \"...\", \"content\": \"...\", \"reason\": \"...\" }],\n \"newPages\": [{ \"path\": \"...\", \"content\": \"...\", \"reason\": \"...\" }],\n \"indexUpdate\": \"...\",\n \"logEntry\": \"...\"\n}`;\n\nfunction assertWithinRoot(absPath: string, root: string): void {\n const resolvedPath = resolve(absPath);\n const resolvedRoot = resolve(root) + \"/\";\n if (!resolvedPath.startsWith(resolvedRoot)) {\n throw new Error(\n `Unsafe path rejected: \"${absPath}\" is outside project root`,\n );\n }\n}\n\nasync function readFileSafe(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nfunction parseIngestResult(raw: string): IngestResult {\n const cleaned = raw\n .replace(/^```(?:json)?\\s*/i, \"\")\n .replace(/\\s*```\\s*$/i, \"\")\n .trim();\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch (err) {\n throw new Error(\n `Invalid LLM response: could not parse JSON. Raw response: ${cleaned.slice(0, 200)}`,\n );\n }\n\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"Invalid LLM response: expected a JSON object\");\n }\n\n const obj = parsed as Record<string, unknown>;\n\n if (\n !obj[\"summary\"] ||\n typeof obj[\"summary\"] !== \"object\" ||\n Array.isArray(obj[\"summary\"])\n ) {\n throw new Error('Invalid LLM response: missing \"summary\" object');\n }\n\n const summary = obj[\"summary\"] as Record<string, unknown>;\n if (\n typeof summary[\"path\"] !== \"string\" ||\n typeof summary[\"content\"] !== \"string\"\n ) {\n throw new Error(\n 'Invalid LLM response: \"summary\" must have \"path\" and \"content\" strings',\n );\n }\n\n if (!Array.isArray(obj[\"updates\"])) {\n throw new Error('Invalid LLM response: \"updates\" must be an array');\n }\n\n if (!Array.isArray(obj[\"newPages\"])) {\n throw new Error('Invalid LLM response: \"newPages\" must be an array');\n }\n\n if (typeof obj[\"indexUpdate\"] !== \"string\") {\n throw new Error('Invalid LLM response: \"indexUpdate\" must be a string');\n }\n\n if (typeof obj[\"logEntry\"] !== \"string\") {\n throw new Error('Invalid LLM response: \"logEntry\" must be a string');\n }\n\n const updates = (obj[\"updates\"] as unknown[]).map((u, i) => {\n if (typeof u !== \"object\" || u === null || Array.isArray(u)) {\n throw new Error(`Invalid LLM response: updates[${i}] must be an object`);\n }\n const update = u as Record<string, unknown>;\n if (\n typeof update[\"path\"] !== \"string\" ||\n typeof update[\"content\"] !== \"string\" ||\n typeof update[\"reason\"] !== \"string\"\n ) {\n throw new Error(\n `Invalid LLM response: updates[${i}] must have path, content, and reason strings`,\n );\n }\n return {\n path: update[\"path\"],\n content: update[\"content\"],\n reason: update[\"reason\"],\n };\n });\n\n const newPages = (obj[\"newPages\"] as unknown[]).map((p, i) => {\n if (typeof p !== \"object\" || p === null || Array.isArray(p)) {\n throw new Error(`Invalid LLM response: newPages[${i}] must be an object`);\n }\n const page = p as Record<string, unknown>;\n if (\n typeof page[\"path\"] !== \"string\" ||\n typeof page[\"content\"] !== \"string\" ||\n typeof page[\"reason\"] !== \"string\"\n ) {\n throw new Error(\n `Invalid LLM response: newPages[${i}] must have path, content, and reason strings`,\n );\n }\n return {\n path: page[\"path\"],\n content: page[\"content\"],\n reason: page[\"reason\"],\n };\n });\n\n return {\n summary: { path: summary[\"path\"], content: summary[\"content\"] },\n updates,\n newPages,\n indexUpdate: obj[\"indexUpdate\"] as string,\n logEntry: obj[\"logEntry\"] as string,\n };\n}\n\nasync function applyIngestResult(\n project: Project,\n result: IngestResult,\n sourceContent: string,\n sourceFilename: string,\n): Promise<void> {\n // Write summary\n const summaryAbsPath = join(project.root, result.summary.path);\n assertWithinRoot(summaryAbsPath, project.root);\n await mkdir(dirname(summaryAbsPath), { recursive: true });\n await writeFile(summaryAbsPath, result.summary.content, \"utf8\");\n\n // Write updated pages\n for (const update of result.updates) {\n const absPath = join(project.root, update.path);\n assertWithinRoot(absPath, project.root);\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, update.content, \"utf8\");\n }\n\n // Write new pages\n for (const newPage of result.newPages) {\n const absPath = join(project.root, newPage.path);\n assertWithinRoot(absPath, project.root);\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, newPage.content, \"utf8\");\n }\n\n // Update _index.md\n const indexPath = join(project.wikiDir, \"_index.md\");\n await writeFile(indexPath, result.indexUpdate, \"utf8\");\n\n // Write source file to sources directory\n const sourceDestPath = join(project.sourcesDir, sourceFilename);\n await mkdir(project.sourcesDir, { recursive: true });\n await writeFile(sourceDestPath, sourceContent, \"utf8\");\n\n // Append to log.md\n const logPath = join(project.wikiDir, \"log.md\");\n const timestamp = new Date().toISOString().split(\"T\")[0];\n const logLine = `- ${timestamp}: ${result.logEntry}\\n`;\n await appendFile(logPath, logLine, \"utf8\");\n\n // Re-index\n await indexProject(project);\n}\n\nexport async function ingestSource(\n project: Project,\n sourcePath: string,\n llm: LlmAdapter,\n options?: IngestOptions,\n): Promise<IngestPlan> {\n const apply = options?.apply ?? false;\n\n // 1. Read source content\n const sourceContent = await readSource(sourcePath);\n\n // 2. Read current wiki index\n const indexPath = join(project.wikiDir, \"_index.md\");\n const currentIndex = await readFileSafe(indexPath);\n\n // 3. Read schema\n const schemaPath = join(project.kbDir, \"schema.md\");\n const schema = await readFileSafe(schemaPath);\n\n // 4. Build user message\n const userMessage = `## Wiki Schema\n${schema}\n\n## Current Wiki Index\n${currentIndex}\n\n## New Source: ${sourceContent.filename}\n${sourceContent.content}\n\nIntegrate this source into the wiki following the schema above.`;\n\n // 5. Call LLM\n const raw = await llm.complete(\n [{ role: \"user\", content: userMessage }],\n SYSTEM_PROMPT,\n );\n\n // 6. Parse response\n const result = parseIngestResult(raw);\n\n // 7. Apply if requested\n if (apply) {\n await applyIngestResult(\n project,\n result,\n sourceContent.content,\n sourceContent.filename,\n );\n }\n\n const sourceFile = join(project.sourcesDir, sourceContent.filename);\n\n return {\n result,\n sourceFile,\n dryRun: !apply,\n };\n}\n","import { readFile, writeFile, mkdir, appendFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname, resolve } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport type { LlmAdapter } from \"./llm.js\";\nimport { openDb, closeDb } from \"./db.js\";\nimport { searchWiki } from \"./search.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport interface QueryResult {\n answer: string;\n sources: string[];\n}\n\nexport interface QueryOptions {\n save?: string;\n}\n\nconst SYSTEM_PROMPT = `You are a knowledgeable assistant answering questions about a project's knowledge base.\nAnswer concisely using only information from the provided wiki pages.\nUse [[page-name]] wikilink syntax to cite specific wiki pages in your answer.\nFormat your answer in markdown.`;\n\nfunction assertWithinRoot(absPath: string, root: string): void {\n const resolvedPath = resolve(absPath);\n const resolvedRoot = resolve(root) + \"/\";\n if (!resolvedPath.startsWith(resolvedRoot)) {\n throw new Error(\n `Unsafe path rejected: \"${absPath}\" is outside project root`,\n );\n }\n}\n\nasync function readFileSafe(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nexport async function queryWiki(\n project: Project,\n question: string,\n llm: LlmAdapter,\n options?: QueryOptions,\n): Promise<QueryResult> {\n // 1. Auto-index if db doesn't exist\n const dbPath = join(project.kbDir, \"index.db\");\n if (!existsSync(dbPath)) {\n await indexProject(project);\n }\n\n // 2. Search for top relevant pages\n const db = openDb(project);\n let searchResults;\n try {\n searchResults = searchWiki(db, question, project.name, { limit: 10 });\n } finally {\n closeDb(db);\n }\n\n // 3. Read full content of each result page\n const pages: Array<{ path: string; title: string; content: string }> = [];\n for (const result of searchResults) {\n const absPath = join(project.root, result.path);\n const content = await readFileSafe(absPath);\n if (content) {\n pages.push({ path: result.path, title: result.title, content });\n }\n }\n\n // 4. Build user message\n const pagesSection =\n pages.length > 0\n ? pages\n .map((p) => `### ${p.title} (${p.path})\\n${p.content}`)\n .join(\"\\n\\n\")\n : \"(No wiki pages found for this query.)\";\n\n const userMessage = `## Question\\n${question}\\n\\n## Relevant Wiki Pages\\n\\n${pagesSection}`;\n\n // 5. Call LLM\n const answer = await llm.complete(\n [{ role: \"user\", content: userMessage }],\n SYSTEM_PROMPT,\n );\n\n const sources = pages.map((p) => p.path);\n\n // 6. If save option provided, write answer as wiki page and append to log\n if (options?.save) {\n const saveRelPath = options.save;\n const saveAbsPath = join(project.root, saveRelPath);\n assertWithinRoot(saveAbsPath, project.root);\n\n await mkdir(dirname(saveAbsPath), { recursive: true });\n await writeFile(saveAbsPath, answer, \"utf8\");\n\n // Append to log.md\n const logPath = join(project.wikiDir, \"log.md\");\n const timestamp = new Date().toISOString().split(\"T\")[0];\n const logEntry = `\\n## ${timestamp} — Queried: ${question}\\n\\nSaved to: ${saveRelPath}\\n`;\n await appendFile(logPath, logEntry, \"utf8\");\n\n // Re-index so saved page is searchable\n await indexProject(project);\n }\n\n return { answer, sources };\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport { join, relative, basename, extname } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport { openDb, closeDb } from \"./db.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport type LintSeverity = \"warning\" | \"info\";\n\nexport interface LintIssue {\n severity: LintSeverity;\n code: string;\n path: string;\n message: string;\n detail?: string;\n}\n\nexport interface LintResult {\n issues: LintIssue[];\n pagesChecked: number;\n sourcesChecked: number;\n}\n\ninterface PageMetaRow {\n path: string;\n outgoing_links: string;\n word_count: number;\n mtime: number;\n updated_at: number;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\nasync function collectSourceFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile())\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\n/**\n * Build a set of \"keys\" for wiki pages for wikilink resolution.\n * A wikilink [[foo-bar]] can match:\n * - a page whose filename (without extension) is \"foo-bar\"\n * - a page whose relative path is \"foo-bar\" or \"foo-bar.md\"\n */\nfunction buildPageKeySet(\n relPaths: string[],\n projectRoot: string,\n wikiDir: string,\n): Set<string> {\n const keys = new Set<string>();\n for (const rp of relPaths) {\n // rp is relative to projectRoot, e.g. \"wiki/concepts/foo.md\"\n keys.add(rp);\n // without extension\n keys.add(rp.replace(/\\.md$/i, \"\"));\n // filename without extension\n const fname = basename(rp, \".md\");\n keys.add(fname);\n // relative path from wikiDir\n const absPath = join(projectRoot, rp);\n const relToWiki = relative(wikiDir, absPath);\n keys.add(relToWiki);\n keys.add(relToWiki.replace(/\\.md$/i, \"\"));\n }\n return keys;\n}\n\nexport async function lintProject(project: Project): Promise<LintResult> {\n // Ensure index is up to date\n await indexProject(project);\n\n const issues: LintIssue[] = [];\n\n // Collect all wiki md files\n const absWikiFiles = await collectMdFiles(project.wikiDir);\n const relWikiPaths = absWikiFiles.map((f) => relative(project.root, f));\n\n const pagesChecked = relWikiPaths.length;\n\n // Always collect source files so sourcesChecked is accurate\n const sourceFiles = await collectSourceFiles(project.sourcesDir);\n const sourcesChecked = sourceFiles.filter(\n (f) => basename(f) !== \".gitkeep\",\n ).length;\n\n if (pagesChecked === 0) {\n return { issues, pagesChecked: 0, sourcesChecked };\n }\n\n // Build page key set for wikilink resolution\n const pageKeySet = buildPageKeySet(\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n\n // Query all page_meta rows\n const db = openDb(project);\n let rows: PageMetaRow[];\n try {\n rows = db\n .prepare<\n [],\n PageMetaRow\n >(\"SELECT path, outgoing_links, word_count, mtime, updated_at FROM page_meta\")\n .all();\n } finally {\n closeDb(db);\n }\n\n // Build a map from path -> row for quick lookup\n const metaMap = new Map<string, PageMetaRow>();\n for (const row of rows) {\n metaMap.set(row.path, row);\n }\n\n // Build inbound link map\n const inboundLinks = new Map<string, Set<string>>();\n for (const rp of relWikiPaths) {\n inboundLinks.set(rp, new Set());\n }\n\n for (const row of rows) {\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n // Find which page this link resolves to\n const resolved = resolveLink(\n link,\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n if (resolved !== null) {\n const set = inboundLinks.get(resolved);\n if (set) {\n set.add(row.path);\n }\n }\n }\n }\n\n // Read _index.md outgoing links for MISSING_INDEX check\n const indexPath = relWikiPaths.find((p) => basename(p) === \"_index.md\");\n let indexLinks: Set<string> = new Set();\n if (indexPath) {\n const indexRow = metaMap.get(indexPath);\n if (indexRow) {\n let links: string[] = [];\n try {\n links = JSON.parse(indexRow.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n const resolved = resolveLink(\n link,\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n if (resolved !== null) {\n indexLinks.add(resolved);\n }\n }\n }\n }\n\n // --- CHECK 1: ORPHAN_PAGE ---\n for (const rp of relWikiPaths) {\n if (basename(rp) === \"_index.md\") continue;\n const inbound = inboundLinks.get(rp);\n if (!inbound || inbound.size === 0) {\n issues.push({\n severity: \"warning\",\n code: \"ORPHAN_PAGE\",\n path: rp,\n message: \"Orphan page (no inbound links)\",\n });\n }\n }\n\n // --- CHECK 2: BROKEN_LINK ---\n for (const row of rows) {\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n if (!isLinkResolvable(link, pageKeySet)) {\n issues.push({\n severity: \"warning\",\n code: \"BROKEN_LINK\",\n path: row.path,\n message: `Broken wikilink [[${link}]] not found`,\n detail: link,\n });\n }\n }\n }\n\n // --- CHECK 3: STUB_PAGE ---\n for (const row of rows) {\n if (basename(row.path) === \"_index.md\") continue;\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n if (links.length === 0 && row.word_count < 50) {\n issues.push({\n severity: \"info\",\n code: \"STUB_PAGE\",\n path: row.path,\n message: `Stub page (no links, < 50 words)`,\n });\n }\n }\n\n // --- CHECK 4: STALE_SUMMARY ---\n // wiki/sources/foo-summary.md <-> sources/foo.*\n const wikiSourcesDir = join(project.wikiDir, \"sources\");\n\n for (const rp of relWikiPaths) {\n // Check if the path is under wiki/sources/\n const absWikiPage = join(project.root, rp);\n const relToWikiSources = relative(wikiSourcesDir, absWikiPage);\n\n // Skip if not under wiki/sources/ (would start with \"..\")\n if (relToWikiSources.startsWith(\"..\")) continue;\n\n // Convention: wiki/sources/foo-summary.md <-> sources/foo.*\n const summaryBasename = basename(rp, \".md\");\n // Strip -summary suffix\n const sourceBasename = summaryBasename.endsWith(\"-summary\")\n ? summaryBasename.slice(0, -\"-summary\".length)\n : summaryBasename;\n\n // Find matching source file\n const matchingSource = sourceFiles.find((sf) => {\n const sfBase = basename(sf, extname(sf));\n return sfBase === sourceBasename;\n });\n\n if (!matchingSource) continue;\n\n try {\n const summaryRow = metaMap.get(rp);\n if (!summaryRow) continue;\n\n const [sourceStat, summaryStat] = await Promise.all([\n stat(matchingSource),\n stat(join(project.root, summaryRow.path)),\n ]);\n\n if (sourceStat.mtimeMs > summaryStat.mtimeMs) {\n issues.push({\n severity: \"warning\",\n code: \"STALE_SUMMARY\",\n path: rp,\n message: \"Source updated after summary\",\n detail: relative(project.root, matchingSource),\n });\n }\n } catch {\n // Ignore stat errors\n }\n }\n\n // --- CHECK 5: MISSING_INDEX ---\n for (const rp of relWikiPaths) {\n if (basename(rp) === \"_index.md\") continue;\n if (!indexPath) {\n // No _index.md exists — skip this check\n continue;\n }\n if (!indexLinks.has(rp)) {\n // Check by filename too\n const fname = basename(rp, \".md\");\n if (!indexLinks.has(fname)) {\n issues.push({\n severity: \"info\",\n code: \"MISSING_INDEX\",\n path: rp,\n message: \"Not in _index.md\",\n });\n }\n }\n }\n\n return { issues, pagesChecked, sourcesChecked };\n}\n\nfunction resolveLink(\n link: string,\n relPaths: string[],\n projectRoot: string,\n wikiDir: string,\n): string | null {\n for (const rp of relPaths) {\n const fname = basename(rp, \".md\");\n if (fname === link) return rp;\n if (rp === link || rp === `${link}.md`) return rp;\n // relative to wikiDir\n const absPath = join(projectRoot, rp);\n const relToWiki = relative(wikiDir, absPath);\n if (relToWiki === link || relToWiki.replace(/\\.md$/i, \"\") === link) {\n return rp;\n }\n }\n return null;\n}\n\nfunction isLinkResolvable(link: string, pageKeySet: Set<string>): boolean {\n return pageKeySet.has(link);\n}\n","// packages/core/src/log-parser.ts\n\nexport interface ParsedLogEntry {\n heading: string;\n body: string;\n}\n\n/**\n * Parses a log.md file into an array of entries.\n * Each entry starts with a level-2 heading (## ...).\n * The top-level \"# Activity Log\" heading is skipped.\n */\nexport function parseLogEntries(content: string): ParsedLogEntry[] {\n const entries: ParsedLogEntry[] = [];\n const sections = content.split(/^(?=## )/m);\n\n for (const section of sections) {\n const trimmed = section.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"# \")) continue;\n if (!trimmed.startsWith(\"## \")) continue;\n\n const newlineIdx = trimmed.indexOf(\"\\n\");\n if (newlineIdx === -1) {\n entries.push({ heading: trimmed.slice(3).trim(), body: \"\" });\n } else {\n const heading = trimmed.slice(3, newlineIdx).trim();\n const body = trimmed.slice(newlineIdx + 1).trim();\n entries.push({ heading, body });\n }\n }\n\n return entries;\n}\n"],"mappings":"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","names":["TOML","import_promises","import_toml","TOML","import_promises","import_node_path","import_node_path","Database","import_promises","matter","import_promises","import_node_path","import_promises","import_node_path","content","filename","raw","import_promises","import_node_path","import_promises","import_node_path","SYSTEM_PROMPT","assertWithinRoot","readFileSafe","import_promises","import_node_path","collectMdFiles"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/init.ts","../src/config.ts","../src/project.ts","../src/db.ts","../src/markdown.ts","../src/indexer.ts","../src/search.ts","../src/source-reader.ts","../src/llm.ts","../src/ingest.ts","../src/query.ts","../src/lint.ts","../src/log-parser.ts"],"sourcesContent":["// Core package — business logic\n\nexport const VERSION = \"0.1.0\";\n\nexport { initProject } from \"./init.js\";\nexport type { InitOptions } from \"./init.js\";\n\nexport { parseConfig } from \"./config.js\";\nexport type { KbConfig } from \"./config.js\";\n\nexport { loadProject, tryLoadProject } from \"./project.js\";\nexport type { Project } from \"./project.js\";\n\nexport { openDb, closeDb } from \"./db.js\";\n\nexport { parsePage } from \"./markdown.js\";\nexport type { ParsedPage } from \"./markdown.js\";\n\nexport { indexProject } from \"./indexer.js\";\nexport type { IndexStats } from \"./indexer.js\";\n\nexport { searchWiki } from \"./search.js\";\nexport type { SearchResult, SearchOptions } from \"./search.js\";\n\nexport { readSource } from \"./source-reader.js\";\nexport type { SourceContent, SourceType } from \"./source-reader.js\";\n\nexport { createLlmAdapter } from \"./llm.js\";\nexport type { LlmAdapter, LlmMessage } from \"./llm.js\";\n\nexport type { IngestResult } from \"./ingest-types.js\";\n\nexport { ingestSource } from \"./ingest.js\";\nexport type { IngestOptions, IngestPlan } from \"./ingest.js\";\n\nexport { queryWiki } from \"./query.js\";\nexport type { QueryResult, QueryOptions } from \"./query.js\";\n\nexport { lintProject } from \"./lint.js\";\nexport type { LintIssue, LintResult, LintSeverity } from \"./lint.js\";\n\nexport { parseLogEntries } from \"./log-parser.js\";\nexport type { ParsedLogEntry } from \"./log-parser.js\";\n","import { mkdir, writeFile, access, rm } from \"node:fs/promises\";\nimport { join, basename } from \"node:path\";\nimport TOML from \"@iarna/toml\";\n\nexport interface InitOptions {\n name: string;\n directory: string; // absolute path where to init\n}\n\nfunction resolveProjectName(options: InitOptions): string {\n return options.name || basename(options.directory);\n}\n\nfunction buildConfigToml(projectName: string): string {\n const config = {\n project: {\n name: projectName,\n version: \"0.1.0\",\n },\n directories: {\n sources: \"sources\",\n wiki: \"wiki\",\n },\n llm: {\n provider: \"anthropic\",\n model: \"claude-sonnet-4-20250514\",\n },\n };\n\n const tomlStr = TOML.stringify(config as TOML.JsonMap);\n return (\n tomlStr +\n '\\n[dependencies]\\n# shared-glossary = { path = \"../shared-glossary\" }\\n'\n );\n}\n\nfunction buildSchemaMd(): string {\n return `# KB Schema — LLM Instructions\n\nThis file defines the conventions for this knowledge base. The \\`kb\\` CLI and any\nLLM operating on this wiki MUST follow these rules.\n\n---\n\n## Wiki Structure Conventions\n\n- All pages live under the \\`wiki/\\` directory.\n- \\`wiki/_index.md\\` is the wiki root and serves as a table of contents.\n- Sub-topics may be organised into sub-directories: \\`wiki/<topic>/_index.md\\`.\n- File names use kebab-case, e.g. \\`wiki/authentication-flow.md\\`.\n- Every page must have a valid YAML frontmatter block.\n\n---\n\n## Frontmatter Schema\n\nEvery wiki page must begin with a YAML frontmatter block:\n\n\\`\\`\\`yaml\n---\ntitle: <Human-readable page title>\ntags: [tag1, tag2] # optional; array of lowercase strings\ncreated: <ISO 8601 date> # e.g. 2026-04-05\nupdated: <ISO 8601 date> # updated whenever content changes\nsource: <path or URL> # optional; original source material\n---\n\\`\\`\\`\n\nRequired fields: \\`title\\`, \\`created\\`.\n\n---\n\n## Page Templates\n\n### Entity Page\nUse for: people, systems, services, tools.\n\n\\`\\`\\`markdown\n---\ntitle: <Entity Name>\ntags: [entity]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# <Entity Name>\n\n**Type**: <system | person | service | tool>\n\n## Overview\n\n<One-paragraph description.>\n\n## Key Attributes\n\n- **Attribute**: value\n\n## Related\n\n- [[related-page]]\n\\`\\`\\`\n\n### Concept Page\nUse for: ideas, patterns, terminology.\n\n\\`\\`\\`markdown\n---\ntitle: <Concept Name>\ntags: [concept]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# <Concept Name>\n\n## Definition\n\n<Clear definition in 1-3 sentences.>\n\n## Context\n\n<When and why this concept matters in the project.>\n\n## See Also\n\n- [[related-concept]]\n\\`\\`\\`\n\n### Source Summary Page\nUse for: summarised source material (docs, papers, meetings).\n\n\\`\\`\\`markdown\n---\ntitle: Summary — <Source Title>\ntags: [source-summary]\ncreated: <ISO date>\nsource: <path or URL>\n---\n\n# Summary — <Source Title>\n\n## Key Points\n\n- Point one\n- Point two\n\n## Decisions / Implications\n\n<What this source means for the project.>\n\n## Raw Source\n\nSee \\`sources/<filename>\\`.\n\\`\\`\\`\n\n### Comparison Page\nUse for: side-by-side evaluation of options.\n\n\\`\\`\\`markdown\n---\ntitle: Comparison — <Topic>\ntags: [comparison]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# Comparison — <Topic>\n\n| Criterion | Option A | Option B |\n|-----------|----------|----------|\n| ... | ... | ... |\n\n## Recommendation\n\n<Which option and why.>\n\\`\\`\\`\n\n---\n\n## Wikilink Conventions\n\n- Basic link: \\`[[page-name]]\\` — links to \\`wiki/page-name.md\\`.\n- Display text: \\`[[page-name|display text]]\\` — renders as \"display text\".\n- Cross-directory: \\`[[topic/sub-page]]\\`.\n- All wikilink targets must be lowercase kebab-case matching the file name without \\`.md\\`.\n\n---\n\n## Ingest Workflow\n\n1. Place the source file in \\`sources/\\` (PDF, Markdown, plain text, etc.).\n2. Run \\`kb ingest sources/<filename>\\`.\n3. The CLI reads the file, calls the configured LLM, and generates a source-summary\n page in \\`wiki/\\`.\n4. The summary page is linked from \\`wiki/_index.md\\` under **Sources**.\n5. An entry is appended to \\`log.md\\`.\n\n---\n\n## Query Workflow\n\n1. Run \\`kb query \"<natural-language question>\"\\`.\n2. The CLI searches the wiki index for relevant pages.\n3. Relevant page content is assembled into a prompt context.\n4. The LLM answers the question, citing wikilinks.\n5. The answer is printed to stdout. Nothing is written to disk unless \\`--save\\` is passed.\n\n---\n\n## Lint Workflow\n\nRun \\`kb lint\\` to check for:\n\n- Pages missing required frontmatter fields (\\`title\\`, \\`created\\`).\n- Broken wikilinks (targets that don't resolve to an existing page).\n- Pages not reachable from \\`wiki/_index.md\\`.\n- Duplicate page titles across the wiki.\n- Frontmatter fields with invalid types or formats.\n\nLint exits with code 0 on success, 1 if errors are found.\n`;\n}\n\nfunction buildIndexMd(projectName: string, isoDate: string): string {\n return `---\ntitle: ${projectName} Knowledge Base\ncreated: ${isoDate}\n---\n\n# ${projectName} Knowledge Base\n\n> This wiki is maintained by the \\`kb\\` CLI tool.\n\n## Pages\n\n(No pages yet. Use \\`kb ingest <source>\\` to add content.)\n\n## Sources\n\n(No sources yet.)\n`;\n}\n\nfunction buildLogMd(projectName: string, isoDate: string): string {\n return `# Activity Log\n\n## ${isoDate} — Project initialized\n\nProject \\`${projectName}\\` initialized.\n`;\n}\n\nasync function kbDirExists(directory: string): Promise<boolean> {\n try {\n await access(join(directory, \".kb\"));\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function initProject(options: InitOptions): Promise<void> {\n const projectName = resolveProjectName(options);\n const { directory } = options;\n\n if (await kbDirExists(directory)) {\n throw new Error(\n `Knowledge base already initialized: .kb/ already exists in ${directory}`,\n );\n }\n\n const isoDate = new Date().toISOString().split(\"T\")[0]!;\n\n try {\n await Promise.all([\n mkdir(join(directory, \".kb\"), { recursive: true }),\n mkdir(join(directory, \"sources\"), { recursive: true }),\n mkdir(join(directory, \"wiki\"), { recursive: true }),\n ]);\n\n await Promise.all([\n writeFile(\n join(directory, \".kb\", \"config.toml\"),\n buildConfigToml(projectName),\n \"utf8\",\n ),\n writeFile(join(directory, \".kb\", \"schema.md\"), buildSchemaMd(), \"utf8\"),\n writeFile(join(directory, \"sources\", \".gitkeep\"), \"\", \"utf8\"),\n writeFile(\n join(directory, \"wiki\", \"_index.md\"),\n buildIndexMd(projectName, isoDate),\n \"utf8\",\n ),\n writeFile(\n join(directory, \"log.md\"),\n buildLogMd(projectName, isoDate),\n \"utf8\",\n ),\n ]);\n } catch (error) {\n // Rollback: remove .kb/ if it was created\n await rm(join(directory, \".kb\"), { recursive: true, force: true });\n throw error;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport TOML from \"@iarna/toml\";\n\nexport interface KbConfig {\n project: {\n name: string;\n version: string;\n };\n directories: {\n sources: string;\n wiki: string;\n };\n llm: {\n provider: \"anthropic\" | \"openai\" | \"ollama\";\n model: string;\n };\n dependencies: Record<\n string,\n { path?: string; git?: string; branch?: string; mode?: string }\n >;\n}\n\nconst VALID_PROVIDERS = [\"anthropic\", \"openai\", \"ollama\"] as const;\n\nfunction requireSafeRelativePath(val: string, field: string): void {\n if (val.startsWith(\"/\") || val.split(\"/\").includes(\"..\")) {\n throw new Error(\n `Invalid config: ${field} must be a safe relative path, got \"${val}\"`,\n );\n }\n}\n\nfunction requireString(\n obj: Record<string, unknown>,\n key: string,\n context: string,\n): string {\n const val = obj[key];\n if (typeof val !== \"string\" || val.trim() === \"\") {\n throw new Error(\n `Invalid config: missing required field \"${context}.${key}\"`,\n );\n }\n return val;\n}\n\nfunction requireSection(\n obj: Record<string, unknown>,\n key: string,\n): Record<string, unknown> {\n const val = obj[key];\n if (\n val === undefined ||\n val === null ||\n typeof val !== \"object\" ||\n Array.isArray(val)\n ) {\n throw new Error(`Invalid config: missing required section \"[${key}]\"`);\n }\n return val as Record<string, unknown>;\n}\n\nexport async function parseConfig(configPath: string): Promise<KbConfig> {\n let raw: string;\n try {\n raw = await readFile(configPath, \"utf8\");\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Config file not found: ${configPath}\\n${message}`);\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = TOML.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid TOML in config file ${configPath}: ${message}`);\n }\n\n const project = requireSection(parsed, \"project\");\n const name = requireString(project, \"name\", \"project\");\n const version = requireString(project, \"version\", \"project\");\n\n const directories = requireSection(parsed, \"directories\");\n const sources = requireString(directories, \"sources\", \"directories\");\n requireSafeRelativePath(sources, \"directories.sources\");\n const wiki = requireString(directories, \"wiki\", \"directories\");\n requireSafeRelativePath(wiki, \"directories.wiki\");\n\n const llm = requireSection(parsed, \"llm\");\n const providerRaw = requireString(llm, \"provider\", \"llm\");\n if (!(VALID_PROVIDERS as readonly string[]).includes(providerRaw)) {\n throw new Error(\n `Invalid config: llm.provider must be one of ${VALID_PROVIDERS.join(\", \")}, got \"${providerRaw}\"`,\n );\n }\n const provider = providerRaw as KbConfig[\"llm\"][\"provider\"];\n const model = requireString(llm, \"model\", \"llm\");\n\n const rawDeps = parsed[\"dependencies\"];\n const dependencies: KbConfig[\"dependencies\"] = {};\n if (\n rawDeps !== undefined &&\n rawDeps !== null &&\n typeof rawDeps === \"object\" &&\n !Array.isArray(rawDeps)\n ) {\n for (const [depKey, depVal] of Object.entries(\n rawDeps as Record<string, unknown>,\n )) {\n if (\n typeof depVal === \"object\" &&\n depVal !== null &&\n !Array.isArray(depVal)\n ) {\n const dep = depVal as Record<string, unknown>;\n dependencies[depKey] = {\n ...(typeof dep[\"path\"] === \"string\" ? { path: dep[\"path\"] } : {}),\n ...(typeof dep[\"git\"] === \"string\" ? { git: dep[\"git\"] } : {}),\n ...(typeof dep[\"branch\"] === \"string\"\n ? { branch: dep[\"branch\"] }\n : {}),\n ...(typeof dep[\"mode\"] === \"string\" ? { mode: dep[\"mode\"] } : {}),\n };\n }\n }\n }\n\n return {\n project: { name, version },\n directories: { sources, wiki },\n llm: { provider, model },\n dependencies,\n };\n}\n","import { access } from \"node:fs/promises\";\nimport { join, dirname, resolve } from \"node:path\";\nimport { parseConfig, type KbConfig } from \"./config.js\";\n\nexport interface Project {\n name: string;\n root: string;\n kbDir: string;\n sourcesDir: string;\n wikiDir: string;\n config: KbConfig;\n}\n\nasync function hasKbDir(dir: string): Promise<boolean> {\n try {\n await access(join(dir, \".kb\", \"config.toml\"));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function findProjectRoot(startDir: string): Promise<string | null> {\n let current = resolve(startDir);\n\n while (true) {\n if (await hasKbDir(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root\n return null;\n }\n current = parent;\n }\n}\n\nexport async function loadProject(startDir: string): Promise<Project> {\n const root = await findProjectRoot(startDir);\n if (root === null) {\n throw new Error(\n `No kb project found. Run \"kb init\" to initialize a knowledge base in the current directory.`,\n );\n }\n\n const kbDir = join(root, \".kb\");\n const configPath = join(kbDir, \"config.toml\");\n const config = await parseConfig(configPath);\n\n return {\n name: config.project.name,\n root,\n kbDir,\n sourcesDir: join(root, config.directories.sources),\n wikiDir: join(root, config.directories.wiki),\n config,\n };\n}\n\nexport async function tryLoadProject(\n startDir: string,\n): Promise<Project | null> {\n try {\n return await loadProject(startDir);\n } catch (err: unknown) {\n if (err instanceof Error && /no kb project found/i.test(err.message)) {\n return null;\n }\n throw err;\n }\n}\n","import Database from \"better-sqlite3\";\nimport { join } from \"node:path\";\nimport type { Project } from \"./project.js\";\n\nconst SCHEMA_SQL = `\nCREATE VIRTUAL TABLE IF NOT EXISTS pages USING fts5(\n path,\n title,\n content,\n tags,\n project,\n tokenize='porter unicode61'\n);\n\nCREATE TABLE IF NOT EXISTS page_meta (\n path TEXT PRIMARY KEY,\n sha256 TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n word_count INTEGER NOT NULL DEFAULT 0,\n frontmatter TEXT NOT NULL DEFAULT '{}',\n outgoing_links TEXT NOT NULL DEFAULT '[]',\n updated_at INTEGER NOT NULL\n);\n`;\n\nexport function openDb(project: Project): Database.Database {\n const dbPath = join(project.kbDir, \"index.db\");\n const db = new Database(dbPath);\n db.pragma(\"journal_mode = WAL\");\n db.exec(SCHEMA_SQL);\n return db;\n}\n\nexport function closeDb(db: Database.Database): void {\n db.close();\n}\n","import { readFile } from \"node:fs/promises\";\nimport matter from \"gray-matter\";\n\nexport interface ParsedPage {\n path: string;\n title: string;\n content: string;\n tags: string;\n frontmatter: Record<string, unknown>;\n outgoingLinks: string[];\n wordCount: number;\n}\n\nconst WIKILINK_RE = /\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]/g;\nconst H1_RE = /^#\\s+(.+)$/m;\n\nfunction extractTitle(\n fm: Record<string, unknown>,\n content: string,\n relativePath: string,\n): string {\n if (typeof fm[\"title\"] === \"string\" && fm[\"title\"].trim() !== \"\") {\n return fm[\"title\"].trim();\n }\n const h1Match = H1_RE.exec(content);\n if (h1Match) {\n return h1Match[1]!.trim();\n }\n // Fallback: use filename without extension\n const filename = relativePath.split(\"/\").pop() ?? relativePath;\n return filename.replace(/\\.md$/i, \"\");\n}\n\nfunction extractTags(fm: Record<string, unknown>): string {\n const tags = fm[\"tags\"];\n if (!Array.isArray(tags)) return \"\";\n return tags.filter((t): t is string => typeof t === \"string\").join(\",\");\n}\n\nfunction extractWikiLinks(content: string): string[] {\n const links: string[] = [];\n let match: RegExpExecArray | null;\n const re = new RegExp(WIKILINK_RE.source, \"g\");\n while ((match = re.exec(content)) !== null) {\n links.push(match[1]!.trim());\n }\n return links;\n}\n\nfunction countWords(text: string): number {\n const trimmed = text.trim();\n if (trimmed === \"\") return 0;\n return trimmed.split(/\\s+/).length;\n}\n\nexport async function parsePage(\n filePath: string,\n relativePath: string,\n rawContent?: string,\n): Promise<ParsedPage> {\n const raw = rawContent ?? (await readFile(filePath, \"utf8\"));\n const parsed = matter(raw);\n const fm = parsed.data as Record<string, unknown>;\n const content = parsed.content;\n\n const title = extractTitle(fm, content, relativePath);\n const tags = extractTags(fm);\n const outgoingLinks = extractWikiLinks(content);\n const wordCount = countWords(content);\n\n return {\n path: relativePath,\n title,\n content,\n tags,\n frontmatter: fm,\n outgoingLinks,\n wordCount,\n };\n}\n","import { createHash } from \"node:crypto\";\nimport { readFile, stat, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport Database from \"better-sqlite3\";\nimport type { Project } from \"./project.js\";\nimport { parsePage } from \"./markdown.js\";\nimport { openDb, closeDb } from \"./db.js\";\n\nexport interface IndexStats {\n indexed: number;\n skipped: number;\n deleted: number;\n errors: number;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\nfunction sha256(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\ninterface PageMetaRow {\n sha256: string;\n}\n\ninterface UpsertStmts {\n deletePages: Database.Statement;\n insertPage: Database.Statement;\n upsertMeta: Database.Statement;\n}\n\nfunction upsertParsedPage(\n stmts: UpsertStmts,\n project: Project,\n page: Awaited<ReturnType<typeof parsePage>>,\n hash: string,\n mtime: number,\n): void {\n stmts.deletePages.run(page.path);\n stmts.insertPage.run(\n page.path,\n page.title,\n page.content,\n page.tags,\n project.name,\n );\n stmts.upsertMeta.run(\n page.path,\n hash,\n mtime,\n page.wordCount,\n JSON.stringify(page.frontmatter),\n JSON.stringify(page.outgoingLinks),\n Date.now(),\n );\n}\n\nexport async function indexProject(\n project: Project,\n rebuild = false,\n): Promise<IndexStats> {\n const db = openDb(project);\n try {\n if (rebuild) {\n db.exec(\"DELETE FROM pages; DELETE FROM page_meta;\");\n }\n\n const files = await collectMdFiles(project.wikiDir);\n const stats: IndexStats = { indexed: 0, skipped: 0, deleted: 0, errors: 0 };\n\n const getMetaStmt = db.prepare<[string], PageMetaRow>(\n \"SELECT sha256 FROM page_meta WHERE path = ?\",\n );\n\n const upsertStmts: UpsertStmts = {\n deletePages: db.prepare(\"DELETE FROM pages WHERE path = ?\"),\n insertPage: db.prepare(\n \"INSERT INTO pages(path, title, content, tags, project) VALUES (?, ?, ?, ?, ?)\",\n ),\n upsertMeta: db.prepare(`\n INSERT INTO page_meta(path, sha256, mtime, word_count, frontmatter, outgoing_links, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(path) DO UPDATE SET\n sha256 = excluded.sha256,\n mtime = excluded.mtime,\n word_count = excluded.word_count,\n frontmatter = excluded.frontmatter,\n outgoing_links = excluded.outgoing_links,\n updated_at = excluded.updated_at\n `),\n };\n\n const deleteStalePages = db.prepare(\"DELETE FROM pages WHERE path = ?\");\n const deleteStaleMeta = db.prepare(\"DELETE FROM page_meta WHERE path = ?\");\n const listMetaStmt = db.prepare<[], { path: string }>(\n \"SELECT path FROM page_meta\",\n );\n\n const processFile = db.transaction(\n (\n page: Awaited<ReturnType<typeof parsePage>>,\n hash: string,\n mtime: number,\n ) => {\n upsertParsedPage(upsertStmts, project, page, hash, mtime);\n },\n );\n\n const onDiskPaths = new Set<string>();\n\n for (const absPath of files) {\n const relPath = relative(project.root, absPath);\n onDiskPaths.add(relPath);\n\n let raw: string;\n try {\n raw = await readFile(absPath, \"utf8\");\n } catch (err) {\n stats.errors++;\n continue;\n }\n\n const hash = sha256(raw);\n const existing = getMetaStmt.get(relPath);\n\n if (existing && existing.sha256 === hash) {\n stats.skipped++;\n continue;\n }\n\n let fileStat: Awaited<ReturnType<typeof stat>>;\n try {\n fileStat = await stat(absPath);\n } catch {\n stats.errors++;\n continue;\n }\n\n let page: Awaited<ReturnType<typeof parsePage>>;\n try {\n page = await parsePage(absPath, relPath, raw);\n } catch {\n stats.errors++;\n continue;\n }\n\n try {\n processFile(page, hash, Math.floor(fileStat.mtimeMs));\n stats.indexed++;\n } catch {\n stats.errors++;\n }\n }\n\n // Remove entries for deleted files\n const allMetaPaths = listMetaStmt.all().map((r) => r.path);\n\n const stalePaths = allMetaPaths.filter((p) => !onDiskPaths.has(p));\n\n const deleteStale = db.transaction((paths: string[]) => {\n for (const p of paths) {\n deleteStalePages.run(p);\n deleteStaleMeta.run(p);\n }\n });\n\n deleteStale(stalePaths);\n stats.deleted += stalePaths.length;\n\n return stats;\n } finally {\n closeDb(db);\n }\n}\n","import Database from \"better-sqlite3\";\n\nexport interface SearchResult {\n rank: number;\n path: string;\n title: string;\n snippet: string;\n tags: string[];\n}\n\nexport interface SearchOptions {\n limit?: number;\n tags?: string[];\n}\n\ninterface FtsRow {\n path: string;\n title: string;\n tags: string;\n rank: number;\n snippet: string;\n}\n\nfunction sanitizeFtsQuery(query: string): string {\n // Split into tokens, quote each one to escape special FTS5 chars.\n // Using individual quoted tokens (AND logic) instead of a single phrase\n // so non-adjacent words still match.\n const tokens = query\n .trim()\n .split(/\\s+/)\n .filter((t) => t.length > 0);\n if (tokens.length === 0) return '\"\"';\n return tokens.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" \");\n}\n\nfunction parseTags(raw: string): string[] {\n if (!raw || raw.trim() === \"\") return [];\n return raw\n .split(\",\")\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n}\n\nexport function searchWiki(\n db: Database.Database,\n query: string,\n projectName: string,\n options?: SearchOptions,\n): SearchResult[] {\n if (!query || query.trim() === \"\") {\n return [];\n }\n\n const limit = options?.limit ?? 10;\n const ftsQuery = sanitizeFtsQuery(query.trim());\n\n // Build dynamic tag WHERE clauses so filtering happens in SQL\n const filterTags = options?.tags?.length\n ? options.tags\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0)\n : [];\n\n const tagClauses = filterTags.map(() => \"AND lower(tags) LIKE ?\").join(\" \");\n const tagParams = filterTags.map((t) => `%${t}%`);\n\n const stmt = db.prepare<[string, string, ...string[], number], FtsRow>(`\n SELECT path, title, tags, bm25(pages) as rank,\n snippet(pages, 2, '', '', '...', 8) as snippet\n FROM pages\n WHERE pages MATCH ? AND project = ?\n ${tagClauses}\n ORDER BY rank\n LIMIT ?\n `);\n\n const rows = stmt.all(ftsQuery, projectName, ...tagParams, limit);\n\n const results: SearchResult[] = rows.map((row) => ({\n rank: row.rank,\n path: row.path,\n title: row.title,\n snippet: row.snippet,\n tags: parseTags(row.tags),\n }));\n\n return results;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\n\nexport type SourceType = \"markdown\" | \"text\" | \"pdf\" | \"url\";\n\nexport interface SourceContent {\n type: SourceType;\n originalPath: string;\n content: string;\n filename: string;\n}\n\nfunction sanitizeFilename(name: string): string {\n return name.toLowerCase().replace(/\\s/g, \"-\");\n}\n\nfunction detectType(sourcePath: string): SourceType {\n if (sourcePath.startsWith(\"http://\") || sourcePath.startsWith(\"https://\")) {\n return \"url\";\n }\n const ext = extname(sourcePath).toLowerCase();\n if (ext === \".pdf\") return \"pdf\";\n if (ext === \".md\") return \"markdown\";\n return \"text\";\n}\n\nfunction filenameFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const parts = parsed.pathname.split(\"/\").filter(Boolean);\n const last = parts[parts.length - 1];\n const pagePart = last\n ? last.includes(\".\")\n ? last\n : `${last}.html`\n : \"index.html\";\n return sanitizeFilename(`${parsed.hostname}-${pagePart}`);\n } catch {\n return \"url-content.html\";\n }\n}\n\nfunction stripHtml(html: string): string {\n // Remove script and style blocks entirely\n let text = html.replace(/<script[\\s\\S]*?<\\/script>/gi, \" \");\n text = text.replace(/<style[\\s\\S]*?<\\/style>/gi, \" \");\n // Strip remaining tags\n text = text.replace(/<[^>]+>/g, \" \");\n // Decode common HTML entities\n text = text\n .replace(/&amp;/gi, \"&\")\n .replace(/&lt;/gi, \"<\")\n .replace(/&gt;/gi, \">\")\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;/gi, \"'\")\n .replace(/&nbsp;/gi, \" \");\n // Collapse whitespace\n text = text.replace(/\\s+/g, \" \").trim();\n return text;\n}\n\nasync function readPdf(filePath: string): Promise<string> {\n const pdfParse: (buf: Buffer) => Promise<{ text: string }> =\n await import(\"pdf-parse\")\n .then((m) => m.default ?? m)\n .catch(() => {\n throw new Error(\n \"PDF support requires pdf-parse: run `npm install pdf-parse` in your project\",\n );\n });\n const buffer = await readFile(filePath);\n const data = await pdfParse(buffer);\n return data.text;\n}\n\nfunction isPrivateUrl(url: string): boolean {\n try {\n const { hostname } = new URL(url);\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname.startsWith(\"169.254.\") || // link-local\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"192.168.\") ||\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(hostname)\n );\n } catch {\n return false;\n }\n}\n\nasync function fetchUrl(url: string): Promise<string> {\n if (isPrivateUrl(url)) {\n throw new Error(`Fetching private/localhost URLs is not allowed: ${url}`);\n }\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch URL ${url}: HTTP ${response.status} ${response.statusText}`,\n );\n }\n const html = await response.text();\n return stripHtml(html);\n}\n\nexport async function readSource(sourcePath: string): Promise<SourceContent> {\n const type = detectType(sourcePath);\n\n if (type === \"url\") {\n const content = await fetchUrl(sourcePath);\n const filename = filenameFromUrl(sourcePath);\n return { type, originalPath: sourcePath, content, filename };\n }\n\n if (type === \"pdf\") {\n const content = await readPdf(sourcePath);\n const raw = basename(sourcePath);\n const filename = sanitizeFilename(raw);\n return { type, originalPath: sourcePath, content, filename };\n }\n\n // markdown or text\n const content = await readFile(sourcePath, \"utf8\");\n const raw = basename(sourcePath);\n const filename = sanitizeFilename(raw);\n return { type, originalPath: sourcePath, content, filename };\n}\n","import type { KbConfig } from \"./config.js\";\n\nexport interface LlmMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\nexport interface LlmAdapter {\n complete(messages: LlmMessage[], systemPrompt: string): Promise<string>;\n}\n\nfunction createAnthropicAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"ANTHROPIC_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"ANTHROPIC_API_KEY environment variable is not set\");\n }\n const Anthropic = await import(\"@anthropic-ai/sdk\").then(\n (m) => m.default ?? m,\n );\n const client = new Anthropic({ apiKey });\n const response = await client.messages.create({\n model,\n max_tokens: 8192,\n system: systemPrompt,\n messages: messages.map((m) => ({\n role: m.role,\n content: m.content,\n })),\n });\n const block = response.content[0];\n if (!block || block.type !== \"text\") {\n throw new Error(\"Anthropic returned no text content\");\n }\n return block.text;\n },\n };\n}\n\nfunction createOpenAiAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"OPENAI_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"OPENAI_API_KEY environment variable is not set\");\n }\n const body = {\n model,\n messages: [\n { role: \"system\", content: systemPrompt },\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n };\n const response = await fetch(\n \"https://api.openai.com/v1/chat/completions\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n },\n );\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OpenAI API error: HTTP ${response.status} — ${text}`);\n }\n const data = (await response.json()) as {\n choices: Array<{ message: { content: string } }>;\n };\n const content = data.choices[0]?.message?.content;\n if (!content) {\n throw new Error(\"OpenAI returned no content\");\n }\n return content;\n },\n };\n}\n\nfunction createOllamaAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const baseUrl =\n process.env[\"OLLAMA_BASE_URL\"] ?? \"http://localhost:11434\";\n const body = {\n model,\n messages: [\n { role: \"system\", content: systemPrompt },\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n stream: false,\n };\n const response = await fetch(`${baseUrl}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Ollama API error: HTTP ${response.status} — ${text}`);\n }\n const data = (await response.json()) as {\n message?: { content: string };\n };\n const content = data.message?.content;\n if (!content) {\n throw new Error(\"Ollama returned no content\");\n }\n return content;\n },\n };\n}\n\nexport function createLlmAdapter(config: KbConfig): LlmAdapter {\n const { provider, model } = config.llm;\n switch (provider) {\n case \"anthropic\":\n return createAnthropicAdapter(model);\n case \"openai\":\n return createOpenAiAdapter(model);\n case \"ollama\":\n return createOllamaAdapter(model);\n default: {\n const _exhaustive: never = provider;\n throw new Error(`Unsupported LLM provider: ${String(_exhaustive)}`);\n }\n }\n}\n","import { readFile, writeFile, mkdir, appendFile } from \"node:fs/promises\";\nimport { join, dirname, resolve } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport type { LlmAdapter } from \"./llm.js\";\nimport type { IngestResult } from \"./ingest-types.js\";\nimport { readSource } from \"./source-reader.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport interface IngestOptions {\n apply?: boolean;\n batch?: boolean;\n}\n\nexport interface IngestPlan {\n result: IngestResult;\n sourceFile: string;\n dryRun: boolean;\n}\n\nconst SYSTEM_PROMPT = `You are an AI assistant maintaining a knowledge base wiki.\nYou will be given a new source document and the current state of the wiki.\nYour task is to integrate the new knowledge into the wiki.\n\nReturn ONLY a JSON object matching this exact schema (no markdown fences):\n{\n \"summary\": { \"path\": \"wiki/sources/<filename>-summary.md\", \"content\": \"...\" },\n \"updates\": [{ \"path\": \"...\", \"content\": \"...\", \"reason\": \"...\" }],\n \"newPages\": [{ \"path\": \"...\", \"content\": \"...\", \"reason\": \"...\" }],\n \"indexUpdate\": \"...\",\n \"logEntry\": \"...\"\n}`;\n\nfunction assertWithinRoot(absPath: string, root: string): void {\n const resolvedPath = resolve(absPath);\n const resolvedRoot = resolve(root) + \"/\";\n if (!resolvedPath.startsWith(resolvedRoot)) {\n throw new Error(\n `Unsafe path rejected: \"${absPath}\" is outside project root`,\n );\n }\n}\n\nasync function readFileSafe(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nfunction parseIngestResult(raw: string): IngestResult {\n const cleaned = raw\n .replace(/^```(?:json)?\\s*/i, \"\")\n .replace(/\\s*```\\s*$/i, \"\")\n .trim();\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch (err) {\n throw new Error(\n `Invalid LLM response: could not parse JSON. Raw response: ${cleaned.slice(0, 200)}`,\n );\n }\n\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"Invalid LLM response: expected a JSON object\");\n }\n\n const obj = parsed as Record<string, unknown>;\n\n if (\n !obj[\"summary\"] ||\n typeof obj[\"summary\"] !== \"object\" ||\n Array.isArray(obj[\"summary\"])\n ) {\n throw new Error('Invalid LLM response: missing \"summary\" object');\n }\n\n const summary = obj[\"summary\"] as Record<string, unknown>;\n if (\n typeof summary[\"path\"] !== \"string\" ||\n typeof summary[\"content\"] !== \"string\"\n ) {\n throw new Error(\n 'Invalid LLM response: \"summary\" must have \"path\" and \"content\" strings',\n );\n }\n\n if (!Array.isArray(obj[\"updates\"])) {\n throw new Error('Invalid LLM response: \"updates\" must be an array');\n }\n\n if (!Array.isArray(obj[\"newPages\"])) {\n throw new Error('Invalid LLM response: \"newPages\" must be an array');\n }\n\n if (typeof obj[\"indexUpdate\"] !== \"string\") {\n throw new Error('Invalid LLM response: \"indexUpdate\" must be a string');\n }\n\n if (typeof obj[\"logEntry\"] !== \"string\") {\n throw new Error('Invalid LLM response: \"logEntry\" must be a string');\n }\n\n const updates = (obj[\"updates\"] as unknown[]).map((u, i) => {\n if (typeof u !== \"object\" || u === null || Array.isArray(u)) {\n throw new Error(`Invalid LLM response: updates[${i}] must be an object`);\n }\n const update = u as Record<string, unknown>;\n if (\n typeof update[\"path\"] !== \"string\" ||\n typeof update[\"content\"] !== \"string\" ||\n typeof update[\"reason\"] !== \"string\"\n ) {\n throw new Error(\n `Invalid LLM response: updates[${i}] must have path, content, and reason strings`,\n );\n }\n return {\n path: update[\"path\"],\n content: update[\"content\"],\n reason: update[\"reason\"],\n };\n });\n\n const newPages = (obj[\"newPages\"] as unknown[]).map((p, i) => {\n if (typeof p !== \"object\" || p === null || Array.isArray(p)) {\n throw new Error(`Invalid LLM response: newPages[${i}] must be an object`);\n }\n const page = p as Record<string, unknown>;\n if (\n typeof page[\"path\"] !== \"string\" ||\n typeof page[\"content\"] !== \"string\" ||\n typeof page[\"reason\"] !== \"string\"\n ) {\n throw new Error(\n `Invalid LLM response: newPages[${i}] must have path, content, and reason strings`,\n );\n }\n return {\n path: page[\"path\"],\n content: page[\"content\"],\n reason: page[\"reason\"],\n };\n });\n\n return {\n summary: { path: summary[\"path\"], content: summary[\"content\"] },\n updates,\n newPages,\n indexUpdate: obj[\"indexUpdate\"] as string,\n logEntry: obj[\"logEntry\"] as string,\n };\n}\n\nasync function applyIngestResult(\n project: Project,\n result: IngestResult,\n sourceContent: string,\n sourceFilename: string,\n): Promise<void> {\n // Write summary\n const summaryAbsPath = join(project.root, result.summary.path);\n assertWithinRoot(summaryAbsPath, project.root);\n await mkdir(dirname(summaryAbsPath), { recursive: true });\n await writeFile(summaryAbsPath, result.summary.content, \"utf8\");\n\n // Write updated pages\n for (const update of result.updates) {\n const absPath = join(project.root, update.path);\n assertWithinRoot(absPath, project.root);\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, update.content, \"utf8\");\n }\n\n // Write new pages\n for (const newPage of result.newPages) {\n const absPath = join(project.root, newPage.path);\n assertWithinRoot(absPath, project.root);\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, newPage.content, \"utf8\");\n }\n\n // Update _index.md\n const indexPath = join(project.wikiDir, \"_index.md\");\n await writeFile(indexPath, result.indexUpdate, \"utf8\");\n\n // Write source file to sources directory\n const sourceDestPath = join(project.sourcesDir, sourceFilename);\n await mkdir(project.sourcesDir, { recursive: true });\n await writeFile(sourceDestPath, sourceContent, \"utf8\");\n\n // Append to log.md\n const logPath = join(project.wikiDir, \"log.md\");\n const timestamp = new Date().toISOString().split(\"T\")[0];\n const logLine = `- ${timestamp}: ${result.logEntry}\\n`;\n await appendFile(logPath, logLine, \"utf8\");\n\n // Re-index\n await indexProject(project);\n}\n\nexport async function ingestSource(\n project: Project,\n sourcePath: string,\n llm: LlmAdapter,\n options?: IngestOptions,\n): Promise<IngestPlan> {\n const apply = options?.apply ?? false;\n\n // 1. Read source content\n const sourceContent = await readSource(sourcePath);\n\n // 2. Read current wiki index\n const indexPath = join(project.wikiDir, \"_index.md\");\n const currentIndex = await readFileSafe(indexPath);\n\n // 3. Read schema\n const schemaPath = join(project.kbDir, \"schema.md\");\n const schema = await readFileSafe(schemaPath);\n\n // 4. Build user message\n const userMessage = `## Wiki Schema\n${schema}\n\n## Current Wiki Index\n${currentIndex}\n\n## New Source: ${sourceContent.filename}\n${sourceContent.content}\n\nIntegrate this source into the wiki following the schema above.`;\n\n // 5. Call LLM\n const raw = await llm.complete(\n [{ role: \"user\", content: userMessage }],\n SYSTEM_PROMPT,\n );\n\n // 6. Parse response\n const result = parseIngestResult(raw);\n\n // 7. Apply if requested\n if (apply) {\n await applyIngestResult(\n project,\n result,\n sourceContent.content,\n sourceContent.filename,\n );\n }\n\n const sourceFile = join(project.sourcesDir, sourceContent.filename);\n\n return {\n result,\n sourceFile,\n dryRun: !apply,\n };\n}\n","import { readFile, writeFile, mkdir, appendFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname, resolve } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport type { LlmAdapter } from \"./llm.js\";\nimport { openDb, closeDb } from \"./db.js\";\nimport { searchWiki } from \"./search.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport interface QueryResult {\n answer: string;\n sources: string[];\n}\n\nexport interface QueryOptions {\n save?: string;\n}\n\nconst SYSTEM_PROMPT = `You are a knowledgeable assistant answering questions about a project's knowledge base.\nAnswer concisely using only information from the provided wiki pages.\nUse [[page-name]] wikilink syntax to cite specific wiki pages in your answer.\nFormat your answer in markdown.`;\n\nfunction assertWithinRoot(absPath: string, root: string): void {\n const resolvedPath = resolve(absPath);\n const resolvedRoot = resolve(root) + \"/\";\n if (!resolvedPath.startsWith(resolvedRoot)) {\n throw new Error(\n `Unsafe path rejected: \"${absPath}\" is outside project root`,\n );\n }\n}\n\nasync function readFileSafe(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nexport async function queryWiki(\n project: Project,\n question: string,\n llm: LlmAdapter,\n options?: QueryOptions,\n): Promise<QueryResult> {\n // 1. Auto-index if db doesn't exist\n const dbPath = join(project.kbDir, \"index.db\");\n if (!existsSync(dbPath)) {\n await indexProject(project);\n }\n\n // 2. Search for top relevant pages\n const db = openDb(project);\n let searchResults;\n try {\n searchResults = searchWiki(db, question, project.name, { limit: 10 });\n } finally {\n closeDb(db);\n }\n\n // 3. Read full content of each result page\n const pages: Array<{ path: string; title: string; content: string }> = [];\n for (const result of searchResults) {\n const absPath = join(project.root, result.path);\n const content = await readFileSafe(absPath);\n if (content) {\n pages.push({ path: result.path, title: result.title, content });\n }\n }\n\n // 4. Build user message\n const pagesSection =\n pages.length > 0\n ? pages\n .map((p) => `### ${p.title} (${p.path})\\n${p.content}`)\n .join(\"\\n\\n\")\n : \"(No wiki pages found for this query.)\";\n\n const userMessage = `## Question\\n${question}\\n\\n## Relevant Wiki Pages\\n\\n${pagesSection}`;\n\n // 5. Call LLM\n const answer = await llm.complete(\n [{ role: \"user\", content: userMessage }],\n SYSTEM_PROMPT,\n );\n\n const sources = pages.map((p) => p.path);\n\n // 6. If save option provided, write answer as wiki page and append to log\n if (options?.save) {\n const saveRelPath = options.save;\n const saveAbsPath = join(project.root, saveRelPath);\n assertWithinRoot(saveAbsPath, project.root);\n\n await mkdir(dirname(saveAbsPath), { recursive: true });\n await writeFile(saveAbsPath, answer, \"utf8\");\n\n // Append to log.md\n const logPath = join(project.wikiDir, \"log.md\");\n const timestamp = new Date().toISOString().split(\"T\")[0];\n const logEntry = `\\n## ${timestamp} — Queried: ${question}\\n\\nSaved to: ${saveRelPath}\\n`;\n await appendFile(logPath, logEntry, \"utf8\");\n\n // Re-index so saved page is searchable\n await indexProject(project);\n }\n\n return { answer, sources };\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport { join, relative, basename, extname } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport { openDb, closeDb } from \"./db.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport type LintSeverity = \"warning\" | \"info\";\n\nexport interface LintIssue {\n severity: LintSeverity;\n code: string;\n path: string;\n message: string;\n detail?: string;\n}\n\nexport interface LintResult {\n issues: LintIssue[];\n pagesChecked: number;\n sourcesChecked: number;\n}\n\ninterface PageMetaRow {\n path: string;\n outgoing_links: string;\n word_count: number;\n mtime: number;\n updated_at: number;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\nasync function collectSourceFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile())\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\n/**\n * Build a set of \"keys\" for wiki pages for wikilink resolution.\n * A wikilink [[foo-bar]] can match:\n * - a page whose filename (without extension) is \"foo-bar\"\n * - a page whose relative path is \"foo-bar\" or \"foo-bar.md\"\n */\nfunction buildPageKeySet(\n relPaths: string[],\n projectRoot: string,\n wikiDir: string,\n): Set<string> {\n const keys = new Set<string>();\n for (const rp of relPaths) {\n // rp is relative to projectRoot, e.g. \"wiki/concepts/foo.md\"\n keys.add(rp);\n // without extension\n keys.add(rp.replace(/\\.md$/i, \"\"));\n // filename without extension\n const fname = basename(rp, \".md\");\n keys.add(fname);\n // relative path from wikiDir\n const absPath = join(projectRoot, rp);\n const relToWiki = relative(wikiDir, absPath);\n keys.add(relToWiki);\n keys.add(relToWiki.replace(/\\.md$/i, \"\"));\n }\n return keys;\n}\n\nexport async function lintProject(project: Project): Promise<LintResult> {\n // Ensure index is up to date\n await indexProject(project);\n\n const issues: LintIssue[] = [];\n\n // Collect all wiki md files\n const absWikiFiles = await collectMdFiles(project.wikiDir);\n const relWikiPaths = absWikiFiles.map((f) => relative(project.root, f));\n\n const pagesChecked = relWikiPaths.length;\n\n // Always collect source files so sourcesChecked is accurate\n const sourceFiles = await collectSourceFiles(project.sourcesDir);\n const sourcesChecked = sourceFiles.filter(\n (f) => basename(f) !== \".gitkeep\",\n ).length;\n\n if (pagesChecked === 0) {\n return { issues, pagesChecked: 0, sourcesChecked };\n }\n\n // Build page key set for wikilink resolution\n const pageKeySet = buildPageKeySet(\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n\n // Query all page_meta rows\n const db = openDb(project);\n let rows: PageMetaRow[];\n try {\n rows = db\n .prepare<\n [],\n PageMetaRow\n >(\"SELECT path, outgoing_links, word_count, mtime, updated_at FROM page_meta\")\n .all();\n } finally {\n closeDb(db);\n }\n\n // Build a map from path -> row for quick lookup\n const metaMap = new Map<string, PageMetaRow>();\n for (const row of rows) {\n metaMap.set(row.path, row);\n }\n\n // Build inbound link map\n const inboundLinks = new Map<string, Set<string>>();\n for (const rp of relWikiPaths) {\n inboundLinks.set(rp, new Set());\n }\n\n for (const row of rows) {\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n // Find which page this link resolves to\n const resolved = resolveLink(\n link,\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n if (resolved !== null) {\n const set = inboundLinks.get(resolved);\n if (set) {\n set.add(row.path);\n }\n }\n }\n }\n\n // Read _index.md outgoing links for MISSING_INDEX check\n const indexPath = relWikiPaths.find((p) => basename(p) === \"_index.md\");\n let indexLinks: Set<string> = new Set();\n if (indexPath) {\n const indexRow = metaMap.get(indexPath);\n if (indexRow) {\n let links: string[] = [];\n try {\n links = JSON.parse(indexRow.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n const resolved = resolveLink(\n link,\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n if (resolved !== null) {\n indexLinks.add(resolved);\n }\n }\n }\n }\n\n // --- CHECK 1: ORPHAN_PAGE ---\n for (const rp of relWikiPaths) {\n if (basename(rp) === \"_index.md\") continue;\n const inbound = inboundLinks.get(rp);\n if (!inbound || inbound.size === 0) {\n issues.push({\n severity: \"warning\",\n code: \"ORPHAN_PAGE\",\n path: rp,\n message: \"Orphan page (no inbound links)\",\n });\n }\n }\n\n // --- CHECK 2: BROKEN_LINK ---\n for (const row of rows) {\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n if (!isLinkResolvable(link, pageKeySet)) {\n issues.push({\n severity: \"warning\",\n code: \"BROKEN_LINK\",\n path: row.path,\n message: `Broken wikilink [[${link}]] not found`,\n detail: link,\n });\n }\n }\n }\n\n // --- CHECK 3: STUB_PAGE ---\n for (const row of rows) {\n if (basename(row.path) === \"_index.md\") continue;\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n if (links.length === 0 && row.word_count < 50) {\n issues.push({\n severity: \"info\",\n code: \"STUB_PAGE\",\n path: row.path,\n message: `Stub page (no links, < 50 words)`,\n });\n }\n }\n\n // --- CHECK 4: STALE_SUMMARY ---\n // wiki/sources/foo-summary.md <-> sources/foo.*\n const wikiSourcesDir = join(project.wikiDir, \"sources\");\n\n for (const rp of relWikiPaths) {\n // Check if the path is under wiki/sources/\n const absWikiPage = join(project.root, rp);\n const relToWikiSources = relative(wikiSourcesDir, absWikiPage);\n\n // Skip if not under wiki/sources/ (would start with \"..\")\n if (relToWikiSources.startsWith(\"..\")) continue;\n\n // Convention: wiki/sources/foo-summary.md <-> sources/foo.*\n const summaryBasename = basename(rp, \".md\");\n // Strip -summary suffix\n const sourceBasename = summaryBasename.endsWith(\"-summary\")\n ? summaryBasename.slice(0, -\"-summary\".length)\n : summaryBasename;\n\n // Find matching source file\n const matchingSource = sourceFiles.find((sf) => {\n const sfBase = basename(sf, extname(sf));\n return sfBase === sourceBasename;\n });\n\n if (!matchingSource) continue;\n\n try {\n const summaryRow = metaMap.get(rp);\n if (!summaryRow) continue;\n\n const [sourceStat, summaryStat] = await Promise.all([\n stat(matchingSource),\n stat(join(project.root, summaryRow.path)),\n ]);\n\n if (sourceStat.mtimeMs > summaryStat.mtimeMs) {\n issues.push({\n severity: \"warning\",\n code: \"STALE_SUMMARY\",\n path: rp,\n message: \"Source updated after summary\",\n detail: relative(project.root, matchingSource),\n });\n }\n } catch {\n // Ignore stat errors\n }\n }\n\n // --- CHECK 5: MISSING_INDEX ---\n for (const rp of relWikiPaths) {\n if (basename(rp) === \"_index.md\") continue;\n if (!indexPath) {\n // No _index.md exists — skip this check\n continue;\n }\n if (!indexLinks.has(rp)) {\n // Check by filename too\n const fname = basename(rp, \".md\");\n if (!indexLinks.has(fname)) {\n issues.push({\n severity: \"info\",\n code: \"MISSING_INDEX\",\n path: rp,\n message: \"Not in _index.md\",\n });\n }\n }\n }\n\n return { issues, pagesChecked, sourcesChecked };\n}\n\nfunction resolveLink(\n link: string,\n relPaths: string[],\n projectRoot: string,\n wikiDir: string,\n): string | null {\n for (const rp of relPaths) {\n const fname = basename(rp, \".md\");\n if (fname === link) return rp;\n if (rp === link || rp === `${link}.md`) return rp;\n // relative to wikiDir\n const absPath = join(projectRoot, rp);\n const relToWiki = relative(wikiDir, absPath);\n if (relToWiki === link || relToWiki.replace(/\\.md$/i, \"\") === link) {\n return rp;\n }\n }\n return null;\n}\n\nfunction isLinkResolvable(link: string, pageKeySet: Set<string>): boolean {\n return pageKeySet.has(link);\n}\n","// packages/core/src/log-parser.ts\n\nexport interface ParsedLogEntry {\n heading: string;\n body: string;\n}\n\n/**\n * Parses a log.md file into an array of entries.\n * Each entry starts with a level-2 heading (## ...).\n * The top-level \"# Activity Log\" heading is skipped.\n */\nexport function parseLogEntries(content: string): ParsedLogEntry[] {\n const entries: ParsedLogEntry[] = [];\n const sections = content.split(/^(?=## )/m);\n\n for (const section of sections) {\n const trimmed = section.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"# \")) continue;\n if (!trimmed.startsWith(\"## \")) continue;\n\n const newlineIdx = trimmed.indexOf(\"\\n\");\n if (newlineIdx === -1) {\n entries.push({ heading: trimmed.slice(3).trim(), body: \"\" });\n } else {\n const heading = trimmed.slice(3, newlineIdx).trim();\n const body = trimmed.slice(newlineIdx + 1).trim();\n entries.push({ heading, body });\n }\n }\n\n return entries;\n}\n"],"mappings":"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","names":["TOML","import_promises","import_toml","TOML","import_promises","import_node_path","import_node_path","Database","import_promises","matter","import_promises","import_node_path","import_promises","import_node_path","content","filename","raw","import_promises","import_node_path","import_promises","import_node_path","SYSTEM_PROMPT","assertWithinRoot","readFileSafe","import_promises","import_node_path","collectMdFiles"]}
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/init.ts","../src/config.ts","../src/project.ts","../src/db.ts","../src/markdown.ts","../src/indexer.ts","../src/search.ts","../src/source-reader.ts","../src/llm.ts","../src/ingest.ts","../src/query.ts","../src/lint.ts","../src/log-parser.ts","../src/index.ts"],"sourcesContent":["import { mkdir, writeFile, access, rm } from \"node:fs/promises\";\nimport { join, basename } from \"node:path\";\nimport TOML from \"@iarna/toml\";\n\nexport interface InitOptions {\n name: string;\n directory: string; // absolute path where to init\n}\n\nfunction resolveProjectName(options: InitOptions): string {\n return options.name || basename(options.directory);\n}\n\nfunction buildConfigToml(projectName: string): string {\n const config = {\n project: {\n name: projectName,\n version: \"0.1.0\",\n },\n directories: {\n sources: \"sources\",\n wiki: \"wiki\",\n },\n llm: {\n provider: \"anthropic\",\n model: \"claude-sonnet-4-20250514\",\n },\n };\n\n const tomlStr = TOML.stringify(config as TOML.JsonMap);\n return (\n tomlStr +\n '\\n[dependencies]\\n# shared-glossary = { path = \"../shared-glossary\" }\\n'\n );\n}\n\nfunction buildSchemaMd(): string {\n return `# KB Schema — LLM Instructions\n\nThis file defines the conventions for this knowledge base. The \\`kb\\` CLI and any\nLLM operating on this wiki MUST follow these rules.\n\n---\n\n## Wiki Structure Conventions\n\n- All pages live under the \\`wiki/\\` directory.\n- \\`wiki/_index.md\\` is the wiki root and serves as a table of contents.\n- Sub-topics may be organised into sub-directories: \\`wiki/<topic>/_index.md\\`.\n- File names use kebab-case, e.g. \\`wiki/authentication-flow.md\\`.\n- Every page must have a valid YAML frontmatter block.\n\n---\n\n## Frontmatter Schema\n\nEvery wiki page must begin with a YAML frontmatter block:\n\n\\`\\`\\`yaml\n---\ntitle: <Human-readable page title>\ntags: [tag1, tag2] # optional; array of lowercase strings\ncreated: <ISO 8601 date> # e.g. 2026-04-05\nupdated: <ISO 8601 date> # updated whenever content changes\nsource: <path or URL> # optional; original source material\n---\n\\`\\`\\`\n\nRequired fields: \\`title\\`, \\`created\\`.\n\n---\n\n## Page Templates\n\n### Entity Page\nUse for: people, systems, services, tools.\n\n\\`\\`\\`markdown\n---\ntitle: <Entity Name>\ntags: [entity]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# <Entity Name>\n\n**Type**: <system | person | service | tool>\n\n## Overview\n\n<One-paragraph description.>\n\n## Key Attributes\n\n- **Attribute**: value\n\n## Related\n\n- [[related-page]]\n\\`\\`\\`\n\n### Concept Page\nUse for: ideas, patterns, terminology.\n\n\\`\\`\\`markdown\n---\ntitle: <Concept Name>\ntags: [concept]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# <Concept Name>\n\n## Definition\n\n<Clear definition in 1-3 sentences.>\n\n## Context\n\n<When and why this concept matters in the project.>\n\n## See Also\n\n- [[related-concept]]\n\\`\\`\\`\n\n### Source Summary Page\nUse for: summarised source material (docs, papers, meetings).\n\n\\`\\`\\`markdown\n---\ntitle: Summary — <Source Title>\ntags: [source-summary]\ncreated: <ISO date>\nsource: <path or URL>\n---\n\n# Summary — <Source Title>\n\n## Key Points\n\n- Point one\n- Point two\n\n## Decisions / Implications\n\n<What this source means for the project.>\n\n## Raw Source\n\nSee \\`sources/<filename>\\`.\n\\`\\`\\`\n\n### Comparison Page\nUse for: side-by-side evaluation of options.\n\n\\`\\`\\`markdown\n---\ntitle: Comparison — <Topic>\ntags: [comparison]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# Comparison — <Topic>\n\n| Criterion | Option A | Option B |\n|-----------|----------|----------|\n| ... | ... | ... |\n\n## Recommendation\n\n<Which option and why.>\n\\`\\`\\`\n\n---\n\n## Wikilink Conventions\n\n- Basic link: \\`[[page-name]]\\` — links to \\`wiki/page-name.md\\`.\n- Display text: \\`[[page-name|display text]]\\` — renders as \"display text\".\n- Cross-directory: \\`[[topic/sub-page]]\\`.\n- All wikilink targets must be lowercase kebab-case matching the file name without \\`.md\\`.\n\n---\n\n## Ingest Workflow\n\n1. Place the source file in \\`sources/\\` (PDF, Markdown, plain text, etc.).\n2. Run \\`kb ingest sources/<filename>\\`.\n3. The CLI reads the file, calls the configured LLM, and generates a source-summary\n page in \\`wiki/\\`.\n4. The summary page is linked from \\`wiki/_index.md\\` under **Sources**.\n5. An entry is appended to \\`log.md\\`.\n\n---\n\n## Query Workflow\n\n1. Run \\`kb query \"<natural-language question>\"\\`.\n2. The CLI searches the wiki index for relevant pages.\n3. Relevant page content is assembled into a prompt context.\n4. The LLM answers the question, citing wikilinks.\n5. The answer is printed to stdout. Nothing is written to disk unless \\`--save\\` is passed.\n\n---\n\n## Lint Workflow\n\nRun \\`kb lint\\` to check for:\n\n- Pages missing required frontmatter fields (\\`title\\`, \\`created\\`).\n- Broken wikilinks (targets that don't resolve to an existing page).\n- Pages not reachable from \\`wiki/_index.md\\`.\n- Duplicate page titles across the wiki.\n- Frontmatter fields with invalid types or formats.\n\nLint exits with code 0 on success, 1 if errors are found.\n`;\n}\n\nfunction buildIndexMd(projectName: string, isoDate: string): string {\n return `---\ntitle: ${projectName} Knowledge Base\ncreated: ${isoDate}\n---\n\n# ${projectName} Knowledge Base\n\n> This wiki is maintained by the \\`kb\\` CLI tool.\n\n## Pages\n\n(No pages yet. Use \\`kb ingest <source>\\` to add content.)\n\n## Sources\n\n(No sources yet.)\n`;\n}\n\nfunction buildLogMd(projectName: string, isoDate: string): string {\n return `# Activity Log\n\n## ${isoDate} — Project initialized\n\nProject \\`${projectName}\\` initialized.\n`;\n}\n\nasync function kbDirExists(directory: string): Promise<boolean> {\n try {\n await access(join(directory, \".kb\"));\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function initProject(options: InitOptions): Promise<void> {\n const projectName = resolveProjectName(options);\n const { directory } = options;\n\n if (await kbDirExists(directory)) {\n throw new Error(\n `Knowledge base already initialized: .kb/ already exists in ${directory}`,\n );\n }\n\n const isoDate = new Date().toISOString().split(\"T\")[0]!;\n\n try {\n await Promise.all([\n mkdir(join(directory, \".kb\"), { recursive: true }),\n mkdir(join(directory, \"sources\"), { recursive: true }),\n mkdir(join(directory, \"wiki\"), { recursive: true }),\n ]);\n\n await Promise.all([\n writeFile(\n join(directory, \".kb\", \"config.toml\"),\n buildConfigToml(projectName),\n \"utf8\",\n ),\n writeFile(join(directory, \".kb\", \"schema.md\"), buildSchemaMd(), \"utf8\"),\n writeFile(join(directory, \"sources\", \".gitkeep\"), \"\", \"utf8\"),\n writeFile(\n join(directory, \"wiki\", \"_index.md\"),\n buildIndexMd(projectName, isoDate),\n \"utf8\",\n ),\n writeFile(\n join(directory, \"log.md\"),\n buildLogMd(projectName, isoDate),\n \"utf8\",\n ),\n ]);\n } catch (error) {\n // Rollback: remove .kb/ if it was created\n await rm(join(directory, \".kb\"), { recursive: true, force: true });\n throw error;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport TOML from \"@iarna/toml\";\n\nexport interface KbConfig {\n project: {\n name: string;\n version: string;\n };\n directories: {\n sources: string;\n wiki: string;\n };\n llm: {\n provider: \"anthropic\" | \"openai\" | \"ollama\";\n model: string;\n };\n dependencies: Record<\n string,\n { path?: string; git?: string; branch?: string; mode?: string }\n >;\n}\n\nconst VALID_PROVIDERS = [\"anthropic\", \"openai\", \"ollama\"] as const;\n\nfunction requireSafeRelativePath(val: string, field: string): void {\n if (val.startsWith(\"/\") || val.split(\"/\").includes(\"..\")) {\n throw new Error(\n `Invalid config: ${field} must be a safe relative path, got \"${val}\"`,\n );\n }\n}\n\nfunction requireString(\n obj: Record<string, unknown>,\n key: string,\n context: string,\n): string {\n const val = obj[key];\n if (typeof val !== \"string\" || val.trim() === \"\") {\n throw new Error(\n `Invalid config: missing required field \"${context}.${key}\"`,\n );\n }\n return val;\n}\n\nfunction requireSection(\n obj: Record<string, unknown>,\n key: string,\n): Record<string, unknown> {\n const val = obj[key];\n if (\n val === undefined ||\n val === null ||\n typeof val !== \"object\" ||\n Array.isArray(val)\n ) {\n throw new Error(`Invalid config: missing required section \"[${key}]\"`);\n }\n return val as Record<string, unknown>;\n}\n\nexport async function parseConfig(configPath: string): Promise<KbConfig> {\n let raw: string;\n try {\n raw = await readFile(configPath, \"utf8\");\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Config file not found: ${configPath}\\n${message}`);\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = TOML.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid TOML in config file ${configPath}: ${message}`);\n }\n\n const project = requireSection(parsed, \"project\");\n const name = requireString(project, \"name\", \"project\");\n const version = requireString(project, \"version\", \"project\");\n\n const directories = requireSection(parsed, \"directories\");\n const sources = requireString(directories, \"sources\", \"directories\");\n requireSafeRelativePath(sources, \"directories.sources\");\n const wiki = requireString(directories, \"wiki\", \"directories\");\n requireSafeRelativePath(wiki, \"directories.wiki\");\n\n const llm = requireSection(parsed, \"llm\");\n const providerRaw = requireString(llm, \"provider\", \"llm\");\n if (!(VALID_PROVIDERS as readonly string[]).includes(providerRaw)) {\n throw new Error(\n `Invalid config: llm.provider must be one of ${VALID_PROVIDERS.join(\", \")}, got \"${providerRaw}\"`,\n );\n }\n const provider = providerRaw as KbConfig[\"llm\"][\"provider\"];\n const model = requireString(llm, \"model\", \"llm\");\n\n const rawDeps = parsed[\"dependencies\"];\n const dependencies: KbConfig[\"dependencies\"] = {};\n if (\n rawDeps !== undefined &&\n rawDeps !== null &&\n typeof rawDeps === \"object\" &&\n !Array.isArray(rawDeps)\n ) {\n for (const [depKey, depVal] of Object.entries(\n rawDeps as Record<string, unknown>,\n )) {\n if (\n typeof depVal === \"object\" &&\n depVal !== null &&\n !Array.isArray(depVal)\n ) {\n const dep = depVal as Record<string, unknown>;\n dependencies[depKey] = {\n ...(typeof dep[\"path\"] === \"string\" ? { path: dep[\"path\"] } : {}),\n ...(typeof dep[\"git\"] === \"string\" ? { git: dep[\"git\"] } : {}),\n ...(typeof dep[\"branch\"] === \"string\"\n ? { branch: dep[\"branch\"] }\n : {}),\n ...(typeof dep[\"mode\"] === \"string\" ? { mode: dep[\"mode\"] } : {}),\n };\n }\n }\n }\n\n return {\n project: { name, version },\n directories: { sources, wiki },\n llm: { provider, model },\n dependencies,\n };\n}\n","import { access } from \"node:fs/promises\";\nimport { join, dirname, resolve } from \"node:path\";\nimport { parseConfig, type KbConfig } from \"./config.js\";\n\nexport interface Project {\n name: string;\n root: string;\n kbDir: string;\n sourcesDir: string;\n wikiDir: string;\n config: KbConfig;\n}\n\nasync function hasKbDir(dir: string): Promise<boolean> {\n try {\n await access(join(dir, \".kb\", \"config.toml\"));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function findProjectRoot(startDir: string): Promise<string | null> {\n let current = resolve(startDir);\n\n while (true) {\n if (await hasKbDir(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root\n return null;\n }\n current = parent;\n }\n}\n\nexport async function loadProject(startDir: string): Promise<Project> {\n const root = await findProjectRoot(startDir);\n if (root === null) {\n throw new Error(\n `No kb project found. Run \"kb init\" to initialize a knowledge base in the current directory.`,\n );\n }\n\n const kbDir = join(root, \".kb\");\n const configPath = join(kbDir, \"config.toml\");\n const config = await parseConfig(configPath);\n\n return {\n name: config.project.name,\n root,\n kbDir,\n sourcesDir: join(root, config.directories.sources),\n wikiDir: join(root, config.directories.wiki),\n config,\n };\n}\n\nexport async function tryLoadProject(\n startDir: string,\n): Promise<Project | null> {\n try {\n return await loadProject(startDir);\n } catch (err: unknown) {\n if (err instanceof Error && /no kb project found/i.test(err.message)) {\n return null;\n }\n throw err;\n }\n}\n","import Database from \"better-sqlite3\";\nimport { join } from \"node:path\";\nimport type { Project } from \"./project.js\";\n\nconst SCHEMA_SQL = `\nCREATE VIRTUAL TABLE IF NOT EXISTS pages USING fts5(\n path,\n title,\n content,\n tags,\n project,\n tokenize='porter unicode61'\n);\n\nCREATE TABLE IF NOT EXISTS page_meta (\n path TEXT PRIMARY KEY,\n sha256 TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n word_count INTEGER NOT NULL DEFAULT 0,\n frontmatter TEXT NOT NULL DEFAULT '{}',\n outgoing_links TEXT NOT NULL DEFAULT '[]',\n updated_at INTEGER NOT NULL\n);\n`;\n\nexport function openDb(project: Project): Database.Database {\n const dbPath = join(project.kbDir, \"index.db\");\n const db = new Database(dbPath);\n db.pragma(\"journal_mode = WAL\");\n db.exec(SCHEMA_SQL);\n return db;\n}\n\nexport function closeDb(db: Database.Database): void {\n db.close();\n}\n","import { readFile } from \"node:fs/promises\";\nimport matter from \"gray-matter\";\n\nexport interface ParsedPage {\n path: string;\n title: string;\n content: string;\n tags: string;\n frontmatter: Record<string, unknown>;\n outgoingLinks: string[];\n wordCount: number;\n}\n\nconst WIKILINK_RE = /\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]/g;\nconst H1_RE = /^#\\s+(.+)$/m;\n\nfunction extractTitle(\n fm: Record<string, unknown>,\n content: string,\n relativePath: string,\n): string {\n if (typeof fm[\"title\"] === \"string\" && fm[\"title\"].trim() !== \"\") {\n return fm[\"title\"].trim();\n }\n const h1Match = H1_RE.exec(content);\n if (h1Match) {\n return h1Match[1]!.trim();\n }\n // Fallback: use filename without extension\n const filename = relativePath.split(\"/\").pop() ?? relativePath;\n return filename.replace(/\\.md$/i, \"\");\n}\n\nfunction extractTags(fm: Record<string, unknown>): string {\n const tags = fm[\"tags\"];\n if (!Array.isArray(tags)) return \"\";\n return tags.filter((t): t is string => typeof t === \"string\").join(\",\");\n}\n\nfunction extractWikiLinks(content: string): string[] {\n const links: string[] = [];\n let match: RegExpExecArray | null;\n const re = new RegExp(WIKILINK_RE.source, \"g\");\n while ((match = re.exec(content)) !== null) {\n links.push(match[1]!.trim());\n }\n return links;\n}\n\nfunction countWords(text: string): number {\n const trimmed = text.trim();\n if (trimmed === \"\") return 0;\n return trimmed.split(/\\s+/).length;\n}\n\nexport async function parsePage(\n filePath: string,\n relativePath: string,\n rawContent?: string,\n): Promise<ParsedPage> {\n const raw = rawContent ?? (await readFile(filePath, \"utf8\"));\n const parsed = matter(raw);\n const fm = parsed.data as Record<string, unknown>;\n const content = parsed.content;\n\n const title = extractTitle(fm, content, relativePath);\n const tags = extractTags(fm);\n const outgoingLinks = extractWikiLinks(content);\n const wordCount = countWords(content);\n\n return {\n path: relativePath,\n title,\n content,\n tags,\n frontmatter: fm,\n outgoingLinks,\n wordCount,\n };\n}\n","import { createHash } from \"node:crypto\";\nimport { readFile, stat, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport Database from \"better-sqlite3\";\nimport type { Project } from \"./project.js\";\nimport { parsePage } from \"./markdown.js\";\nimport { openDb, closeDb } from \"./db.js\";\n\nexport interface IndexStats {\n indexed: number;\n skipped: number;\n deleted: number;\n errors: number;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\nfunction sha256(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\ninterface PageMetaRow {\n sha256: string;\n}\n\ninterface UpsertStmts {\n deletePages: Database.Statement;\n insertPage: Database.Statement;\n upsertMeta: Database.Statement;\n}\n\nfunction upsertParsedPage(\n stmts: UpsertStmts,\n project: Project,\n page: Awaited<ReturnType<typeof parsePage>>,\n hash: string,\n mtime: number,\n): void {\n stmts.deletePages.run(page.path);\n stmts.insertPage.run(\n page.path,\n page.title,\n page.content,\n page.tags,\n project.name,\n );\n stmts.upsertMeta.run(\n page.path,\n hash,\n mtime,\n page.wordCount,\n JSON.stringify(page.frontmatter),\n JSON.stringify(page.outgoingLinks),\n Date.now(),\n );\n}\n\nexport async function indexProject(\n project: Project,\n rebuild = false,\n): Promise<IndexStats> {\n const db = openDb(project);\n try {\n if (rebuild) {\n db.exec(\"DELETE FROM pages; DELETE FROM page_meta;\");\n }\n\n const files = await collectMdFiles(project.wikiDir);\n const stats: IndexStats = { indexed: 0, skipped: 0, deleted: 0, errors: 0 };\n\n const getMetaStmt = db.prepare<[string], PageMetaRow>(\n \"SELECT sha256 FROM page_meta WHERE path = ?\",\n );\n\n const upsertStmts: UpsertStmts = {\n deletePages: db.prepare(\"DELETE FROM pages WHERE path = ?\"),\n insertPage: db.prepare(\n \"INSERT INTO pages(path, title, content, tags, project) VALUES (?, ?, ?, ?, ?)\",\n ),\n upsertMeta: db.prepare(`\n INSERT INTO page_meta(path, sha256, mtime, word_count, frontmatter, outgoing_links, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(path) DO UPDATE SET\n sha256 = excluded.sha256,\n mtime = excluded.mtime,\n word_count = excluded.word_count,\n frontmatter = excluded.frontmatter,\n outgoing_links = excluded.outgoing_links,\n updated_at = excluded.updated_at\n `),\n };\n\n const deleteStalePages = db.prepare(\"DELETE FROM pages WHERE path = ?\");\n const deleteStaleMeta = db.prepare(\"DELETE FROM page_meta WHERE path = ?\");\n const listMetaStmt = db.prepare<[], { path: string }>(\n \"SELECT path FROM page_meta\",\n );\n\n const processFile = db.transaction(\n (\n page: Awaited<ReturnType<typeof parsePage>>,\n hash: string,\n mtime: number,\n ) => {\n upsertParsedPage(upsertStmts, project, page, hash, mtime);\n },\n );\n\n const onDiskPaths = new Set<string>();\n\n for (const absPath of files) {\n const relPath = relative(project.root, absPath);\n onDiskPaths.add(relPath);\n\n let raw: string;\n try {\n raw = await readFile(absPath, \"utf8\");\n } catch (err) {\n stats.errors++;\n continue;\n }\n\n const hash = sha256(raw);\n const existing = getMetaStmt.get(relPath);\n\n if (existing && existing.sha256 === hash) {\n stats.skipped++;\n continue;\n }\n\n let fileStat: Awaited<ReturnType<typeof stat>>;\n try {\n fileStat = await stat(absPath);\n } catch {\n stats.errors++;\n continue;\n }\n\n let page: Awaited<ReturnType<typeof parsePage>>;\n try {\n page = await parsePage(absPath, relPath, raw);\n } catch {\n stats.errors++;\n continue;\n }\n\n try {\n processFile(page, hash, Math.floor(fileStat.mtimeMs));\n stats.indexed++;\n } catch {\n stats.errors++;\n }\n }\n\n // Remove entries for deleted files\n const allMetaPaths = listMetaStmt.all().map((r) => r.path);\n\n const stalePaths = allMetaPaths.filter((p) => !onDiskPaths.has(p));\n\n const deleteStale = db.transaction((paths: string[]) => {\n for (const p of paths) {\n deleteStalePages.run(p);\n deleteStaleMeta.run(p);\n }\n });\n\n deleteStale(stalePaths);\n stats.deleted += stalePaths.length;\n\n return stats;\n } finally {\n closeDb(db);\n }\n}\n","import Database from \"better-sqlite3\";\n\nexport interface SearchResult {\n rank: number;\n path: string;\n title: string;\n snippet: string;\n tags: string[];\n}\n\nexport interface SearchOptions {\n limit?: number;\n tags?: string[];\n}\n\ninterface FtsRow {\n path: string;\n title: string;\n tags: string;\n rank: number;\n snippet: string;\n}\n\nfunction sanitizeFtsQuery(query: string): string {\n // Split into tokens, quote each one to escape special FTS5 chars.\n // Using individual quoted tokens (AND logic) instead of a single phrase\n // so non-adjacent words still match.\n const tokens = query\n .trim()\n .split(/\\s+/)\n .filter((t) => t.length > 0);\n if (tokens.length === 0) return '\"\"';\n return tokens.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" \");\n}\n\nfunction parseTags(raw: string): string[] {\n if (!raw || raw.trim() === \"\") return [];\n return raw\n .split(\",\")\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n}\n\nexport function searchWiki(\n db: Database.Database,\n query: string,\n projectName: string,\n options?: SearchOptions,\n): SearchResult[] {\n if (!query || query.trim() === \"\") {\n return [];\n }\n\n const limit = options?.limit ?? 10;\n const ftsQuery = sanitizeFtsQuery(query.trim());\n\n // Build dynamic tag WHERE clauses so filtering happens in SQL\n const filterTags = options?.tags?.length\n ? options.tags\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0)\n : [];\n\n const tagClauses = filterTags.map(() => \"AND lower(tags) LIKE ?\").join(\" \");\n const tagParams = filterTags.map((t) => `%${t}%`);\n\n const stmt = db.prepare<[string, string, ...string[], number], FtsRow>(`\n SELECT path, title, tags, bm25(pages) as rank,\n snippet(pages, 2, '', '', '...', 8) as snippet\n FROM pages\n WHERE pages MATCH ? AND project = ?\n ${tagClauses}\n ORDER BY rank\n LIMIT ?\n `);\n\n const rows = stmt.all(ftsQuery, projectName, ...tagParams, limit);\n\n const results: SearchResult[] = rows.map((row) => ({\n rank: row.rank,\n path: row.path,\n title: row.title,\n snippet: row.snippet,\n tags: parseTags(row.tags),\n }));\n\n return results;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\n\nexport type SourceType = \"markdown\" | \"text\" | \"pdf\" | \"url\";\n\nexport interface SourceContent {\n type: SourceType;\n originalPath: string;\n content: string;\n filename: string;\n}\n\nfunction sanitizeFilename(name: string): string {\n return name.toLowerCase().replace(/\\s/g, \"-\");\n}\n\nfunction detectType(sourcePath: string): SourceType {\n if (sourcePath.startsWith(\"http://\") || sourcePath.startsWith(\"https://\")) {\n return \"url\";\n }\n const ext = extname(sourcePath).toLowerCase();\n if (ext === \".pdf\") return \"pdf\";\n if (ext === \".md\") return \"markdown\";\n return \"text\";\n}\n\nfunction filenameFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const parts = parsed.pathname.split(\"/\").filter(Boolean);\n const last = parts[parts.length - 1];\n const pagePart = last\n ? last.includes(\".\")\n ? last\n : `${last}.html`\n : \"index.html\";\n return sanitizeFilename(`${parsed.hostname}-${pagePart}`);\n } catch {\n return \"url-content.html\";\n }\n}\n\nfunction stripHtml(html: string): string {\n // Remove script and style blocks entirely\n let text = html.replace(/<script[\\s\\S]*?<\\/script>/gi, \" \");\n text = text.replace(/<style[\\s\\S]*?<\\/style>/gi, \" \");\n // Strip remaining tags\n text = text.replace(/<[^>]+>/g, \" \");\n // Decode common HTML entities\n text = text\n .replace(/&amp;/gi, \"&\")\n .replace(/&lt;/gi, \"<\")\n .replace(/&gt;/gi, \">\")\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;/gi, \"'\")\n .replace(/&nbsp;/gi, \" \");\n // Collapse whitespace\n text = text.replace(/\\s+/g, \" \").trim();\n return text;\n}\n\nasync function readPdf(filePath: string): Promise<string> {\n // Dynamic import to avoid issues if pdf-parse not available in test env\n const pdfParse = await import(\"pdf-parse\").then((m) => m.default ?? m);\n const buffer = await readFile(filePath);\n const data = await pdfParse(buffer);\n return data.text;\n}\n\nfunction isPrivateUrl(url: string): boolean {\n try {\n const { hostname } = new URL(url);\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname.startsWith(\"169.254.\") || // link-local\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"192.168.\") ||\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(hostname)\n );\n } catch {\n return false;\n }\n}\n\nasync function fetchUrl(url: string): Promise<string> {\n if (isPrivateUrl(url)) {\n throw new Error(`Fetching private/localhost URLs is not allowed: ${url}`);\n }\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch URL ${url}: HTTP ${response.status} ${response.statusText}`,\n );\n }\n const html = await response.text();\n return stripHtml(html);\n}\n\nexport async function readSource(sourcePath: string): Promise<SourceContent> {\n const type = detectType(sourcePath);\n\n if (type === \"url\") {\n const content = await fetchUrl(sourcePath);\n const filename = filenameFromUrl(sourcePath);\n return { type, originalPath: sourcePath, content, filename };\n }\n\n if (type === \"pdf\") {\n const content = await readPdf(sourcePath);\n const raw = basename(sourcePath);\n const filename = sanitizeFilename(raw);\n return { type, originalPath: sourcePath, content, filename };\n }\n\n // markdown or text\n const content = await readFile(sourcePath, \"utf8\");\n const raw = basename(sourcePath);\n const filename = sanitizeFilename(raw);\n return { type, originalPath: sourcePath, content, filename };\n}\n","import type { KbConfig } from \"./config.js\";\n\nexport interface LlmMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\nexport interface LlmAdapter {\n complete(messages: LlmMessage[], systemPrompt: string): Promise<string>;\n}\n\nfunction createAnthropicAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"ANTHROPIC_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"ANTHROPIC_API_KEY environment variable is not set\");\n }\n const Anthropic = await import(\"@anthropic-ai/sdk\").then(\n (m) => m.default ?? m,\n );\n const client = new Anthropic({ apiKey });\n const response = await client.messages.create({\n model,\n max_tokens: 8192,\n system: systemPrompt,\n messages: messages.map((m) => ({\n role: m.role,\n content: m.content,\n })),\n });\n const block = response.content[0];\n if (!block || block.type !== \"text\") {\n throw new Error(\"Anthropic returned no text content\");\n }\n return block.text;\n },\n };\n}\n\nfunction createOpenAiAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"OPENAI_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"OPENAI_API_KEY environment variable is not set\");\n }\n const body = {\n model,\n messages: [\n { role: \"system\", content: systemPrompt },\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n };\n const response = await fetch(\n \"https://api.openai.com/v1/chat/completions\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n },\n );\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OpenAI API error: HTTP ${response.status} — ${text}`);\n }\n const data = (await response.json()) as {\n choices: Array<{ message: { content: string } }>;\n };\n const content = data.choices[0]?.message?.content;\n if (!content) {\n throw new Error(\"OpenAI returned no content\");\n }\n return content;\n },\n };\n}\n\nfunction createOllamaAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const baseUrl =\n process.env[\"OLLAMA_BASE_URL\"] ?? \"http://localhost:11434\";\n const body = {\n model,\n messages: [\n { role: \"system\", content: systemPrompt },\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n stream: false,\n };\n const response = await fetch(`${baseUrl}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Ollama API error: HTTP ${response.status} — ${text}`);\n }\n const data = (await response.json()) as {\n message?: { content: string };\n };\n const content = data.message?.content;\n if (!content) {\n throw new Error(\"Ollama returned no content\");\n }\n return content;\n },\n };\n}\n\nexport function createLlmAdapter(config: KbConfig): LlmAdapter {\n const { provider, model } = config.llm;\n switch (provider) {\n case \"anthropic\":\n return createAnthropicAdapter(model);\n case \"openai\":\n return createOpenAiAdapter(model);\n case \"ollama\":\n return createOllamaAdapter(model);\n default: {\n const _exhaustive: never = provider;\n throw new Error(`Unsupported LLM provider: ${String(_exhaustive)}`);\n }\n }\n}\n","import { readFile, writeFile, mkdir, appendFile } from \"node:fs/promises\";\nimport { join, dirname, resolve } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport type { LlmAdapter } from \"./llm.js\";\nimport type { IngestResult } from \"./ingest-types.js\";\nimport { readSource } from \"./source-reader.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport interface IngestOptions {\n apply?: boolean;\n batch?: boolean;\n}\n\nexport interface IngestPlan {\n result: IngestResult;\n sourceFile: string;\n dryRun: boolean;\n}\n\nconst SYSTEM_PROMPT = `You are an AI assistant maintaining a knowledge base wiki.\nYou will be given a new source document and the current state of the wiki.\nYour task is to integrate the new knowledge into the wiki.\n\nReturn ONLY a JSON object matching this exact schema (no markdown fences):\n{\n \"summary\": { \"path\": \"wiki/sources/<filename>-summary.md\", \"content\": \"...\" },\n \"updates\": [{ \"path\": \"...\", \"content\": \"...\", \"reason\": \"...\" }],\n \"newPages\": [{ \"path\": \"...\", \"content\": \"...\", \"reason\": \"...\" }],\n \"indexUpdate\": \"...\",\n \"logEntry\": \"...\"\n}`;\n\nfunction assertWithinRoot(absPath: string, root: string): void {\n const resolvedPath = resolve(absPath);\n const resolvedRoot = resolve(root) + \"/\";\n if (!resolvedPath.startsWith(resolvedRoot)) {\n throw new Error(\n `Unsafe path rejected: \"${absPath}\" is outside project root`,\n );\n }\n}\n\nasync function readFileSafe(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nfunction parseIngestResult(raw: string): IngestResult {\n const cleaned = raw\n .replace(/^```(?:json)?\\s*/i, \"\")\n .replace(/\\s*```\\s*$/i, \"\")\n .trim();\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch (err) {\n throw new Error(\n `Invalid LLM response: could not parse JSON. Raw response: ${cleaned.slice(0, 200)}`,\n );\n }\n\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"Invalid LLM response: expected a JSON object\");\n }\n\n const obj = parsed as Record<string, unknown>;\n\n if (\n !obj[\"summary\"] ||\n typeof obj[\"summary\"] !== \"object\" ||\n Array.isArray(obj[\"summary\"])\n ) {\n throw new Error('Invalid LLM response: missing \"summary\" object');\n }\n\n const summary = obj[\"summary\"] as Record<string, unknown>;\n if (\n typeof summary[\"path\"] !== \"string\" ||\n typeof summary[\"content\"] !== \"string\"\n ) {\n throw new Error(\n 'Invalid LLM response: \"summary\" must have \"path\" and \"content\" strings',\n );\n }\n\n if (!Array.isArray(obj[\"updates\"])) {\n throw new Error('Invalid LLM response: \"updates\" must be an array');\n }\n\n if (!Array.isArray(obj[\"newPages\"])) {\n throw new Error('Invalid LLM response: \"newPages\" must be an array');\n }\n\n if (typeof obj[\"indexUpdate\"] !== \"string\") {\n throw new Error('Invalid LLM response: \"indexUpdate\" must be a string');\n }\n\n if (typeof obj[\"logEntry\"] !== \"string\") {\n throw new Error('Invalid LLM response: \"logEntry\" must be a string');\n }\n\n const updates = (obj[\"updates\"] as unknown[]).map((u, i) => {\n if (typeof u !== \"object\" || u === null || Array.isArray(u)) {\n throw new Error(`Invalid LLM response: updates[${i}] must be an object`);\n }\n const update = u as Record<string, unknown>;\n if (\n typeof update[\"path\"] !== \"string\" ||\n typeof update[\"content\"] !== \"string\" ||\n typeof update[\"reason\"] !== \"string\"\n ) {\n throw new Error(\n `Invalid LLM response: updates[${i}] must have path, content, and reason strings`,\n );\n }\n return {\n path: update[\"path\"],\n content: update[\"content\"],\n reason: update[\"reason\"],\n };\n });\n\n const newPages = (obj[\"newPages\"] as unknown[]).map((p, i) => {\n if (typeof p !== \"object\" || p === null || Array.isArray(p)) {\n throw new Error(`Invalid LLM response: newPages[${i}] must be an object`);\n }\n const page = p as Record<string, unknown>;\n if (\n typeof page[\"path\"] !== \"string\" ||\n typeof page[\"content\"] !== \"string\" ||\n typeof page[\"reason\"] !== \"string\"\n ) {\n throw new Error(\n `Invalid LLM response: newPages[${i}] must have path, content, and reason strings`,\n );\n }\n return {\n path: page[\"path\"],\n content: page[\"content\"],\n reason: page[\"reason\"],\n };\n });\n\n return {\n summary: { path: summary[\"path\"], content: summary[\"content\"] },\n updates,\n newPages,\n indexUpdate: obj[\"indexUpdate\"] as string,\n logEntry: obj[\"logEntry\"] as string,\n };\n}\n\nasync function applyIngestResult(\n project: Project,\n result: IngestResult,\n sourceContent: string,\n sourceFilename: string,\n): Promise<void> {\n // Write summary\n const summaryAbsPath = join(project.root, result.summary.path);\n assertWithinRoot(summaryAbsPath, project.root);\n await mkdir(dirname(summaryAbsPath), { recursive: true });\n await writeFile(summaryAbsPath, result.summary.content, \"utf8\");\n\n // Write updated pages\n for (const update of result.updates) {\n const absPath = join(project.root, update.path);\n assertWithinRoot(absPath, project.root);\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, update.content, \"utf8\");\n }\n\n // Write new pages\n for (const newPage of result.newPages) {\n const absPath = join(project.root, newPage.path);\n assertWithinRoot(absPath, project.root);\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, newPage.content, \"utf8\");\n }\n\n // Update _index.md\n const indexPath = join(project.wikiDir, \"_index.md\");\n await writeFile(indexPath, result.indexUpdate, \"utf8\");\n\n // Write source file to sources directory\n const sourceDestPath = join(project.sourcesDir, sourceFilename);\n await mkdir(project.sourcesDir, { recursive: true });\n await writeFile(sourceDestPath, sourceContent, \"utf8\");\n\n // Append to log.md\n const logPath = join(project.wikiDir, \"log.md\");\n const timestamp = new Date().toISOString().split(\"T\")[0];\n const logLine = `- ${timestamp}: ${result.logEntry}\\n`;\n await appendFile(logPath, logLine, \"utf8\");\n\n // Re-index\n await indexProject(project);\n}\n\nexport async function ingestSource(\n project: Project,\n sourcePath: string,\n llm: LlmAdapter,\n options?: IngestOptions,\n): Promise<IngestPlan> {\n const apply = options?.apply ?? false;\n\n // 1. Read source content\n const sourceContent = await readSource(sourcePath);\n\n // 2. Read current wiki index\n const indexPath = join(project.wikiDir, \"_index.md\");\n const currentIndex = await readFileSafe(indexPath);\n\n // 3. Read schema\n const schemaPath = join(project.kbDir, \"schema.md\");\n const schema = await readFileSafe(schemaPath);\n\n // 4. Build user message\n const userMessage = `## Wiki Schema\n${schema}\n\n## Current Wiki Index\n${currentIndex}\n\n## New Source: ${sourceContent.filename}\n${sourceContent.content}\n\nIntegrate this source into the wiki following the schema above.`;\n\n // 5. Call LLM\n const raw = await llm.complete(\n [{ role: \"user\", content: userMessage }],\n SYSTEM_PROMPT,\n );\n\n // 6. Parse response\n const result = parseIngestResult(raw);\n\n // 7. Apply if requested\n if (apply) {\n await applyIngestResult(\n project,\n result,\n sourceContent.content,\n sourceContent.filename,\n );\n }\n\n const sourceFile = join(project.sourcesDir, sourceContent.filename);\n\n return {\n result,\n sourceFile,\n dryRun: !apply,\n };\n}\n","import { readFile, writeFile, mkdir, appendFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname, resolve } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport type { LlmAdapter } from \"./llm.js\";\nimport { openDb, closeDb } from \"./db.js\";\nimport { searchWiki } from \"./search.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport interface QueryResult {\n answer: string;\n sources: string[];\n}\n\nexport interface QueryOptions {\n save?: string;\n}\n\nconst SYSTEM_PROMPT = `You are a knowledgeable assistant answering questions about a project's knowledge base.\nAnswer concisely using only information from the provided wiki pages.\nUse [[page-name]] wikilink syntax to cite specific wiki pages in your answer.\nFormat your answer in markdown.`;\n\nfunction assertWithinRoot(absPath: string, root: string): void {\n const resolvedPath = resolve(absPath);\n const resolvedRoot = resolve(root) + \"/\";\n if (!resolvedPath.startsWith(resolvedRoot)) {\n throw new Error(\n `Unsafe path rejected: \"${absPath}\" is outside project root`,\n );\n }\n}\n\nasync function readFileSafe(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nexport async function queryWiki(\n project: Project,\n question: string,\n llm: LlmAdapter,\n options?: QueryOptions,\n): Promise<QueryResult> {\n // 1. Auto-index if db doesn't exist\n const dbPath = join(project.kbDir, \"index.db\");\n if (!existsSync(dbPath)) {\n await indexProject(project);\n }\n\n // 2. Search for top relevant pages\n const db = openDb(project);\n let searchResults;\n try {\n searchResults = searchWiki(db, question, project.name, { limit: 10 });\n } finally {\n closeDb(db);\n }\n\n // 3. Read full content of each result page\n const pages: Array<{ path: string; title: string; content: string }> = [];\n for (const result of searchResults) {\n const absPath = join(project.root, result.path);\n const content = await readFileSafe(absPath);\n if (content) {\n pages.push({ path: result.path, title: result.title, content });\n }\n }\n\n // 4. Build user message\n const pagesSection =\n pages.length > 0\n ? pages\n .map((p) => `### ${p.title} (${p.path})\\n${p.content}`)\n .join(\"\\n\\n\")\n : \"(No wiki pages found for this query.)\";\n\n const userMessage = `## Question\\n${question}\\n\\n## Relevant Wiki Pages\\n\\n${pagesSection}`;\n\n // 5. Call LLM\n const answer = await llm.complete(\n [{ role: \"user\", content: userMessage }],\n SYSTEM_PROMPT,\n );\n\n const sources = pages.map((p) => p.path);\n\n // 6. If save option provided, write answer as wiki page and append to log\n if (options?.save) {\n const saveRelPath = options.save;\n const saveAbsPath = join(project.root, saveRelPath);\n assertWithinRoot(saveAbsPath, project.root);\n\n await mkdir(dirname(saveAbsPath), { recursive: true });\n await writeFile(saveAbsPath, answer, \"utf8\");\n\n // Append to log.md\n const logPath = join(project.wikiDir, \"log.md\");\n const timestamp = new Date().toISOString().split(\"T\")[0];\n const logEntry = `\\n## ${timestamp} — Queried: ${question}\\n\\nSaved to: ${saveRelPath}\\n`;\n await appendFile(logPath, logEntry, \"utf8\");\n\n // Re-index so saved page is searchable\n await indexProject(project);\n }\n\n return { answer, sources };\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport { join, relative, basename, extname } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport { openDb, closeDb } from \"./db.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport type LintSeverity = \"warning\" | \"info\";\n\nexport interface LintIssue {\n severity: LintSeverity;\n code: string;\n path: string;\n message: string;\n detail?: string;\n}\n\nexport interface LintResult {\n issues: LintIssue[];\n pagesChecked: number;\n sourcesChecked: number;\n}\n\ninterface PageMetaRow {\n path: string;\n outgoing_links: string;\n word_count: number;\n mtime: number;\n updated_at: number;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\nasync function collectSourceFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile())\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\n/**\n * Build a set of \"keys\" for wiki pages for wikilink resolution.\n * A wikilink [[foo-bar]] can match:\n * - a page whose filename (without extension) is \"foo-bar\"\n * - a page whose relative path is \"foo-bar\" or \"foo-bar.md\"\n */\nfunction buildPageKeySet(\n relPaths: string[],\n projectRoot: string,\n wikiDir: string,\n): Set<string> {\n const keys = new Set<string>();\n for (const rp of relPaths) {\n // rp is relative to projectRoot, e.g. \"wiki/concepts/foo.md\"\n keys.add(rp);\n // without extension\n keys.add(rp.replace(/\\.md$/i, \"\"));\n // filename without extension\n const fname = basename(rp, \".md\");\n keys.add(fname);\n // relative path from wikiDir\n const absPath = join(projectRoot, rp);\n const relToWiki = relative(wikiDir, absPath);\n keys.add(relToWiki);\n keys.add(relToWiki.replace(/\\.md$/i, \"\"));\n }\n return keys;\n}\n\nexport async function lintProject(project: Project): Promise<LintResult> {\n // Ensure index is up to date\n await indexProject(project);\n\n const issues: LintIssue[] = [];\n\n // Collect all wiki md files\n const absWikiFiles = await collectMdFiles(project.wikiDir);\n const relWikiPaths = absWikiFiles.map((f) => relative(project.root, f));\n\n const pagesChecked = relWikiPaths.length;\n\n // Always collect source files so sourcesChecked is accurate\n const sourceFiles = await collectSourceFiles(project.sourcesDir);\n const sourcesChecked = sourceFiles.filter(\n (f) => basename(f) !== \".gitkeep\",\n ).length;\n\n if (pagesChecked === 0) {\n return { issues, pagesChecked: 0, sourcesChecked };\n }\n\n // Build page key set for wikilink resolution\n const pageKeySet = buildPageKeySet(\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n\n // Query all page_meta rows\n const db = openDb(project);\n let rows: PageMetaRow[];\n try {\n rows = db\n .prepare<\n [],\n PageMetaRow\n >(\"SELECT path, outgoing_links, word_count, mtime, updated_at FROM page_meta\")\n .all();\n } finally {\n closeDb(db);\n }\n\n // Build a map from path -> row for quick lookup\n const metaMap = new Map<string, PageMetaRow>();\n for (const row of rows) {\n metaMap.set(row.path, row);\n }\n\n // Build inbound link map\n const inboundLinks = new Map<string, Set<string>>();\n for (const rp of relWikiPaths) {\n inboundLinks.set(rp, new Set());\n }\n\n for (const row of rows) {\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n // Find which page this link resolves to\n const resolved = resolveLink(\n link,\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n if (resolved !== null) {\n const set = inboundLinks.get(resolved);\n if (set) {\n set.add(row.path);\n }\n }\n }\n }\n\n // Read _index.md outgoing links for MISSING_INDEX check\n const indexPath = relWikiPaths.find((p) => basename(p) === \"_index.md\");\n let indexLinks: Set<string> = new Set();\n if (indexPath) {\n const indexRow = metaMap.get(indexPath);\n if (indexRow) {\n let links: string[] = [];\n try {\n links = JSON.parse(indexRow.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n const resolved = resolveLink(\n link,\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n if (resolved !== null) {\n indexLinks.add(resolved);\n }\n }\n }\n }\n\n // --- CHECK 1: ORPHAN_PAGE ---\n for (const rp of relWikiPaths) {\n if (basename(rp) === \"_index.md\") continue;\n const inbound = inboundLinks.get(rp);\n if (!inbound || inbound.size === 0) {\n issues.push({\n severity: \"warning\",\n code: \"ORPHAN_PAGE\",\n path: rp,\n message: \"Orphan page (no inbound links)\",\n });\n }\n }\n\n // --- CHECK 2: BROKEN_LINK ---\n for (const row of rows) {\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n if (!isLinkResolvable(link, pageKeySet)) {\n issues.push({\n severity: \"warning\",\n code: \"BROKEN_LINK\",\n path: row.path,\n message: `Broken wikilink [[${link}]] not found`,\n detail: link,\n });\n }\n }\n }\n\n // --- CHECK 3: STUB_PAGE ---\n for (const row of rows) {\n if (basename(row.path) === \"_index.md\") continue;\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n if (links.length === 0 && row.word_count < 50) {\n issues.push({\n severity: \"info\",\n code: \"STUB_PAGE\",\n path: row.path,\n message: `Stub page (no links, < 50 words)`,\n });\n }\n }\n\n // --- CHECK 4: STALE_SUMMARY ---\n // wiki/sources/foo-summary.md <-> sources/foo.*\n const wikiSourcesDir = join(project.wikiDir, \"sources\");\n\n for (const rp of relWikiPaths) {\n // Check if the path is under wiki/sources/\n const absWikiPage = join(project.root, rp);\n const relToWikiSources = relative(wikiSourcesDir, absWikiPage);\n\n // Skip if not under wiki/sources/ (would start with \"..\")\n if (relToWikiSources.startsWith(\"..\")) continue;\n\n // Convention: wiki/sources/foo-summary.md <-> sources/foo.*\n const summaryBasename = basename(rp, \".md\");\n // Strip -summary suffix\n const sourceBasename = summaryBasename.endsWith(\"-summary\")\n ? summaryBasename.slice(0, -\"-summary\".length)\n : summaryBasename;\n\n // Find matching source file\n const matchingSource = sourceFiles.find((sf) => {\n const sfBase = basename(sf, extname(sf));\n return sfBase === sourceBasename;\n });\n\n if (!matchingSource) continue;\n\n try {\n const summaryRow = metaMap.get(rp);\n if (!summaryRow) continue;\n\n const [sourceStat, summaryStat] = await Promise.all([\n stat(matchingSource),\n stat(join(project.root, summaryRow.path)),\n ]);\n\n if (sourceStat.mtimeMs > summaryStat.mtimeMs) {\n issues.push({\n severity: \"warning\",\n code: \"STALE_SUMMARY\",\n path: rp,\n message: \"Source updated after summary\",\n detail: relative(project.root, matchingSource),\n });\n }\n } catch {\n // Ignore stat errors\n }\n }\n\n // --- CHECK 5: MISSING_INDEX ---\n for (const rp of relWikiPaths) {\n if (basename(rp) === \"_index.md\") continue;\n if (!indexPath) {\n // No _index.md exists — skip this check\n continue;\n }\n if (!indexLinks.has(rp)) {\n // Check by filename too\n const fname = basename(rp, \".md\");\n if (!indexLinks.has(fname)) {\n issues.push({\n severity: \"info\",\n code: \"MISSING_INDEX\",\n path: rp,\n message: \"Not in _index.md\",\n });\n }\n }\n }\n\n return { issues, pagesChecked, sourcesChecked };\n}\n\nfunction resolveLink(\n link: string,\n relPaths: string[],\n projectRoot: string,\n wikiDir: string,\n): string | null {\n for (const rp of relPaths) {\n const fname = basename(rp, \".md\");\n if (fname === link) return rp;\n if (rp === link || rp === `${link}.md`) return rp;\n // relative to wikiDir\n const absPath = join(projectRoot, rp);\n const relToWiki = relative(wikiDir, absPath);\n if (relToWiki === link || relToWiki.replace(/\\.md$/i, \"\") === link) {\n return rp;\n }\n }\n return null;\n}\n\nfunction isLinkResolvable(link: string, pageKeySet: Set<string>): boolean {\n return pageKeySet.has(link);\n}\n","// packages/core/src/log-parser.ts\n\nexport interface ParsedLogEntry {\n heading: string;\n body: string;\n}\n\n/**\n * Parses a log.md file into an array of entries.\n * Each entry starts with a level-2 heading (## ...).\n * The top-level \"# Activity Log\" heading is skipped.\n */\nexport function parseLogEntries(content: string): ParsedLogEntry[] {\n const entries: ParsedLogEntry[] = [];\n const sections = content.split(/^(?=## )/m);\n\n for (const section of sections) {\n const trimmed = section.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"# \")) continue;\n if (!trimmed.startsWith(\"## \")) continue;\n\n const newlineIdx = trimmed.indexOf(\"\\n\");\n if (newlineIdx === -1) {\n entries.push({ heading: trimmed.slice(3).trim(), body: \"\" });\n } else {\n const heading = trimmed.slice(3, newlineIdx).trim();\n const body = trimmed.slice(newlineIdx + 1).trim();\n entries.push({ heading, body });\n }\n }\n\n return entries;\n}\n","// Core package — business logic\n\nexport const VERSION = \"0.1.0\";\n\nexport { initProject } from \"./init.js\";\nexport type { InitOptions } from \"./init.js\";\n\nexport { parseConfig } from \"./config.js\";\nexport type { KbConfig } from \"./config.js\";\n\nexport { loadProject, tryLoadProject } from \"./project.js\";\nexport type { Project } from \"./project.js\";\n\nexport { openDb, closeDb } from \"./db.js\";\n\nexport { parsePage } from \"./markdown.js\";\nexport type { ParsedPage } from \"./markdown.js\";\n\nexport { indexProject } from \"./indexer.js\";\nexport type { IndexStats } from \"./indexer.js\";\n\nexport { searchWiki } from \"./search.js\";\nexport type { SearchResult, SearchOptions } from \"./search.js\";\n\nexport { readSource } from \"./source-reader.js\";\nexport type { SourceContent, SourceType } from \"./source-reader.js\";\n\nexport { createLlmAdapter } from \"./llm.js\";\nexport type { LlmAdapter, LlmMessage } from \"./llm.js\";\n\nexport type { IngestResult } from \"./ingest-types.js\";\n\nexport { ingestSource } from \"./ingest.js\";\nexport type { IngestOptions, IngestPlan } from \"./ingest.js\";\n\nexport { queryWiki } from \"./query.js\";\nexport type { QueryResult, QueryOptions } from \"./query.js\";\n\nexport { lintProject } from \"./lint.js\";\nexport type { LintIssue, LintResult, LintSeverity } from \"./lint.js\";\n\nexport { parseLogEntries } from \"./log-parser.js\";\nexport type { ParsedLogEntry } from \"./log-parser.js\";\n"],"mappings":"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","names":["TOML","access","join","access","join","join","readFile","readFile","join","join","readFile","readFile","basename","content","filename","raw","readFile","writeFile","mkdir","join","dirname","resolve","resolve","readFile","join","mkdir","dirname","writeFile","readFile","writeFile","mkdir","appendFile","join","dirname","resolve","SYSTEM_PROMPT","assertWithinRoot","resolve","readFileSafe","readFile","join","mkdir","dirname","writeFile","appendFile","readdir","stat","join","relative","basename","extname","collectMdFiles","readdir","join","basename","relative","extname","stat"]}
1
+ {"version":3,"sources":["../src/init.ts","../src/config.ts","../src/project.ts","../src/db.ts","../src/markdown.ts","../src/indexer.ts","../src/search.ts","../src/source-reader.ts","../src/llm.ts","../src/ingest.ts","../src/query.ts","../src/lint.ts","../src/log-parser.ts","../src/index.ts"],"sourcesContent":["import { mkdir, writeFile, access, rm } from \"node:fs/promises\";\nimport { join, basename } from \"node:path\";\nimport TOML from \"@iarna/toml\";\n\nexport interface InitOptions {\n name: string;\n directory: string; // absolute path where to init\n}\n\nfunction resolveProjectName(options: InitOptions): string {\n return options.name || basename(options.directory);\n}\n\nfunction buildConfigToml(projectName: string): string {\n const config = {\n project: {\n name: projectName,\n version: \"0.1.0\",\n },\n directories: {\n sources: \"sources\",\n wiki: \"wiki\",\n },\n llm: {\n provider: \"anthropic\",\n model: \"claude-sonnet-4-20250514\",\n },\n };\n\n const tomlStr = TOML.stringify(config as TOML.JsonMap);\n return (\n tomlStr +\n '\\n[dependencies]\\n# shared-glossary = { path = \"../shared-glossary\" }\\n'\n );\n}\n\nfunction buildSchemaMd(): string {\n return `# KB Schema — LLM Instructions\n\nThis file defines the conventions for this knowledge base. The \\`kb\\` CLI and any\nLLM operating on this wiki MUST follow these rules.\n\n---\n\n## Wiki Structure Conventions\n\n- All pages live under the \\`wiki/\\` directory.\n- \\`wiki/_index.md\\` is the wiki root and serves as a table of contents.\n- Sub-topics may be organised into sub-directories: \\`wiki/<topic>/_index.md\\`.\n- File names use kebab-case, e.g. \\`wiki/authentication-flow.md\\`.\n- Every page must have a valid YAML frontmatter block.\n\n---\n\n## Frontmatter Schema\n\nEvery wiki page must begin with a YAML frontmatter block:\n\n\\`\\`\\`yaml\n---\ntitle: <Human-readable page title>\ntags: [tag1, tag2] # optional; array of lowercase strings\ncreated: <ISO 8601 date> # e.g. 2026-04-05\nupdated: <ISO 8601 date> # updated whenever content changes\nsource: <path or URL> # optional; original source material\n---\n\\`\\`\\`\n\nRequired fields: \\`title\\`, \\`created\\`.\n\n---\n\n## Page Templates\n\n### Entity Page\nUse for: people, systems, services, tools.\n\n\\`\\`\\`markdown\n---\ntitle: <Entity Name>\ntags: [entity]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# <Entity Name>\n\n**Type**: <system | person | service | tool>\n\n## Overview\n\n<One-paragraph description.>\n\n## Key Attributes\n\n- **Attribute**: value\n\n## Related\n\n- [[related-page]]\n\\`\\`\\`\n\n### Concept Page\nUse for: ideas, patterns, terminology.\n\n\\`\\`\\`markdown\n---\ntitle: <Concept Name>\ntags: [concept]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# <Concept Name>\n\n## Definition\n\n<Clear definition in 1-3 sentences.>\n\n## Context\n\n<When and why this concept matters in the project.>\n\n## See Also\n\n- [[related-concept]]\n\\`\\`\\`\n\n### Source Summary Page\nUse for: summarised source material (docs, papers, meetings).\n\n\\`\\`\\`markdown\n---\ntitle: Summary — <Source Title>\ntags: [source-summary]\ncreated: <ISO date>\nsource: <path or URL>\n---\n\n# Summary — <Source Title>\n\n## Key Points\n\n- Point one\n- Point two\n\n## Decisions / Implications\n\n<What this source means for the project.>\n\n## Raw Source\n\nSee \\`sources/<filename>\\`.\n\\`\\`\\`\n\n### Comparison Page\nUse for: side-by-side evaluation of options.\n\n\\`\\`\\`markdown\n---\ntitle: Comparison — <Topic>\ntags: [comparison]\ncreated: <ISO date>\nupdated: <ISO date>\n---\n\n# Comparison — <Topic>\n\n| Criterion | Option A | Option B |\n|-----------|----------|----------|\n| ... | ... | ... |\n\n## Recommendation\n\n<Which option and why.>\n\\`\\`\\`\n\n---\n\n## Wikilink Conventions\n\n- Basic link: \\`[[page-name]]\\` — links to \\`wiki/page-name.md\\`.\n- Display text: \\`[[page-name|display text]]\\` — renders as \"display text\".\n- Cross-directory: \\`[[topic/sub-page]]\\`.\n- All wikilink targets must be lowercase kebab-case matching the file name without \\`.md\\`.\n\n---\n\n## Ingest Workflow\n\n1. Place the source file in \\`sources/\\` (PDF, Markdown, plain text, etc.).\n2. Run \\`kb ingest sources/<filename>\\`.\n3. The CLI reads the file, calls the configured LLM, and generates a source-summary\n page in \\`wiki/\\`.\n4. The summary page is linked from \\`wiki/_index.md\\` under **Sources**.\n5. An entry is appended to \\`log.md\\`.\n\n---\n\n## Query Workflow\n\n1. Run \\`kb query \"<natural-language question>\"\\`.\n2. The CLI searches the wiki index for relevant pages.\n3. Relevant page content is assembled into a prompt context.\n4. The LLM answers the question, citing wikilinks.\n5. The answer is printed to stdout. Nothing is written to disk unless \\`--save\\` is passed.\n\n---\n\n## Lint Workflow\n\nRun \\`kb lint\\` to check for:\n\n- Pages missing required frontmatter fields (\\`title\\`, \\`created\\`).\n- Broken wikilinks (targets that don't resolve to an existing page).\n- Pages not reachable from \\`wiki/_index.md\\`.\n- Duplicate page titles across the wiki.\n- Frontmatter fields with invalid types or formats.\n\nLint exits with code 0 on success, 1 if errors are found.\n`;\n}\n\nfunction buildIndexMd(projectName: string, isoDate: string): string {\n return `---\ntitle: ${projectName} Knowledge Base\ncreated: ${isoDate}\n---\n\n# ${projectName} Knowledge Base\n\n> This wiki is maintained by the \\`kb\\` CLI tool.\n\n## Pages\n\n(No pages yet. Use \\`kb ingest <source>\\` to add content.)\n\n## Sources\n\n(No sources yet.)\n`;\n}\n\nfunction buildLogMd(projectName: string, isoDate: string): string {\n return `# Activity Log\n\n## ${isoDate} — Project initialized\n\nProject \\`${projectName}\\` initialized.\n`;\n}\n\nasync function kbDirExists(directory: string): Promise<boolean> {\n try {\n await access(join(directory, \".kb\"));\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function initProject(options: InitOptions): Promise<void> {\n const projectName = resolveProjectName(options);\n const { directory } = options;\n\n if (await kbDirExists(directory)) {\n throw new Error(\n `Knowledge base already initialized: .kb/ already exists in ${directory}`,\n );\n }\n\n const isoDate = new Date().toISOString().split(\"T\")[0]!;\n\n try {\n await Promise.all([\n mkdir(join(directory, \".kb\"), { recursive: true }),\n mkdir(join(directory, \"sources\"), { recursive: true }),\n mkdir(join(directory, \"wiki\"), { recursive: true }),\n ]);\n\n await Promise.all([\n writeFile(\n join(directory, \".kb\", \"config.toml\"),\n buildConfigToml(projectName),\n \"utf8\",\n ),\n writeFile(join(directory, \".kb\", \"schema.md\"), buildSchemaMd(), \"utf8\"),\n writeFile(join(directory, \"sources\", \".gitkeep\"), \"\", \"utf8\"),\n writeFile(\n join(directory, \"wiki\", \"_index.md\"),\n buildIndexMd(projectName, isoDate),\n \"utf8\",\n ),\n writeFile(\n join(directory, \"log.md\"),\n buildLogMd(projectName, isoDate),\n \"utf8\",\n ),\n ]);\n } catch (error) {\n // Rollback: remove .kb/ if it was created\n await rm(join(directory, \".kb\"), { recursive: true, force: true });\n throw error;\n }\n}\n","import { readFile } from \"node:fs/promises\";\nimport TOML from \"@iarna/toml\";\n\nexport interface KbConfig {\n project: {\n name: string;\n version: string;\n };\n directories: {\n sources: string;\n wiki: string;\n };\n llm: {\n provider: \"anthropic\" | \"openai\" | \"ollama\";\n model: string;\n };\n dependencies: Record<\n string,\n { path?: string; git?: string; branch?: string; mode?: string }\n >;\n}\n\nconst VALID_PROVIDERS = [\"anthropic\", \"openai\", \"ollama\"] as const;\n\nfunction requireSafeRelativePath(val: string, field: string): void {\n if (val.startsWith(\"/\") || val.split(\"/\").includes(\"..\")) {\n throw new Error(\n `Invalid config: ${field} must be a safe relative path, got \"${val}\"`,\n );\n }\n}\n\nfunction requireString(\n obj: Record<string, unknown>,\n key: string,\n context: string,\n): string {\n const val = obj[key];\n if (typeof val !== \"string\" || val.trim() === \"\") {\n throw new Error(\n `Invalid config: missing required field \"${context}.${key}\"`,\n );\n }\n return val;\n}\n\nfunction requireSection(\n obj: Record<string, unknown>,\n key: string,\n): Record<string, unknown> {\n const val = obj[key];\n if (\n val === undefined ||\n val === null ||\n typeof val !== \"object\" ||\n Array.isArray(val)\n ) {\n throw new Error(`Invalid config: missing required section \"[${key}]\"`);\n }\n return val as Record<string, unknown>;\n}\n\nexport async function parseConfig(configPath: string): Promise<KbConfig> {\n let raw: string;\n try {\n raw = await readFile(configPath, \"utf8\");\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Config file not found: ${configPath}\\n${message}`);\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = TOML.parse(raw) as Record<string, unknown>;\n } catch (err: unknown) {\n const message = err instanceof Error ? err.message : String(err);\n throw new Error(`Invalid TOML in config file ${configPath}: ${message}`);\n }\n\n const project = requireSection(parsed, \"project\");\n const name = requireString(project, \"name\", \"project\");\n const version = requireString(project, \"version\", \"project\");\n\n const directories = requireSection(parsed, \"directories\");\n const sources = requireString(directories, \"sources\", \"directories\");\n requireSafeRelativePath(sources, \"directories.sources\");\n const wiki = requireString(directories, \"wiki\", \"directories\");\n requireSafeRelativePath(wiki, \"directories.wiki\");\n\n const llm = requireSection(parsed, \"llm\");\n const providerRaw = requireString(llm, \"provider\", \"llm\");\n if (!(VALID_PROVIDERS as readonly string[]).includes(providerRaw)) {\n throw new Error(\n `Invalid config: llm.provider must be one of ${VALID_PROVIDERS.join(\", \")}, got \"${providerRaw}\"`,\n );\n }\n const provider = providerRaw as KbConfig[\"llm\"][\"provider\"];\n const model = requireString(llm, \"model\", \"llm\");\n\n const rawDeps = parsed[\"dependencies\"];\n const dependencies: KbConfig[\"dependencies\"] = {};\n if (\n rawDeps !== undefined &&\n rawDeps !== null &&\n typeof rawDeps === \"object\" &&\n !Array.isArray(rawDeps)\n ) {\n for (const [depKey, depVal] of Object.entries(\n rawDeps as Record<string, unknown>,\n )) {\n if (\n typeof depVal === \"object\" &&\n depVal !== null &&\n !Array.isArray(depVal)\n ) {\n const dep = depVal as Record<string, unknown>;\n dependencies[depKey] = {\n ...(typeof dep[\"path\"] === \"string\" ? { path: dep[\"path\"] } : {}),\n ...(typeof dep[\"git\"] === \"string\" ? { git: dep[\"git\"] } : {}),\n ...(typeof dep[\"branch\"] === \"string\"\n ? { branch: dep[\"branch\"] }\n : {}),\n ...(typeof dep[\"mode\"] === \"string\" ? { mode: dep[\"mode\"] } : {}),\n };\n }\n }\n }\n\n return {\n project: { name, version },\n directories: { sources, wiki },\n llm: { provider, model },\n dependencies,\n };\n}\n","import { access } from \"node:fs/promises\";\nimport { join, dirname, resolve } from \"node:path\";\nimport { parseConfig, type KbConfig } from \"./config.js\";\n\nexport interface Project {\n name: string;\n root: string;\n kbDir: string;\n sourcesDir: string;\n wikiDir: string;\n config: KbConfig;\n}\n\nasync function hasKbDir(dir: string): Promise<boolean> {\n try {\n await access(join(dir, \".kb\", \"config.toml\"));\n return true;\n } catch {\n return false;\n }\n}\n\nasync function findProjectRoot(startDir: string): Promise<string | null> {\n let current = resolve(startDir);\n\n while (true) {\n if (await hasKbDir(current)) {\n return current;\n }\n const parent = dirname(current);\n if (parent === current) {\n // Reached filesystem root\n return null;\n }\n current = parent;\n }\n}\n\nexport async function loadProject(startDir: string): Promise<Project> {\n const root = await findProjectRoot(startDir);\n if (root === null) {\n throw new Error(\n `No kb project found. Run \"kb init\" to initialize a knowledge base in the current directory.`,\n );\n }\n\n const kbDir = join(root, \".kb\");\n const configPath = join(kbDir, \"config.toml\");\n const config = await parseConfig(configPath);\n\n return {\n name: config.project.name,\n root,\n kbDir,\n sourcesDir: join(root, config.directories.sources),\n wikiDir: join(root, config.directories.wiki),\n config,\n };\n}\n\nexport async function tryLoadProject(\n startDir: string,\n): Promise<Project | null> {\n try {\n return await loadProject(startDir);\n } catch (err: unknown) {\n if (err instanceof Error && /no kb project found/i.test(err.message)) {\n return null;\n }\n throw err;\n }\n}\n","import Database from \"better-sqlite3\";\nimport { join } from \"node:path\";\nimport type { Project } from \"./project.js\";\n\nconst SCHEMA_SQL = `\nCREATE VIRTUAL TABLE IF NOT EXISTS pages USING fts5(\n path,\n title,\n content,\n tags,\n project,\n tokenize='porter unicode61'\n);\n\nCREATE TABLE IF NOT EXISTS page_meta (\n path TEXT PRIMARY KEY,\n sha256 TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n word_count INTEGER NOT NULL DEFAULT 0,\n frontmatter TEXT NOT NULL DEFAULT '{}',\n outgoing_links TEXT NOT NULL DEFAULT '[]',\n updated_at INTEGER NOT NULL\n);\n`;\n\nexport function openDb(project: Project): Database.Database {\n const dbPath = join(project.kbDir, \"index.db\");\n const db = new Database(dbPath);\n db.pragma(\"journal_mode = WAL\");\n db.exec(SCHEMA_SQL);\n return db;\n}\n\nexport function closeDb(db: Database.Database): void {\n db.close();\n}\n","import { readFile } from \"node:fs/promises\";\nimport matter from \"gray-matter\";\n\nexport interface ParsedPage {\n path: string;\n title: string;\n content: string;\n tags: string;\n frontmatter: Record<string, unknown>;\n outgoingLinks: string[];\n wordCount: number;\n}\n\nconst WIKILINK_RE = /\\[\\[([^\\]|]+)(?:\\|[^\\]]+)?\\]\\]/g;\nconst H1_RE = /^#\\s+(.+)$/m;\n\nfunction extractTitle(\n fm: Record<string, unknown>,\n content: string,\n relativePath: string,\n): string {\n if (typeof fm[\"title\"] === \"string\" && fm[\"title\"].trim() !== \"\") {\n return fm[\"title\"].trim();\n }\n const h1Match = H1_RE.exec(content);\n if (h1Match) {\n return h1Match[1]!.trim();\n }\n // Fallback: use filename without extension\n const filename = relativePath.split(\"/\").pop() ?? relativePath;\n return filename.replace(/\\.md$/i, \"\");\n}\n\nfunction extractTags(fm: Record<string, unknown>): string {\n const tags = fm[\"tags\"];\n if (!Array.isArray(tags)) return \"\";\n return tags.filter((t): t is string => typeof t === \"string\").join(\",\");\n}\n\nfunction extractWikiLinks(content: string): string[] {\n const links: string[] = [];\n let match: RegExpExecArray | null;\n const re = new RegExp(WIKILINK_RE.source, \"g\");\n while ((match = re.exec(content)) !== null) {\n links.push(match[1]!.trim());\n }\n return links;\n}\n\nfunction countWords(text: string): number {\n const trimmed = text.trim();\n if (trimmed === \"\") return 0;\n return trimmed.split(/\\s+/).length;\n}\n\nexport async function parsePage(\n filePath: string,\n relativePath: string,\n rawContent?: string,\n): Promise<ParsedPage> {\n const raw = rawContent ?? (await readFile(filePath, \"utf8\"));\n const parsed = matter(raw);\n const fm = parsed.data as Record<string, unknown>;\n const content = parsed.content;\n\n const title = extractTitle(fm, content, relativePath);\n const tags = extractTags(fm);\n const outgoingLinks = extractWikiLinks(content);\n const wordCount = countWords(content);\n\n return {\n path: relativePath,\n title,\n content,\n tags,\n frontmatter: fm,\n outgoingLinks,\n wordCount,\n };\n}\n","import { createHash } from \"node:crypto\";\nimport { readFile, stat, readdir } from \"node:fs/promises\";\nimport { join, relative } from \"node:path\";\nimport Database from \"better-sqlite3\";\nimport type { Project } from \"./project.js\";\nimport { parsePage } from \"./markdown.js\";\nimport { openDb, closeDb } from \"./db.js\";\n\nexport interface IndexStats {\n indexed: number;\n skipped: number;\n deleted: number;\n errors: number;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\nfunction sha256(content: string): string {\n return createHash(\"sha256\").update(content).digest(\"hex\");\n}\n\ninterface PageMetaRow {\n sha256: string;\n}\n\ninterface UpsertStmts {\n deletePages: Database.Statement;\n insertPage: Database.Statement;\n upsertMeta: Database.Statement;\n}\n\nfunction upsertParsedPage(\n stmts: UpsertStmts,\n project: Project,\n page: Awaited<ReturnType<typeof parsePage>>,\n hash: string,\n mtime: number,\n): void {\n stmts.deletePages.run(page.path);\n stmts.insertPage.run(\n page.path,\n page.title,\n page.content,\n page.tags,\n project.name,\n );\n stmts.upsertMeta.run(\n page.path,\n hash,\n mtime,\n page.wordCount,\n JSON.stringify(page.frontmatter),\n JSON.stringify(page.outgoingLinks),\n Date.now(),\n );\n}\n\nexport async function indexProject(\n project: Project,\n rebuild = false,\n): Promise<IndexStats> {\n const db = openDb(project);\n try {\n if (rebuild) {\n db.exec(\"DELETE FROM pages; DELETE FROM page_meta;\");\n }\n\n const files = await collectMdFiles(project.wikiDir);\n const stats: IndexStats = { indexed: 0, skipped: 0, deleted: 0, errors: 0 };\n\n const getMetaStmt = db.prepare<[string], PageMetaRow>(\n \"SELECT sha256 FROM page_meta WHERE path = ?\",\n );\n\n const upsertStmts: UpsertStmts = {\n deletePages: db.prepare(\"DELETE FROM pages WHERE path = ?\"),\n insertPage: db.prepare(\n \"INSERT INTO pages(path, title, content, tags, project) VALUES (?, ?, ?, ?, ?)\",\n ),\n upsertMeta: db.prepare(`\n INSERT INTO page_meta(path, sha256, mtime, word_count, frontmatter, outgoing_links, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(path) DO UPDATE SET\n sha256 = excluded.sha256,\n mtime = excluded.mtime,\n word_count = excluded.word_count,\n frontmatter = excluded.frontmatter,\n outgoing_links = excluded.outgoing_links,\n updated_at = excluded.updated_at\n `),\n };\n\n const deleteStalePages = db.prepare(\"DELETE FROM pages WHERE path = ?\");\n const deleteStaleMeta = db.prepare(\"DELETE FROM page_meta WHERE path = ?\");\n const listMetaStmt = db.prepare<[], { path: string }>(\n \"SELECT path FROM page_meta\",\n );\n\n const processFile = db.transaction(\n (\n page: Awaited<ReturnType<typeof parsePage>>,\n hash: string,\n mtime: number,\n ) => {\n upsertParsedPage(upsertStmts, project, page, hash, mtime);\n },\n );\n\n const onDiskPaths = new Set<string>();\n\n for (const absPath of files) {\n const relPath = relative(project.root, absPath);\n onDiskPaths.add(relPath);\n\n let raw: string;\n try {\n raw = await readFile(absPath, \"utf8\");\n } catch (err) {\n stats.errors++;\n continue;\n }\n\n const hash = sha256(raw);\n const existing = getMetaStmt.get(relPath);\n\n if (existing && existing.sha256 === hash) {\n stats.skipped++;\n continue;\n }\n\n let fileStat: Awaited<ReturnType<typeof stat>>;\n try {\n fileStat = await stat(absPath);\n } catch {\n stats.errors++;\n continue;\n }\n\n let page: Awaited<ReturnType<typeof parsePage>>;\n try {\n page = await parsePage(absPath, relPath, raw);\n } catch {\n stats.errors++;\n continue;\n }\n\n try {\n processFile(page, hash, Math.floor(fileStat.mtimeMs));\n stats.indexed++;\n } catch {\n stats.errors++;\n }\n }\n\n // Remove entries for deleted files\n const allMetaPaths = listMetaStmt.all().map((r) => r.path);\n\n const stalePaths = allMetaPaths.filter((p) => !onDiskPaths.has(p));\n\n const deleteStale = db.transaction((paths: string[]) => {\n for (const p of paths) {\n deleteStalePages.run(p);\n deleteStaleMeta.run(p);\n }\n });\n\n deleteStale(stalePaths);\n stats.deleted += stalePaths.length;\n\n return stats;\n } finally {\n closeDb(db);\n }\n}\n","import Database from \"better-sqlite3\";\n\nexport interface SearchResult {\n rank: number;\n path: string;\n title: string;\n snippet: string;\n tags: string[];\n}\n\nexport interface SearchOptions {\n limit?: number;\n tags?: string[];\n}\n\ninterface FtsRow {\n path: string;\n title: string;\n tags: string;\n rank: number;\n snippet: string;\n}\n\nfunction sanitizeFtsQuery(query: string): string {\n // Split into tokens, quote each one to escape special FTS5 chars.\n // Using individual quoted tokens (AND logic) instead of a single phrase\n // so non-adjacent words still match.\n const tokens = query\n .trim()\n .split(/\\s+/)\n .filter((t) => t.length > 0);\n if (tokens.length === 0) return '\"\"';\n return tokens.map((t) => `\"${t.replace(/\"/g, '\"\"')}\"`).join(\" \");\n}\n\nfunction parseTags(raw: string): string[] {\n if (!raw || raw.trim() === \"\") return [];\n return raw\n .split(\",\")\n .map((t) => t.trim())\n .filter((t) => t.length > 0);\n}\n\nexport function searchWiki(\n db: Database.Database,\n query: string,\n projectName: string,\n options?: SearchOptions,\n): SearchResult[] {\n if (!query || query.trim() === \"\") {\n return [];\n }\n\n const limit = options?.limit ?? 10;\n const ftsQuery = sanitizeFtsQuery(query.trim());\n\n // Build dynamic tag WHERE clauses so filtering happens in SQL\n const filterTags = options?.tags?.length\n ? options.tags\n .map((t) => t.trim().toLowerCase())\n .filter((t) => t.length > 0)\n : [];\n\n const tagClauses = filterTags.map(() => \"AND lower(tags) LIKE ?\").join(\" \");\n const tagParams = filterTags.map((t) => `%${t}%`);\n\n const stmt = db.prepare<[string, string, ...string[], number], FtsRow>(`\n SELECT path, title, tags, bm25(pages) as rank,\n snippet(pages, 2, '', '', '...', 8) as snippet\n FROM pages\n WHERE pages MATCH ? AND project = ?\n ${tagClauses}\n ORDER BY rank\n LIMIT ?\n `);\n\n const rows = stmt.all(ftsQuery, projectName, ...tagParams, limit);\n\n const results: SearchResult[] = rows.map((row) => ({\n rank: row.rank,\n path: row.path,\n title: row.title,\n snippet: row.snippet,\n tags: parseTags(row.tags),\n }));\n\n return results;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { basename, extname } from \"node:path\";\n\nexport type SourceType = \"markdown\" | \"text\" | \"pdf\" | \"url\";\n\nexport interface SourceContent {\n type: SourceType;\n originalPath: string;\n content: string;\n filename: string;\n}\n\nfunction sanitizeFilename(name: string): string {\n return name.toLowerCase().replace(/\\s/g, \"-\");\n}\n\nfunction detectType(sourcePath: string): SourceType {\n if (sourcePath.startsWith(\"http://\") || sourcePath.startsWith(\"https://\")) {\n return \"url\";\n }\n const ext = extname(sourcePath).toLowerCase();\n if (ext === \".pdf\") return \"pdf\";\n if (ext === \".md\") return \"markdown\";\n return \"text\";\n}\n\nfunction filenameFromUrl(url: string): string {\n try {\n const parsed = new URL(url);\n const parts = parsed.pathname.split(\"/\").filter(Boolean);\n const last = parts[parts.length - 1];\n const pagePart = last\n ? last.includes(\".\")\n ? last\n : `${last}.html`\n : \"index.html\";\n return sanitizeFilename(`${parsed.hostname}-${pagePart}`);\n } catch {\n return \"url-content.html\";\n }\n}\n\nfunction stripHtml(html: string): string {\n // Remove script and style blocks entirely\n let text = html.replace(/<script[\\s\\S]*?<\\/script>/gi, \" \");\n text = text.replace(/<style[\\s\\S]*?<\\/style>/gi, \" \");\n // Strip remaining tags\n text = text.replace(/<[^>]+>/g, \" \");\n // Decode common HTML entities\n text = text\n .replace(/&amp;/gi, \"&\")\n .replace(/&lt;/gi, \"<\")\n .replace(/&gt;/gi, \">\")\n .replace(/&quot;/gi, '\"')\n .replace(/&#39;/gi, \"'\")\n .replace(/&nbsp;/gi, \" \");\n // Collapse whitespace\n text = text.replace(/\\s+/g, \" \").trim();\n return text;\n}\n\nasync function readPdf(filePath: string): Promise<string> {\n const pdfParse: (buf: Buffer) => Promise<{ text: string }> =\n await import(\"pdf-parse\")\n .then((m) => m.default ?? m)\n .catch(() => {\n throw new Error(\n \"PDF support requires pdf-parse: run `npm install pdf-parse` in your project\",\n );\n });\n const buffer = await readFile(filePath);\n const data = await pdfParse(buffer);\n return data.text;\n}\n\nfunction isPrivateUrl(url: string): boolean {\n try {\n const { hostname } = new URL(url);\n return (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"::1\" ||\n hostname.startsWith(\"169.254.\") || // link-local\n hostname.startsWith(\"10.\") ||\n hostname.startsWith(\"192.168.\") ||\n /^172\\.(1[6-9]|2\\d|3[01])\\./.test(hostname)\n );\n } catch {\n return false;\n }\n}\n\nasync function fetchUrl(url: string): Promise<string> {\n if (isPrivateUrl(url)) {\n throw new Error(`Fetching private/localhost URLs is not allowed: ${url}`);\n }\n const response = await fetch(url);\n if (!response.ok) {\n throw new Error(\n `Failed to fetch URL ${url}: HTTP ${response.status} ${response.statusText}`,\n );\n }\n const html = await response.text();\n return stripHtml(html);\n}\n\nexport async function readSource(sourcePath: string): Promise<SourceContent> {\n const type = detectType(sourcePath);\n\n if (type === \"url\") {\n const content = await fetchUrl(sourcePath);\n const filename = filenameFromUrl(sourcePath);\n return { type, originalPath: sourcePath, content, filename };\n }\n\n if (type === \"pdf\") {\n const content = await readPdf(sourcePath);\n const raw = basename(sourcePath);\n const filename = sanitizeFilename(raw);\n return { type, originalPath: sourcePath, content, filename };\n }\n\n // markdown or text\n const content = await readFile(sourcePath, \"utf8\");\n const raw = basename(sourcePath);\n const filename = sanitizeFilename(raw);\n return { type, originalPath: sourcePath, content, filename };\n}\n","import type { KbConfig } from \"./config.js\";\n\nexport interface LlmMessage {\n role: \"user\" | \"assistant\";\n content: string;\n}\n\nexport interface LlmAdapter {\n complete(messages: LlmMessage[], systemPrompt: string): Promise<string>;\n}\n\nfunction createAnthropicAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"ANTHROPIC_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"ANTHROPIC_API_KEY environment variable is not set\");\n }\n const Anthropic = await import(\"@anthropic-ai/sdk\").then(\n (m) => m.default ?? m,\n );\n const client = new Anthropic({ apiKey });\n const response = await client.messages.create({\n model,\n max_tokens: 8192,\n system: systemPrompt,\n messages: messages.map((m) => ({\n role: m.role,\n content: m.content,\n })),\n });\n const block = response.content[0];\n if (!block || block.type !== \"text\") {\n throw new Error(\"Anthropic returned no text content\");\n }\n return block.text;\n },\n };\n}\n\nfunction createOpenAiAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"OPENAI_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"OPENAI_API_KEY environment variable is not set\");\n }\n const body = {\n model,\n messages: [\n { role: \"system\", content: systemPrompt },\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n };\n const response = await fetch(\n \"https://api.openai.com/v1/chat/completions\",\n {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n Authorization: `Bearer ${apiKey}`,\n },\n body: JSON.stringify(body),\n },\n );\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`OpenAI API error: HTTP ${response.status} — ${text}`);\n }\n const data = (await response.json()) as {\n choices: Array<{ message: { content: string } }>;\n };\n const content = data.choices[0]?.message?.content;\n if (!content) {\n throw new Error(\"OpenAI returned no content\");\n }\n return content;\n },\n };\n}\n\nfunction createOllamaAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const baseUrl =\n process.env[\"OLLAMA_BASE_URL\"] ?? \"http://localhost:11434\";\n const body = {\n model,\n messages: [\n { role: \"system\", content: systemPrompt },\n ...messages.map((m) => ({ role: m.role, content: m.content })),\n ],\n stream: false,\n };\n const response = await fetch(`${baseUrl}/api/chat`, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n });\n if (!response.ok) {\n const text = await response.text();\n throw new Error(`Ollama API error: HTTP ${response.status} — ${text}`);\n }\n const data = (await response.json()) as {\n message?: { content: string };\n };\n const content = data.message?.content;\n if (!content) {\n throw new Error(\"Ollama returned no content\");\n }\n return content;\n },\n };\n}\n\nexport function createLlmAdapter(config: KbConfig): LlmAdapter {\n const { provider, model } = config.llm;\n switch (provider) {\n case \"anthropic\":\n return createAnthropicAdapter(model);\n case \"openai\":\n return createOpenAiAdapter(model);\n case \"ollama\":\n return createOllamaAdapter(model);\n default: {\n const _exhaustive: never = provider;\n throw new Error(`Unsupported LLM provider: ${String(_exhaustive)}`);\n }\n }\n}\n","import { readFile, writeFile, mkdir, appendFile } from \"node:fs/promises\";\nimport { join, dirname, resolve } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport type { LlmAdapter } from \"./llm.js\";\nimport type { IngestResult } from \"./ingest-types.js\";\nimport { readSource } from \"./source-reader.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport interface IngestOptions {\n apply?: boolean;\n batch?: boolean;\n}\n\nexport interface IngestPlan {\n result: IngestResult;\n sourceFile: string;\n dryRun: boolean;\n}\n\nconst SYSTEM_PROMPT = `You are an AI assistant maintaining a knowledge base wiki.\nYou will be given a new source document and the current state of the wiki.\nYour task is to integrate the new knowledge into the wiki.\n\nReturn ONLY a JSON object matching this exact schema (no markdown fences):\n{\n \"summary\": { \"path\": \"wiki/sources/<filename>-summary.md\", \"content\": \"...\" },\n \"updates\": [{ \"path\": \"...\", \"content\": \"...\", \"reason\": \"...\" }],\n \"newPages\": [{ \"path\": \"...\", \"content\": \"...\", \"reason\": \"...\" }],\n \"indexUpdate\": \"...\",\n \"logEntry\": \"...\"\n}`;\n\nfunction assertWithinRoot(absPath: string, root: string): void {\n const resolvedPath = resolve(absPath);\n const resolvedRoot = resolve(root) + \"/\";\n if (!resolvedPath.startsWith(resolvedRoot)) {\n throw new Error(\n `Unsafe path rejected: \"${absPath}\" is outside project root`,\n );\n }\n}\n\nasync function readFileSafe(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nfunction parseIngestResult(raw: string): IngestResult {\n const cleaned = raw\n .replace(/^```(?:json)?\\s*/i, \"\")\n .replace(/\\s*```\\s*$/i, \"\")\n .trim();\n let parsed: unknown;\n try {\n parsed = JSON.parse(cleaned);\n } catch (err) {\n throw new Error(\n `Invalid LLM response: could not parse JSON. Raw response: ${cleaned.slice(0, 200)}`,\n );\n }\n\n if (parsed === null || typeof parsed !== \"object\" || Array.isArray(parsed)) {\n throw new Error(\"Invalid LLM response: expected a JSON object\");\n }\n\n const obj = parsed as Record<string, unknown>;\n\n if (\n !obj[\"summary\"] ||\n typeof obj[\"summary\"] !== \"object\" ||\n Array.isArray(obj[\"summary\"])\n ) {\n throw new Error('Invalid LLM response: missing \"summary\" object');\n }\n\n const summary = obj[\"summary\"] as Record<string, unknown>;\n if (\n typeof summary[\"path\"] !== \"string\" ||\n typeof summary[\"content\"] !== \"string\"\n ) {\n throw new Error(\n 'Invalid LLM response: \"summary\" must have \"path\" and \"content\" strings',\n );\n }\n\n if (!Array.isArray(obj[\"updates\"])) {\n throw new Error('Invalid LLM response: \"updates\" must be an array');\n }\n\n if (!Array.isArray(obj[\"newPages\"])) {\n throw new Error('Invalid LLM response: \"newPages\" must be an array');\n }\n\n if (typeof obj[\"indexUpdate\"] !== \"string\") {\n throw new Error('Invalid LLM response: \"indexUpdate\" must be a string');\n }\n\n if (typeof obj[\"logEntry\"] !== \"string\") {\n throw new Error('Invalid LLM response: \"logEntry\" must be a string');\n }\n\n const updates = (obj[\"updates\"] as unknown[]).map((u, i) => {\n if (typeof u !== \"object\" || u === null || Array.isArray(u)) {\n throw new Error(`Invalid LLM response: updates[${i}] must be an object`);\n }\n const update = u as Record<string, unknown>;\n if (\n typeof update[\"path\"] !== \"string\" ||\n typeof update[\"content\"] !== \"string\" ||\n typeof update[\"reason\"] !== \"string\"\n ) {\n throw new Error(\n `Invalid LLM response: updates[${i}] must have path, content, and reason strings`,\n );\n }\n return {\n path: update[\"path\"],\n content: update[\"content\"],\n reason: update[\"reason\"],\n };\n });\n\n const newPages = (obj[\"newPages\"] as unknown[]).map((p, i) => {\n if (typeof p !== \"object\" || p === null || Array.isArray(p)) {\n throw new Error(`Invalid LLM response: newPages[${i}] must be an object`);\n }\n const page = p as Record<string, unknown>;\n if (\n typeof page[\"path\"] !== \"string\" ||\n typeof page[\"content\"] !== \"string\" ||\n typeof page[\"reason\"] !== \"string\"\n ) {\n throw new Error(\n `Invalid LLM response: newPages[${i}] must have path, content, and reason strings`,\n );\n }\n return {\n path: page[\"path\"],\n content: page[\"content\"],\n reason: page[\"reason\"],\n };\n });\n\n return {\n summary: { path: summary[\"path\"], content: summary[\"content\"] },\n updates,\n newPages,\n indexUpdate: obj[\"indexUpdate\"] as string,\n logEntry: obj[\"logEntry\"] as string,\n };\n}\n\nasync function applyIngestResult(\n project: Project,\n result: IngestResult,\n sourceContent: string,\n sourceFilename: string,\n): Promise<void> {\n // Write summary\n const summaryAbsPath = join(project.root, result.summary.path);\n assertWithinRoot(summaryAbsPath, project.root);\n await mkdir(dirname(summaryAbsPath), { recursive: true });\n await writeFile(summaryAbsPath, result.summary.content, \"utf8\");\n\n // Write updated pages\n for (const update of result.updates) {\n const absPath = join(project.root, update.path);\n assertWithinRoot(absPath, project.root);\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, update.content, \"utf8\");\n }\n\n // Write new pages\n for (const newPage of result.newPages) {\n const absPath = join(project.root, newPage.path);\n assertWithinRoot(absPath, project.root);\n await mkdir(dirname(absPath), { recursive: true });\n await writeFile(absPath, newPage.content, \"utf8\");\n }\n\n // Update _index.md\n const indexPath = join(project.wikiDir, \"_index.md\");\n await writeFile(indexPath, result.indexUpdate, \"utf8\");\n\n // Write source file to sources directory\n const sourceDestPath = join(project.sourcesDir, sourceFilename);\n await mkdir(project.sourcesDir, { recursive: true });\n await writeFile(sourceDestPath, sourceContent, \"utf8\");\n\n // Append to log.md\n const logPath = join(project.wikiDir, \"log.md\");\n const timestamp = new Date().toISOString().split(\"T\")[0];\n const logLine = `- ${timestamp}: ${result.logEntry}\\n`;\n await appendFile(logPath, logLine, \"utf8\");\n\n // Re-index\n await indexProject(project);\n}\n\nexport async function ingestSource(\n project: Project,\n sourcePath: string,\n llm: LlmAdapter,\n options?: IngestOptions,\n): Promise<IngestPlan> {\n const apply = options?.apply ?? false;\n\n // 1. Read source content\n const sourceContent = await readSource(sourcePath);\n\n // 2. Read current wiki index\n const indexPath = join(project.wikiDir, \"_index.md\");\n const currentIndex = await readFileSafe(indexPath);\n\n // 3. Read schema\n const schemaPath = join(project.kbDir, \"schema.md\");\n const schema = await readFileSafe(schemaPath);\n\n // 4. Build user message\n const userMessage = `## Wiki Schema\n${schema}\n\n## Current Wiki Index\n${currentIndex}\n\n## New Source: ${sourceContent.filename}\n${sourceContent.content}\n\nIntegrate this source into the wiki following the schema above.`;\n\n // 5. Call LLM\n const raw = await llm.complete(\n [{ role: \"user\", content: userMessage }],\n SYSTEM_PROMPT,\n );\n\n // 6. Parse response\n const result = parseIngestResult(raw);\n\n // 7. Apply if requested\n if (apply) {\n await applyIngestResult(\n project,\n result,\n sourceContent.content,\n sourceContent.filename,\n );\n }\n\n const sourceFile = join(project.sourcesDir, sourceContent.filename);\n\n return {\n result,\n sourceFile,\n dryRun: !apply,\n };\n}\n","import { readFile, writeFile, mkdir, appendFile } from \"node:fs/promises\";\nimport { existsSync } from \"node:fs\";\nimport { join, dirname, resolve } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport type { LlmAdapter } from \"./llm.js\";\nimport { openDb, closeDb } from \"./db.js\";\nimport { searchWiki } from \"./search.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport interface QueryResult {\n answer: string;\n sources: string[];\n}\n\nexport interface QueryOptions {\n save?: string;\n}\n\nconst SYSTEM_PROMPT = `You are a knowledgeable assistant answering questions about a project's knowledge base.\nAnswer concisely using only information from the provided wiki pages.\nUse [[page-name]] wikilink syntax to cite specific wiki pages in your answer.\nFormat your answer in markdown.`;\n\nfunction assertWithinRoot(absPath: string, root: string): void {\n const resolvedPath = resolve(absPath);\n const resolvedRoot = resolve(root) + \"/\";\n if (!resolvedPath.startsWith(resolvedRoot)) {\n throw new Error(\n `Unsafe path rejected: \"${absPath}\" is outside project root`,\n );\n }\n}\n\nasync function readFileSafe(filePath: string): Promise<string> {\n try {\n return await readFile(filePath, \"utf8\");\n } catch {\n return \"\";\n }\n}\n\nexport async function queryWiki(\n project: Project,\n question: string,\n llm: LlmAdapter,\n options?: QueryOptions,\n): Promise<QueryResult> {\n // 1. Auto-index if db doesn't exist\n const dbPath = join(project.kbDir, \"index.db\");\n if (!existsSync(dbPath)) {\n await indexProject(project);\n }\n\n // 2. Search for top relevant pages\n const db = openDb(project);\n let searchResults;\n try {\n searchResults = searchWiki(db, question, project.name, { limit: 10 });\n } finally {\n closeDb(db);\n }\n\n // 3. Read full content of each result page\n const pages: Array<{ path: string; title: string; content: string }> = [];\n for (const result of searchResults) {\n const absPath = join(project.root, result.path);\n const content = await readFileSafe(absPath);\n if (content) {\n pages.push({ path: result.path, title: result.title, content });\n }\n }\n\n // 4. Build user message\n const pagesSection =\n pages.length > 0\n ? pages\n .map((p) => `### ${p.title} (${p.path})\\n${p.content}`)\n .join(\"\\n\\n\")\n : \"(No wiki pages found for this query.)\";\n\n const userMessage = `## Question\\n${question}\\n\\n## Relevant Wiki Pages\\n\\n${pagesSection}`;\n\n // 5. Call LLM\n const answer = await llm.complete(\n [{ role: \"user\", content: userMessage }],\n SYSTEM_PROMPT,\n );\n\n const sources = pages.map((p) => p.path);\n\n // 6. If save option provided, write answer as wiki page and append to log\n if (options?.save) {\n const saveRelPath = options.save;\n const saveAbsPath = join(project.root, saveRelPath);\n assertWithinRoot(saveAbsPath, project.root);\n\n await mkdir(dirname(saveAbsPath), { recursive: true });\n await writeFile(saveAbsPath, answer, \"utf8\");\n\n // Append to log.md\n const logPath = join(project.wikiDir, \"log.md\");\n const timestamp = new Date().toISOString().split(\"T\")[0];\n const logEntry = `\\n## ${timestamp} — Queried: ${question}\\n\\nSaved to: ${saveRelPath}\\n`;\n await appendFile(logPath, logEntry, \"utf8\");\n\n // Re-index so saved page is searchable\n await indexProject(project);\n }\n\n return { answer, sources };\n}\n","import { readdir, stat } from \"node:fs/promises\";\nimport { join, relative, basename, extname } from \"node:path\";\nimport type { Project } from \"./project.js\";\nimport { openDb, closeDb } from \"./db.js\";\nimport { indexProject } from \"./indexer.js\";\n\nexport type LintSeverity = \"warning\" | \"info\";\n\nexport interface LintIssue {\n severity: LintSeverity;\n code: string;\n path: string;\n message: string;\n detail?: string;\n}\n\nexport interface LintResult {\n issues: LintIssue[];\n pagesChecked: number;\n sourcesChecked: number;\n}\n\ninterface PageMetaRow {\n path: string;\n outgoing_links: string;\n word_count: number;\n mtime: number;\n updated_at: number;\n}\n\nasync function collectMdFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile() && e.name.endsWith(\".md\"))\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\nasync function collectSourceFiles(dir: string): Promise<string[]> {\n try {\n const entries = await readdir(dir, {\n recursive: true,\n withFileTypes: true,\n });\n return (\n entries\n .filter((e) => e.isFile())\n // parentPath added Node 21.4+; fall back to the pre-deprecation path property\n .map((e) => join((e as any).parentPath ?? (e as any).path, e.name))\n );\n } catch (err: unknown) {\n if ((err as NodeJS.ErrnoException).code !== \"ENOENT\") throw err;\n return [];\n }\n}\n\n/**\n * Build a set of \"keys\" for wiki pages for wikilink resolution.\n * A wikilink [[foo-bar]] can match:\n * - a page whose filename (without extension) is \"foo-bar\"\n * - a page whose relative path is \"foo-bar\" or \"foo-bar.md\"\n */\nfunction buildPageKeySet(\n relPaths: string[],\n projectRoot: string,\n wikiDir: string,\n): Set<string> {\n const keys = new Set<string>();\n for (const rp of relPaths) {\n // rp is relative to projectRoot, e.g. \"wiki/concepts/foo.md\"\n keys.add(rp);\n // without extension\n keys.add(rp.replace(/\\.md$/i, \"\"));\n // filename without extension\n const fname = basename(rp, \".md\");\n keys.add(fname);\n // relative path from wikiDir\n const absPath = join(projectRoot, rp);\n const relToWiki = relative(wikiDir, absPath);\n keys.add(relToWiki);\n keys.add(relToWiki.replace(/\\.md$/i, \"\"));\n }\n return keys;\n}\n\nexport async function lintProject(project: Project): Promise<LintResult> {\n // Ensure index is up to date\n await indexProject(project);\n\n const issues: LintIssue[] = [];\n\n // Collect all wiki md files\n const absWikiFiles = await collectMdFiles(project.wikiDir);\n const relWikiPaths = absWikiFiles.map((f) => relative(project.root, f));\n\n const pagesChecked = relWikiPaths.length;\n\n // Always collect source files so sourcesChecked is accurate\n const sourceFiles = await collectSourceFiles(project.sourcesDir);\n const sourcesChecked = sourceFiles.filter(\n (f) => basename(f) !== \".gitkeep\",\n ).length;\n\n if (pagesChecked === 0) {\n return { issues, pagesChecked: 0, sourcesChecked };\n }\n\n // Build page key set for wikilink resolution\n const pageKeySet = buildPageKeySet(\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n\n // Query all page_meta rows\n const db = openDb(project);\n let rows: PageMetaRow[];\n try {\n rows = db\n .prepare<\n [],\n PageMetaRow\n >(\"SELECT path, outgoing_links, word_count, mtime, updated_at FROM page_meta\")\n .all();\n } finally {\n closeDb(db);\n }\n\n // Build a map from path -> row for quick lookup\n const metaMap = new Map<string, PageMetaRow>();\n for (const row of rows) {\n metaMap.set(row.path, row);\n }\n\n // Build inbound link map\n const inboundLinks = new Map<string, Set<string>>();\n for (const rp of relWikiPaths) {\n inboundLinks.set(rp, new Set());\n }\n\n for (const row of rows) {\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n // Find which page this link resolves to\n const resolved = resolveLink(\n link,\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n if (resolved !== null) {\n const set = inboundLinks.get(resolved);\n if (set) {\n set.add(row.path);\n }\n }\n }\n }\n\n // Read _index.md outgoing links for MISSING_INDEX check\n const indexPath = relWikiPaths.find((p) => basename(p) === \"_index.md\");\n let indexLinks: Set<string> = new Set();\n if (indexPath) {\n const indexRow = metaMap.get(indexPath);\n if (indexRow) {\n let links: string[] = [];\n try {\n links = JSON.parse(indexRow.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n const resolved = resolveLink(\n link,\n relWikiPaths,\n project.root,\n project.wikiDir,\n );\n if (resolved !== null) {\n indexLinks.add(resolved);\n }\n }\n }\n }\n\n // --- CHECK 1: ORPHAN_PAGE ---\n for (const rp of relWikiPaths) {\n if (basename(rp) === \"_index.md\") continue;\n const inbound = inboundLinks.get(rp);\n if (!inbound || inbound.size === 0) {\n issues.push({\n severity: \"warning\",\n code: \"ORPHAN_PAGE\",\n path: rp,\n message: \"Orphan page (no inbound links)\",\n });\n }\n }\n\n // --- CHECK 2: BROKEN_LINK ---\n for (const row of rows) {\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n for (const link of links) {\n if (!isLinkResolvable(link, pageKeySet)) {\n issues.push({\n severity: \"warning\",\n code: \"BROKEN_LINK\",\n path: row.path,\n message: `Broken wikilink [[${link}]] not found`,\n detail: link,\n });\n }\n }\n }\n\n // --- CHECK 3: STUB_PAGE ---\n for (const row of rows) {\n if (basename(row.path) === \"_index.md\") continue;\n let links: string[] = [];\n try {\n links = JSON.parse(row.outgoing_links) as string[];\n } catch {\n links = [];\n }\n if (links.length === 0 && row.word_count < 50) {\n issues.push({\n severity: \"info\",\n code: \"STUB_PAGE\",\n path: row.path,\n message: `Stub page (no links, < 50 words)`,\n });\n }\n }\n\n // --- CHECK 4: STALE_SUMMARY ---\n // wiki/sources/foo-summary.md <-> sources/foo.*\n const wikiSourcesDir = join(project.wikiDir, \"sources\");\n\n for (const rp of relWikiPaths) {\n // Check if the path is under wiki/sources/\n const absWikiPage = join(project.root, rp);\n const relToWikiSources = relative(wikiSourcesDir, absWikiPage);\n\n // Skip if not under wiki/sources/ (would start with \"..\")\n if (relToWikiSources.startsWith(\"..\")) continue;\n\n // Convention: wiki/sources/foo-summary.md <-> sources/foo.*\n const summaryBasename = basename(rp, \".md\");\n // Strip -summary suffix\n const sourceBasename = summaryBasename.endsWith(\"-summary\")\n ? summaryBasename.slice(0, -\"-summary\".length)\n : summaryBasename;\n\n // Find matching source file\n const matchingSource = sourceFiles.find((sf) => {\n const sfBase = basename(sf, extname(sf));\n return sfBase === sourceBasename;\n });\n\n if (!matchingSource) continue;\n\n try {\n const summaryRow = metaMap.get(rp);\n if (!summaryRow) continue;\n\n const [sourceStat, summaryStat] = await Promise.all([\n stat(matchingSource),\n stat(join(project.root, summaryRow.path)),\n ]);\n\n if (sourceStat.mtimeMs > summaryStat.mtimeMs) {\n issues.push({\n severity: \"warning\",\n code: \"STALE_SUMMARY\",\n path: rp,\n message: \"Source updated after summary\",\n detail: relative(project.root, matchingSource),\n });\n }\n } catch {\n // Ignore stat errors\n }\n }\n\n // --- CHECK 5: MISSING_INDEX ---\n for (const rp of relWikiPaths) {\n if (basename(rp) === \"_index.md\") continue;\n if (!indexPath) {\n // No _index.md exists — skip this check\n continue;\n }\n if (!indexLinks.has(rp)) {\n // Check by filename too\n const fname = basename(rp, \".md\");\n if (!indexLinks.has(fname)) {\n issues.push({\n severity: \"info\",\n code: \"MISSING_INDEX\",\n path: rp,\n message: \"Not in _index.md\",\n });\n }\n }\n }\n\n return { issues, pagesChecked, sourcesChecked };\n}\n\nfunction resolveLink(\n link: string,\n relPaths: string[],\n projectRoot: string,\n wikiDir: string,\n): string | null {\n for (const rp of relPaths) {\n const fname = basename(rp, \".md\");\n if (fname === link) return rp;\n if (rp === link || rp === `${link}.md`) return rp;\n // relative to wikiDir\n const absPath = join(projectRoot, rp);\n const relToWiki = relative(wikiDir, absPath);\n if (relToWiki === link || relToWiki.replace(/\\.md$/i, \"\") === link) {\n return rp;\n }\n }\n return null;\n}\n\nfunction isLinkResolvable(link: string, pageKeySet: Set<string>): boolean {\n return pageKeySet.has(link);\n}\n","// packages/core/src/log-parser.ts\n\nexport interface ParsedLogEntry {\n heading: string;\n body: string;\n}\n\n/**\n * Parses a log.md file into an array of entries.\n * Each entry starts with a level-2 heading (## ...).\n * The top-level \"# Activity Log\" heading is skipped.\n */\nexport function parseLogEntries(content: string): ParsedLogEntry[] {\n const entries: ParsedLogEntry[] = [];\n const sections = content.split(/^(?=## )/m);\n\n for (const section of sections) {\n const trimmed = section.trim();\n if (!trimmed) continue;\n if (trimmed.startsWith(\"# \")) continue;\n if (!trimmed.startsWith(\"## \")) continue;\n\n const newlineIdx = trimmed.indexOf(\"\\n\");\n if (newlineIdx === -1) {\n entries.push({ heading: trimmed.slice(3).trim(), body: \"\" });\n } else {\n const heading = trimmed.slice(3, newlineIdx).trim();\n const body = trimmed.slice(newlineIdx + 1).trim();\n entries.push({ heading, body });\n }\n }\n\n return entries;\n}\n","// Core package — business logic\n\nexport const VERSION = \"0.1.0\";\n\nexport { initProject } from \"./init.js\";\nexport type { InitOptions } from \"./init.js\";\n\nexport { parseConfig } from \"./config.js\";\nexport type { KbConfig } from \"./config.js\";\n\nexport { loadProject, tryLoadProject } from \"./project.js\";\nexport type { Project } from \"./project.js\";\n\nexport { openDb, closeDb } from \"./db.js\";\n\nexport { parsePage } from \"./markdown.js\";\nexport type { ParsedPage } from \"./markdown.js\";\n\nexport { indexProject } from \"./indexer.js\";\nexport type { IndexStats } from \"./indexer.js\";\n\nexport { searchWiki } from \"./search.js\";\nexport type { SearchResult, SearchOptions } from \"./search.js\";\n\nexport { readSource } from \"./source-reader.js\";\nexport type { SourceContent, SourceType } from \"./source-reader.js\";\n\nexport { createLlmAdapter } from \"./llm.js\";\nexport type { LlmAdapter, LlmMessage } from \"./llm.js\";\n\nexport type { IngestResult } from \"./ingest-types.js\";\n\nexport { ingestSource } from \"./ingest.js\";\nexport type { IngestOptions, IngestPlan } from \"./ingest.js\";\n\nexport { queryWiki } from \"./query.js\";\nexport type { QueryResult, QueryOptions } from \"./query.js\";\n\nexport { lintProject } from \"./lint.js\";\nexport type { LintIssue, LintResult, LintSeverity } from \"./lint.js\";\n\nexport { parseLogEntries } from \"./log-parser.js\";\nexport type { ParsedLogEntry } from \"./log-parser.js\";\n"],"mappings":"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","names":["TOML","access","join","access","join","join","readFile","readFile","join","join","readFile","readFile","basename","content","filename","raw","readFile","writeFile","mkdir","join","dirname","resolve","resolve","readFile","join","mkdir","dirname","writeFile","readFile","writeFile","mkdir","appendFile","join","dirname","resolve","SYSTEM_PROMPT","assertWithinRoot","resolve","readFileSafe","readFile","join","mkdir","dirname","writeFile","appendFile","readdir","stat","join","relative","basename","extname","collectMdFiles","readdir","join","basename","relative","extname","stat"]}
@@ -1 +1 @@
1
- {"version":3,"file":"source-reader.d.ts","sourceRoot":"","sources":["../src/source-reader.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA0FD,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqB3E"}
1
+ {"version":3,"file":"source-reader.d.ts","sourceRoot":"","sources":["../src/source-reader.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,UAAU,GAAG,UAAU,GAAG,MAAM,GAAG,KAAK,GAAG,KAAK,CAAC;AAE7D,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAgGD,wBAAsB,UAAU,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,aAAa,CAAC,CAqB3E"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "kb-core",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -16,10 +16,8 @@
16
16
  "@anthropic-ai/sdk": "^0.30.0",
17
17
  "@iarna/toml": "^2.2.5",
18
18
  "@types/better-sqlite3": "^7.6.0",
19
- "@types/pdf-parse": "^1.1.4",
20
19
  "better-sqlite3": "^9.0.0",
21
20
  "gray-matter": "^4.0.3",
22
- "pdf-parse": "^1.1.4",
23
21
  "remark": "^15.0.0",
24
22
  "remark-frontmatter": "^5.0.0",
25
23
  "remark-wiki-link": "^2.0.0"