kb-core 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/config.d.ts +1 -1
- package/dist/config.d.ts.map +1 -1
- package/dist/index.cjs +38 -44
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +38 -44
- package/dist/index.js.map +1 -1
- package/dist/llm.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs.map
CHANGED
|
@@ -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(/&/gi, \"&\")\n .replace(/</gi, \"<\")\n .replace(/>/gi, \">\")\n .replace(/"/gi, '\"')\n .replace(/'/gi, \"'\")\n .replace(/ /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"]}
|
|
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\" | \"zai\";\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\", \"zai\"] 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(/&/gi, \"&\")\n .replace(/</gi, \"<\")\n .replace(/>/gi, \">\")\n .replace(/"/gi, '\"')\n .replace(/'/gi, \"'\")\n .replace(/ /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\nfunction createZaiAdapter(model: string): LlmAdapter {\n return {\n async complete(messages, systemPrompt) {\n const apiKey = process.env[\"ZAI_API_KEY\"];\n if (!apiKey) {\n throw new Error(\"ZAI_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.z.ai/api/paas/v4/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(`Z.AI 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(\"Z.AI 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 case \"zai\":\n return createZaiAdapter(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
CHANGED
|
@@ -677,7 +677,9 @@ function stripHtml(html) {
|
|
|
677
677
|
return text;
|
|
678
678
|
}
|
|
679
679
|
async function readPdf(filePath) {
|
|
680
|
-
const pdfParse = await import("pdf-parse").then((m) => m.default ?? m)
|
|
680
|
+
const pdfParse = await import("pdf-parse").then((m) => m.default ?? m).catch(() => {
|
|
681
|
+
throw new Error("PDF support requires pdf-parse: run `npm install pdf-parse` in your project");
|
|
682
|
+
});
|
|
681
683
|
const buffer = await readFile4(filePath);
|
|
682
684
|
const data = await pdfParse(buffer);
|
|
683
685
|
return data.text;
|
|
@@ -984,7 +986,7 @@ Integrate this source into the wiki following the schema above.`;
|
|
|
984
986
|
};
|
|
985
987
|
}
|
|
986
988
|
|
|
987
|
-
// src/query.
|
|
989
|
+
// src/query.js
|
|
988
990
|
import { readFile as readFile6, writeFile as writeFile3, mkdir as mkdir3, appendFile as appendFile2 } from "fs/promises";
|
|
989
991
|
import { existsSync } from "fs";
|
|
990
992
|
import { join as join6, dirname as dirname3, resolve as resolve3 } from "path";
|
|
@@ -996,9 +998,7 @@ function assertWithinRoot2(absPath, root) {
|
|
|
996
998
|
const resolvedPath = resolve3(absPath);
|
|
997
999
|
const resolvedRoot = resolve3(root) + "/";
|
|
998
1000
|
if (!resolvedPath.startsWith(resolvedRoot)) {
|
|
999
|
-
throw new Error(
|
|
1000
|
-
`Unsafe path rejected: "${absPath}" is outside project root`
|
|
1001
|
-
);
|
|
1001
|
+
throw new Error(`Unsafe path rejected: "${absPath}" is outside project root`);
|
|
1002
1002
|
}
|
|
1003
1003
|
}
|
|
1004
1004
|
async function readFileSafe2(filePath) {
|
|
@@ -1036,10 +1036,7 @@ ${question}
|
|
|
1036
1036
|
## Relevant Wiki Pages
|
|
1037
1037
|
|
|
1038
1038
|
${pagesSection}`;
|
|
1039
|
-
const answer = await llm.complete(
|
|
1040
|
-
[{ role: "user", content: userMessage }],
|
|
1041
|
-
SYSTEM_PROMPT2
|
|
1042
|
-
);
|
|
1039
|
+
const answer = await llm.complete([{ role: "user", content: userMessage }], SYSTEM_PROMPT2);
|
|
1043
1040
|
const sources = pages.map((p) => p.path);
|
|
1044
1041
|
if (options?.save) {
|
|
1045
1042
|
const saveRelPath = options.save;
|
|
@@ -1060,7 +1057,7 @@ Saved to: ${saveRelPath}
|
|
|
1060
1057
|
return { answer, sources };
|
|
1061
1058
|
}
|
|
1062
1059
|
|
|
1063
|
-
// src/lint.
|
|
1060
|
+
// src/lint.js
|
|
1064
1061
|
import { readdir as readdir2, stat as stat2 } from "fs/promises";
|
|
1065
1062
|
import { join as join7, relative as relative2, basename as basename3, extname as extname2 } from "path";
|
|
1066
1063
|
async function collectMdFiles2(dir) {
|
|
@@ -1071,7 +1068,8 @@ async function collectMdFiles2(dir) {
|
|
|
1071
1068
|
});
|
|
1072
1069
|
return entries.filter((e) => e.isFile() && e.name.endsWith(".md")).map((e) => join7(e.parentPath ?? e.path, e.name));
|
|
1073
1070
|
} catch (err) {
|
|
1074
|
-
if (err.code !== "ENOENT")
|
|
1071
|
+
if (err.code !== "ENOENT")
|
|
1072
|
+
throw err;
|
|
1075
1073
|
return [];
|
|
1076
1074
|
}
|
|
1077
1075
|
}
|
|
@@ -1083,7 +1081,8 @@ async function collectSourceFiles(dir) {
|
|
|
1083
1081
|
});
|
|
1084
1082
|
return entries.filter((e) => e.isFile()).map((e) => join7(e.parentPath ?? e.path, e.name));
|
|
1085
1083
|
} catch (err) {
|
|
1086
|
-
if (err.code !== "ENOENT")
|
|
1084
|
+
if (err.code !== "ENOENT")
|
|
1085
|
+
throw err;
|
|
1087
1086
|
return [];
|
|
1088
1087
|
}
|
|
1089
1088
|
}
|
|
@@ -1108,17 +1107,11 @@ async function lintProject(project) {
|
|
|
1108
1107
|
const relWikiPaths = absWikiFiles.map((f) => relative2(project.root, f));
|
|
1109
1108
|
const pagesChecked = relWikiPaths.length;
|
|
1110
1109
|
const sourceFiles = await collectSourceFiles(project.sourcesDir);
|
|
1111
|
-
const sourcesChecked = sourceFiles.filter(
|
|
1112
|
-
(f) => basename3(f) !== ".gitkeep"
|
|
1113
|
-
).length;
|
|
1110
|
+
const sourcesChecked = sourceFiles.filter((f) => basename3(f) !== ".gitkeep").length;
|
|
1114
1111
|
if (pagesChecked === 0) {
|
|
1115
1112
|
return { issues, pagesChecked: 0, sourcesChecked };
|
|
1116
1113
|
}
|
|
1117
|
-
const pageKeySet = buildPageKeySet(
|
|
1118
|
-
relWikiPaths,
|
|
1119
|
-
project.root,
|
|
1120
|
-
project.wikiDir
|
|
1121
|
-
);
|
|
1114
|
+
const pageKeySet = buildPageKeySet(relWikiPaths, project.root, project.wikiDir);
|
|
1122
1115
|
const db = openDb(project);
|
|
1123
1116
|
let rows;
|
|
1124
1117
|
try {
|
|
@@ -1142,12 +1135,7 @@ async function lintProject(project) {
|
|
|
1142
1135
|
links = [];
|
|
1143
1136
|
}
|
|
1144
1137
|
for (const link of links) {
|
|
1145
|
-
const resolved = resolveLink(
|
|
1146
|
-
link,
|
|
1147
|
-
relWikiPaths,
|
|
1148
|
-
project.root,
|
|
1149
|
-
project.wikiDir
|
|
1150
|
-
);
|
|
1138
|
+
const resolved = resolveLink(link, relWikiPaths, project.root, project.wikiDir);
|
|
1151
1139
|
if (resolved !== null) {
|
|
1152
1140
|
const set = inboundLinks.get(resolved);
|
|
1153
1141
|
if (set) {
|
|
@@ -1168,12 +1156,7 @@ async function lintProject(project) {
|
|
|
1168
1156
|
links = [];
|
|
1169
1157
|
}
|
|
1170
1158
|
for (const link of links) {
|
|
1171
|
-
const resolved = resolveLink(
|
|
1172
|
-
link,
|
|
1173
|
-
relWikiPaths,
|
|
1174
|
-
project.root,
|
|
1175
|
-
project.wikiDir
|
|
1176
|
-
);
|
|
1159
|
+
const resolved = resolveLink(link, relWikiPaths, project.root, project.wikiDir);
|
|
1177
1160
|
if (resolved !== null) {
|
|
1178
1161
|
indexLinks.add(resolved);
|
|
1179
1162
|
}
|
|
@@ -1181,7 +1164,8 @@ async function lintProject(project) {
|
|
|
1181
1164
|
}
|
|
1182
1165
|
}
|
|
1183
1166
|
for (const rp of relWikiPaths) {
|
|
1184
|
-
if (basename3(rp) === "_index.md")
|
|
1167
|
+
if (basename3(rp) === "_index.md")
|
|
1168
|
+
continue;
|
|
1185
1169
|
const inbound = inboundLinks.get(rp);
|
|
1186
1170
|
if (!inbound || inbound.size === 0) {
|
|
1187
1171
|
issues.push({
|
|
@@ -1212,7 +1196,8 @@ async function lintProject(project) {
|
|
|
1212
1196
|
}
|
|
1213
1197
|
}
|
|
1214
1198
|
for (const row of rows) {
|
|
1215
|
-
if (basename3(row.path) === "_index.md")
|
|
1199
|
+
if (basename3(row.path) === "_index.md")
|
|
1200
|
+
continue;
|
|
1216
1201
|
let links = [];
|
|
1217
1202
|
try {
|
|
1218
1203
|
links = JSON.parse(row.outgoing_links);
|
|
@@ -1232,17 +1217,20 @@ async function lintProject(project) {
|
|
|
1232
1217
|
for (const rp of relWikiPaths) {
|
|
1233
1218
|
const absWikiPage = join7(project.root, rp);
|
|
1234
1219
|
const relToWikiSources = relative2(wikiSourcesDir, absWikiPage);
|
|
1235
|
-
if (relToWikiSources.startsWith(".."))
|
|
1220
|
+
if (relToWikiSources.startsWith(".."))
|
|
1221
|
+
continue;
|
|
1236
1222
|
const summaryBasename = basename3(rp, ".md");
|
|
1237
1223
|
const sourceBasename = summaryBasename.endsWith("-summary") ? summaryBasename.slice(0, -"-summary".length) : summaryBasename;
|
|
1238
1224
|
const matchingSource = sourceFiles.find((sf) => {
|
|
1239
1225
|
const sfBase = basename3(sf, extname2(sf));
|
|
1240
1226
|
return sfBase === sourceBasename;
|
|
1241
1227
|
});
|
|
1242
|
-
if (!matchingSource)
|
|
1228
|
+
if (!matchingSource)
|
|
1229
|
+
continue;
|
|
1243
1230
|
try {
|
|
1244
1231
|
const summaryRow = metaMap.get(rp);
|
|
1245
|
-
if (!summaryRow)
|
|
1232
|
+
if (!summaryRow)
|
|
1233
|
+
continue;
|
|
1246
1234
|
const [sourceStat, summaryStat] = await Promise.all([
|
|
1247
1235
|
stat2(matchingSource),
|
|
1248
1236
|
stat2(join7(project.root, summaryRow.path))
|
|
@@ -1260,7 +1248,8 @@ async function lintProject(project) {
|
|
|
1260
1248
|
}
|
|
1261
1249
|
}
|
|
1262
1250
|
for (const rp of relWikiPaths) {
|
|
1263
|
-
if (basename3(rp) === "_index.md")
|
|
1251
|
+
if (basename3(rp) === "_index.md")
|
|
1252
|
+
continue;
|
|
1264
1253
|
if (!indexPath) {
|
|
1265
1254
|
continue;
|
|
1266
1255
|
}
|
|
@@ -1281,8 +1270,10 @@ async function lintProject(project) {
|
|
|
1281
1270
|
function resolveLink(link, relPaths, projectRoot, wikiDir) {
|
|
1282
1271
|
for (const rp of relPaths) {
|
|
1283
1272
|
const fname = basename3(rp, ".md");
|
|
1284
|
-
if (fname === link)
|
|
1285
|
-
|
|
1273
|
+
if (fname === link)
|
|
1274
|
+
return rp;
|
|
1275
|
+
if (rp === link || rp === `${link}.md`)
|
|
1276
|
+
return rp;
|
|
1286
1277
|
const absPath = join7(projectRoot, rp);
|
|
1287
1278
|
const relToWiki = relative2(wikiDir, absPath);
|
|
1288
1279
|
if (relToWiki === link || relToWiki.replace(/\.md$/i, "") === link) {
|
|
@@ -1295,15 +1286,18 @@ function isLinkResolvable(link, pageKeySet) {
|
|
|
1295
1286
|
return pageKeySet.has(link);
|
|
1296
1287
|
}
|
|
1297
1288
|
|
|
1298
|
-
// src/log-parser.
|
|
1289
|
+
// src/log-parser.js
|
|
1299
1290
|
function parseLogEntries(content) {
|
|
1300
1291
|
const entries = [];
|
|
1301
1292
|
const sections = content.split(/^(?=## )/m);
|
|
1302
1293
|
for (const section of sections) {
|
|
1303
1294
|
const trimmed = section.trim();
|
|
1304
|
-
if (!trimmed)
|
|
1305
|
-
|
|
1306
|
-
if (
|
|
1295
|
+
if (!trimmed)
|
|
1296
|
+
continue;
|
|
1297
|
+
if (trimmed.startsWith("# "))
|
|
1298
|
+
continue;
|
|
1299
|
+
if (!trimmed.startsWith("## "))
|
|
1300
|
+
continue;
|
|
1307
1301
|
const newlineIdx = trimmed.indexOf("\n");
|
|
1308
1302
|
if (newlineIdx === -1) {
|
|
1309
1303
|
entries.push({ heading: trimmed.slice(3).trim(), body: "" });
|