6878 lines
289 KiB
HTML
6878 lines
289 KiB
HTML
<!DOCTYPE HTML>
|
||
<html lang="en" class="rust sidebar-visible" dir="ltr">
|
||
<head>
|
||
<!-- Book generated using mdBook -->
|
||
<meta charset="UTF-8">
|
||
<title>KOGRAL Documentation</title>
|
||
<meta name="robots" content="noindex">
|
||
|
||
|
||
<!-- Custom HTML head -->
|
||
|
||
<meta name="description" content="Complete documentation for KOGRAL - Git-native knowledge graphs for developer teams">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="theme-color" content="#ffffff">
|
||
|
||
<link rel="icon" href="favicon.svg">
|
||
<link rel="shortcut icon" href="favicon.png">
|
||
<link rel="stylesheet" href="css/variables.css">
|
||
<link rel="stylesheet" href="css/general.css">
|
||
<link rel="stylesheet" href="css/chrome.css">
|
||
<link rel="stylesheet" href="css/print.css" media="print">
|
||
|
||
<!-- Fonts -->
|
||
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
|
||
<link rel="stylesheet" href="fonts/fonts.css">
|
||
|
||
<!-- Highlight.js Stylesheets -->
|
||
<link rel="stylesheet" id="highlight-css" href="highlight.css">
|
||
<link rel="stylesheet" id="tomorrow-night-css" href="tomorrow-night.css">
|
||
<link rel="stylesheet" id="ayu-highlight-css" href="ayu-highlight.css">
|
||
|
||
<!-- Custom theme stylesheets -->
|
||
|
||
|
||
<!-- Provide site root and default themes to javascript -->
|
||
<script>
|
||
const path_to_root = "";
|
||
const default_light_theme = "rust";
|
||
const default_dark_theme = "navy";
|
||
</script>
|
||
<!-- Start loading toc.js asap -->
|
||
<script src="toc.js"></script>
|
||
</head>
|
||
<body>
|
||
<div id="mdbook-help-container">
|
||
<div id="mdbook-help-popup">
|
||
<h2 class="mdbook-help-title">Keyboard shortcuts</h2>
|
||
<div>
|
||
<p>Press <kbd>←</kbd> or <kbd>→</kbd> to navigate between chapters</p>
|
||
<p>Press <kbd>S</kbd> or <kbd>/</kbd> to search in the book</p>
|
||
<p>Press <kbd>?</kbd> to show this help</p>
|
||
<p>Press <kbd>Esc</kbd> to hide this help</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<div id="body-container">
|
||
<!-- Work around some values being stored in localStorage wrapped in quotes -->
|
||
<script>
|
||
try {
|
||
let theme = localStorage.getItem('mdbook-theme');
|
||
let sidebar = localStorage.getItem('mdbook-sidebar');
|
||
|
||
if (theme.startsWith('"') && theme.endsWith('"')) {
|
||
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
|
||
}
|
||
|
||
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
|
||
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
|
||
}
|
||
} catch (e) { }
|
||
</script>
|
||
|
||
<!-- Set the theme before any content is loaded, prevents flash -->
|
||
<script>
|
||
const default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? default_dark_theme : default_light_theme;
|
||
let theme;
|
||
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
|
||
if (theme === null || theme === undefined) { theme = default_theme; }
|
||
const html = document.documentElement;
|
||
html.classList.remove('rust')
|
||
html.classList.add(theme);
|
||
html.classList.add("js");
|
||
</script>
|
||
|
||
<input type="checkbox" id="sidebar-toggle-anchor" class="hidden">
|
||
|
||
<!-- Hide / unhide sidebar before it is displayed -->
|
||
<script>
|
||
let sidebar = null;
|
||
const sidebar_toggle = document.getElementById("sidebar-toggle-anchor");
|
||
if (document.body.clientWidth >= 1080) {
|
||
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
|
||
sidebar = sidebar || 'visible';
|
||
} else {
|
||
sidebar = 'hidden';
|
||
}
|
||
sidebar_toggle.checked = sidebar === 'visible';
|
||
html.classList.remove('sidebar-visible');
|
||
html.classList.add("sidebar-" + sidebar);
|
||
</script>
|
||
|
||
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
|
||
<!-- populated by js -->
|
||
<mdbook-sidebar-scrollbox class="sidebar-scrollbox"></mdbook-sidebar-scrollbox>
|
||
<noscript>
|
||
<iframe class="sidebar-iframe-outer" src="toc.html"></iframe>
|
||
</noscript>
|
||
<div id="sidebar-resize-handle" class="sidebar-resize-handle">
|
||
<div class="sidebar-resize-indicator"></div>
|
||
</div>
|
||
</nav>
|
||
|
||
<div id="page-wrapper" class="page-wrapper">
|
||
|
||
<div class="page">
|
||
<div id="menu-bar-hover-placeholder"></div>
|
||
<div id="menu-bar" class="menu-bar sticky">
|
||
<div class="left-buttons">
|
||
<label id="sidebar-toggle" class="icon-button" for="sidebar-toggle-anchor" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
|
||
<i class="fa fa-bars"></i>
|
||
</label>
|
||
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
|
||
<i class="fa fa-paint-brush"></i>
|
||
</button>
|
||
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
|
||
<li role="none"><button role="menuitem" class="theme" id="default_theme">Auto</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="light">Light</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
|
||
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
|
||
</ul>
|
||
<button id="search-toggle" class="icon-button" type="button" title="Search (`/`)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="/ s" aria-controls="searchbar">
|
||
<i class="fa fa-search"></i>
|
||
</button>
|
||
</div>
|
||
|
||
<h1 class="menu-title">KOGRAL Documentation</h1>
|
||
|
||
<div class="right-buttons">
|
||
<a href="print.html" title="Print this book" aria-label="Print this book">
|
||
<i id="print-button" class="fa fa-print"></i>
|
||
</a>
|
||
<a href="https://github.com/your-org/knowledge-base" title="Git repository" aria-label="Git repository">
|
||
<i id="git-repository-button" class="fa fa-github"></i>
|
||
</a>
|
||
|
||
</div>
|
||
</div>
|
||
|
||
<div id="search-wrapper" class="hidden">
|
||
<form id="searchbar-outer" class="searchbar-outer">
|
||
<input type="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
|
||
</form>
|
||
<div id="searchresults-outer" class="searchresults-outer hidden">
|
||
<div id="searchresults-header" class="searchresults-header"></div>
|
||
<ul id="searchresults">
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
|
||
<script>
|
||
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
|
||
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
|
||
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
|
||
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
|
||
});
|
||
</script>
|
||
|
||
<div id="content" class="content">
|
||
<main>
|
||
<h1 id="kogral-documentation"><a class="header" href="#kogral-documentation">KOGRAL Documentation</a></h1>
|
||
<p>Welcome to the KOGRAL documentation! This directory contains comprehensive documentation for KOGRAL (<strong>KO</strong>wledge <strong>GRA</strong>phs, <strong>L</strong>ocal-first), built with <a href="https://rust-lang.github.io/mdBook/">mdBook</a>.</p>
|
||
<h2 id="-reading-the-documentation"><a class="header" href="#-reading-the-documentation">📚 Reading the Documentation</a></h2>
|
||
<p>You have several options for reading the documentation:</p>
|
||
<h3 id="option-1-serve-locally-with-mdbook-recommended"><a class="header" href="#option-1-serve-locally-with-mdbook-recommended">Option 1: Serve Locally with mdBook (Recommended)</a></h3>
|
||
<p>The best reading experience with navigation, search, and live reload:</p>
|
||
<pre><code class="language-bash"># Serve documentation at http://localhost:3000
|
||
just docs::serve
|
||
</code></pre>
|
||
<p>This will:</p>
|
||
<ul>
|
||
<li>Build the mdBook</li>
|
||
<li>Start a local web server on port 3000</li>
|
||
<li>Open your browser automatically</li>
|
||
<li>Watch for changes and auto-reload</li>
|
||
</ul>
|
||
<h3 id="option-2-build-static-html"><a class="header" href="#option-2-build-static-html">Option 2: Build Static HTML</a></h3>
|
||
<p>Generate static HTML files you can browse offline:</p>
|
||
<pre><code class="language-bash"># Build mdBook to docs/book/
|
||
just docs::build
|
||
</code></pre>
|
||
<p>Then open <code>docs/book/index.html</code> in your browser.</p>
|
||
<h3 id="option-3-read-markdown-files-directly"><a class="header" href="#option-3-read-markdown-files-directly">Option 3: Read Markdown Files Directly</a></h3>
|
||
<p>All documentation is written in Markdown and can be read directly:</p>
|
||
<ul>
|
||
<li>Browse via GitHub/GitLab web interface</li>
|
||
<li>Use your editor's Markdown preview</li>
|
||
<li>Read from terminal with <code>bat</code>, <code>glow</code>, or similar tools</li>
|
||
</ul>
|
||
<p><strong>Navigation</strong>: See <a href="SUMMARY.html">SUMMARY.md</a> for the complete table of contents.</p>
|
||
<h2 id="-documentation-commands"><a class="header" href="#-documentation-commands">🛠️ Documentation Commands</a></h2>
|
||
<p>We use <code>just</code> recipes for documentation tasks. All commands assume you're in the project root directory.</p>
|
||
<h3 id="build-and-serve"><a class="header" href="#build-and-serve">Build and Serve</a></h3>
|
||
<pre><code class="language-bash"># Serve documentation locally (recommended)
|
||
just docs::serve
|
||
|
||
# Build static HTML
|
||
just docs::build
|
||
|
||
# Watch and rebuild on file changes
|
||
just docs::watch
|
||
</code></pre>
|
||
<h3 id="validation"><a class="header" href="#validation">Validation</a></h3>
|
||
<pre><code class="language-bash"># Test code examples in documentation
|
||
just docs::test
|
||
|
||
# Check for broken links
|
||
just docs::check-links
|
||
</code></pre>
|
||
<h3 id="cleanup"><a class="header" href="#cleanup">Cleanup</a></h3>
|
||
<pre><code class="language-bash"># Remove build artifacts
|
||
just docs::clean
|
||
</code></pre>
|
||
<h3 id="view-all-documentation-commands"><a class="header" href="#view-all-documentation-commands">View All Documentation Commands</a></h3>
|
||
<pre><code class="language-bash">just docs::help
|
||
</code></pre>
|
||
<h2 id="-installing-mdbook"><a class="header" href="#-installing-mdbook">📦 Installing mdBook</a></h2>
|
||
<p>mdBook is required to build and serve the documentation.</p>
|
||
<h3 id="install-via-cargo"><a class="header" href="#install-via-cargo">Install via Cargo</a></h3>
|
||
<pre><code class="language-bash">cargo install mdbook
|
||
</code></pre>
|
||
<h3 id="install-optional-tools"><a class="header" href="#install-optional-tools">Install Optional Tools</a></h3>
|
||
<p>For enhanced functionality:</p>
|
||
<pre><code class="language-bash"># Link checker (validates internal/external links)
|
||
cargo install mdbook-linkcheck
|
||
|
||
# Mermaid diagram support
|
||
cargo install mdbook-mermaid
|
||
|
||
# PlantUML diagram support
|
||
cargo install mdbook-plantuml
|
||
</code></pre>
|
||
<h3 id="verify-installation"><a class="header" href="#verify-installation">Verify Installation</a></h3>
|
||
<pre><code class="language-bash">mdbook --version
|
||
# Should output: mdbook v0.4.x or later
|
||
</code></pre>
|
||
<h2 id="-documentation-structure"><a class="header" href="#-documentation-structure">📖 Documentation Structure</a></h2>
|
||
<p>The documentation is organized into the following sections:</p>
|
||
<h3 id="1-kogral-definition-kogral"><a class="header" href="#1-kogral-definition-kogral">1. <strong>KOGRAL Definition</strong> (<code>kogral/</code>)</a></h3>
|
||
<ul>
|
||
<li>What is KOGRAL and why it exists</li>
|
||
<li>Core concepts (nodes, edges, graphs)</li>
|
||
<li>Design philosophy</li>
|
||
</ul>
|
||
<h3 id="2-guides-guides"><a class="header" href="#2-guides-guides">2. <strong>Guides</strong> (<code>guides/</code>)</a></h3>
|
||
<ul>
|
||
<li>Quick start (5 minutes)</li>
|
||
<li>Installation guide</li>
|
||
<li>Use cases with examples</li>
|
||
</ul>
|
||
<h3 id="3-architecture-architecture"><a class="header" href="#3-architecture-architecture">3. <strong>Architecture</strong> (<code>architecture/</code>)</a></h3>
|
||
<ul>
|
||
<li>System overview with diagrams</li>
|
||
<li>Config-driven architecture</li>
|
||
<li>Graph model details</li>
|
||
<li>ADRs (Architectural Decision Records)</li>
|
||
</ul>
|
||
<h3 id="4-setup-setup"><a class="header" href="#4-setup-setup">4. <strong>Setup</strong> (<code>setup/</code>)</a></h3>
|
||
<ul>
|
||
<li>Initial setup</li>
|
||
<li>Development environment</li>
|
||
<li>Production deployment</li>
|
||
<li>Testing environment</li>
|
||
<li>CI/CD integration</li>
|
||
</ul>
|
||
<h3 id="5-configuration-config"><a class="header" href="#5-configuration-config">5. <strong>Configuration</strong> (<code>config/</code>)</a></h3>
|
||
<ul>
|
||
<li>Configuration overview</li>
|
||
<li>Nickel schema reference</li>
|
||
<li>Runtime configuration</li>
|
||
<li>Environment modes (dev/prod/test)</li>
|
||
</ul>
|
||
<h3 id="6-storage-storage"><a class="header" href="#6-storage-storage">6. <strong>Storage</strong> (<code>storage/</code>)</a></h3>
|
||
<ul>
|
||
<li>Storage architecture (hybrid strategy)</li>
|
||
<li>Filesystem backend</li>
|
||
<li>SurrealDB backend</li>
|
||
<li>In-memory backend</li>
|
||
<li>Sync mechanism</li>
|
||
</ul>
|
||
<h3 id="7-ai--embeddings-ai"><a class="header" href="#7-ai--embeddings-ai">7. <strong>AI & Embeddings</strong> (<code>ai/</code>)</a></h3>
|
||
<ul>
|
||
<li>Semantic search</li>
|
||
<li>Embedding providers</li>
|
||
<li>Provider comparison</li>
|
||
<li>Configuration examples</li>
|
||
</ul>
|
||
<h3 id="8-templates-templates"><a class="header" href="#8-templates-templates">8. <strong>Templates</strong> (<code>templates/</code>)</a></h3>
|
||
<ul>
|
||
<li>Template system (Tera)</li>
|
||
<li>Document templates</li>
|
||
<li>Export templates</li>
|
||
<li>Custom templates</li>
|
||
</ul>
|
||
<h3 id="9-cli-reference-cli"><a class="header" href="#9-cli-reference-cli">9. <strong>CLI Reference</strong> (<code>cli/</code>)</a></h3>
|
||
<ul>
|
||
<li>All kb-cli commands</li>
|
||
<li>Common workflows</li>
|
||
<li>Advanced usage</li>
|
||
<li>Troubleshooting</li>
|
||
</ul>
|
||
<h3 id="10-apps--integrations-apps"><a class="header" href="#10-apps--integrations-apps">10. <strong>Apps & Integrations</strong> (<code>apps/</code>)</a></h3>
|
||
<ul>
|
||
<li>MCP quick guide (Claude Code)</li>
|
||
<li>Logseq integration</li>
|
||
<li>Vapora integration</li>
|
||
</ul>
|
||
<h3 id="11-api-reference-api"><a class="header" href="#11-api-reference-api">11. <strong>API Reference</strong> (<code>api/</code>)</a></h3>
|
||
<ul>
|
||
<li>MCP tools specification</li>
|
||
<li>Storage trait</li>
|
||
<li>Embedding trait</li>
|
||
<li>REST API (future)</li>
|
||
</ul>
|
||
<h3 id="12-contributing-contributing"><a class="header" href="#12-contributing-contributing">12. <strong>Contributing</strong> (<code>contributing/</code>)</a></h3>
|
||
<ul>
|
||
<li>Development setup</li>
|
||
<li>Code guidelines</li>
|
||
<li>Testing standards</li>
|
||
<li>Documentation guidelines</li>
|
||
</ul>
|
||
<h2 id="-visual-diagrams"><a class="header" href="#-visual-diagrams">🎨 Visual Diagrams</a></h2>
|
||
<p>The documentation includes SVG diagrams for visual understanding:</p>
|
||
<ul>
|
||
<li><strong><a href="diagrams/architecture-overview.svg">architecture-overview.svg</a></strong> - Complete system architecture</li>
|
||
<li><strong><a href="diagrams/core-concepts.svg">core-concepts.svg</a></strong> - Node types and relationships</li>
|
||
<li><strong><a href="diagrams/config-composition.svg">config-composition.svg</a></strong> - Configuration flow (Nickel → JSON → Rust)</li>
|
||
<li><strong><a href="diagrams/storage-architecture.svg">storage-architecture.svg</a></strong> - Hybrid storage strategy</li>
|
||
</ul>
|
||
<p>These diagrams are embedded in relevant documentation pages and can be viewed standalone in a browser.</p>
|
||
<h2 id="-searching-the-documentation"><a class="header" href="#-searching-the-documentation">🔍 Searching the Documentation</a></h2>
|
||
<p>When using <code>just docs::serve</code>, you get a built-in search feature:</p>
|
||
<ol>
|
||
<li>Click the search icon (🔍) in the top-left corner</li>
|
||
<li>Type your query</li>
|
||
<li>Press Enter to navigate results</li>
|
||
</ol>
|
||
<p>The search indexes all documentation content including:</p>
|
||
<ul>
|
||
<li>Page titles</li>
|
||
<li>Headers</li>
|
||
<li>Body text</li>
|
||
<li>Code examples (optionally)</li>
|
||
</ul>
|
||
<h2 id="-editing-documentation"><a class="header" href="#-editing-documentation">✏️ Editing Documentation</a></h2>
|
||
<h3 id="file-format"><a class="header" href="#file-format">File Format</a></h3>
|
||
<p>All documentation is written in <strong>GitHub Flavored Markdown</strong> with mdBook extensions.</p>
|
||
<p>See <a href="contributing/documentation.html">contributing/documentation.md</a> for detailed editing guidelines.</p>
|
||
<h3 id="adding-a-new-page"><a class="header" href="#adding-a-new-page">Adding a New Page</a></h3>
|
||
<ol>
|
||
<li>Create the markdown file in the appropriate directory</li>
|
||
<li>Add it to <code>SUMMARY.md</code> for navigation</li>
|
||
<li>Build to verify: <code>just docs::build</code></li>
|
||
</ol>
|
||
<h3 id="adding-a-new-section"><a class="header" href="#adding-a-new-section">Adding a New Section</a></h3>
|
||
<ol>
|
||
<li>Create the directory</li>
|
||
<li>Add a <code>README.md</code> for the section landing page</li>
|
||
<li>Add section to <code>SUMMARY.md</code></li>
|
||
</ol>
|
||
<h2 id="-testing-documentation"><a class="header" href="#-testing-documentation">🧪 Testing Documentation</a></h2>
|
||
<h3 id="test-code-examples"><a class="header" href="#test-code-examples">Test Code Examples</a></h3>
|
||
<pre><code class="language-bash">just docs::test
|
||
</code></pre>
|
||
<p>This runs all Rust code examples in the documentation to ensure they compile.</p>
|
||
<h3 id="check-links"><a class="header" href="#check-links">Check Links</a></h3>
|
||
<pre><code class="language-bash">just docs::check-links
|
||
</code></pre>
|
||
<p>This validates all internal and external links.</p>
|
||
<h2 id="-documentation-standards"><a class="header" href="#-documentation-standards">📝 Documentation Standards</a></h2>
|
||
<p>When contributing to documentation:</p>
|
||
<ol>
|
||
<li><strong>Use clear, concise language</strong> - Write for developers and AI agents</li>
|
||
<li><strong>Include code examples</strong> - Show, don't just tell</li>
|
||
<li><strong>Add diagrams where helpful</strong> - Visual aids improve understanding</li>
|
||
<li><strong>Link related concepts</strong> - Help readers discover related content</li>
|
||
<li><strong>Test code examples</strong> - Ensure code compiles and works</li>
|
||
<li><strong>Use consistent formatting</strong> - Follow existing page structure</li>
|
||
<li><strong>Update SUMMARY.md</strong> - New pages must be in navigation</li>
|
||
<li><strong>Run checks before committing</strong>:</li>
|
||
</ol>
|
||
<pre><code class="language-bash">just docs::build
|
||
just docs::test
|
||
just docs::check-links
|
||
</code></pre>
|
||
<h2 id="-tips"><a class="header" href="#-tips">💡 Tips</a></h2>
|
||
<h3 id="live-reload-while-writing"><a class="header" href="#live-reload-while-writing">Live Reload While Writing</a></h3>
|
||
<pre><code class="language-bash">just docs::watch
|
||
</code></pre>
|
||
<p>This watches for changes and rebuilds automatically. Open http://localhost:3000 in your browser to see updates in real-time.</p>
|
||
<h3 id="markdown-preview-in-editor"><a class="header" href="#markdown-preview-in-editor">Markdown Preview in Editor</a></h3>
|
||
<p>Most editors have Markdown preview:</p>
|
||
<ul>
|
||
<li><strong>VS Code</strong>: <code>Ctrl+Shift+V</code> (Cmd+Shift+V on Mac)</li>
|
||
<li><strong>IntelliJ/CLion</strong>: Preview pane (right side)</li>
|
||
<li><strong>Vim/Neovim</strong>: Use plugins like <code>markdown-preview.nvim</code></li>
|
||
</ul>
|
||
<h3 id="quick-reference"><a class="header" href="#quick-reference">Quick Reference</a></h3>
|
||
<ul>
|
||
<li><strong>SUMMARY.md</strong> - Table of contents (edit to add/remove pages)</li>
|
||
<li><strong>book.toml</strong> - mdBook configuration</li>
|
||
<li><strong>theme/</strong> - Custom CSS/JS (if needed)</li>
|
||
<li><strong>diagrams/</strong> - SVG diagrams</li>
|
||
</ul>
|
||
<h2 id="-troubleshooting"><a class="header" href="#-troubleshooting">🐛 Troubleshooting</a></h2>
|
||
<h3 id="mdbook-command-not-found"><a class="header" href="#mdbook-command-not-found">mdbook command not found</a></h3>
|
||
<pre><code class="language-bash"># Install mdBook
|
||
cargo install mdbook
|
||
|
||
# Verify installation
|
||
mdbook --version
|
||
</code></pre>
|
||
<h3 id="port-3000-already-in-use"><a class="header" href="#port-3000-already-in-use">Port 3000 already in use</a></h3>
|
||
<pre><code class="language-bash"># Serve on different port
|
||
cd docs
|
||
mdbook serve --port 3001
|
||
</code></pre>
|
||
<h3 id="links-broken-after-moving-files"><a class="header" href="#links-broken-after-moving-files">Links broken after moving files</a></h3>
|
||
<pre><code class="language-bash"># Check all links
|
||
just docs::check-links
|
||
|
||
# Update internal links in affected files
|
||
# Then rebuild
|
||
just docs::build
|
||
</code></pre>
|
||
<h2 id="-resources"><a class="header" href="#-resources">📚 Resources</a></h2>
|
||
<ul>
|
||
<li><a href="https://rust-lang.github.io/mdBook/">mdBook User Guide</a></li>
|
||
<li><a href="https://github.github.com/gfm/">GitHub Flavored Markdown Spec</a></li>
|
||
<li><a href="https://www.markdownguide.org/">Markdown Guide</a></li>
|
||
</ul>
|
||
<h2 id="-contributing-to-documentation"><a class="header" href="#-contributing-to-documentation">🤝 Contributing to Documentation</a></h2>
|
||
<p>Documentation improvements are always welcome! To contribute:</p>
|
||
<ol>
|
||
<li>Fork the repository</li>
|
||
<li>Create a feature branch</li>
|
||
<li>Make your changes</li>
|
||
<li>Test with <code>just docs::build</code> and <code>just docs::test</code></li>
|
||
<li>Submit a pull request</li>
|
||
</ol>
|
||
<p>See <a href="contributing/documentation.html">contributing/documentation.md</a> for detailed guidelines.</p>
|
||
<hr />
|
||
<p><strong>Happy documenting! 📖</strong></p>
|
||
<p>If you have questions or need help, please open an issue or reach out to the maintainers.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="what-is-kogral"><a class="header" href="#what-is-kogral">What is KOGRAL?</a></h1>
|
||
<p>KOGRAL (<strong>KO</strong>wledge <strong>GRA</strong>phs, <strong>L</strong>ocal-first) is a <strong>git-native knowledge graph system</strong> designed for developer teams to capture, connect, and query structured knowledge.</p>
|
||
<h2 id="purpose"><a class="header" href="#purpose">Purpose</a></h2>
|
||
<p>KOGRAL solves the problem of <strong>knowledge fragmentation</strong> in software development:</p>
|
||
<ul>
|
||
<li>📝 Notes scattered across tools</li>
|
||
<li>🤔 Decisions lost in chat histories</li>
|
||
<li>📚 Guidelines buried in wikis</li>
|
||
<li>🔄 Patterns rediscovered repeatedly</li>
|
||
<li>🤖 AI agents lacking project context</li>
|
||
</ul>
|
||
<h2 id="solution"><a class="header" href="#solution">Solution</a></h2>
|
||
<p>KOGRAL provides a <strong>unified, queryable knowledge graph</strong> that:</p>
|
||
<ol>
|
||
<li><strong>Captures</strong> knowledge in structured, git-friendly markdown</li>
|
||
<li><strong>Connects</strong> concepts through typed relationships</li>
|
||
<li><strong>Queries</strong> via text and semantic similarity</li>
|
||
<li><strong>Integrates</strong> with AI tools (Claude Code via MCP)</li>
|
||
<li><strong>Syncs</strong> across local (filesystem) and shared (SurrealDB) storage</li>
|
||
</ol>
|
||
<h2 id="core-philosophy"><a class="header" href="#core-philosophy">Core Philosophy</a></h2>
|
||
<h3 id="config-driven"><a class="header" href="#config-driven">Config-Driven</a></h3>
|
||
<p>Behavior defined in Nickel schemas, not hardcoded:</p>
|
||
<pre><code class="language-nickel">{
|
||
graph = { name = "my-project" },
|
||
storage = { primary = 'filesystem },
|
||
embeddings = { provider = 'fastembed },
|
||
templates = { templates_dir = "templates" },
|
||
}
|
||
</code></pre>
|
||
<p>Every aspect configurable: storage, embeddings, templates, query behavior.</p>
|
||
<h3 id="git-friendly"><a class="header" href="#git-friendly">Git-Friendly</a></h3>
|
||
<p>Knowledge stored as markdown files with YAML frontmatter:</p>
|
||
<pre><code class="language-markdown">---
|
||
id: note-123
|
||
type: note
|
||
title: Error Handling Patterns
|
||
tags: [rust, error-handling]
|
||
---
|
||
|
||
# Error Handling Patterns
|
||
|
||
Use `thiserror` for custom error types...
|
||
</code></pre>
|
||
<p>Changes tracked via git, reviewable in PRs, mergeable across branches.</p>
|
||
<h3 id="ai-native"><a class="header" href="#ai-native">AI-Native</a></h3>
|
||
<p>Built for agent collaboration:</p>
|
||
<ul>
|
||
<li><strong>MCP Protocol</strong>: Native integration with Claude Code</li>
|
||
<li><strong>Semantic Search</strong>: Find concepts, not just keywords</li>
|
||
<li><strong>Auto-Linking</strong>: Suggest relationships based on content</li>
|
||
<li><strong>Context Injection</strong>: Agents query relevant guidelines before coding</li>
|
||
</ul>
|
||
<h2 id="key-concepts"><a class="header" href="#key-concepts">Key Concepts</a></h2>
|
||
<h3 id="nodes"><a class="header" href="#nodes">Nodes</a></h3>
|
||
<p>6 types of knowledge nodes:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Type</th><th>Purpose</th><th>Example</th></tr></thead><tbody>
|
||
<tr><td><strong>Note</strong></td><td>General observations</td><td>"Rust ownership patterns"</td></tr>
|
||
<tr><td><strong>Decision</strong></td><td>ADRs (Architectural Decision Records)</td><td>"Use SurrealDB for storage"</td></tr>
|
||
<tr><td><strong>Guideline</strong></td><td>Code standards</td><td>"Error handling with thiserror"</td></tr>
|
||
<tr><td><strong>Pattern</strong></td><td>Reusable solutions</td><td>"Repository pattern for DB access"</td></tr>
|
||
<tr><td><strong>Journal</strong></td><td>Daily reflections</td><td>"2026-01-17 progress notes"</td></tr>
|
||
<tr><td><strong>Execution</strong></td><td>Agent task records</td><td>"Implemented auth module"</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="relationships"><a class="header" href="#relationships">Relationships</a></h3>
|
||
<p>6 typed edges connecting nodes:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Relation</th><th>Meaning</th><th>Example</th></tr></thead><tbody>
|
||
<tr><td><code>relates_to</code></td><td>Conceptual link</td><td>Note ↔ Note</td></tr>
|
||
<tr><td><code>depends_on</code></td><td>Prerequisite</td><td>Pattern → Guideline</td></tr>
|
||
<tr><td><code>implements</code></td><td>Concrete realization</td><td>Code → Pattern</td></tr>
|
||
<tr><td><code>extends</code></td><td>Inheritance</td><td>ProjectGuideline → BaseGuideline</td></tr>
|
||
<tr><td><code>supersedes</code></td><td>Replacement</td><td>DecisionV2 → DecisionV1</td></tr>
|
||
<tr><td><code>explains</code></td><td>Documentation</td><td>Note → Execution</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="multi-graph-architecture"><a class="header" href="#multi-graph-architecture">Multi-Graph Architecture</a></h3>
|
||
<p><strong>Local Graph</strong> (per project):</p>
|
||
<ul>
|
||
<li>Stored in <code>.kogral/</code> directory</li>
|
||
<li>Git-tracked for version control</li>
|
||
<li>Project-specific knowledge</li>
|
||
</ul>
|
||
<p><strong>Shared Graph</strong> (organization-wide):</p>
|
||
<ul>
|
||
<li>Centralized guidelines and patterns</li>
|
||
<li>SurrealDB for scalability</li>
|
||
<li>Inherited by projects</li>
|
||
</ul>
|
||
<p><strong>Inheritance Resolution</strong>:</p>
|
||
<pre><code>Shared Guidelines (priority: 50)
|
||
↓
|
||
Project Guidelines (priority: 100)
|
||
↓
|
||
Effective Guidelines (higher priority wins)
|
||
</code></pre>
|
||
<h2 id="use-cases"><a class="header" href="#use-cases">Use Cases</a></h2>
|
||
<h3 id="for-developers"><a class="header" href="#for-developers">For Developers</a></h3>
|
||
<ul>
|
||
<li><strong>Capture decisions</strong> as you make them (ADRs)</li>
|
||
<li><strong>Document patterns</strong> for future reference</li>
|
||
<li><strong>Track daily progress</strong> in journal entries</li>
|
||
<li><strong>Query past decisions</strong> before new implementations</li>
|
||
</ul>
|
||
<h3 id="for-teams"><a class="header" href="#for-teams">For Teams</a></h3>
|
||
<ul>
|
||
<li><strong>Share guidelines</strong> across projects</li>
|
||
<li><strong>Standardize patterns</strong> organization-wide</li>
|
||
<li><strong>Onboard new members</strong> with searchable knowledge</li>
|
||
<li><strong>Review decisions</strong> in context with git history</li>
|
||
</ul>
|
||
<h3 id="for-ai-agents"><a class="header" href="#for-ai-agents">For AI Agents</a></h3>
|
||
<ul>
|
||
<li><strong>Query guidelines</strong> before generating code</li>
|
||
<li><strong>Check past decisions</strong> for context</li>
|
||
<li><strong>Document executions</strong> for audit trails</li>
|
||
<li><strong>Suggest related patterns</strong> during implementation</li>
|
||
</ul>
|
||
<h2 id="comparison-with-other-tools"><a class="header" href="#comparison-with-other-tools">Comparison with Other Tools</a></h2>
|
||
<h3 id="vs-logseq"><a class="header" href="#vs-logseq">vs. Logseq</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>KOGRAL</th><th>Logseq</th></tr></thead><tbody>
|
||
<tr><td><strong>Storage</strong></td><td>Git-friendly markdown + DB</td><td>Local markdown</td></tr>
|
||
<tr><td><strong>AI Integration</strong></td><td>Native MCP protocol</td><td>Plugin-based</td></tr>
|
||
<tr><td><strong>Config</strong></td><td>Type-safe Nickel schemas</td><td>JSON files</td></tr>
|
||
<tr><td><strong>Multi-Backend</strong></td><td>Filesystem + SurrealDB</td><td>Filesystem only</td></tr>
|
||
<tr><td><strong>Semantic Search</strong></td><td>Multiple AI providers</td><td>Local only</td></tr>
|
||
<tr><td><strong>Graph Queries</strong></td><td>SurrealDB graph queries</td><td>Block references</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>Compatibility</strong>: KOGRAL can import/export Logseq graphs for visual editing.</p>
|
||
<h3 id="vs-obsidian"><a class="header" href="#vs-obsidian">vs. Obsidian</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>KOGRAL</th><th>Obsidian</th></tr></thead><tbody>
|
||
<tr><td><strong>Target Audience</strong></td><td>Developers + AI agents</td><td>Knowledge workers</td></tr>
|
||
<tr><td><strong>Focus</strong></td><td>Structured knowledge graph</td><td>Flexible note-taking</td></tr>
|
||
<tr><td><strong>Configuration</strong></td><td>Config-driven (Nickel)</td><td>Settings UI</td></tr>
|
||
<tr><td><strong>CLI</strong></td><td>Full CLI + MCP server</td><td>Limited CLI</td></tr>
|
||
<tr><td><strong>Version Control</strong></td><td>Git-native</td><td>Git plugin</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>Use Case</strong>: KOGRAL for developer knowledge, Obsidian for personal notes.</p>
|
||
<h3 id="vs-notionconfluence"><a class="header" href="#vs-notionconfluence">vs. Notion/Confluence</a></h3>
|
||
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>KOGRAL</th><th>Notion/Confluence</th></tr></thead><tbody>
|
||
<tr><td><strong>Storage</strong></td><td>Local-first</td><td>Cloud-only</td></tr>
|
||
<tr><td><strong>Format</strong></td><td>Plain markdown</td><td>Proprietary</td></tr>
|
||
<tr><td><strong>AI Access</strong></td><td>Programmatic API</td><td>Web scraping</td></tr>
|
||
<tr><td><strong>Offline</strong></td><td>Full functionality</td><td>Limited</td></tr>
|
||
<tr><td><strong>Privacy</strong></td><td>Self-hosted</td><td>Third-party servers</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>Advantage</strong>: KOGRAL keeps sensitive knowledge on-premises.</p>
|
||
<h2 id="architecture-overview"><a class="header" href="#architecture-overview">Architecture Overview</a></h2>
|
||
<pre><code>┌─────────────────────────────────────────────────────┐
|
||
│ kb-core │
|
||
│ (Rust library: graph, storage, config, query) │
|
||
└──────────────┬─────────────┬────────────────────────┘
|
||
│ │
|
||
┌─────────┴──────┐ ┌──┴─────────┐
|
||
│ │ │ │
|
||
┌────▼─────┐ ┌──────▼──▼───┐ ┌─────▼─────┐
|
||
│ kb-cli │ │ kb-mcp │ │ NuShell │
|
||
│ (13 cmds)│ │ (MCP server) │ │ (scripts) │
|
||
└──────────┘ └──────────────┘ └───────────┘
|
||
</code></pre>
|
||
<p><strong>Layers</strong>:</p>
|
||
<ol>
|
||
<li><strong>kb-core</strong>: Core library (models, storage, query)</li>
|
||
<li><strong>kb-cli</strong>: Command-line interface</li>
|
||
<li><strong>kb-mcp</strong>: MCP server for AI integration</li>
|
||
<li><strong>Scripts</strong>: NuShell automation (sync, backup, etc.)</li>
|
||
</ol>
|
||
<h2 id="when-to-use-kogral"><a class="header" href="#when-to-use-kogral">When to Use KOGRAL</a></h2>
|
||
<p>✅ <strong>Good fit</strong>:</p>
|
||
<ul>
|
||
<li>Developer teams building software</li>
|
||
<li>Projects with architectural decisions</li>
|
||
<li>Organizations standardizing patterns</li>
|
||
<li>AI-assisted development workflows</li>
|
||
<li>Knowledge requiring version control</li>
|
||
</ul>
|
||
<p>❌ <strong>Not ideal for</strong>:</p>
|
||
<ul>
|
||
<li>Personal journaling (use Obsidian)</li>
|
||
<li>Team wikis (use Notion)</li>
|
||
<li>Customer documentation (use mdBook)</li>
|
||
<li>Simple note-taking (use Logseq)</li>
|
||
</ul>
|
||
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
|
||
<ul>
|
||
<li><strong>New to KOGRAL?</strong> → <a href="kogral/../guides/installation.html">Installation Guide</a></li>
|
||
<li><strong>Ready to start?</strong> → <a href="kogral/../guides/quickstart.html">Quick Start</a></li>
|
||
<li><strong>Want examples?</strong> → <a href="kogral/../guides/use-cases.html">Use Cases</a></li>
|
||
<li><strong>Understand design?</strong> → <a href="kogral/../architecture/overview.html">Architecture Overview</a></li>
|
||
</ul>
|
||
<hr />
|
||
<blockquote>
|
||
<p>"Knowledge is only valuable when it's accessible, connected, and queryable."</p>
|
||
</blockquote>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="core-concepts"><a class="header" href="#core-concepts">Core Concepts</a></h1>
|
||
<p>Understanding the fundamental concepts behind KOGRAL.</p>
|
||
<h2 id="the-knowledge-graph"><a class="header" href="#the-knowledge-graph">The Knowledge Graph</a></h2>
|
||
<p>At its core, KOGRAL is a <strong>directed graph</strong> where:</p>
|
||
<ul>
|
||
<li><strong>Nodes</strong> = pieces of knowledge (notes, decisions, guidelines, patterns)</li>
|
||
<li><strong>Edges</strong> = typed relationships between concepts</li>
|
||
</ul>
|
||
<p><img src="kogral/../diagrams/core-concepts.svg" alt="Node Types and Relationships" /></p>
|
||
<p>This graph structure enables:</p>
|
||
<ul>
|
||
<li><strong>Discovery</strong>: Find related concepts through traversal</li>
|
||
<li><strong>Context</strong>: Understand how ideas connect</li>
|
||
<li><strong>Evolution</strong>: Track how knowledge changes over time</li>
|
||
</ul>
|
||
<h2 id="node-types"><a class="header" href="#node-types">Node Types</a></h2>
|
||
<h3 id="1-note"><a class="header" href="#1-note">1. Note</a></h3>
|
||
<p><strong>Purpose</strong>: Capture general observations, learnings, and discoveries.</p>
|
||
<p><strong>When to use</strong>:</p>
|
||
<ul>
|
||
<li>Documenting a concept you learned</li>
|
||
<li>Recording implementation details</li>
|
||
<li>Capturing meeting notes</li>
|
||
<li>Quick knowledge capture</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-yaml">---
|
||
type: note
|
||
title: Async Trait Patterns in Rust
|
||
tags: [rust, async, patterns]
|
||
---
|
||
|
||
# Async Trait Patterns in Rust
|
||
|
||
Using async traits with the async-trait crate...
|
||
</code></pre>
|
||
<h3 id="2-decision-adr"><a class="header" href="#2-decision-adr">2. Decision (ADR)</a></h3>
|
||
<p><strong>Purpose</strong>: Record architectural decisions with full context.</p>
|
||
<p><strong>When to use</strong>:</p>
|
||
<ul>
|
||
<li>Choosing between alternatives (REST vs GraphQL)</li>
|
||
<li>Major technical decisions (database selection)</li>
|
||
<li>Trade-off analysis</li>
|
||
<li>Explaining "why" for future reference</li>
|
||
</ul>
|
||
<p><strong>Structure</strong>:</p>
|
||
<ul>
|
||
<li><strong>Context</strong>: Background and problem</li>
|
||
<li><strong>Decision</strong>: What was chosen</li>
|
||
<li><strong>Consequences</strong>: Positive and negative outcomes</li>
|
||
<li><strong>Alternatives</strong>: What was considered but rejected</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-yaml">---
|
||
type: decision
|
||
title: Use SurrealDB for Storage
|
||
status: accepted
|
||
---
|
||
|
||
## Context
|
||
Need a graph database that supports our relationship model...
|
||
|
||
## Decision
|
||
Adopt SurrealDB as the primary storage backend.
|
||
|
||
## Consequences
|
||
+ Better graph query performance
|
||
+ Native relationship support
|
||
- Additional infrastructure dependency
|
||
- Team learning curve
|
||
</code></pre>
|
||
<h3 id="3-guideline"><a class="header" href="#3-guideline">3. Guideline</a></h3>
|
||
<p><strong>Purpose</strong>: Define coding standards, best practices, and conventions.</p>
|
||
<p><strong>When to use</strong>:</p>
|
||
<ul>
|
||
<li>Code style rules</li>
|
||
<li>Architecture patterns to follow</li>
|
||
<li>Security requirements</li>
|
||
<li>Testing standards</li>
|
||
</ul>
|
||
<p><strong>Can be</strong>:</p>
|
||
<ul>
|
||
<li><strong>Shared</strong>: Organization-wide (in shared KOGRAL)</li>
|
||
<li><strong>Project-specific</strong>: Overrides shared guidelines</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-yaml">---
|
||
type: guideline
|
||
language: rust
|
||
category: error-handling
|
||
---
|
||
|
||
# Rust Error Handling Guidelines
|
||
|
||
1. Use `thiserror` for custom error types
|
||
2. Never use `unwrap()` in production code
|
||
3. Always propagate errors with `?`
|
||
</code></pre>
|
||
<h3 id="4-pattern"><a class="header" href="#4-pattern">4. Pattern</a></h3>
|
||
<p><strong>Purpose</strong>: Document reusable solutions to common problems.</p>
|
||
<p><strong>When to use</strong>:</p>
|
||
<ul>
|
||
<li>Recurring implementation patterns</li>
|
||
<li>Tested solutions</li>
|
||
<li>Best practice implementations</li>
|
||
<li>Code templates</li>
|
||
</ul>
|
||
<p><strong>Structure</strong>:</p>
|
||
<ul>
|
||
<li><strong>Problem</strong>: What challenge does this solve?</li>
|
||
<li><strong>Solution</strong>: The pattern/approach</li>
|
||
<li><strong>Context</strong>: When to use/not use</li>
|
||
<li><strong>Example</strong>: Working code</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-yaml">---
|
||
type: pattern
|
||
title: Repository Pattern for Database Access
|
||
tags: [architecture, database, pattern]
|
||
---
|
||
|
||
## Problem
|
||
Need consistent database access across modules.
|
||
|
||
## Solution
|
||
Repository pattern with trait abstraction...
|
||
|
||
## Example
|
||
\`\`\`rust
|
||
trait UserRepository {
|
||
async fn find_by_id(&self, id: Uuid) -> Result<User>;
|
||
}
|
||
\`\`\`
|
||
</code></pre>
|
||
<h3 id="5-journal"><a class="header" href="#5-journal">5. Journal</a></h3>
|
||
<p><strong>Purpose</strong>: Daily development log for tracking progress and reflections.</p>
|
||
<p><strong>When to use</strong>:</p>
|
||
<ul>
|
||
<li>End of day summaries</li>
|
||
<li>Daily standup notes</li>
|
||
<li>Progress tracking</li>
|
||
<li>Blocker documentation</li>
|
||
</ul>
|
||
<p><strong>Auto-linked</strong>: KOGRAL can auto-link journal entries to mentioned concepts.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-yaml">---
|
||
type: journal
|
||
date: 2026-01-17
|
||
---
|
||
|
||
## Progress
|
||
- Implemented authentication module
|
||
- Fixed cache race condition
|
||
|
||
## Blockers
|
||
- Need API versioning discussion
|
||
|
||
## Learnings
|
||
- tokio::select! perfect for timeouts
|
||
</code></pre>
|
||
<h3 id="6-execution"><a class="header" href="#6-execution">6. Execution</a></h3>
|
||
<p><strong>Purpose</strong>: Record AI agent execution results (from Vapora integration).</p>
|
||
<p><strong>When to use</strong>:</p>
|
||
<ul>
|
||
<li>Agent task completion</li>
|
||
<li>Execution metrics</li>
|
||
<li>Agent decision history</li>
|
||
<li>Audit trail</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-yaml">---
|
||
type: execution
|
||
task_type: code_generation
|
||
agent: rust-expert
|
||
outcome: success
|
||
duration: 45s
|
||
---
|
||
|
||
Generated authentication module following project guidelines.
|
||
</code></pre>
|
||
<h2 id="relationship-types"><a class="header" href="#relationship-types">Relationship Types</a></h2>
|
||
<h3 id="1-relates_to"><a class="header" href="#1-relates_to">1. <code>relates_to</code></a></h3>
|
||
<p><strong>Meaning</strong>: General conceptual relationship.</p>
|
||
<p><strong>Use</strong>: Connect related ideas without specific dependency.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>[Note: Async Patterns] --relates_to--> [Note: Tokio Runtime]
|
||
</code></pre>
|
||
<h3 id="2-depends_on"><a class="header" href="#2-depends_on">2. <code>depends_on</code></a></h3>
|
||
<p><strong>Meaning</strong>: Prerequisite relationship. Source requires target to exist/be understood first.</p>
|
||
<p><strong>Use</strong>: Learning paths, implementation order.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>[Pattern: Advanced Error Handling] --depends_on--> [Guideline: Basic Errors]
|
||
</code></pre>
|
||
<h3 id="3-implements"><a class="header" href="#3-implements">3. <code>implements</code></a></h3>
|
||
<p><strong>Meaning</strong>: Concrete implementation of an abstract concept.</p>
|
||
<p><strong>Use</strong>: Connect code to patterns/guidelines.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>[Note: Auth Module Implementation] --implements--> [Pattern: Repository Pattern]
|
||
</code></pre>
|
||
<h3 id="4-extends"><a class="header" href="#4-extends">4. <code>extends</code></a></h3>
|
||
<p><strong>Meaning</strong>: Inheritance/extension relationship.</p>
|
||
<p><strong>Use</strong>: Guideline overrides, pattern variations.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>[Guideline: Project Error Handling] --extends--> [Guideline: Shared Error Handling]
|
||
</code></pre>
|
||
<h3 id="5-supersedes"><a class="header" href="#5-supersedes">5. <code>supersedes</code></a></h3>
|
||
<p><strong>Meaning</strong>: Replacement relationship. Source replaces target.</p>
|
||
<p><strong>Use</strong>: Track evolution of decisions/patterns.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>[Decision: Use GraphQL v2] --supersedes--> [Decision: Use REST]
|
||
</code></pre>
|
||
<h3 id="6-explains"><a class="header" href="#6-explains">6. <code>explains</code></a></h3>
|
||
<p><strong>Meaning</strong>: Documentation/clarification relationship.</p>
|
||
<p><strong>Use</strong>: Connect notes to implementations, executions to rationale.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>[Note: Why We Chose Rust] --explains--> [Decision: Adopt Rust]
|
||
</code></pre>
|
||
<h2 id="multi-graph-architecture-1"><a class="header" href="#multi-graph-architecture-1">Multi-Graph Architecture</a></h2>
|
||
<p>KB supports <strong>multiple knowledge graphs</strong>:</p>
|
||
<h3 id="local-graph-project-specific"><a class="header" href="#local-graph-project-specific">Local Graph (Project-Specific)</a></h3>
|
||
<p><strong>Location</strong>: <code>.kogral/</code> in project directory</p>
|
||
<p><strong>Purpose</strong>: Project-specific knowledge</p>
|
||
<ul>
|
||
<li>Project decisions</li>
|
||
<li>Implementation notes</li>
|
||
<li>Local patterns</li>
|
||
<li>Daily journals</li>
|
||
</ul>
|
||
<p><strong>Storage</strong>: Filesystem (git-tracked)</p>
|
||
<p><strong>Scope</strong>: Single project</p>
|
||
<h3 id="shared-graph-organization-wide"><a class="header" href="#shared-graph-organization-wide">Shared Graph (Organization-Wide)</a></h3>
|
||
<p><strong>Location</strong>: Configurable (e.g., <code>~/org/.kogral-shared</code>)</p>
|
||
<p><strong>Purpose</strong>: Shared organizational knowledge</p>
|
||
<ul>
|
||
<li>Coding guidelines</li>
|
||
<li>Standard patterns</li>
|
||
<li>Architecture principles</li>
|
||
<li>Security policies</li>
|
||
</ul>
|
||
<p><strong>Storage</strong>: SurrealDB (centralized) or filesystem (synced)</p>
|
||
<p><strong>Scope</strong>: All projects</p>
|
||
<h3 id="inheritance-model"><a class="header" href="#inheritance-model">Inheritance Model</a></h3>
|
||
<pre><code>Shared Guidelines (priority: 50)
|
||
↓ [inherited by]
|
||
Project Guidelines (priority: 100)
|
||
↓ [effective]
|
||
Combined Guidelines (higher priority wins)
|
||
</code></pre>
|
||
<p><strong>Example</strong>:</p>
|
||
<p>Shared guideline: "Use Result<T> for errors"
|
||
Project override: "Use Result<T> + log all errors with tracing"
|
||
Effective: Both rules apply, project adds requirement</p>
|
||
<h2 id="config-driven-behavior"><a class="header" href="#config-driven-behavior">Config-Driven Behavior</a></h2>
|
||
<p><strong>Everything is configurable</strong> via Nickel schemas:</p>
|
||
<pre><code class="language-nickel">{
|
||
graph = { name = "my-project" }, # Graph metadata
|
||
storage = { primary = 'filesystem }, # Where to store
|
||
embeddings = { provider = 'fastembed }, # AI provider
|
||
templates = { /* ... */ }, # Document templates
|
||
query = { similarity_threshold = 0.6 }, # Search behavior
|
||
}
|
||
</code></pre>
|
||
<p><strong>No hardcoding</strong>: Change behavior without code changes.</p>
|
||
<h2 id="semantic-search"><a class="header" href="#semantic-search">Semantic Search</a></h2>
|
||
<p>Beyond keyword matching, KOGRAL uses <strong>embeddings</strong> to find concepts:</p>
|
||
<p><strong>Keyword search</strong>: "Find 'error handling'"</p>
|
||
<ul>
|
||
<li>Matches exact phrase "error handling"</li>
|
||
</ul>
|
||
<p><strong>Semantic search</strong>: "How to handle failures gracefully?"</p>
|
||
<ul>
|
||
<li>Finds: error handling, exception patterns, Result types, panic recovery</li>
|
||
<li>Understands: "failures" = errors, "gracefully" = best practices</li>
|
||
</ul>
|
||
<p><strong>How it works</strong>:</p>
|
||
<ol>
|
||
<li>Text → Embedding (384 or 1536 dimensional vector)</li>
|
||
<li>Similarity search (cosine distance)</li>
|
||
<li>Return nodes above threshold (e.g., 0.6)</li>
|
||
</ol>
|
||
<p><strong>Providers</strong>:</p>
|
||
<ul>
|
||
<li><strong>fastembed</strong>: Local, free, offline (384d)</li>
|
||
<li><strong>OpenAI</strong>: Cloud, best quality (1536d)</li>
|
||
<li><strong>Claude</strong>: Cloud, excellent (1024d)</li>
|
||
<li><strong>Ollama</strong>: Self-hosted (768d)</li>
|
||
</ul>
|
||
<h2 id="templates"><a class="header" href="#templates">Templates</a></h2>
|
||
<p><strong>Tera templates</strong> generate consistent documents:</p>
|
||
<pre><code class="language-jinja2">---
|
||
id: {{ id }}
|
||
type: {{ type }}
|
||
title: {{ title }}
|
||
tags: [{% for tag in tags %}"{{ tag }}"{% endfor %}]
|
||
---
|
||
|
||
# {{ title }}
|
||
|
||
{{ content }}
|
||
</code></pre>
|
||
<p><strong>Customizable</strong>: Override templates per project.</p>
|
||
<p><strong>Export formats</strong>: Logseq, JSON, Markdown.</p>
|
||
<h2 id="mcp-integration"><a class="header" href="#mcp-integration">MCP Integration</a></h2>
|
||
<p><strong>Model Context Protocol</strong> connects KOGRAL to Claude Code:</p>
|
||
<pre><code>Claude Code
|
||
↓ [JSON-RPC 2.0]
|
||
kb-mcp Server
|
||
↓ [Rust API]
|
||
kb-core Library
|
||
↓ [Storage]
|
||
Knowledge Graph
|
||
</code></pre>
|
||
<p><strong>Tools</strong>: <code>kogral/search</code>, <code>kogral/add_note</code>, <code>kogral/get_guidelines</code>, etc.</p>
|
||
<p><strong>Resources</strong>: <code>kogral://project/notes</code>, <code>kogral://shared/guidelines</code></p>
|
||
<p><strong>Prompts</strong>: <code>kogral/summarize_project</code>, <code>kogral/find_related</code></p>
|
||
<h2 id="git-friendly-storage"><a class="header" href="#git-friendly-storage">Git-Friendly Storage</a></h2>
|
||
<p><strong>Markdown + YAML frontmatter</strong> = git-tracked knowledge:</p>
|
||
<pre><code class="language-markdown">---
|
||
id: note-123
|
||
type: note
|
||
title: My Note
|
||
---
|
||
|
||
# Content here
|
||
</code></pre>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>✅ Diffs in PRs (reviewable changes)</li>
|
||
<li>✅ Branches (experiment with knowledge)</li>
|
||
<li>✅ Merges (combine knowledge from feature branches)</li>
|
||
<li>✅ History (track evolution over time)</li>
|
||
<li>✅ Blame (who added this knowledge?)</li>
|
||
</ul>
|
||
<h2 id="logseq-content-blocks"><a class="header" href="#logseq-content-blocks">Logseq Content Blocks</a></h2>
|
||
<p>KOGRAL provides full support for <strong>Logseq-style outliner blocks</strong> with rich metadata and structure.</p>
|
||
<h3 id="what-are-blocks"><a class="header" href="#what-are-blocks">What are Blocks?</a></h3>
|
||
<p>Blocks are the fundamental unit of content in Logseq's outliner format:</p>
|
||
<ul>
|
||
<li>Each bullet point is a block</li>
|
||
<li>Blocks can have children (nested blocks)</li>
|
||
<li>Blocks support tasks, tags, and custom properties</li>
|
||
<li>Blocks can reference other blocks or pages</li>
|
||
</ul>
|
||
<h3 id="block-features"><a class="header" href="#block-features">Block Features</a></h3>
|
||
<p><strong>Task Status</strong>:</p>
|
||
<pre><code class="language-markdown">- TODO Implement authentication #high-priority
|
||
- DOING Write tests
|
||
- DONE Deploy to staging
|
||
- LATER Consider GraphQL API
|
||
- NOW Fix critical bug
|
||
- WAITING Code review from @alice
|
||
- CANCELLED Old approach
|
||
</code></pre>
|
||
<p><strong>Inline Tags</strong>:</p>
|
||
<pre><code class="language-markdown">- Learning Rust ownership #rust #learning #card
|
||
- Prevents data races at compile time
|
||
- Borrowing rules enforce safety
|
||
</code></pre>
|
||
<p><strong>Custom Properties</strong>:</p>
|
||
<pre><code class="language-markdown">- Research paper summary
|
||
priority:: high
|
||
reviewed:: 2026-01-17
|
||
source:: https://example.com/paper.pdf
|
||
- Key findings...
|
||
</code></pre>
|
||
<p><strong>Block and Page References</strong>:</p>
|
||
<pre><code class="language-markdown">- Meeting notes from [[2026-01-17]]
|
||
- Discussed architecture ((block-ref-123))
|
||
- Action items: [[Project Roadmap]]
|
||
</code></pre>
|
||
<p><strong>Hierarchical Structure</strong>:</p>
|
||
<pre><code class="language-markdown">- Parent block #top-level
|
||
- Child block 1
|
||
- Nested child
|
||
- Child block 2
|
||
</code></pre>
|
||
<h3 id="configuration"><a class="header" href="#configuration">Configuration</a></h3>
|
||
<p>Blocks support is opt-in via configuration:</p>
|
||
<pre><code class="language-nickel">{
|
||
blocks = {
|
||
enabled = true, # Enable blocks parsing
|
||
parse_on_import = true, # Auto-parse from Logseq imports
|
||
serialize_on_export = true, # Serialize to outliner format
|
||
enable_mcp_tools = true, # Enable block-related MCP tools
|
||
},
|
||
}
|
||
</code></pre>
|
||
<h3 id="use-cases-1"><a class="header" href="#use-cases-1">Use Cases</a></h3>
|
||
<p><strong>1. Task Management</strong>:</p>
|
||
<pre><code class="language-markdown">- TODO Weekly sprint planning #meeting
|
||
- DONE Review last sprint
|
||
- DOING Plan current sprint
|
||
- TODO Assign tasks
|
||
</code></pre>
|
||
<p><strong>2. Flashcards (Spaced Repetition)</strong>:</p>
|
||
<pre><code class="language-markdown">- What is Rust's ownership model? #card #rust
|
||
- Ownership prevents data races at compile time
|
||
- Each value has a single owner
|
||
- When owner goes out of scope, value is dropped
|
||
</code></pre>
|
||
<p><strong>3. Knowledge Capture with Metadata</strong>:</p>
|
||
<pre><code class="language-markdown">- Tokio async runtime patterns #rust #async
|
||
category:: architecture
|
||
difficulty:: intermediate
|
||
- Use tokio::select! for concurrent operations
|
||
- spawn_blocking() for CPU-intensive work
|
||
</code></pre>
|
||
<p><strong>4. Linked Notes</strong>:</p>
|
||
<pre><code class="language-markdown">- Discussed [[ADR-001]] in architecture meeting
|
||
- Decided on SurrealDB
|
||
- See ((meeting-notes-block-id)) for details
|
||
</code></pre>
|
||
<h3 id="block-queries-mcp-tools"><a class="header" href="#block-queries-mcp-tools">Block Queries (MCP Tools)</a></h3>
|
||
<p>Query blocks across your knowledge base:</p>
|
||
<p><strong>Find blocks by tag</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/find_blocks",
|
||
"arguments": { "tag": "card" }
|
||
}
|
||
</code></pre>
|
||
<p><strong>Find all TODOs</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/find_todos",
|
||
"arguments": { "limit": 20 }
|
||
}
|
||
</code></pre>
|
||
<p><strong>Find flashcards</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/find_cards",
|
||
"arguments": { "limit": 10 }
|
||
}
|
||
</code></pre>
|
||
<p><strong>Find blocks by property</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/find_blocks",
|
||
"arguments": {
|
||
"property_key": "priority",
|
||
"property_value": "high"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="architecture"><a class="header" href="#architecture">Architecture</a></h3>
|
||
<p><strong>Hybrid Model</strong>:</p>
|
||
<ul>
|
||
<li>Content stored as markdown string (source of truth)</li>
|
||
<li>Blocks lazily parsed on first access</li>
|
||
<li>Cached block structure for fast queries</li>
|
||
<li>Bidirectional: markdown ↔ blocks</li>
|
||
</ul>
|
||
<p><strong>BlockParser</strong>:</p>
|
||
<ul>
|
||
<li>Parse outliner markdown → Block structures</li>
|
||
<li>Serialize Block structures → outliner markdown</li>
|
||
<li>Preserve all metadata (tags, status, properties, references)</li>
|
||
<li>Round-trip fidelity for Logseq compatibility</li>
|
||
</ul>
|
||
<p><strong>Storage</strong>:</p>
|
||
<ul>
|
||
<li>Filesystem: markdown with blocks inline</li>
|
||
<li>SurrealDB: dedicated <code>block</code> table with indexes</li>
|
||
<li>Indexes: tags, status, parent_id, full-text search</li>
|
||
</ul>
|
||
<p><strong>See Also</strong>:</p>
|
||
<ul>
|
||
<li><a href="kogral/../architecture/adrs/004-logseq-blocks-support.html">ADR-004: Logseq Blocks Support</a></li>
|
||
<li><a href="kogral/../architecture/logseq-blocks-design.html">Logseq Blocks Design</a></li>
|
||
<li><a href="kogral/../api/mcp-tools.html#block-tools">MCP Block Tools</a></li>
|
||
</ul>
|
||
<h2 id="key-principles"><a class="header" href="#key-principles">Key Principles</a></h2>
|
||
<ol>
|
||
<li><strong>Capture During Work</strong>: Don't wait, document as you go</li>
|
||
<li><strong>Link as You Learn</strong>: Connect related concepts immediately</li>
|
||
<li><strong>Query When Needed</strong>: AI-assisted discovery of relevant knowledge</li>
|
||
<li><strong>Evolve Over Time</strong>: Update decisions, supersede patterns</li>
|
||
<li><strong>Share Wisely</strong>: Shared guidelines, local specifics</li>
|
||
</ol>
|
||
<h2 id="next-steps-1"><a class="header" href="#next-steps-1">Next Steps</a></h2>
|
||
<ul>
|
||
<li><strong>Understand motivation</strong>: <a href="kogral/why-kogral.html">Why KOGRAL?</a></li>
|
||
<li><strong>Learn philosophy</strong>: <a href="kogral/design-philosophy.html">Design Philosophy</a></li>
|
||
<li><strong>See architecture</strong>: <a href="kogral/../architecture/overview.html">System Overview</a></li>
|
||
<li><strong>Start using</strong>: <a href="kogral/../guides/quickstart.html">Quick Start</a></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="why-kb"><a class="header" href="#why-kb">Why KB?</a></h1>
|
||
<p>Understanding the motivation behind the Knowledge Base system.</p>
|
||
<h2 id="the-problem-knowledge-fragmentation"><a class="header" href="#the-problem-knowledge-fragmentation">The Problem: Knowledge Fragmentation</a></h2>
|
||
<p>Modern software development generates enormous amounts of knowledge:</p>
|
||
<p>📝 <strong>Notes</strong>: Implementation details, learnings, discoveries
|
||
🤔 <strong>Decisions</strong>: Why we chose X over Y
|
||
📚 <strong>Guidelines</strong>: How we write code, architecture patterns
|
||
🔄 <strong>Patterns</strong>: Reusable solutions we've discovered
|
||
💭 <strong>Discussions</strong>: Slack threads, meeting notes, PR comments
|
||
🐛 <strong>Bug Fixes</strong>: How we solved issues</p>
|
||
<p><strong>But where does this knowledge live?</strong></p>
|
||
<ul>
|
||
<li>Meeting notes in Google Docs</li>
|
||
<li>Decisions buried in Slack threads</li>
|
||
<li>Guidelines in a wiki nobody updates</li>
|
||
<li>Patterns in someone's head</li>
|
||
<li>PR discussions lost in GitHub history</li>
|
||
<li>Bug solutions in closed Jira tickets</li>
|
||
</ul>
|
||
<p><strong>The result?</strong></p>
|
||
<p>❌ <strong>Rediscovering solutions</strong> to problems we've already solved
|
||
❌ <strong>Inconsistent practices</strong> because guidelines aren't accessible
|
||
❌ <strong>Slow onboarding</strong> as new developers lack context
|
||
❌ <strong>Lost context</strong> when team members leave
|
||
❌ <strong>Repeated mistakes</strong> because past lessons aren't preserved</p>
|
||
<h2 id="the-solution-unified-queryable-knowledge-graph"><a class="header" href="#the-solution-unified-queryable-knowledge-graph">The Solution: Unified, Queryable Knowledge Graph</a></h2>
|
||
<p>Knowledge Base provides a <strong>single source of truth</strong> for project knowledge:</p>
|
||
<p>✅ <strong>Capture once</strong>: Notes, decisions, guidelines, patterns
|
||
✅ <strong>Connect concepts</strong>: Typed relationships between ideas
|
||
✅ <strong>Query naturally</strong>: Text and semantic search
|
||
✅ <strong>AI-assisted</strong>: Claude Code integration via MCP
|
||
✅ <strong>Git-tracked</strong>: Version control for knowledge
|
||
✅ <strong>Shared wisdom</strong>: Organization-wide guidelines + project-specific</p>
|
||
<h2 id="what-makes-kb-different"><a class="header" href="#what-makes-kb-different">What Makes KB Different?</a></h2>
|
||
<h3 id="1-git-native"><a class="header" href="#1-git-native">1. Git-Native</a></h3>
|
||
<p><strong>Other tools</strong>: Notion, Confluence, wikis in web apps</p>
|
||
<p><strong>KB</strong>: Markdown files in <code>.kogral/</code> directory</p>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<pre><code class="language-bash"># Review knowledge changes in PRs
|
||
git diff .kogral/decisions/
|
||
|
||
# Branch knowledge with code
|
||
git checkout feature-branch
|
||
# .kogral/ follows the branch
|
||
|
||
# Merge knowledge from feature work
|
||
git merge feature-branch
|
||
# Knowledge merges like code
|
||
</code></pre>
|
||
<p><strong>Result</strong>: Knowledge versioned alongside code it describes.</p>
|
||
<h3 id="2-ai-native"><a class="header" href="#2-ai-native">2. AI-Native</a></h3>
|
||
<p><strong>Other tools</strong>: Manual search, browse folders</p>
|
||
<p><strong>KB</strong>: AI queries via Claude Code</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>You: "Find anything about error handling"
|
||
|
||
Claude: [Searches semantically, not just keywords]
|
||
Found 5 concepts:
|
||
- Pattern: Error Handling with thiserror
|
||
- Guideline: Result Type Best Practices
|
||
- Decision: Use anyhow for Application Errors
|
||
- Note: Custom Error Types
|
||
- Journal: Fixed error propagation bug
|
||
|
||
[All related, even without exact keyword match]
|
||
</code></pre>
|
||
<p><strong>Result</strong>: Find concepts, not just documents with keywords.</p>
|
||
<h3 id="3-config-driven"><a class="header" href="#3-config-driven">3. Config-Driven</a></h3>
|
||
<p><strong>Other tools</strong>: Hardcoded behavior, limited customization</p>
|
||
<p><strong>KB</strong>: Nickel schemas define everything</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-nickel"># Development: local embeddings, no costs
|
||
{ embeddings = { provider = 'fastembed } }
|
||
|
||
# Production: cloud embeddings, best quality
|
||
{ embeddings = { provider = 'openai, model = "text-embedding-3-large" } }
|
||
</code></pre>
|
||
<p><strong>Result</strong>: Adapt behavior without code changes.</p>
|
||
<h3 id="4-multi-graph"><a class="header" href="#4-multi-graph">4. Multi-Graph</a></h3>
|
||
<p><strong>Other tools</strong>: One knowledge base per project</p>
|
||
<p><strong>KB</strong>: Shared organizational knowledge + project-specific</p>
|
||
<p><strong>Structure</strong>:</p>
|
||
<pre><code>Organization Shared KB
|
||
├── Rust Guidelines (applies to all projects)
|
||
├── Security Patterns (applies to all projects)
|
||
└── Testing Standards (applies to all projects)
|
||
|
||
Project A KB
|
||
├── Inherits shared guidelines
|
||
├── Project-specific decisions
|
||
└── Can override shared guidelines
|
||
|
||
Project B KB
|
||
├── Inherits same shared guidelines
|
||
├── Different project decisions
|
||
└── Different overrides
|
||
</code></pre>
|
||
<p><strong>Result</strong>: Consistency across organization, flexibility per project.</p>
|
||
<h3 id="5-structured-relationships"><a class="header" href="#5-structured-relationships">5. Structured Relationships</a></h3>
|
||
<p><strong>Other tools</strong>: Backlinks, tags, folders</p>
|
||
<p><strong>KB</strong>: Typed relationships with meaning</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>Pattern: Repository Pattern
|
||
↑ [implements]
|
||
Note: User Service Implementation
|
||
↑ [relates_to]
|
||
Decision: Use PostgreSQL
|
||
↑ [depends_on]
|
||
Guideline: Database Access Patterns
|
||
</code></pre>
|
||
<p><strong>Result</strong>: Understand how knowledge connects, not just that it connects.</p>
|
||
<h2 id="real-world-impact"><a class="header" href="#real-world-impact">Real-World Impact</a></h2>
|
||
<h3 id="before-kb"><a class="header" href="#before-kb">Before KB</a></h3>
|
||
<p><strong>New developer joins</strong>:</p>
|
||
<ol>
|
||
<li>Read outdated wiki (2 hours)</li>
|
||
<li>Ask teammates for context (1 hour per question × 10 questions)</li>
|
||
<li>Discover guidelines by reading code (days)</li>
|
||
<li>Miss important decisions (leads to mistakes)</li>
|
||
</ol>
|
||
<p><strong>Time to productivity</strong>: 2-4 weeks</p>
|
||
<h3 id="with-kb"><a class="header" href="#with-kb">With KB</a></h3>
|
||
<p><strong>New developer joins</strong>:</p>
|
||
<ol>
|
||
<li><code>kb search "architectural decisions"</code> → finds all ADRs</li>
|
||
<li>Ask Claude: "What are our coding guidelines?" → gets current guidelines with inheritance</li>
|
||
<li>Browse related notes via graph traversal</li>
|
||
<li>Full context available, no tribal knowledge</li>
|
||
</ol>
|
||
<p><strong>Time to productivity</strong>: 3-5 days</p>
|
||
<h3 id="before-kb-1"><a class="header" href="#before-kb-1">Before KB</a></h3>
|
||
<p><strong>Team makes decision</strong>:</p>
|
||
<ol>
|
||
<li>Discussion in Slack (lost after a week)</li>
|
||
<li>Someone documents in wiki (maybe)</li>
|
||
<li>6 months later: "Why did we choose X?" → nobody remembers</li>
|
||
<li>Re-debate the same decision</li>
|
||
</ol>
|
||
<h3 id="with-kb-1"><a class="header" href="#with-kb-1">With KB</a></h3>
|
||
<p><strong>Team makes decision</strong>:</p>
|
||
<ol>
|
||
<li>Discussion captured as ADR during meeting</li>
|
||
<li>Context, decision, consequences documented</li>
|
||
<li>Linked to related patterns and guidelines</li>
|
||
<li>6 months later: <code>kb show decision-use-x</code> → full context instantly</li>
|
||
</ol>
|
||
<h3 id="before-kb-2"><a class="header" href="#before-kb-2">Before KB</a></h3>
|
||
<p><strong>Solving a bug</strong>:</p>
|
||
<ol>
|
||
<li>Encounter race condition in cache</li>
|
||
<li>Debug for 2 hours</li>
|
||
<li>Fix it</li>
|
||
<li>Solution lost in PR comments</li>
|
||
</ol>
|
||
<p><strong>Two months later</strong>: Different developer, same bug, 2 more hours</p>
|
||
<h3 id="with-kb-2"><a class="header" href="#with-kb-2">With KB</a></h3>
|
||
<p><strong>Solving a bug</strong>:</p>
|
||
<ol>
|
||
<li>Encounter race condition</li>
|
||
<li>Ask Claude: "Have we seen cache race conditions before?"</li>
|
||
<li>Claude finds journal entry from 2 months ago with solution</li>
|
||
<li>Apply fix in 10 minutes</li>
|
||
</ol>
|
||
<p><strong>Two months later</strong>: Same query, same instant solution</p>
|
||
<h2 id="who-benefits"><a class="header" href="#who-benefits">Who Benefits?</a></h2>
|
||
<h3 id="individual-developers"><a class="header" href="#individual-developers">Individual Developers</a></h3>
|
||
<p>✅ <strong>Personal knowledge base</strong>: Capture learnings, build expertise
|
||
✅ <strong>Quick recall</strong>: "How did I solve this before?"
|
||
✅ <strong>Context switching</strong>: Return to old projects with full context
|
||
✅ <strong>Career growth</strong>: Document what you learn, portfolio of knowledge</p>
|
||
<h3 id="teams"><a class="header" href="#teams">Teams</a></h3>
|
||
<p>✅ <strong>Shared context</strong>: Everyone has access to team knowledge
|
||
✅ <strong>Onboarding</strong>: New members ramp up faster
|
||
✅ <strong>Consistency</strong>: Follow shared guidelines and patterns
|
||
✅ <strong>Collaboration</strong>: Build on each other's knowledge</p>
|
||
<h3 id="organizations"><a class="header" href="#organizations">Organizations</a></h3>
|
||
<p>✅ <strong>Institutional memory</strong>: Knowledge persists beyond individual tenure
|
||
✅ <strong>Best practices</strong>: Standardize across teams
|
||
✅ <strong>Compliance</strong>: Document security and architecture decisions
|
||
✅ <strong>Efficiency</strong>: Stop solving the same problems repeatedly</p>
|
||
<h3 id="ai-agents"><a class="header" href="#ai-agents">AI Agents</a></h3>
|
||
<p>✅ <strong>Context injection</strong>: Agents query guidelines before generating code
|
||
✅ <strong>Decision awareness</strong>: Agents check past decisions
|
||
✅ <strong>Pattern following</strong>: Agents use documented patterns
|
||
✅ <strong>Execution tracking</strong>: Agent actions documented automatically</p>
|
||
<h2 id="when-not-to-use-kb"><a class="header" href="#when-not-to-use-kb">When NOT to Use KB</a></h2>
|
||
<p>KB is <strong>not</strong> ideal for:</p>
|
||
<p>❌ <strong>Personal journaling</strong>: Use Obsidian or Logseq
|
||
❌ <strong>Team wikis</strong>: Use Notion or Confluence
|
||
❌ <strong>Customer docs</strong>: Use mdBook or Docusaurus
|
||
❌ <strong>Project management</strong>: Use Jira or Linear
|
||
❌ <strong>Code comments</strong>: Use inline documentation</p>
|
||
<p>KB is <strong>perfect</strong> for:</p>
|
||
<p>✅ Developer knowledge graphs
|
||
✅ Architectural decision records
|
||
✅ Pattern libraries
|
||
✅ Coding guidelines
|
||
✅ Technical context
|
||
✅ AI-assisted development</p>
|
||
<h2 id="the-vision"><a class="header" href="#the-vision">The Vision</a></h2>
|
||
<p><strong>Today's development</strong>:</p>
|
||
<pre><code>Developer → writes code → commits
|
||
</code></pre>
|
||
<p><strong>With KB</strong>:</p>
|
||
<pre><code>Developer → writes code → documents decisions → links to patterns → commits code + knowledge
|
||
↓
|
||
AI Agent queries knowledge → generates better code
|
||
↓
|
||
Team discovers patterns → reuses solutions → faster development
|
||
</code></pre>
|
||
<p><strong>Knowledge becomes an active participant in development</strong>, not an afterthought.</p>
|
||
<h2 id="design-philosophy"><a class="header" href="#design-philosophy">Design Philosophy</a></h2>
|
||
<p>Three core principles drive KB:</p>
|
||
<h3 id="1-knowledge-should-be-captured-during-work-not-after"><a class="header" href="#1-knowledge-should-be-captured-during-work-not-after">1. <strong>Knowledge should be captured during work, not after</strong></a></h3>
|
||
<p>❌ "I'll document this later" → never happens
|
||
✅ "Claude, document this decision" → done in 30 seconds</p>
|
||
<h3 id="2-knowledge-should-be-connected-not-isolated"><a class="header" href="#2-knowledge-should-be-connected-not-isolated">2. <strong>Knowledge should be connected, not isolated</strong></a></h3>
|
||
<p>❌ Standalone documents in folders
|
||
✅ Graph of interconnected concepts</p>
|
||
<h3 id="3-knowledge-should-be-queryable-not-browsable"><a class="header" href="#3-knowledge-should-be-queryable-not-browsable">3. <strong>Knowledge should be queryable, not browsable</strong></a></h3>
|
||
<p>❌ "Let me look through 50 docs to find..."
|
||
✅ "Find anything related to error handling" → instant semantic results</p>
|
||
<h2 id="get-started"><a class="header" href="#get-started">Get Started</a></h2>
|
||
<p>Ready to stop losing knowledge?</p>
|
||
<ul>
|
||
<li><strong>Understand concepts</strong>: <a href="kogral/core-concepts.html">Core Concepts</a></li>
|
||
<li><strong>Learn philosophy</strong>: <a href="kogral/design-philosophy.html">Design Philosophy</a></li>
|
||
<li><strong>Quick start</strong>: <a href="kogral/../guides/quickstart.html">Quick Start Guide</a></li>
|
||
<li><strong>See examples</strong>: <a href="kogral/../guides/use-cases.html">Use Cases</a></li>
|
||
</ul>
|
||
<hr />
|
||
<blockquote>
|
||
<p>"The best time to document was during implementation. The second best time is now."</p>
|
||
</blockquote>
|
||
<p>But with KB + AI, "now" is instant.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="design-philosophy-1"><a class="header" href="#design-philosophy-1">Design Philosophy</a></h1>
|
||
<p>The principles and values guiding Knowledge Base design and implementation.</p>
|
||
<h2 id="core-tenets"><a class="header" href="#core-tenets">Core Tenets</a></h2>
|
||
<h3 id="1-config-driven-not-hardcoded"><a class="header" href="#1-config-driven-not-hardcoded">1. Config-Driven, Not Hardcoded</a></h3>
|
||
<p><strong>Principle</strong>: All behavior should be configurable via schemas, not baked into code.</p>
|
||
<p><strong>Why</strong>: Flexibility without code changes. Users adapt KB to their workflows, not vice versa.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// ❌ Bad: Hardcoded
|
||
impl EmbeddingProvider {
|
||
fn new() -> Self {
|
||
FastEmbedProvider::new("BAAI/bge-small-en-v1.5") // Can't change
|
||
}
|
||
}
|
||
|
||
// ✅ Good: Config-driven
|
||
impl EmbeddingProvider {
|
||
fn from_config(config: &EmbeddingConfig) -> Result<Box<dyn EmbeddingProvider>> {
|
||
match config.provider {
|
||
'fastembed => Ok(Box::new(FastEmbedProvider::new(&config.model)?)),
|
||
'openai => Ok(Box::new(OpenAIProvider::new(&config.model)?)),
|
||
}
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>Switch embedding providers without recompilation</li>
|
||
<li>Different configs for dev/prod</li>
|
||
<li>User choice, not developer mandate</li>
|
||
</ul>
|
||
<h3 id="2-type-safe-configuration"><a class="header" href="#2-type-safe-configuration">2. Type-Safe Configuration</a></h3>
|
||
<p><strong>Principle</strong>: Validate configuration before runtime, not during.</p>
|
||
<p><strong>Why</strong>: Catch errors early, reduce runtime failures.</p>
|
||
<p><strong>Implementation</strong>: Nickel contracts + serde validation = double validation</p>
|
||
<pre><code class="language-nickel"># Schema defines valid values
|
||
EmbeddingProvider = [| 'openai, 'claude, 'fastembed |]
|
||
|
||
# Typo caught at export time, not runtime
|
||
{ provider = 'opena1 } # Error: Invalid variant
|
||
</code></pre>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>Errors found during <code>nickel export</code>, not during execution</li>
|
||
<li>Self-documenting: schema is the spec</li>
|
||
<li>Refactoring safe: change schema, find all usages</li>
|
||
</ul>
|
||
<h3 id="3-local-first-cloud-optional"><a class="header" href="#3-local-first-cloud-optional">3. Local-First, Cloud-Optional</a></h3>
|
||
<p><strong>Principle</strong>: Core functionality works offline, cloud is enhancement.</p>
|
||
<p><strong>Why</strong>: Privacy, cost control, offline development.</p>
|
||
<p><strong>Examples</strong>:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>Local</th><th>Cloud</th></tr></thead><tbody>
|
||
<tr><td>Storage</td><td>Filesystem</td><td>SurrealDB</td></tr>
|
||
<tr><td>Embeddings</td><td>fastembed</td><td>OpenAI/Claude</td></tr>
|
||
<tr><td>Search</td><td>Text-based</td><td>Semantic</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>Works on planes, trains, areas with poor internet</li>
|
||
<li>No API costs for small projects</li>
|
||
<li>Privacy-sensitive projects keep data local</li>
|
||
<li>Production can use cloud for scale</li>
|
||
</ul>
|
||
<h3 id="4-git-friendly-by-default"><a class="header" href="#4-git-friendly-by-default">4. Git-Friendly by Default</a></h3>
|
||
<p><strong>Principle</strong>: Knowledge should version alongside code.</p>
|
||
<p><strong>Why</strong>: Knowledge describes code, should evolve with it.</p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<ul>
|
||
<li>Markdown + YAML frontmatter (text-based, diffable)</li>
|
||
<li>One file per node (granular commits)</li>
|
||
<li>Wikilinks preserved (works in Logseq, Obsidian)</li>
|
||
</ul>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<pre><code class="language-bash"># Review knowledge changes in PRs
|
||
git diff .kogral/decisions/
|
||
|
||
# Knowledge follows branches
|
||
git checkout feature-x
|
||
# .kogral/ reflects feature-x decisions
|
||
|
||
# Knowledge merges
|
||
git merge feature-x
|
||
# Merge conflicts = knowledge conflicts (resolve intentionally)
|
||
</code></pre>
|
||
<h3 id="5-ai-native-human-readable"><a class="header" href="#5-ai-native-human-readable">5. AI-Native, Human-Readable</a></h3>
|
||
<p><strong>Principle</strong>: Optimized for AI consumption, readable by humans.</p>
|
||
<p><strong>Why</strong>: Best of both worlds - AI-assisted discovery, human verification.</p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<ul>
|
||
<li><strong>Structured</strong>: YAML frontmatter for AI parsing</li>
|
||
<li><strong>Semantic</strong>: Embeddings for AI queries</li>
|
||
<li><strong>Readable</strong>: Markdown for human consumption</li>
|
||
<li><strong>Linked</strong>: Typed relationships for AI traversal</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-markdown">---
|
||
id: note-auth
|
||
type: note
|
||
title: Authentication Implementation
|
||
tags: [security, auth]
|
||
relates_to: [guideline-security, pattern-jwt]
|
||
---
|
||
|
||
# Authentication Implementation
|
||
|
||
Humans read this markdown normally.
|
||
|
||
AI can:
|
||
- Parse frontmatter for metadata
|
||
- Extract tags for filtering
|
||
- Follow relates_to links
|
||
- Generate embeddings for semantic search
|
||
</code></pre>
|
||
<h3 id="6-composition-over-inheritance"><a class="header" href="#6-composition-over-inheritance">6. Composition Over Inheritance</a></h3>
|
||
<p><strong>Principle</strong>: Build systems by composing small, focused components.</p>
|
||
<p><strong>Why</strong>: Flexibility, testability, maintainability.</p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// Small, focused traits
|
||
trait Storage { ... }
|
||
trait EmbeddingProvider { ... }
|
||
trait TemplateEngine { ... }
|
||
|
||
// Compose into systems
|
||
struct KnowledgeBase {
|
||
storage: Box<dyn Storage>,
|
||
embeddings: Option<Box<dyn EmbeddingProvider>>,
|
||
templates: TemplateEngine,
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>Swap storage without affecting embeddings</li>
|
||
<li>Disable embeddings without breaking storage</li>
|
||
<li>Test components in isolation</li>
|
||
<li>Add new providers by implementing trait</li>
|
||
</ul>
|
||
<h3 id="7-fail-fast-fail-clearly"><a class="header" href="#7-fail-fast-fail-clearly">7. Fail Fast, Fail Clearly</a></h3>
|
||
<p><strong>Principle</strong>: Detect errors early, provide clear messages.</p>
|
||
<p><strong>Why</strong>: Developer experience - fast feedback, actionable errors.</p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// ❌ Bad: Silent failure
|
||
fn load_config(path: &Path) -> Option<Config> {
|
||
std::fs::read_to_string(path)
|
||
.ok()
|
||
.and_then(|s| serde_json::from_str(&s).ok())
|
||
}
|
||
|
||
// ✅ Good: Explicit errors
|
||
fn load_config(path: &Path) -> Result<Config, ConfigError> {
|
||
let content = std::fs::read_to_string(path)
|
||
.map_err(|e| ConfigError::ReadFailed(path.to_path_buf(), e))?;
|
||
|
||
serde_json::from_str(&content)
|
||
.map_err(|e| ConfigError::ParseFailed(path.to_path_buf(), e))
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Error messages</strong>:</p>
|
||
<pre><code>❌ "Failed to load config"
|
||
✅ "Failed to read config file '/path/to/config.ncl': Permission denied"
|
||
</code></pre>
|
||
<h3 id="8-convention-over-configuration-with-escape-hatches"><a class="header" href="#8-convention-over-configuration-with-escape-hatches">8. Convention Over Configuration (With Escape Hatches)</a></h3>
|
||
<p><strong>Principle</strong>: Sane defaults, but everything customizable.</p>
|
||
<p><strong>Why</strong>: Easy to start, flexible as you grow.</p>
|
||
<p><strong>Examples</strong>:</p>
|
||
<pre><code class="language-nickel"># Minimal config (uses conventions)
|
||
{ graph = { name = "my-project" } }
|
||
# Defaults: filesystem storage, no embeddings, standard templates
|
||
|
||
# Full config (explicit everything)
|
||
{
|
||
graph = { name = "my-project" },
|
||
storage = { primary = 'surrealdb, /* ... */ },
|
||
embeddings = { provider = 'openai, /* ... */ },
|
||
templates = { templates_dir = "custom" },
|
||
}
|
||
</code></pre>
|
||
<p><strong>Conventions</strong>:</p>
|
||
<ul>
|
||
<li><code>.kogral/</code> directory in project root</li>
|
||
<li>Filesystem storage by default</li>
|
||
<li>YAML frontmatter + markdown body</li>
|
||
<li>Standard template names (note.md.tera, decision.md.tera)</li>
|
||
</ul>
|
||
<p><strong>Escape hatches</strong>:</p>
|
||
<ul>
|
||
<li><code>--kb-dir</code> to use different location</li>
|
||
<li>Configure alternative storage backends</li>
|
||
<li>Custom frontmatter schemas</li>
|
||
<li>Override any template</li>
|
||
</ul>
|
||
<h3 id="9-documentation-as-code"><a class="header" href="#9-documentation-as-code">9. Documentation as Code</a></h3>
|
||
<p><strong>Principle</strong>: Documentation lives with code, versioned together.</p>
|
||
<p><strong>Why</strong>: Outdated docs are worse than no docs.</p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<ul>
|
||
<li>ADRs in <code>.kogral/decisions/</code> (alongside code)</li>
|
||
<li>Guidelines in <code>.kogral/guidelines/</code> (versioned with code)</li>
|
||
<li>Patterns in <code>.kogral/patterns/</code> (evolve with implementations)</li>
|
||
</ul>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<pre><code class="language-bash"># Code and docs branch together
|
||
git checkout old-version
|
||
# .kogral/ reflects that version's decisions
|
||
|
||
# Code and docs merge together
|
||
git merge feature
|
||
# Merge includes new patterns, updated guidelines
|
||
|
||
# Code and docs reviewed together
|
||
# PR shows code + decision + guideline updates
|
||
</code></pre>
|
||
<h3 id="10-optimize-for-discoverability"><a class="header" href="#10-optimize-for-discoverability">10. Optimize for Discoverability</a></h3>
|
||
<p><strong>Principle</strong>: Knowledge is useless if you can't find it.</p>
|
||
<p><strong>Why</strong>: The point is to <strong>use</strong> knowledge, not just store it.</p>
|
||
<p><strong>Features</strong>:</p>
|
||
<ul>
|
||
<li><strong>Text search</strong>: Find exact keywords</li>
|
||
<li><strong>Semantic search</strong>: Find related concepts</li>
|
||
<li><strong>Graph traversal</strong>: Follow relationships</li>
|
||
<li><strong>Tag filtering</strong>: Narrow by category</li>
|
||
<li><strong>MCP integration</strong>: AI-assisted discovery</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<p>User doesn't remember exact term, but knows the concept:</p>
|
||
<pre><code>"Find anything about handling errors gracefully"
|
||
</code></pre>
|
||
<p>KB finds (semantically):</p>
|
||
<ul>
|
||
<li>"Error Handling with thiserror" (pattern)</li>
|
||
<li>"Result Type Best Practices" (guideline)</li>
|
||
<li>"Panic Recovery" (note)</li>
|
||
<li>"Graceful Degradation" (pattern)</li>
|
||
</ul>
|
||
<p>No exact keyword match needed, concept match sufficient.</p>
|
||
<h3 id="11-build-for-humans-enable-ai"><a class="header" href="#11-build-for-humans-enable-ai">11. Build for Humans, Enable AI</a></h3>
|
||
<p><strong>Principle</strong>: Humans are the primary users, AI is the assistant.</p>
|
||
<p><strong>Why</strong>: AI should enhance human workflows, not replace them.</p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<ul>
|
||
<li><strong>Human-readable formats</strong>: Markdown, YAML</li>
|
||
<li><strong>Human-editable</strong>: Any text editor works</li>
|
||
<li><strong>Human-discoverable</strong>: <code>ls .kogral/notes/</code> shows files</li>
|
||
<li><strong>AI-enhanced</strong>: MCP for AI-assisted queries</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-bash"># Human workflow
|
||
vim .kogral/notes/my-note.md # Edit directly
|
||
git add .kogral/notes/my-note.md
|
||
git commit -m "Add note about X"
|
||
|
||
# AI-enhanced workflow
|
||
# (in Claude Code)
|
||
"Add a note about X with tags Y, Z"
|
||
# AI creates file, human reviews
|
||
</code></pre>
|
||
<h3 id="12-embrace-the-graph"><a class="header" href="#12-embrace-the-graph">12. Embrace the Graph</a></h3>
|
||
<p><strong>Principle</strong>: Knowledge is interconnected, embrace the relationships.</p>
|
||
<p><strong>Why</strong>: Context comes from connections, not isolation.</p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<ul>
|
||
<li>Typed relationships (not just "related")</li>
|
||
<li>Bidirectional traversal</li>
|
||
<li>Relationship strength (0.0-1.0)</li>
|
||
<li>Multi-hop queries</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>Find all patterns that:
|
||
- Are implemented by current project
|
||
- Depend on shared guidelines
|
||
- Were added in the last 6 months
|
||
|
||
# Graph query, not sequential file search
|
||
</code></pre>
|
||
<h2 id="anti-patterns-to-avoid"><a class="header" href="#anti-patterns-to-avoid">Anti-Patterns to Avoid</a></h2>
|
||
<h3 id="1--hardcoding-behavior"><a class="header" href="#1--hardcoding-behavior">1. ❌ Hardcoding Behavior</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// Don't
|
||
const EMBEDDING_MODEL: &str = "BAAI/bge-small-en-v1.5";
|
||
|
||
// Do
|
||
let model = config.embeddings.model.as_str();
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="2--runtime-schema-validation"><a class="header" href="#2--runtime-schema-validation">2. ❌ Runtime Schema Validation</a></h3>
|
||
<pre><code class="language-nickel">// Don't validate at runtime
|
||
let provider = config.provider; // Might be invalid
|
||
|
||
// Do validate at export time (Nickel contracts)
|
||
provider | [| 'openai, 'claude, 'fastembed |]
|
||
</code></pre>
|
||
<h3 id="3--opaque-errors"><a class="header" href="#3--opaque-errors">3. ❌ Opaque Errors</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// Don't
|
||
Err("Failed".into())
|
||
|
||
// Do
|
||
Err(KbError::NodeNotFound(id.to_string()))
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="4--coupling-components"><a class="header" href="#4--coupling-components">4. ❌ Coupling Components</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// Don't
|
||
impl KnowledgeBase {
|
||
fn search(&self) -> Vec<Node> {
|
||
let embeddings = FastEmbedProvider::new(); // Hardcoded!
|
||
// ...
|
||
}
|
||
}
|
||
|
||
// Do
|
||
impl KnowledgeBase {
|
||
fn search(&self) -> Vec<Node> {
|
||
if let Some(provider) = &self.embeddings {
|
||
// Use configured provider
|
||
}
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="5--proprietary-formats"><a class="header" href="#5--proprietary-formats">5. ❌ Proprietary Formats</a></h3>
|
||
<pre><code>// Don't
|
||
Binary blob: [0x4B, 0x42, 0x01, ...]
|
||
|
||
// Do
|
||
Markdown + YAML:
|
||
---
|
||
id: note-1
|
||
---
|
||
# Content
|
||
</code></pre>
|
||
<h2 id="influences"><a class="header" href="#influences">Influences</a></h2>
|
||
<p>KB draws inspiration from:</p>
|
||
<ul>
|
||
<li><strong>Logseq</strong>: Outliner, graph view, wikilinks</li>
|
||
<li><strong>Obsidian</strong>: Markdown-first, local storage, plugins</li>
|
||
<li><strong>Zettelkasten</strong>: Atomic notes, links, emergence</li>
|
||
<li><strong>ADR</strong>: Decision records, context preservation</li>
|
||
<li><strong>Git</strong>: Version control, branching, merging</li>
|
||
<li><strong>Nickel</strong>: Type-safe configuration, contracts</li>
|
||
<li><strong>MCP</strong>: AI integration protocol</li>
|
||
<li><strong>SurrealDB</strong>: Graph database, relationships</li>
|
||
</ul>
|
||
<h2 id="implementation-principles"><a class="header" href="#implementation-principles">Implementation Principles</a></h2>
|
||
<h3 id="rust"><a class="header" href="#rust">Rust</a></h3>
|
||
<ul>
|
||
<li>Zero unsafe code (<code>#![forbid(unsafe_code)]</code>)</li>
|
||
<li>No <code>unwrap()</code> in production code</li>
|
||
<li>Always use <code>Result<T></code> for fallibility</li>
|
||
<li>Comprehensive error types with <code>thiserror</code></li>
|
||
<li>Full test coverage (100+ tests)</li>
|
||
</ul>
|
||
<h3 id="nickel"><a class="header" href="#nickel">Nickel</a></h3>
|
||
<ul>
|
||
<li>Contracts for validation</li>
|
||
<li>Defaults in schemas</li>
|
||
<li>Documentation in contracts</li>
|
||
<li>Composition via imports</li>
|
||
</ul>
|
||
<h3 id="nushell"><a class="header" href="#nushell">NuShell</a></h3>
|
||
<ul>
|
||
<li>Structured data pipelines</li>
|
||
<li>Error handling in scripts</li>
|
||
<li>Colored output for UX</li>
|
||
<li>Dry-run modes</li>
|
||
</ul>
|
||
<h2 id="evolution-strategy"><a class="header" href="#evolution-strategy">Evolution Strategy</a></h2>
|
||
<p>KB follows these guidelines for evolution:</p>
|
||
<ol>
|
||
<li><strong>Backward Compatibility</strong>: Don't break existing configs</li>
|
||
<li><strong>Deprecation Period</strong>: Warn before removal (1 major version)</li>
|
||
<li><strong>Migration Tools</strong>: Provide automated migrations</li>
|
||
<li><strong>Semantic Versioning</strong>: MAJOR.MINOR.PATCH strictly</li>
|
||
</ol>
|
||
<h2 id="conclusion"><a class="header" href="#conclusion">Conclusion</a></h2>
|
||
<p>These principles guide every decision:</p>
|
||
<p>✅ <strong>Config-driven</strong>: Behavior in schemas, not code
|
||
✅ <strong>Type-safe</strong>: Validate before runtime
|
||
✅ <strong>Local-first</strong>: Works offline, cloud optional
|
||
✅ <strong>Git-friendly</strong>: Knowledge versions with code
|
||
✅ <strong>AI-native</strong>: Optimized for AI, readable by humans
|
||
✅ <strong>Composable</strong>: Small pieces, loosely coupled
|
||
✅ <strong>Fast feedback</strong>: Fail early, clear errors
|
||
✅ <strong>Discoverable</strong>: Easy to find what you need</p>
|
||
<p>The goal: <strong>Knowledge management that developers actually use.</strong></p>
|
||
<h2 id="next-steps-2"><a class="header" href="#next-steps-2">Next Steps</a></h2>
|
||
<ul>
|
||
<li><strong>See it in action</strong>: <a href="kogral/../guides/use-cases.html">Use Cases</a></li>
|
||
<li><strong>Understand architecture</strong>: <a href="kogral/../architecture/overview.html">System Overview</a></li>
|
||
<li><strong>Start using</strong>: <a href="kogral/../guides/quickstart.html">Quick Start</a></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="installation"><a class="header" href="#installation">Installation</a></h1>
|
||
<p>This guide covers installing and setting up the KOGRAL.</p>
|
||
<h2 id="prerequisites"><a class="header" href="#prerequisites">Prerequisites</a></h2>
|
||
<h3 id="required"><a class="header" href="#required">Required</a></h3>
|
||
<ul>
|
||
<li>
|
||
<p><strong>Rust</strong> 1.70 or later</p>
|
||
<pre><code class="language-bash">curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||
</code></pre>
|
||
</li>
|
||
<li>
|
||
<p><strong>Nickel</strong> CLI for configuration management</p>
|
||
<pre><code class="language-bash">cargo install nickel-lang-cli
|
||
</code></pre>
|
||
</li>
|
||
</ul>
|
||
<h3 id="optional"><a class="header" href="#optional">Optional</a></h3>
|
||
<ul>
|
||
<li>
|
||
<p><strong>NuShell</strong> for maintenance scripts</p>
|
||
<pre><code class="language-bash">cargo install nu
|
||
</code></pre>
|
||
</li>
|
||
<li>
|
||
<p><strong>SurrealDB</strong> for scalable storage backend</p>
|
||
<pre><code class="language-bash"># macOS
|
||
brew install surrealdb/tap/surreal
|
||
|
||
# Linux/Windows
|
||
curl -sSf https://install.surrealdb.com | sh
|
||
</code></pre>
|
||
</li>
|
||
</ul>
|
||
<h2 id="installation-methods"><a class="header" href="#installation-methods">Installation Methods</a></h2>
|
||
<h3 id="method-1-install-from-source-recommended"><a class="header" href="#method-1-install-from-source-recommended">Method 1: Install from Source (Recommended)</a></h3>
|
||
<pre><code class="language-bash"># Clone the repository
|
||
git clone <repository-url> knowledge-base
|
||
cd knowledge-base
|
||
|
||
# Build the workspace
|
||
cargo build --release
|
||
|
||
# Install CLI tool
|
||
cargo install --path crates/kb-cli
|
||
|
||
# Verify installation
|
||
kb --version
|
||
</code></pre>
|
||
<h3 id="method-2-build-specific-crates"><a class="header" href="#method-2-build-specific-crates">Method 2: Build Specific Crates</a></h3>
|
||
<pre><code class="language-bash"># Build only kb-core library
|
||
cargo build --package kb-core --release
|
||
|
||
# Build only kb-cli
|
||
cargo build --package kb-cli --release
|
||
|
||
# Build only kogral-mcp server
|
||
cargo build --package kb-mcp --release
|
||
</code></pre>
|
||
<h3 id="method-3-build-with-features"><a class="header" href="#method-3-build-with-features">Method 3: Build with Features</a></h3>
|
||
<pre><code class="language-bash"># Build with all features (filesystem + SurrealDB + fastembed)
|
||
cargo build --workspace --all-features --release
|
||
|
||
# Build with specific features
|
||
cargo build --package kb-core --features "surrealdb,fastembed" --release
|
||
</code></pre>
|
||
<h2 id="feature-flags"><a class="header" href="#feature-flags">Feature Flags</a></h2>
|
||
<p>kb-core supports optional features:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Feature</th><th>Description</th><th>Default</th></tr></thead><tbody>
|
||
<tr><td><code>filesystem</code></td><td>Filesystem storage backend</td><td>✅ Yes</td></tr>
|
||
<tr><td><code>surrealdb</code></td><td>SurrealDB storage backend</td><td>❌ No</td></tr>
|
||
<tr><td><code>fastembed</code></td><td>Local embedding generation</td><td>❌ No</td></tr>
|
||
<tr><td><code>full</code></td><td>All features enabled</td><td>❌ No</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p>Example usage:</p>
|
||
<pre><code class="language-bash"># Enable SurrealDB backend
|
||
cargo build --features surrealdb
|
||
|
||
# Enable all features
|
||
cargo build --features full
|
||
</code></pre>
|
||
<h2 id="environment-setup"><a class="header" href="#environment-setup">Environment Setup</a></h2>
|
||
<h3 id="1-initialize-a-knowledge-base"><a class="header" href="#1-initialize-a-knowledge-base">1. Initialize a Knowledge Base</a></h3>
|
||
<pre><code class="language-bash"># Navigate to your project
|
||
cd /path/to/your/project
|
||
|
||
# Initialize .kb directory
|
||
kb init
|
||
|
||
# Or initialize with custom name
|
||
kb init --name "My Project" --description "Project knowledge base"
|
||
</code></pre>
|
||
<p>This creates:</p>
|
||
<pre><code>your-project/
|
||
└── .kogral/
|
||
├── config.toml
|
||
├── notes/
|
||
├── decisions/
|
||
├── guidelines/
|
||
├── patterns/
|
||
└── journal/
|
||
</code></pre>
|
||
<h3 id="2-configure-nickel-schemas-optional"><a class="header" href="#2-configure-nickel-schemas-optional">2. Configure Nickel Schemas (Optional)</a></h3>
|
||
<p>If you want advanced configuration:</p>
|
||
<pre><code class="language-bash"># Copy default config
|
||
cp /path/to/knowledge-base/config/defaults.ncl .kogral/config.ncl
|
||
|
||
# Edit configuration
|
||
$EDITOR .kogral/config.ncl
|
||
|
||
# Export to TOML (for kb-cli compatibility)
|
||
nickel export --format json .kogral/config.ncl | kb config import
|
||
</code></pre>
|
||
<h3 id="3-set-up-shared-knowledge-base-optional"><a class="header" href="#3-set-up-shared-knowledge-base-optional">3. Set Up Shared Knowledge Base (Optional)</a></h3>
|
||
<p>For shared guidelines and patterns across projects:</p>
|
||
<pre><code class="language-bash"># Create shared KB location
|
||
mkdir -p ~/Tools/.kogral-shared
|
||
cd ~/Tools/.kogral-shared
|
||
|
||
# Initialize shared KB
|
||
kb init --name "Shared Knowledge" --description "Cross-project guidelines"
|
||
|
||
# Configure inheritance in project
|
||
kb config set inheritance.base ~/Tools/.kogral-shared
|
||
</code></pre>
|
||
<h2 id="verify-installation-1"><a class="header" href="#verify-installation-1">Verify Installation</a></h2>
|
||
<h3 id="test-cli"><a class="header" href="#test-cli">Test CLI</a></h3>
|
||
<pre><code class="language-bash"># Check version
|
||
kb --version
|
||
|
||
# Show help
|
||
kb --help
|
||
|
||
# Test initialization (dry-run)
|
||
kb init --dry-run
|
||
</code></pre>
|
||
<h3 id="test-mcp-server"><a class="header" href="#test-mcp-server">Test MCP Server</a></h3>
|
||
<pre><code class="language-bash"># Start MCP server in test mode
|
||
kb serve --transport stdio
|
||
|
||
# In another terminal, test with echo
|
||
echo '{"jsonrpc":"2.0","id":1,"method":"kogral/search","params":{"query":"test"}}' | kb serve
|
||
</code></pre>
|
||
<h3 id="run-tests"><a class="header" href="#run-tests">Run Tests</a></h3>
|
||
<pre><code class="language-bash"># Run all tests
|
||
cargo test --workspace
|
||
|
||
# Run integration tests
|
||
cargo test --package kb-core --test '*'
|
||
|
||
# Run with all features
|
||
cargo test --workspace --all-features
|
||
</code></pre>
|
||
<h2 id="surrealdb-setup-optional"><a class="header" href="#surrealdb-setup-optional">SurrealDB Setup (Optional)</a></h2>
|
||
<p>If using SurrealDB backend:</p>
|
||
<h3 id="start-surrealdb-server"><a class="header" href="#start-surrealdb-server">Start SurrealDB Server</a></h3>
|
||
<pre><code class="language-bash"># Start server
|
||
surreal start --log trace --user root --pass root file://data.db
|
||
|
||
# Or use memory backend for testing
|
||
surreal start --log trace --user root --pass root memory
|
||
</code></pre>
|
||
<h3 id="configure-kb-core"><a class="header" href="#configure-kb-core">Configure kb-core</a></h3>
|
||
<p>Edit <code>.kogral/config.ncl</code>:</p>
|
||
<pre><code class="language-nickel">{
|
||
storage = {
|
||
primary = 'filesystem,
|
||
secondary = {
|
||
enabled = true,
|
||
type = 'surrealdb,
|
||
url = "ws://localhost:8000",
|
||
namespace = "kb",
|
||
database = "default",
|
||
username = "root",
|
||
password = "root",
|
||
},
|
||
},
|
||
}
|
||
</code></pre>
|
||
<h3 id="initialize-schema"><a class="header" href="#initialize-schema">Initialize Schema</a></h3>
|
||
<pre><code class="language-bash"># Run migration script
|
||
nu scripts/kb-migrate.nu --target latest
|
||
|
||
# Or manually import schema
|
||
surreal import --conn ws://localhost:8000 --user root --pass root --ns kb --db default schema.surql
|
||
</code></pre>
|
||
<h2 id="embedding-provider-setup-optional"><a class="header" href="#embedding-provider-setup-optional">Embedding Provider Setup (Optional)</a></h2>
|
||
<h3 id="local-fastembed"><a class="header" href="#local-fastembed">Local fastembed</a></h3>
|
||
<pre><code class="language-bash"># Build with fastembed feature
|
||
cargo build --package kb-core --features fastembed
|
||
|
||
# Models will be downloaded on first use
|
||
</code></pre>
|
||
<h3 id="api-providers-openai-claude-ollama"><a class="header" href="#api-providers-openai-claude-ollama">API Providers (OpenAI, Claude, Ollama)</a></h3>
|
||
<p>Set environment variables:</p>
|
||
<pre><code class="language-bash"># OpenAI
|
||
export OPENAI_API_KEY="sk-..."
|
||
|
||
# Claude (Anthropic)
|
||
export ANTHROPIC_API_KEY="sk-ant-..."
|
||
|
||
# Ollama (local)
|
||
export OLLAMA_API_BASE="http://localhost:11434"
|
||
</code></pre>
|
||
<p>Configure provider in <code>.kogral/config.ncl</code>:</p>
|
||
<pre><code class="language-nickel">{
|
||
embeddings = {
|
||
enabled = true,
|
||
provider = 'openai, # or 'claude, 'ollama, 'fastembed
|
||
model = "text-embedding-3-small",
|
||
api_key_env = "OPENAI_API_KEY",
|
||
},
|
||
}
|
||
</code></pre>
|
||
<h2 id="claude-code-integration"><a class="header" href="#claude-code-integration">Claude Code Integration</a></h2>
|
||
<p>To use kb-mcp with Claude Code:</p>
|
||
<h3 id="1-build-mcp-server"><a class="header" href="#1-build-mcp-server">1. Build MCP Server</a></h3>
|
||
<pre><code class="language-bash">cargo build --package kb-mcp --release
|
||
</code></pre>
|
||
<h3 id="2-configure-claude-code"><a class="header" href="#2-configure-claude-code">2. Configure Claude Code</a></h3>
|
||
<p>Add to <code>~/.config/claude/config.json</code>:</p>
|
||
<pre><code class="language-json">{
|
||
"mcpServers": {
|
||
"kogral-mcp": {
|
||
"command": "/path/to/knowledge-base/target/release/kb-mcp",
|
||
"args": ["serve"],
|
||
"env": {}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-test-connection"><a class="header" href="#3-test-connection">3. Test Connection</a></h3>
|
||
<p>Start Claude Code and verify:</p>
|
||
<pre><code>> kb/search "test"
|
||
</code></pre>
|
||
<h2 id="troubleshooting"><a class="header" href="#troubleshooting">Troubleshooting</a></h2>
|
||
<h3 id="nickel-not-found"><a class="header" href="#nickel-not-found">Nickel Not Found</a></h3>
|
||
<pre><code class="language-bash"># Verify nickel is installed
|
||
nickel --version
|
||
|
||
# If not, install
|
||
cargo install nickel-lang-cli
|
||
|
||
# Add cargo bin to PATH
|
||
export PATH="$HOME/.cargo/bin:$PATH"
|
||
</code></pre>
|
||
<h3 id="compilation-errors"><a class="header" href="#compilation-errors">Compilation Errors</a></h3>
|
||
<pre><code class="language-bash"># Update Rust
|
||
rustup update stable
|
||
|
||
# Clean and rebuild
|
||
cargo clean
|
||
cargo build --workspace
|
||
</code></pre>
|
||
<h3 id="surrealdb-connection-failed"><a class="header" href="#surrealdb-connection-failed">SurrealDB Connection Failed</a></h3>
|
||
<pre><code class="language-bash"># Check SurrealDB is running
|
||
curl http://localhost:8000/health
|
||
|
||
# Start SurrealDB with correct settings
|
||
surreal start --bind 0.0.0.0:8000 --user root --pass root memory
|
||
</code></pre>
|
||
<h3 id="mcp-server-not-responding"><a class="header" href="#mcp-server-not-responding">MCP Server Not Responding</a></h3>
|
||
<pre><code class="language-bash"># Test stdio communication
|
||
echo '{"jsonrpc":"2.0","id":1,"method":"ping"}' | kb serve
|
||
|
||
# Check logs
|
||
kb serve --log-level debug
|
||
</code></pre>
|
||
<h2 id="next-steps-3"><a class="header" href="#next-steps-3">Next Steps</a></h2>
|
||
<ul>
|
||
<li><a href="guides/user-guide/quickstart.html">Quick Start Guide</a> - Create your first knowledge base</li>
|
||
<li><a href="guides/user-guide/configuration.html">Configuration Reference</a> - Customize behavior</li>
|
||
<li><a href="guides/api/mcp-tools.html">MCP Tools</a> - Integrate with Claude Code</li>
|
||
</ul>
|
||
<h2 id="uninstallation"><a class="header" href="#uninstallation">Uninstallation</a></h2>
|
||
<pre><code class="language-bash"># Remove installed binary
|
||
cargo uninstall kb-cli
|
||
|
||
# Remove project knowledge base
|
||
rm -rf /path/to/project/.kogral
|
||
|
||
# Remove shared knowledge base
|
||
rm -rf ~/Tools/.kogral-shared
|
||
</code></pre>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="quick-start-guide"><a class="header" href="#quick-start-guide">Quick Start Guide</a></h1>
|
||
<p>Get up and running with the KOGRAL in 5 minutes.</p>
|
||
<h2 id="prerequisites-1"><a class="header" href="#prerequisites-1">Prerequisites</a></h2>
|
||
<ul>
|
||
<li>Rust 1.70+ installed</li>
|
||
<li>Nickel CLI installed (<code>cargo install nickel-lang-cli</code>)</li>
|
||
<li>kb-cli installed (see <a href="guides/../installation.html">Installation</a>)</li>
|
||
</ul>
|
||
<h2 id="step-1-initialize-your-knowledge-base"><a class="header" href="#step-1-initialize-your-knowledge-base">Step 1: Initialize Your Knowledge Base</a></h2>
|
||
<p>Navigate to your project directory and initialize:</p>
|
||
<pre><code class="language-bash">cd /path/to/your/project
|
||
kb init
|
||
</code></pre>
|
||
<p>This creates a <code>.kogral/</code> directory with the following structure:</p>
|
||
<pre><code>.kogral/
|
||
├── config.toml # Configuration
|
||
├── notes/ # General notes
|
||
├── decisions/ # Architectural decisions
|
||
├── guidelines/ # Project guidelines
|
||
├── patterns/ # Reusable patterns
|
||
└── journal/ # Daily journal entries
|
||
</code></pre>
|
||
<h2 id="step-2-create-your-first-note"><a class="header" href="#step-2-create-your-first-note">Step 2: Create Your First Note</a></h2>
|
||
<p>Add a note to your knowledge base:</p>
|
||
<pre><code class="language-bash">kb add note "Getting Started with Rust" \
|
||
--tags rust,programming,learning \
|
||
--content "Key concepts for learning Rust effectively."
|
||
</code></pre>
|
||
<p>Or create interactively:</p>
|
||
<pre><code class="language-bash">kb add note
|
||
# Follow the prompts to enter title, tags, and content
|
||
</code></pre>
|
||
<h2 id="step-3-create-a-decision-record"><a class="header" href="#step-3-create-a-decision-record">Step 3: Create a Decision Record</a></h2>
|
||
<p>Document an architectural decision:</p>
|
||
<pre><code class="language-bash">kb add decision "Use SurrealDB for Storage" \
|
||
--context "Need scalable storage for knowledge graph" \
|
||
--decision "Adopt SurrealDB for its graph capabilities" \
|
||
--consequence "Better query performance" \
|
||
--consequence "Additional infrastructure dependency"
|
||
</code></pre>
|
||
<h2 id="step-4-link-nodes-together"><a class="header" href="#step-4-link-nodes-together">Step 4: Link Nodes Together</a></h2>
|
||
<p>Create relationships between nodes:</p>
|
||
<pre><code class="language-bash"># Find node IDs
|
||
kb list
|
||
|
||
# Create a relationship
|
||
kb link <note-id> <decision-id> relates_to
|
||
</code></pre>
|
||
<p>Available relationship types:</p>
|
||
<ul>
|
||
<li><code>relates_to</code> - General conceptual link</li>
|
||
<li><code>depends_on</code> - Dependency relationship</li>
|
||
<li><code>implements</code> - Implementation of a concept</li>
|
||
<li><code>extends</code> - Inheritance/extension</li>
|
||
<li><code>supersedes</code> - Replaces older version</li>
|
||
<li><code>explains</code> - Documentation/clarification</li>
|
||
</ul>
|
||
<h2 id="step-5-search-your-knowledge-base"><a class="header" href="#step-5-search-your-knowledge-base">Step 5: Search Your Knowledge Base</a></h2>
|
||
<p>Search by text:</p>
|
||
<pre><code class="language-bash"># Simple search
|
||
kb search "rust"
|
||
|
||
# Filter by type
|
||
kb search "architecture" --type decision
|
||
|
||
# Limit results
|
||
kb search "error handling" --limit 5
|
||
</code></pre>
|
||
<p>Search semantically (requires embeddings):</p>
|
||
<pre><code class="language-bash"># Semantic search finds conceptually related content
|
||
kb search "memory safety" --semantic --threshold 0.7
|
||
</code></pre>
|
||
<h2 id="step-6-view-node-details"><a class="header" href="#step-6-view-node-details">Step 6: View Node Details</a></h2>
|
||
<pre><code class="language-bash"># Show node by ID
|
||
kb show <node-id>
|
||
|
||
# Show node with relationships
|
||
kb show <node-id> --with-relationships
|
||
</code></pre>
|
||
<h2 id="step-7-edit-documents-directly"><a class="header" href="#step-7-edit-documents-directly">Step 7: Edit Documents Directly</a></h2>
|
||
<p>All knowledge base documents are markdown files you can edit:</p>
|
||
<pre><code class="language-bash"># Open in your editor
|
||
$EDITOR .kogral/notes/getting-started-with-rust.md
|
||
|
||
# Or use your favorite markdown editor
|
||
code .kogral/notes/
|
||
</code></pre>
|
||
<p>Document format:</p>
|
||
<pre><code class="language-markdown">---
|
||
id: unique-id
|
||
type: note
|
||
title: Getting Started with Rust
|
||
created: 2026-01-17T10:30:00Z
|
||
modified: 2026-01-17T10:30:00Z
|
||
tags: [rust, programming, learning]
|
||
status: active
|
||
---
|
||
|
||
# Getting Started with Rust
|
||
|
||
Content goes here with [[wikilinks]] to other nodes.
|
||
|
||
## Key Concepts
|
||
|
||
- Ownership
|
||
- Borrowing
|
||
- Lifetimes
|
||
</code></pre>
|
||
<h2 id="step-8-create-a-daily-journal-entry"><a class="header" href="#step-8-create-a-daily-journal-entry">Step 8: Create a Daily Journal Entry</a></h2>
|
||
<p>Start journaling your development progress:</p>
|
||
<pre><code class="language-bash">kb add journal "Today's Progress" \
|
||
--content "Learned about trait objects and dynamic dispatch in Rust."
|
||
</code></pre>
|
||
<p>Or open today's journal directly:</p>
|
||
<pre><code class="language-bash">$EDITOR .kogral/journal/$(date +%Y-%m-%d).md
|
||
</code></pre>
|
||
<h2 id="step-9-export-to-logseq-optional"><a class="header" href="#step-9-export-to-logseq-optional">Step 9: Export to Logseq (Optional)</a></h2>
|
||
<p>If you use Logseq, export your knowledge base:</p>
|
||
<pre><code class="language-bash">nu scripts/kb-export-logseq.nu /path/to/logseq-graph
|
||
</code></pre>
|
||
<p>This creates a Logseq-compatible graph you can open in Logseq for visual editing.</p>
|
||
<h2 id="step-10-start-mcp-server-for-claude-code"><a class="header" href="#step-10-start-mcp-server-for-claude-code">Step 10: Start MCP Server for Claude Code</a></h2>
|
||
<p>Integrate with Claude Code for AI-assisted knowledge management:</p>
|
||
<pre><code class="language-bash"># Start MCP server
|
||
kb serve
|
||
|
||
# Configure in ~/.config/claude/config.json
|
||
# Then use in Claude Code:
|
||
# > kb/search "rust ownership"
|
||
</code></pre>
|
||
<h2 id="common-workflows"><a class="header" href="#common-workflows">Common Workflows</a></h2>
|
||
<h3 id="capture-quick-notes"><a class="header" href="#capture-quick-notes">Capture Quick Notes</a></h3>
|
||
<pre><code class="language-bash"># Quick note
|
||
kb add note "Remember to check error handling in parser module" --tags todo,parser
|
||
</code></pre>
|
||
<h3 id="document-architectural-decisions"><a class="header" href="#document-architectural-decisions">Document Architectural Decisions</a></h3>
|
||
<pre><code class="language-bash"># Create ADR
|
||
kb add decision "Adopt Async Rust for I/O Operations" \
|
||
--status accepted \
|
||
--tags architecture,async
|
||
</code></pre>
|
||
<h3 id="build-a-pattern-library"><a class="header" href="#build-a-pattern-library">Build a Pattern Library</a></h3>
|
||
<pre><code class="language-bash"># Add a pattern
|
||
kb add pattern "Error Handling with thiserror" \
|
||
--tags rust,error-handling \
|
||
--content "Standard pattern for error types in this project."
|
||
</code></pre>
|
||
<h3 id="track-daily-progress"><a class="header" href="#track-daily-progress">Track Daily Progress</a></h3>
|
||
<pre><code class="language-bash"># Add journal entry
|
||
kb add journal --content "Implemented search functionality. Need to add semantic search next."
|
||
</code></pre>
|
||
<h2 id="next-steps-4"><a class="header" href="#next-steps-4">Next Steps</a></h2>
|
||
<h3 id="customize-configuration"><a class="header" href="#customize-configuration">Customize Configuration</a></h3>
|
||
<p>Edit <code>.kogral/config.ncl</code> for advanced configuration:</p>
|
||
<pre><code class="language-nickel">{
|
||
graph = {
|
||
name = "My Project",
|
||
version = "1.0.0",
|
||
},
|
||
|
||
embeddings = {
|
||
enabled = true,
|
||
provider = 'fastembed,
|
||
},
|
||
|
||
templates = {
|
||
templates_dir = "templates",
|
||
},
|
||
}
|
||
</code></pre>
|
||
<p>See <a href="guides/configuration.html">Configuration Reference</a> for all options.</p>
|
||
<h3 id="set-up-shared-guidelines"><a class="header" href="#set-up-shared-guidelines">Set Up Shared Guidelines</a></h3>
|
||
<p>Create a shared knowledge base for organization-wide standards:</p>
|
||
<pre><code class="language-bash"># Create shared KB
|
||
mkdir -p ~/Tools/.kogral-shared
|
||
cd ~/Tools/.kogral-shared
|
||
kb init --name "Shared Guidelines"
|
||
|
||
# Add guidelines
|
||
kb add guideline "Rust Error Handling" \
|
||
--language rust \
|
||
--category error-handling
|
||
|
||
# Configure inheritance in projects
|
||
kb config set inheritance.base ~/Tools/.kogral-shared
|
||
</code></pre>
|
||
<h3 id="automate-with-nushell-scripts"><a class="header" href="#automate-with-nushell-scripts">Automate with NuShell Scripts</a></h3>
|
||
<pre><code class="language-bash"># Backup regularly
|
||
nu scripts/kb-backup.nu --compress
|
||
|
||
# Sync with SurrealDB
|
||
nu scripts/kb-sync.nu --direction bidirectional
|
||
|
||
# Generate statistics
|
||
nu scripts/kb-stats.nu --show-tags
|
||
</code></pre>
|
||
<h3 id="integrate-with-git"><a class="header" href="#integrate-with-git">Integrate with Git</a></h3>
|
||
<pre><code class="language-bash"># Add to version control
|
||
git add .kogral/
|
||
git commit -m "docs: Add knowledge base"
|
||
|
||
# Add to .gitignore (optional: exclude certain types)
|
||
echo ".kogral/journal/" >> .gitignore
|
||
</code></pre>
|
||
<h2 id="tips-and-tricks"><a class="header" href="#tips-and-tricks">Tips and Tricks</a></h2>
|
||
<h3 id="use-wikilinks"><a class="header" href="#use-wikilinks">Use Wikilinks</a></h3>
|
||
<p>Link to other nodes naturally in markdown:</p>
|
||
<pre><code class="language-markdown">See [[getting-started-with-rust]] for basics.
|
||
Related decision: [[use-surrealdb-for-storage]].
|
||
</code></pre>
|
||
<h3 id="reference-code"><a class="header" href="#reference-code">Reference Code</a></h3>
|
||
<p>Link to specific code locations:</p>
|
||
<pre><code class="language-markdown">Error handling implementation: @src/parser.rs:42
|
||
</code></pre>
|
||
<h3 id="tag-consistently"><a class="header" href="#tag-consistently">Tag Consistently</a></h3>
|
||
<p>Use consistent tagging for better searchability:</p>
|
||
<pre><code class="language-bash"># Good tagging
|
||
--tags rust,error-handling,pattern
|
||
|
||
# Avoid
|
||
--tags Rust,ErrorHandling,patterns
|
||
</code></pre>
|
||
<h3 id="leverage-templates"><a class="header" href="#leverage-templates">Leverage Templates</a></h3>
|
||
<p>Customize templates for your workflow:</p>
|
||
<pre><code class="language-bash"># Copy template
|
||
cp templates/note.md.tera templates/meeting-notes.md.tera
|
||
|
||
# Edit for meeting notes format
|
||
$EDITOR templates/meeting-notes.md.tera
|
||
</code></pre>
|
||
<h2 id="troubleshooting-1"><a class="header" href="#troubleshooting-1">Troubleshooting</a></h2>
|
||
<h3 id="kb-directory-not-found"><a class="header" href="#kb-directory-not-found">"KB directory not found"</a></h3>
|
||
<pre><code class="language-bash"># Make sure you initialized
|
||
kb init
|
||
|
||
# Or specify KB directory
|
||
kb --kb-dir /path/to/.kb search "query"
|
||
</code></pre>
|
||
<h3 id="node-not-found"><a class="header" href="#node-not-found">"Node not found"</a></h3>
|
||
<pre><code class="language-bash"># List all nodes to find ID
|
||
kb list
|
||
|
||
# Search for node
|
||
kb search "partial title"
|
||
</code></pre>
|
||
<h3 id="failed-to-parse-frontmatter"><a class="header" href="#failed-to-parse-frontmatter">"Failed to parse frontmatter"</a></h3>
|
||
<p>Check your markdown file has valid YAML frontmatter:</p>
|
||
<pre><code class="language-yaml">---
|
||
id: my-note
|
||
type: note
|
||
title: My Note
|
||
---
|
||
</code></pre>
|
||
<h2 id="further-reading"><a class="header" href="#further-reading">Further Reading</a></h2>
|
||
<ul>
|
||
<li><a href="guides/configuration.html">Configuration Reference</a> - Full configuration options</li>
|
||
<li><a href="guides/cli-commands.html">CLI Commands</a> - All available commands</li>
|
||
<li><a href="guides/document-format.html">Document Format</a> - Markdown and frontmatter details</li>
|
||
<li><a href="guides/../api/mcp-tools.html">MCP Tools</a> - Claude Code integration</li>
|
||
</ul>
|
||
<h2 id="getting-help"><a class="header" href="#getting-help">Getting Help</a></h2>
|
||
<ul>
|
||
<li>Check <code>kb --help</code> for command usage</li>
|
||
<li>Read inline help: <code>kb add --help</code></li>
|
||
<li>Report issues on GitHub</li>
|
||
<li>Join community discussions</li>
|
||
</ul>
|
||
<hr />
|
||
<p><strong>Congratulations!</strong> You've created your first knowledge base. Start capturing knowledge and building connections.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="daily-workflows"><a class="header" href="#daily-workflows">Daily Workflows</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="use-cases-2"><a class="header" href="#use-cases-2">Use Cases</a></h1>
|
||
<p>Real-world scenarios showing how KOGRAL solves common development challenges.</p>
|
||
<h2 id="use-case-1-onboarding-new-team-members"><a class="header" href="#use-case-1-onboarding-new-team-members">Use Case 1: Onboarding New Team Members</a></h2>
|
||
<h3 id="problem"><a class="header" href="#problem">Problem</a></h3>
|
||
<p>New developer joins the team. Needs to understand:</p>
|
||
<ul>
|
||
<li>Why we chose SurrealDB over PostgreSQL</li>
|
||
<li>How error handling works in this project</li>
|
||
<li>What coding patterns we follow</li>
|
||
<li>Recent architectural decisions</li>
|
||
</ul>
|
||
<h3 id="solution-with-kogral"><a class="header" href="#solution-with-kogral">Solution with KOGRAL</a></h3>
|
||
<p><strong>Team lead</strong>:</p>
|
||
<pre><code class="language-bash"># Export project knowledge to Logseq for visual exploration
|
||
kb export logseq ~/onboarding-docs
|
||
</code></pre>
|
||
<p><strong>New developer</strong> (with Claude Code):</p>
|
||
<pre><code>Summarize our architectural decisions from the last 6 months
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (via MCP):</p>
|
||
<pre><code>Found 8 decisions:
|
||
|
||
1. **Use SurrealDB for Storage** (accepted, 3 months ago)
|
||
- Context: Need graph database for knowledge relationships
|
||
- Consequences: Better queries, additional infrastructure
|
||
|
||
2. **Adopt Rust for Core Logic** (accepted, 5 months ago)
|
||
- Context: Type safety and performance critical
|
||
- Consequences: Steeper learning curve, excellent reliability
|
||
|
||
... [6 more decisions]
|
||
</code></pre>
|
||
<p><strong>New developer</strong>:</p>
|
||
<pre><code>What are our error handling guidelines?
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (via <code>kb/get_guidelines</code>):</p>
|
||
<pre><code>## Rust Error Handling Guidelines
|
||
|
||
### Project Guidelines (priority: 150)
|
||
- Use thiserror for custom error types
|
||
- Never unwrap() in production
|
||
- Propagate with ? operator
|
||
|
||
### Shared Guidelines (priority: 50)
|
||
- Always use Result<T> for fallible operations
|
||
- Add context with .context()
|
||
|
||
[Links to full guideline documents]
|
||
</code></pre>
|
||
<p><strong>Result</strong>: New developer productive in days, not weeks.</p>
|
||
<hr />
|
||
<h2 id="use-case-2-architectural-decision-records-adr"><a class="header" href="#use-case-2-architectural-decision-records-adr">Use Case 2: Architectural Decision Records (ADR)</a></h2>
|
||
<h3 id="problem-1"><a class="header" href="#problem-1">Problem</a></h3>
|
||
<p>Team discussing whether to add Redis caching. Multiple opinions, need to document the decision for future reference.</p>
|
||
<h3 id="solution-with-kogral-1"><a class="header" href="#solution-with-kogral-1">Solution with KOGRAL</a></h3>
|
||
<p><strong>During meeting</strong>:</p>
|
||
<pre><code>Document decision: Add Redis caching for API responses
|
||
|
||
Context:
|
||
- Current database queries average 200ms
|
||
- 80% of queries are repeated within 5 minutes
|
||
- Need sub-50ms response times for user experience
|
||
|
||
Decision:
|
||
- Add Redis with 5-minute TTL for GET endpoints
|
||
- Cache invalidation on POST/PUT/DELETE
|
||
|
||
Consequences:
|
||
- ✅ Expected 80% reduction in database load
|
||
- ✅ Sub-50ms response times achieved
|
||
- ❌ Additional infrastructure (Redis server)
|
||
- ❌ Cache consistency challenges
|
||
- ❌ Debugging complexity increases
|
||
|
||
Status: proposed (pending infrastructure setup)
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (creates ADR):</p>
|
||
<pre><code>Created: decision-add-redis-caching
|
||
Type: decision
|
||
Status: proposed
|
||
|
||
Added relationships:
|
||
- Relates to: note-performance-requirements
|
||
- Depends on: decision-use-docker-compose (infrastructure)
|
||
|
||
Decision documented and linked to related knowledge.
|
||
</code></pre>
|
||
<p><strong>Two months later</strong>, someone asks "Why Redis?":</p>
|
||
<pre><code>kb show decision-add-redis-caching
|
||
</code></pre>
|
||
<p>Full context available instantly.</p>
|
||
<hr />
|
||
<h2 id="use-case-3-pattern-library"><a class="header" href="#use-case-3-pattern-library">Use Case 3: Pattern Library</a></h2>
|
||
<h3 id="problem-2"><a class="header" href="#problem-2">Problem</a></h3>
|
||
<p>Team solves the same problems repeatedly:</p>
|
||
<ul>
|
||
<li>How to structure error types</li>
|
||
<li>Database connection patterns</li>
|
||
<li>Testing strategies</li>
|
||
</ul>
|
||
<p>Each developer rediscovers solutions.</p>
|
||
<h3 id="solution-with-kogral-2"><a class="header" href="#solution-with-kogral-2">Solution with KOGRAL</a></h3>
|
||
<p><strong>First time</strong> solving error handling:</p>
|
||
<pre><code class="language-bash">kb add pattern "Error Handling with thiserror" \
|
||
--tags rust,error-handling,pattern \
|
||
--content "$(cat docs/error-pattern.md)"
|
||
</code></pre>
|
||
<p><strong>Next time</strong> someone needs error handling:</p>
|
||
<pre><code>I'm implementing a new module with errors. What's our standard pattern?
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (via <code>kogral/search</code>):</p>
|
||
<pre><code>Found pattern: Error Handling with thiserror
|
||
|
||
## Pattern: Error Handling with thiserror
|
||
|
||
### Problem
|
||
Need structured, type-safe error handling with good error messages.
|
||
|
||
### Solution
|
||
```rust
|
||
use thiserror::Error;
|
||
|
||
#[derive(Error, Debug)]
|
||
pub enum ModuleError {
|
||
#[error("Database connection failed: {0}")]
|
||
Database(#[from] sqlx::Error),
|
||
|
||
#[error("Validation failed: {0}")]
|
||
Validation(String),
|
||
}
|
||
</code></pre>
|
||
<h3 id="usage"><a class="header" href="#usage">Usage</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>fn do_work() -> Result<(), ModuleError> {
|
||
let conn = connect_db()?; // Auto-converts via #[from]
|
||
validate_input()?;
|
||
Ok(())
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p>[Full pattern with examples]</p>
|
||
<p>Would you like me to create a new error module following this pattern?</p>
|
||
<pre><code>
|
||
**Result**: Consistent error handling across codebase, zero time wasted rediscovering patterns.
|
||
|
||
---
|
||
|
||
## Use Case 4: Daily Development Journal
|
||
|
||
### Problem
|
||
|
||
Developer wants to track:
|
||
- What they worked on each day
|
||
- Blockers encountered
|
||
- Solutions discovered
|
||
- Ideas for future work
|
||
|
||
### Solution with KOGRAL
|
||
|
||
**End of each day**:
|
||
|
||
```bash
|
||
kb add journal "2026-01-17" --content "
|
||
## Progress
|
||
- Implemented user authentication
|
||
- Fixed race condition in cache layer
|
||
- Started work on API rate limiting
|
||
|
||
## Blockers
|
||
- SurrealDB migration tool not working with nested objects
|
||
- Need to discuss API versioning strategy with team
|
||
|
||
## Learnings
|
||
- tokio::select! is perfect for timeout patterns
|
||
- serde_json::Value useful for dynamic JSON handling
|
||
|
||
## Tomorrow
|
||
- Finish rate limiting implementation
|
||
- Review PR #234
|
||
- Team meeting: discuss API versioning
|
||
"
|
||
</code></pre>
|
||
<p><strong>With Claude Code</strong> (even easier):</p>
|
||
<pre><code>End of day journal:
|
||
|
||
Implemented authentication, fixed cache race condition, started rate limiting.
|
||
Blocked on SurrealDB migrations and need API versioning discussion.
|
||
Learned about tokio::select! for timeouts.
|
||
Tomorrow: finish rate limiting, review PR #234, versioning meeting.
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (creates journal entry):</p>
|
||
<pre><code>Created journal entry: 2026-01-17
|
||
|
||
Tags auto-detected: authentication, cache, rate-limiting, surrealdb
|
||
|
||
Linked to:
|
||
- pattern-timeout-handling (implements)
|
||
- decision-use-surrealdb (relates_to)
|
||
|
||
Journal entry saved.
|
||
</code></pre>
|
||
<p><strong>Two weeks later</strong> (forgot how you solved that race condition):</p>
|
||
<pre><code>How did I fix the cache race condition?
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (semantic search):</p>
|
||
<pre><code>From your journal entry on 2026-01-17:
|
||
|
||
"Fixed race condition in cache layer by using DashMap instead of
|
||
RwLock<HashMap>. DashMap provides lock-free concurrent access."
|
||
|
||
[Link to full journal entry]
|
||
</code></pre>
|
||
<p><strong>Result</strong>: Development history preserved, solutions easy to recall.</p>
|
||
<hr />
|
||
<h2 id="use-case-5-code-review-preparation"><a class="header" href="#use-case-5-code-review-preparation">Use Case 5: Code Review Preparation</a></h2>
|
||
<h3 id="problem-3"><a class="header" href="#problem-3">Problem</a></h3>
|
||
<p>Preparing for code review. Need to:</p>
|
||
<ul>
|
||
<li>Document why certain decisions were made</li>
|
||
<li>Link implementation to requirements</li>
|
||
<li>Explain trade-offs to reviewers</li>
|
||
</ul>
|
||
<h3 id="solution-with-kogral-3"><a class="header" href="#solution-with-kogral-3">Solution with KOGRAL</a></h3>
|
||
<p><strong>Before creating PR</strong>:</p>
|
||
<pre><code>I just implemented the caching layer.
|
||
Add a note documenting the implementation and link it to our caching decision.
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (via <code>kb/add_note</code> + <code>kb/link</code>):</p>
|
||
<pre><code>Created note: note-caching-layer-implementation
|
||
|
||
Content:
|
||
# Caching Layer Implementation
|
||
|
||
## Approach
|
||
- Redis with 5-minute TTL
|
||
- Cache-aside pattern
|
||
- Invalidation on mutations
|
||
|
||
## Trade-offs
|
||
- Chose simplicity over advanced features (no LRU eviction)
|
||
- 5-minute TTL balances freshness vs cache hit rate
|
||
- Cache-aside pattern chosen over write-through (simpler)
|
||
|
||
## Testing
|
||
- Unit tests for cache logic
|
||
- Integration tests with real Redis
|
||
- Load test: 1000 req/s sustained
|
||
|
||
Links:
|
||
- Implements: decision-add-redis-caching
|
||
- Follows: pattern-cache-aside
|
||
- Tests: guideline-integration-testing
|
||
|
||
Note added and linked to decision-add-redis-caching.
|
||
</code></pre>
|
||
<p><strong>In PR description</strong>:</p>
|
||
<pre><code class="language-markdown">## Implementation
|
||
|
||
Implements caching layer per decision-add-redis-caching.
|
||
|
||
See note-caching-layer-implementation for detailed rationale.
|
||
|
||
## Trade-offs
|
||
|
||
[Copy from note]
|
||
</code></pre>
|
||
<p><strong>Reviewers</strong>:</p>
|
||
<pre><code>kb show decision-add-redis-caching
|
||
kb show note-caching-layer-implementation
|
||
</code></pre>
|
||
<p>Full context available, review focuses on code quality not "why this approach?"</p>
|
||
<hr />
|
||
<h2 id="use-case-6-cross-project-guidelines"><a class="header" href="#use-case-6-cross-project-guidelines">Use Case 6: Cross-Project Guidelines</a></h2>
|
||
<h3 id="problem-4"><a class="header" href="#problem-4">Problem</a></h3>
|
||
<p>Organization has 10 projects. Each should follow:</p>
|
||
<ul>
|
||
<li>Same Rust error handling patterns</li>
|
||
<li>Same security guidelines</li>
|
||
<li>Same testing standards</li>
|
||
</ul>
|
||
<p>Currently: duplicated wikis, inconsistent practices.</p>
|
||
<h3 id="solution-with-kogral-4"><a class="header" href="#solution-with-kogral-4">Solution with KOGRAL</a></h3>
|
||
<p><strong>Setup shared KOGRAL</strong>:</p>
|
||
<pre><code class="language-bash"># Create organization-wide shared KB
|
||
mkdir -p ~/org/.kogral-shared
|
||
cd ~/org/.kogral-shared
|
||
kb init --name "Organization Guidelines"
|
||
|
||
# Add shared guidelines
|
||
kb add guideline "Rust Error Handling" \
|
||
--language rust \
|
||
--category error-handling \
|
||
--content "$(cat org-rust-errors.md)"
|
||
|
||
kb add guideline "Security Best Practices" \
|
||
--category security \
|
||
--content "$(cat org-security.md)"
|
||
</code></pre>
|
||
<p><strong>In each project</strong>:</p>
|
||
<pre><code class="language-nickel"># project/.kogral/config.ncl
|
||
{
|
||
inheritance = {
|
||
base = "~/org/.kogral-shared",
|
||
priority = 100, # Project overrides shared
|
||
},
|
||
}
|
||
</code></pre>
|
||
<p><strong>Developer in Project A</strong>:</p>
|
||
<pre><code>What are our error handling guidelines?
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (via <code>kb/get_guidelines</code> with inheritance):</p>
|
||
<pre><code>## Rust Error Handling Guidelines
|
||
|
||
### Shared Guidelines (priority: 50)
|
||
From ~/org/.kogral-shared:
|
||
- Use thiserror for custom errors
|
||
- Never unwrap() in production
|
||
- Always propagate with ?
|
||
|
||
### Project Guidelines (priority: 100)
|
||
From project .kogral/:
|
||
- Additionally: log all errors with tracing
|
||
- Use anyhow for application-level errors
|
||
|
||
[Combined effective guidelines]
|
||
</code></pre>
|
||
<p><strong>Result</strong>:</p>
|
||
<ul>
|
||
<li>Consistent practices across organization</li>
|
||
<li>Projects can specialize where needed</li>
|
||
<li>Single source of truth for shared knowledge</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="use-case-7-bug-investigation"><a class="header" href="#use-case-7-bug-investigation">Use Case 7: Bug Investigation</a></h2>
|
||
<h3 id="problem-5"><a class="header" href="#problem-5">Problem</a></h3>
|
||
<p>Bug in production. Developer needs to:</p>
|
||
<ul>
|
||
<li>Understand relevant decisions</li>
|
||
<li>Find related code patterns</li>
|
||
<li>Check if similar issues were solved before</li>
|
||
</ul>
|
||
<h3 id="solution-with-kogral-5"><a class="header" href="#solution-with-kogral-5">Solution with KOGRAL</a></h3>
|
||
<p><strong>Developer</strong>:</p>
|
||
<pre><code>We have a race condition in the cache layer.
|
||
What past decisions or patterns might be relevant?
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (semantic search):</p>
|
||
<pre><code>Found 4 relevant items:
|
||
|
||
1. **Decision: Use DashMap for Concurrent Access** (accepted, 1 month ago)
|
||
"Chose DashMap over RwLock<HashMap> to avoid race conditions in cache"
|
||
|
||
2. **Pattern: Safe Concurrent Cache Access**
|
||
"Always use lock-free data structures for high-contention caches"
|
||
|
||
3. **Note: Cache Race Condition Fix** (from 2026-01-17 journal)
|
||
"Fixed race in cache by switching to DashMap"
|
||
|
||
4. **Guideline: Concurrency Safety**
|
||
"Use atomic operations and lock-free structures where possible"
|
||
|
||
It looks like we've seen this before! Check the journal entry from 2026-01-17.
|
||
</code></pre>
|
||
<p><strong>Developer reads journal</strong>:</p>
|
||
<pre><code class="language-bash">kb show journal-2026-01-17
|
||
</code></pre>
|
||
<p><strong>Finds</strong>:</p>
|
||
<ul>
|
||
<li>Exact same issue</li>
|
||
<li>Solution: switch to DashMap</li>
|
||
<li>Code example included</li>
|
||
<li>Performance benchmarks</li>
|
||
</ul>
|
||
<p><strong>Applies fix</strong> in 10 minutes instead of 2 hours of debugging.</p>
|
||
<hr />
|
||
<h2 id="use-case-8-api-design-collaboration"><a class="header" href="#use-case-8-api-design-collaboration">Use Case 8: API Design Collaboration</a></h2>
|
||
<h3 id="problem-6"><a class="header" href="#problem-6">Problem</a></h3>
|
||
<p>Team discussing new API design. Multiple approaches:</p>
|
||
<ul>
|
||
<li>REST vs GraphQL</li>
|
||
<li>Versioning strategy</li>
|
||
<li>Authentication method</li>
|
||
</ul>
|
||
<p>Need to capture discussion and decision.</p>
|
||
<h3 id="solution-with-kogral-6"><a class="header" href="#solution-with-kogral-6">Solution with KOGRAL</a></h3>
|
||
<p><strong>During design meeting</strong> (using Claude Code):</p>
|
||
<pre><code>We're designing the new API. Let me capture this discussion:
|
||
|
||
Approaches discussed:
|
||
1. REST with versioned endpoints (/v1/, /v2/)
|
||
2. GraphQL with schema versioning
|
||
3. gRPC for internal services
|
||
|
||
Team consensus:
|
||
- REST for public API (easier for partners)
|
||
- GraphQL for internal admin tools (flexibility)
|
||
- gRPC for service-to-service (performance)
|
||
|
||
Create a decision record for each, linking them together.
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (creates 3 linked ADRs):</p>
|
||
<pre><code>Created 3 decisions:
|
||
|
||
1. decision-rest-public-api
|
||
Status: accepted
|
||
Context: Public API needs broad compatibility
|
||
Decision: REST with /v1/, /v2/ versioning
|
||
|
||
2. decision-graphql-admin
|
||
Status: accepted
|
||
Context: Admin tools need flexible queries
|
||
Decision: GraphQL with schema evolution
|
||
|
||
3. decision-grpc-internal
|
||
Status: accepted
|
||
Context: Service-to-service needs performance
|
||
Decision: gRPC with protobuf
|
||
|
||
Linked:
|
||
decision-rest-public-api --[relates_to]--> decision-graphql-admin
|
||
decision-rest-public-api --[relates_to]--> decision-grpc-internal
|
||
decision-graphql-admin --[relates_to]--> decision-grpc-internal
|
||
|
||
All decisions documented and interconnected.
|
||
</code></pre>
|
||
<p><strong>Two months later</strong>, someone asks "Why REST not GraphQL for public API?":</p>
|
||
<pre><code class="language-bash">kb show decision-rest-public-api
|
||
</code></pre>
|
||
<p>Full context preserved, including relationship to GraphQL decision.</p>
|
||
<hr />
|
||
<h2 id="summary"><a class="header" href="#summary">Summary</a></h2>
|
||
<p>Knowledge Base excels at:</p>
|
||
<p>✅ <strong>Onboarding</strong>: Historical context instantly available
|
||
✅ <strong>Decision Making</strong>: ADRs preserve rationale
|
||
✅ <strong>Pattern Reuse</strong>: Solutions documented once, used forever
|
||
✅ <strong>Daily Tracking</strong>: Development journal with semantic search
|
||
✅ <strong>Code Review</strong>: Implementation rationale linked to decisions
|
||
✅ <strong>Cross-Project</strong>: Shared guidelines with project overrides
|
||
✅ <strong>Bug Investigation</strong>: Past solutions easily discovered
|
||
✅ <strong>Collaboration</strong>: Discussions captured and interconnected</p>
|
||
<p><strong>Common Theme</strong>: Knowledge captured during work, queryable when needed, connected to related concepts.</p>
|
||
<hr />
|
||
<h2 id="next-steps-5"><a class="header" href="#next-steps-5">Next Steps</a></h2>
|
||
<ul>
|
||
<li><strong>Start simple</strong>: <a href="guides/quickstart.html">Quick Start Guide</a></li>
|
||
<li><strong>Integrate AI</strong>: <a href="guides/../apps/mcp-quickguide.html">MCP Quick Guide</a></li>
|
||
<li><strong>Advanced features</strong>: <a href="guides/../config/overview.html">Configuration Reference</a></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="best-practices"><a class="header" href="#best-practices">Best Practices</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="system-architecture"><a class="header" href="#system-architecture">System Architecture</a></h1>
|
||
<p>Comprehensive overview of the KOGRAL architecture.</p>
|
||
<h2 id="high-level-architecture"><a class="header" href="#high-level-architecture">High-Level Architecture</a></h2>
|
||
<p><img src="architecture/../diagrams/architecture-overview.svg" alt="Architecture Overview" /></p>
|
||
<p>The KOGRAL consists of three main layers:</p>
|
||
<ol>
|
||
<li><strong>User Interfaces</strong>: kb-cli (terminal), kb-mcp (AI integration), NuShell scripts (automation)</li>
|
||
<li><strong>Core Library (kb-core)</strong>: Rust library with graph engine, storage abstraction, embeddings, query engine</li>
|
||
<li><strong>Storage Backends</strong>: Filesystem (git-friendly), SurrealDB (scalable), In-Memory (cache/testing)</li>
|
||
</ol>
|
||
<h2 id="component-details"><a class="header" href="#component-details">Component Details</a></h2>
|
||
<h3 id="kb-cli-command-line-interface"><a class="header" href="#kb-cli-command-line-interface">kb-cli (Command-Line Interface)</a></h3>
|
||
<p><strong>Purpose</strong>: Primary user interface for local knowledge management.</p>
|
||
<p><strong>Commands</strong> (13 total):</p>
|
||
<ul>
|
||
<li><code>init</code>: Initialize <code>.kogral/</code> directory</li>
|
||
<li><code>add</code>: Create nodes (note, decision, guideline, pattern, journal)</li>
|
||
<li><code>search</code>: Text and semantic search</li>
|
||
<li><code>link</code>: Create relationships between nodes</li>
|
||
<li><code>list</code>: List all nodes</li>
|
||
<li><code>show</code>: Display node details</li>
|
||
<li><code>delete</code>: Remove nodes</li>
|
||
<li><code>graph</code>: Visualize knowledge graph</li>
|
||
<li><code>sync</code>: Sync filesystem ↔ SurrealDB</li>
|
||
<li><code>serve</code>: Start MCP server</li>
|
||
<li><code>import</code>: Import from Logseq</li>
|
||
<li><code>export</code>: Export to Logseq/JSON</li>
|
||
<li><code>config</code>: Manage configuration</li>
|
||
</ul>
|
||
<p><strong>Technology</strong>: Rust + clap (derive API)</p>
|
||
<p><strong>Features</strong>:</p>
|
||
<ul>
|
||
<li>Colored terminal output</li>
|
||
<li>Interactive prompts</li>
|
||
<li>Dry-run modes</li>
|
||
<li>Validation before operations</li>
|
||
</ul>
|
||
<h3 id="kb-mcp-mcp-server"><a class="header" href="#kb-mcp-mcp-server">kb-mcp (MCP Server)</a></h3>
|
||
<p><strong>Purpose</strong>: AI integration via Model Context Protocol.</p>
|
||
<p><strong>Protocol</strong>: JSON-RPC 2.0 over stdio</p>
|
||
<p><strong>Components</strong>:</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Tools</strong> (7):</p>
|
||
<ul>
|
||
<li><code>kogral/search</code>: Query knowledge base</li>
|
||
<li><code>kb/add_note</code>: Create notes</li>
|
||
<li><code>kb/add_decision</code>: Create ADRs</li>
|
||
<li><code>kb/link</code>: Create relationships</li>
|
||
<li><code>kb/get_guidelines</code>: Retrieve guidelines with inheritance</li>
|
||
<li><code>kb/list_graphs</code>: List available graphs</li>
|
||
<li><code>kb/export</code>: Export to formats</li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<p><strong>Resources</strong> (6 URIs):</p>
|
||
<ul>
|
||
<li><code>kogral://project/notes</code></li>
|
||
<li><code>kogral://project/decisions</code></li>
|
||
<li><code>kogral://project/guidelines</code></li>
|
||
<li><code>kogral://project/patterns</code></li>
|
||
<li><code>kogral://shared/guidelines</code></li>
|
||
<li><code>kogral://shared/patterns</code></li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<p><strong>Prompts</strong> (2):</p>
|
||
<ul>
|
||
<li><code>kb/summarize_project</code>: Generate project summary</li>
|
||
<li><code>kb/find_related</code>: Find related nodes</li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
<p><strong>Integration</strong>: Claude Code via <code>~/.config/claude/config.json</code></p>
|
||
<h3 id="nushell-scripts"><a class="header" href="#nushell-scripts">NuShell Scripts</a></h3>
|
||
<p><strong>Purpose</strong>: Automation and maintenance tasks.</p>
|
||
<p><strong>Scripts</strong> (6):</p>
|
||
<ul>
|
||
<li><code>kb-sync.nu</code>: Filesystem ↔ SurrealDB sync</li>
|
||
<li><code>kb-backup.nu</code>: Archive knowledge base</li>
|
||
<li><code>kb-reindex.nu</code>: Rebuild embeddings</li>
|
||
<li><code>kb-import-logseq.nu</code>: Import from Logseq</li>
|
||
<li><code>kb-export-logseq.nu</code>: Export to Logseq</li>
|
||
<li><code>kb-stats.nu</code>: Graph statistics</li>
|
||
</ul>
|
||
<p><strong>Features</strong>:</p>
|
||
<ul>
|
||
<li>Colored output</li>
|
||
<li>Dry-run modes</li>
|
||
<li>Progress indicators</li>
|
||
<li>Error handling</li>
|
||
</ul>
|
||
<h2 id="core-library-kb-core"><a class="header" href="#core-library-kb-core">Core Library (kb-core)</a></h2>
|
||
<h3 id="models"><a class="header" href="#models">Models</a></h3>
|
||
<p><strong>Graph</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub struct Graph {
|
||
pub name: String,
|
||
pub version: String,
|
||
pub nodes: HashMap<String, Node>, // ID → Node
|
||
pub edges: Vec<Edge>,
|
||
pub metadata: HashMap<String, Value>,
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Node</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub struct Node {
|
||
pub id: String,
|
||
pub node_type: NodeType,
|
||
pub title: String,
|
||
pub content: String,
|
||
pub tags: Vec<String>,
|
||
pub status: NodeStatus,
|
||
pub created: DateTime<Utc>,
|
||
pub modified: DateTime<Utc>,
|
||
// ... relationships, metadata
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Edge</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub struct Edge {
|
||
pub from: String,
|
||
pub to: String,
|
||
pub relation: EdgeType,
|
||
pub strength: f32,
|
||
pub created: DateTime<Utc>,
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="storage-trait"><a class="header" href="#storage-trait">Storage Trait</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>#[async_trait]
|
||
pub trait Storage: Send + Sync {
|
||
async fn save_graph(&self, graph: &Graph) -> Result<()>;
|
||
async fn load_graph(&self, name: &str) -> Result<Graph>;
|
||
async fn delete_graph(&self, name: &str) -> Result<()>;
|
||
async fn list_graphs(&self) -> Result<Vec<String>>;
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Implementations</strong>:</p>
|
||
<ol>
|
||
<li><code>FilesystemStorage</code>: Git-friendly markdown files</li>
|
||
<li><code>MemoryStorage</code>: In-memory with DashMap</li>
|
||
<li><code>SurrealDbStorage</code>: Scalable graph database</li>
|
||
</ol>
|
||
<h3 id="embedding-provider-trait"><a class="header" href="#embedding-provider-trait">Embedding Provider Trait</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>#[async_trait]
|
||
pub trait EmbeddingProvider: Send + Sync {
|
||
async fn embed(&self, texts: Vec<String>) -> Result<Vec<Vec<f32>>>;
|
||
fn dimensions(&self) -> usize;
|
||
fn model_name(&self) -> &str;
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Implementations</strong>:</p>
|
||
<ol>
|
||
<li><code>FastEmbedProvider</code>: Local fastembed</li>
|
||
<li><code>RigEmbeddingProvider</code>: OpenAI, Claude, Ollama (via rig-core)</li>
|
||
</ol>
|
||
<h3 id="parser"><a class="header" href="#parser">Parser</a></h3>
|
||
<p><strong>Input</strong>: Markdown file with YAML frontmatter</p>
|
||
<p><strong>Output</strong>: <code>Node</code> struct</p>
|
||
<p><strong>Features</strong>:</p>
|
||
<ul>
|
||
<li>YAML frontmatter extraction</li>
|
||
<li>Markdown body parsing</li>
|
||
<li>Wikilink detection (<code>[[linked-note]]</code>)</li>
|
||
<li>Code reference parsing (<code>@file.rs:42</code>)</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-markdown">---
|
||
id: note-123
|
||
type: note
|
||
title: My Note
|
||
tags: [rust, async]
|
||
---
|
||
|
||
# My Note
|
||
|
||
Content with [[other-note]] and @src/main.rs:10
|
||
</code></pre>
|
||
<p>→</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>Node {
|
||
id: "note-123",
|
||
node_type: NodeType::Note,
|
||
title: "My Note",
|
||
content: "Content with [[other-note]] and @src/main.rs:10",
|
||
tags: vec!["rust", "async"],
|
||
// ... parsed wikilinks, code refs
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="configuration-system"><a class="header" href="#configuration-system">Configuration System</a></h2>
|
||
<h3 id="nickel-schema"><a class="header" href="#nickel-schema">Nickel Schema</a></h3>
|
||
<pre><code class="language-nickel"># schemas/kb-config.ncl
|
||
{
|
||
KbConfig = {
|
||
graph | GraphConfig,
|
||
storage | StorageConfig,
|
||
embeddings | EmbeddingConfig,
|
||
templates | TemplateConfig,
|
||
query | QueryConfig,
|
||
mcp | McpConfig,
|
||
sync | SyncConfig,
|
||
},
|
||
}
|
||
</code></pre>
|
||
<h3 id="loading-process"><a class="header" href="#loading-process">Loading Process</a></h3>
|
||
<pre><code>User writes: .kogral/config.ncl
|
||
↓ [nickel export --format json]
|
||
JSON intermediate
|
||
↓ [serde_json::from_str]
|
||
KbConfig struct (Rust)
|
||
↓
|
||
Runtime behavior
|
||
</code></pre>
|
||
<p><strong>Double Validation</strong>:</p>
|
||
<ol>
|
||
<li>Nickel contracts: Type-safe, enum validation</li>
|
||
<li>Serde deserialization: Rust type checking</li>
|
||
</ol>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>Errors caught at export time</li>
|
||
<li>Runtime guaranteed valid config</li>
|
||
<li>Self-documenting schemas</li>
|
||
</ul>
|
||
<h2 id="storage-architecture"><a class="header" href="#storage-architecture">Storage Architecture</a></h2>
|
||
<h3 id="hybrid-strategy"><a class="header" href="#hybrid-strategy">Hybrid Strategy</a></h3>
|
||
<p><strong>Local Graph</strong> (per project):</p>
|
||
<ul>
|
||
<li>Storage: Filesystem (<code>.kogral/</code> directory)</li>
|
||
<li>Format: Markdown + YAML frontmatter</li>
|
||
<li>Version control: Git</li>
|
||
<li>Scope: Project-specific knowledge</li>
|
||
</ul>
|
||
<p><strong>Shared Graph</strong> (organization):</p>
|
||
<ul>
|
||
<li>Storage: SurrealDB (or synced filesystem)</li>
|
||
<li>Format: Same markdown (for compatibility)</li>
|
||
<li>Version control: Optional</li>
|
||
<li>Scope: Organization-wide guidelines</li>
|
||
</ul>
|
||
<p><strong>Sync</strong>:</p>
|
||
<pre><code>Filesystem (.kogral/)
|
||
↕ [bidirectional sync]
|
||
SurrealDB (central)
|
||
</code></pre>
|
||
<h3 id="file-layout"><a class="header" href="#file-layout">File Layout</a></h3>
|
||
<pre><code>.kogral/
|
||
├── config.toml # Graph metadata
|
||
├── notes/
|
||
│ ├── async-patterns.md # Individual note
|
||
│ └── error-handling.md
|
||
├── decisions/
|
||
│ ├── 0001-use-rust.md # ADR format
|
||
│ └── 0002-surrealdb.md
|
||
├── guidelines/
|
||
│ ├── rust-errors.md # Project guideline
|
||
│ └── testing.md
|
||
├── patterns/
|
||
│ └── repository.md
|
||
└── journal/
|
||
├── 2026-01-17.md # Daily journal
|
||
└── 2026-01-18.md
|
||
</code></pre>
|
||
<h2 id="query-engine"><a class="header" href="#query-engine">Query Engine</a></h2>
|
||
<h3 id="text-search"><a class="header" href="#text-search">Text Search</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>let results = graph.nodes.values()
|
||
.filter(|node| {
|
||
node.title.contains(&query) ||
|
||
node.content.contains(&query) ||
|
||
node.tags.iter().any(|tag| tag.contains(&query))
|
||
})
|
||
.collect();
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="semantic-search-1"><a class="header" href="#semantic-search-1">Semantic Search</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>let query_embedding = embeddings.embed(vec![query]).await?;
|
||
let mut scored: Vec<_> = graph.nodes.values()
|
||
.filter_map(|node| {
|
||
let node_embedding = node.embedding.as_ref()?;
|
||
let similarity = cosine_similarity(&query_embedding[0], node_embedding);
|
||
(similarity >= threshold).then_some((node, similarity))
|
||
})
|
||
.collect();
|
||
scored.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="cross-graph-query"><a class="header" href="#cross-graph-query">Cross-Graph Query</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// Query both project and shared graphs
|
||
let project_results = project_graph.search(&query).await?;
|
||
let shared_results = shared_graph.search(&query).await?;
|
||
|
||
// Merge with deduplication
|
||
let combined = merge_results(project_results, shared_results);
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="mcp-protocol-flow"><a class="header" href="#mcp-protocol-flow">MCP Protocol Flow</a></h2>
|
||
<pre><code>Claude Code kb-mcp kb-core
|
||
│ │ │
|
||
├─ JSON-RPC request ───→ │ │
|
||
│ kb/search │ │
|
||
│ {"query": "rust"} │ │
|
||
│ ├─ search() ──────────→ │
|
||
│ │ │
|
||
│ │ Query engine
|
||
│ │ Text + semantic
|
||
│ │ │
|
||
│ │ ←──── results ─────────┤
|
||
│ │ │
|
||
│ ←─ JSON-RPC response ──┤ │
|
||
│ {"results": [...]} │ │
|
||
</code></pre>
|
||
<h2 id="template-system"><a class="header" href="#template-system">Template System</a></h2>
|
||
<p><strong>Engine</strong>: Tera (Jinja2-like)</p>
|
||
<p><strong>Templates</strong>:</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Document Templates</strong> (6):</p>
|
||
<ul>
|
||
<li><code>note.md.tera</code></li>
|
||
<li><code>decision.md.tera</code></li>
|
||
<li><code>guideline.md.tera</code></li>
|
||
<li><code>pattern.md.tera</code></li>
|
||
<li><code>journal.md.tera</code></li>
|
||
<li><code>execution.md.tera</code></li>
|
||
</ul>
|
||
</li>
|
||
<li>
|
||
<p><strong>Export Templates</strong> (4):</p>
|
||
<ul>
|
||
<li><code>logseq-page.md.tera</code></li>
|
||
<li><code>logseq-journal.md.tera</code></li>
|
||
<li><code>summary.md.tera</code></li>
|
||
<li><code>graph.json.tera</code></li>
|
||
</ul>
|
||
</li>
|
||
</ol>
|
||
<p><strong>Usage</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>let mut tera = Tera::new("templates/**/*.tera")?;
|
||
let rendered = tera.render("note.md.tera", &context)?;
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="error-handling"><a class="header" href="#error-handling">Error Handling</a></h2>
|
||
<p><strong>Strategy</strong>: <code>thiserror</code> for structured errors</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>#[derive(Error, Debug)]
|
||
pub enum KbError {
|
||
#[error("Storage error: {0}")]
|
||
Storage(String),
|
||
|
||
#[error("Node not found: {0}")]
|
||
NodeNotFound(String),
|
||
|
||
#[error("Configuration error: {0}")]
|
||
Config(String),
|
||
|
||
#[error("Parse error: {0}")]
|
||
Parse(String),
|
||
|
||
#[error("Embedding error: {0}")]
|
||
Embedding(String),
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Propagation</strong>: <code>?</code> operator throughout</p>
|
||
<h2 id="testing-strategy"><a class="header" href="#testing-strategy">Testing Strategy</a></h2>
|
||
<p><strong>Unit Tests</strong>: Per module (models, parser, storage)</p>
|
||
<p><strong>Integration Tests</strong>: Full workflow (add → save → load → query)</p>
|
||
<p><strong>Test Coverage</strong>:</p>
|
||
<ul>
|
||
<li>kb-core: 48 tests</li>
|
||
<li>kb-mcp: 5 tests</li>
|
||
<li>Total: 56 tests</li>
|
||
</ul>
|
||
<p><strong>Test Data</strong>: Fixtures in <code>tests/fixtures/</code></p>
|
||
<h2 id="performance-considerations"><a class="header" href="#performance-considerations">Performance Considerations</a></h2>
|
||
<p><strong>Node Lookup</strong>: O(1) via HashMap</p>
|
||
<p><strong>Semantic Search</strong>: O(n) with early termination (threshold filter)</p>
|
||
<p><strong>Storage</strong>:</p>
|
||
<ul>
|
||
<li>Filesystem: Lazy loading (load on demand)</li>
|
||
<li>Memory: Full graph in RAM</li>
|
||
<li>SurrealDB: Query optimization (indexes)</li>
|
||
</ul>
|
||
<p><strong>Embeddings</strong>:</p>
|
||
<ul>
|
||
<li>Cache embeddings in node metadata</li>
|
||
<li>Batch processing (configurable batch size)</li>
|
||
<li>Async generation (non-blocking)</li>
|
||
</ul>
|
||
<h2 id="security"><a class="header" href="#security">Security</a></h2>
|
||
<p><strong>No unsafe code</strong>: <code>#![forbid(unsafe_code)]</code></p>
|
||
<p><strong>Input validation</strong>:</p>
|
||
<ul>
|
||
<li>Nickel contracts validate config</li>
|
||
<li>serde validates JSON</li>
|
||
<li>Custom validation for user input</li>
|
||
</ul>
|
||
<p><strong>File operations</strong>:</p>
|
||
<ul>
|
||
<li>Path sanitization (no <code>../</code> traversal)</li>
|
||
<li>Permissions checking</li>
|
||
<li>Atomic writes (temp file + rename)</li>
|
||
</ul>
|
||
<h2 id="scalability"><a class="header" href="#scalability">Scalability</a></h2>
|
||
<p><strong>Small Projects</strong> (< 1000 nodes):</p>
|
||
<ul>
|
||
<li>Filesystem storage</li>
|
||
<li>In-memory search</li>
|
||
<li>Local embeddings (fastembed)</li>
|
||
</ul>
|
||
<p><strong>Medium Projects</strong> (1000-10,000 nodes):</p>
|
||
<ul>
|
||
<li>Filesystem + SurrealDB sync</li>
|
||
<li>Semantic search with caching</li>
|
||
<li>Cloud embeddings (OpenAI/Claude)</li>
|
||
</ul>
|
||
<p><strong>Large Organizations</strong> (> 10,000 nodes):</p>
|
||
<ul>
|
||
<li>SurrealDB primary</li>
|
||
<li>Distributed embeddings</li>
|
||
<li>Multi-graph federation</li>
|
||
</ul>
|
||
<h2 id="next-steps-6"><a class="header" href="#next-steps-6">Next Steps</a></h2>
|
||
<ul>
|
||
<li><strong>Graph Model Details</strong>: <a href="architecture/graph-model.html">Graph Model</a></li>
|
||
<li><strong>Storage Deep Dive</strong>: <a href="architecture/storage-architecture.html">Storage Architecture</a></li>
|
||
<li><strong>ADRs</strong>: <a href="architecture/adrs/001-nickel-vs-toml.html">Architectural Decisions</a></li>
|
||
<li><strong>Implementation</strong>: <a href="architecture/../contributing/development.html">Development Guide</a></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="graph-model"><a class="header" href="#graph-model">Graph Model</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="config-driven-architecture"><a class="header" href="#config-driven-architecture">Config-Driven Architecture</a></h1>
|
||
<p>The KOGRAL follows a <strong>config-driven architecture</strong> where all behavior is defined through Nickel configuration files rather than hardcoded in Rust.</p>
|
||
<h2 id="philosophy"><a class="header" href="#philosophy">Philosophy</a></h2>
|
||
<p><strong>"Configuration, not code, defines behavior"</strong></p>
|
||
<p>Instead of hardcoding storage backends, embedding providers, or query parameters, KB uses a layered configuration system that composes settings from multiple sources:</p>
|
||
<ol>
|
||
<li><strong>Schema contracts</strong> (type definitions)</li>
|
||
<li><strong>Defaults</strong> (base values)</li>
|
||
<li><strong>Mode overlays</strong> (dev/prod/test optimizations)</li>
|
||
<li><strong>User customizations</strong> (project-specific overrides)</li>
|
||
</ol>
|
||
<p>This approach provides:</p>
|
||
<ul>
|
||
<li>✅ <strong>Type safety</strong> - Nickel contracts validate configuration before runtime</li>
|
||
<li>✅ <strong>Composability</strong> - Mix and match configurations for different environments</li>
|
||
<li>✅ <strong>Discoverability</strong> - Self-documenting schemas with inline documentation</li>
|
||
<li>✅ <strong>Hot-reload</strong> - Change behavior without recompiling Rust code</li>
|
||
<li>✅ <strong>Double validation</strong> - Nickel contracts + serde ensure correctness</li>
|
||
</ul>
|
||
<h2 id="configuration-composition-flow"><a class="header" href="#configuration-composition-flow">Configuration Composition Flow</a></h2>
|
||
<p><img src="architecture/../diagrams/config-composition.svg" alt="Configuration Composition" /></p>
|
||
<p>The configuration system uses a <strong>four-layer composition pattern</strong>:</p>
|
||
<h3 id="layer-1-schema-contracts"><a class="header" href="#layer-1-schema-contracts">Layer 1: Schema Contracts</a></h3>
|
||
<p><strong>Location</strong>: <code>schemas/kb/contracts.ncl</code></p>
|
||
<p><strong>Purpose</strong>: Define types and validation rules using Nickel contracts.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-nickel">{
|
||
StorageType = [| 'filesystem, 'memory, 'surrealdb |],
|
||
|
||
StorageConfig = {
|
||
primary | StorageType
|
||
| doc "Primary storage backend"
|
||
| default = 'filesystem,
|
||
|
||
secondary | SecondaryStorageConfig
|
||
| doc "Optional secondary storage"
|
||
| default = { enabled = false },
|
||
},
|
||
}
|
||
</code></pre>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>Enum validation (only valid storage types accepted)</li>
|
||
<li>Required vs optional fields</li>
|
||
<li>Default values for optional fields</li>
|
||
<li>Documentation attached to types</li>
|
||
</ul>
|
||
<h3 id="layer-2-defaults"><a class="header" href="#layer-2-defaults">Layer 2: Defaults</a></h3>
|
||
<p><strong>Location</strong>: <code>schemas/kb/defaults.ncl</code></p>
|
||
<p><strong>Purpose</strong>: Provide sensible base values for all configuration options.</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code class="language-nickel">{
|
||
base = {
|
||
storage = {
|
||
primary = 'filesystem,
|
||
secondary = {
|
||
enabled = false,
|
||
type = 'surrealdb,
|
||
url = "ws://localhost:8000",
|
||
},
|
||
},
|
||
embeddings = {
|
||
enabled = true,
|
||
provider = 'fastembed,
|
||
model = "BAAI/bge-small-en-v1.5",
|
||
dimensions = 384,
|
||
},
|
||
} | contracts.KbConfig,
|
||
}
|
||
</code></pre>
|
||
<p><strong>Validated by</strong>: <code>contracts.KbConfig</code> contract ensures defaults are valid.</p>
|
||
<h3 id="layer-3-mode-overlays"><a class="header" href="#layer-3-mode-overlays">Layer 3: Mode Overlays</a></h3>
|
||
<p><strong>Location</strong>: <code>schemas/kb/modes/{dev,prod,test}.ncl</code></p>
|
||
<p><strong>Purpose</strong>: Environment-specific optimizations and tuning.</p>
|
||
<h4 id="development-mode-devncl"><a class="header" href="#development-mode-devncl">Development Mode (<code>dev.ncl</code>)</a></h4>
|
||
<p>Optimized for: Fast iteration, local development, debugging</p>
|
||
<pre><code class="language-nickel">{
|
||
storage = {
|
||
primary = 'filesystem,
|
||
secondary = { enabled = false }, # No database overhead
|
||
},
|
||
embeddings = {
|
||
provider = 'fastembed, # Local, no API costs
|
||
},
|
||
sync = {
|
||
auto_index = false, # Manual control
|
||
},
|
||
}
|
||
</code></pre>
|
||
<h4 id="production-mode-prodncl"><a class="header" href="#production-mode-prodncl">Production Mode (<code>prod.ncl</code>)</a></h4>
|
||
<p>Optimized for: Performance, reliability, scalability</p>
|
||
<pre><code class="language-nickel">{
|
||
storage = {
|
||
secondary = { enabled = true }, # SurrealDB for scale
|
||
},
|
||
embeddings = {
|
||
provider = 'openai, # High-quality cloud embeddings
|
||
model = "text-embedding-3-small",
|
||
dimensions = 1536,
|
||
},
|
||
sync = {
|
||
auto_index = true,
|
||
debounce_ms = 300, # Fast response
|
||
},
|
||
}
|
||
</code></pre>
|
||
<h4 id="test-mode-testncl"><a class="header" href="#test-mode-testncl">Test Mode (<code>test.ncl</code>)</a></h4>
|
||
<p>Optimized for: Fast tests, isolation, determinism</p>
|
||
<pre><code class="language-nickel">{
|
||
storage = {
|
||
primary = 'memory, # Ephemeral, no disk I/O
|
||
},
|
||
embeddings = {
|
||
enabled = false, # Disable for speed
|
||
},
|
||
sync = {
|
||
auto_index = false,
|
||
debounce_ms = 0, # No delays in tests
|
||
},
|
||
}
|
||
</code></pre>
|
||
<h3 id="layer-4-user-customizations"><a class="header" href="#layer-4-user-customizations">Layer 4: User Customizations</a></h3>
|
||
<p><strong>Location</strong>: <code>.kb-config/core/kb.ncl</code> or <code>.kb-config/platform/{dev,prod,test}.ncl</code></p>
|
||
<p><strong>Purpose</strong>: Project-specific or deployment-specific overrides.</p>
|
||
<p><strong>Example</strong> (user project config):</p>
|
||
<pre><code class="language-nickel">let mode = import "../../schemas/kb/modes/dev.ncl" in
|
||
|
||
let user_custom = {
|
||
graph = {
|
||
name = "my-project",
|
||
},
|
||
embeddings = {
|
||
provider = 'claude, # Override to use Claude
|
||
model = "claude-3-haiku-20240307",
|
||
},
|
||
query = {
|
||
similarity_threshold = 0.7, # Stricter threshold
|
||
},
|
||
} in
|
||
|
||
helpers.compose_config defaults.base mode user_custom
|
||
| contracts.KbConfig
|
||
</code></pre>
|
||
<h2 id="composition-mechanism"><a class="header" href="#composition-mechanism">Composition Mechanism</a></h2>
|
||
<p>The <code>helpers.ncl</code> module provides the composition function:</p>
|
||
<pre><code class="language-nickel">{
|
||
# Recursively merge with override precedence
|
||
merge_with_override = fun base override => /* ... */,
|
||
|
||
# Compose three layers
|
||
compose_config = fun defaults mode_config user_custom =>
|
||
let with_mode = merge_with_override defaults mode_config in
|
||
merge_with_override with_mode user_custom,
|
||
}
|
||
</code></pre>
|
||
<p><strong>Merge behavior</strong>:</p>
|
||
<ul>
|
||
<li>Records are merged recursively</li>
|
||
<li>Override values take precedence over base values</li>
|
||
<li>Arrays are not merged, override replaces base</li>
|
||
<li>Null in override keeps base value</li>
|
||
</ul>
|
||
<p><strong>Example merge</strong>:</p>
|
||
<pre><code class="language-nickel">base = { storage = { primary = 'filesystem }, embeddings = { enabled = true } }
|
||
override = { storage = { primary = 'memory } }
|
||
# Result: { storage = { primary = 'memory }, embeddings = { enabled = true } }
|
||
</code></pre>
|
||
<h2 id="export-to-json"><a class="header" href="#export-to-json">Export to JSON</a></h2>
|
||
<p>Once composed, the Nickel configuration is exported to JSON for Rust consumption:</p>
|
||
<pre><code class="language-bash">nickel export --format json .kb-config/core/kb.ncl > .kb-config/targets/kb-core.json
|
||
</code></pre>
|
||
<p><strong>Output</strong> (<code>.kb-config/targets/kb-core.json</code>):</p>
|
||
<pre><code class="language-json">{
|
||
"graph": {
|
||
"name": "my-project",
|
||
"version": "1.0.0"
|
||
},
|
||
"storage": {
|
||
"primary": "memory",
|
||
"secondary": {
|
||
"enabled": false
|
||
}
|
||
},
|
||
"embeddings": {
|
||
"enabled": true,
|
||
"provider": "claude",
|
||
"model": "claude-3-haiku-20240307",
|
||
"dimensions": 768
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="rust-integration"><a class="header" href="#rust-integration">Rust Integration</a></h2>
|
||
<p>The Rust code deserializes the JSON into typed structs using serde:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>use serde::{Deserialize, Serialize};
|
||
|
||
#[derive(Debug, Deserialize, Serialize)]
|
||
pub struct KbConfig {
|
||
pub graph: GraphConfig,
|
||
pub storage: StorageConfig,
|
||
pub embeddings: EmbeddingConfig,
|
||
pub templates: TemplateConfig,
|
||
pub query: QueryConfig,
|
||
pub mcp: McpConfig,
|
||
pub sync: SyncConfig,
|
||
}
|
||
|
||
impl KbConfig {
|
||
pub fn from_file(path: &Path) -> Result<Self> {
|
||
let json = std::fs::read_to_string(path)?;
|
||
let config: KbConfig = serde_json::from_str(&json)?;
|
||
Ok(config)
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Usage in kb-core</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>let config = KbConfig::from_file(".kb-config/targets/kb-core.json")?;
|
||
|
||
// Config drives behavior
|
||
let storage: Box<dyn Storage> = match config.storage.primary {
|
||
StorageType::Filesystem => Box::new(FilesystemStorage::new(&config)?),
|
||
StorageType::Memory => Box::new(MemoryStorage::new()),
|
||
StorageType::SurrealDb => Box::new(SurrealDbStorage::new(&config).await?),
|
||
};
|
||
|
||
let embeddings: Box<dyn EmbeddingProvider> = match config.embeddings.provider {
|
||
EmbeddingProviderType::FastEmbed => Box::new(FastEmbedProvider::new()?),
|
||
EmbeddingProviderType::OpenAI => Box::new(RigEmbeddingProvider::openai(&config)?),
|
||
EmbeddingProviderType::Claude => Box::new(RigEmbeddingProvider::claude(&config)?),
|
||
EmbeddingProviderType::Ollama => Box::new(RigEmbeddingProvider::ollama(&config)?),
|
||
};
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="double-validation"><a class="header" href="#double-validation">Double Validation</a></h2>
|
||
<p>Configuration is validated <strong>twice</strong>:</p>
|
||
<h3 id="1-nickel-contract-validation"><a class="header" href="#1-nickel-contract-validation">1. Nickel Contract Validation</a></h3>
|
||
<p>At export time, Nickel validates:</p>
|
||
<ul>
|
||
<li>✅ Types match contracts (e.g., <code>primary | StorageType</code>)</li>
|
||
<li>✅ Required fields are present</li>
|
||
<li>✅ Enums have valid values</li>
|
||
<li>✅ Nested structure is correct</li>
|
||
</ul>
|
||
<p><strong>Error example</strong>:</p>
|
||
<pre><code>error: contract broken by a value
|
||
┌─ .kb-config/core/kb.ncl:15:5
|
||
│
|
||
15│ primary = 'invalid,
|
||
│ ^^^^^^^^^^^^^^^^^^^ applied to this expression
|
||
│
|
||
= This value is not in the enum ['filesystem, 'memory, 'surrealdb]
|
||
</code></pre>
|
||
<h3 id="2-serde-deserialization-validation"><a class="header" href="#2-serde-deserialization-validation">2. Serde Deserialization Validation</a></h3>
|
||
<p>At runtime, serde validates:</p>
|
||
<ul>
|
||
<li>✅ JSON structure matches Rust types</li>
|
||
<li>✅ Field names match (with rename support)</li>
|
||
<li>✅ Values can be converted to Rust types</li>
|
||
<li>✅ Required fields are not null</li>
|
||
</ul>
|
||
<p><strong>Error example</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>Error: missing field `graph` at line 1 column 123
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="benefits-of-config-driven-architecture"><a class="header" href="#benefits-of-config-driven-architecture">Benefits of Config-Driven Architecture</a></h2>
|
||
<h3 id="1-zero-hardcoding"><a class="header" href="#1-zero-hardcoding">1. Zero Hardcoding</a></h3>
|
||
<p><strong>Bad</strong> (hardcoded):</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// Hardcoded - requires recompilation to change
|
||
let storage = FilesystemStorage::new("/fixed/path");
|
||
let threshold = 0.6;
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Good</strong> (config-driven):</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// Config-driven - change via .ncl file
|
||
let storage = create_storage(&config)?;
|
||
let threshold = config.query.similarity_threshold;
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="2-environment-flexibility"><a class="header" href="#2-environment-flexibility">2. Environment Flexibility</a></h3>
|
||
<p>Same codebase, different behavior:</p>
|
||
<pre><code class="language-bash"># Development
|
||
nickel export .kb-config/platform/dev.ncl > targets/kb-core.json
|
||
# → Filesystem storage, fastembed, no auto-sync
|
||
|
||
# Production
|
||
nickel export .kb-config/platform/prod.ncl > targets/kb-core.json
|
||
# → SurrealDB enabled, OpenAI embeddings, auto-sync
|
||
|
||
# Testing
|
||
nickel export .kb-config/platform/test.ncl > targets/kb-core.json
|
||
# → In-memory storage, no embeddings, isolated
|
||
</code></pre>
|
||
<h3 id="3-self-documenting"><a class="header" href="#3-self-documenting">3. Self-Documenting</a></h3>
|
||
<p>Nickel contracts include inline documentation:</p>
|
||
<pre><code class="language-nickel">StorageType = [| 'filesystem, 'memory, 'surrealdb |]
|
||
| doc "Storage backend type: filesystem (git-tracked), memory (ephemeral), surrealdb (scalable)",
|
||
</code></pre>
|
||
<p>IDEs can show this documentation when editing <code>.ncl</code> files.</p>
|
||
<h3 id="4-type-safe-evolution"><a class="header" href="#4-type-safe-evolution">4. Type-Safe Evolution</a></h3>
|
||
<p>When adding new features:</p>
|
||
<ol>
|
||
<li>Update contract in <code>contracts.ncl</code></li>
|
||
<li>Add default in <code>defaults.ncl</code></li>
|
||
<li>Export validates existing configs</li>
|
||
<li>Rust compilation validates deserialization</li>
|
||
</ol>
|
||
<p>Breaking changes are caught <strong>before runtime</strong>.</p>
|
||
<h3 id="5-testability"><a class="header" href="#5-testability">5. Testability</a></h3>
|
||
<p>Different test scenarios without code changes:</p>
|
||
<pre><code class="language-nickel"># test-semantic-search.ncl
|
||
let test_config = defaults.base & {
|
||
embeddings = { enabled = true, provider = 'fastembed },
|
||
query = { similarity_threshold = 0.3 },
|
||
} in test_config
|
||
</code></pre>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>#[test]
|
||
fn test_semantic_search() {
|
||
let config = KbConfig::from_file("test-semantic-search.json")?;
|
||
// Config drives test behavior
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="configuration-discovery"><a class="header" href="#configuration-discovery">Configuration Discovery</a></h2>
|
||
<p>KB tools automatically discover configuration:</p>
|
||
<ol>
|
||
<li><strong>Check <code>.kb-config/targets/kb-core.json</code></strong> (pre-exported)</li>
|
||
<li><strong>Check <code>.kb-config/core/kb.ncl</code></strong> (export on-demand)</li>
|
||
<li><strong>Check environment variable <code>KB_CONFIG</code></strong></li>
|
||
<li><strong>Fall back to embedded defaults</strong></li>
|
||
</ol>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>impl KbConfig {
|
||
pub fn discover() -> Result<Self> {
|
||
if let Ok(config) = Self::from_file(".kb-config/targets/kb-core.json") {
|
||
return Ok(config);
|
||
}
|
||
|
||
if Path::new(".kb-config/core/kb.ncl").exists() {
|
||
// Export and load
|
||
let output = Command::new("nickel")
|
||
.args(["export", "--format", "json", ".kb-config/core/kb.ncl"])
|
||
.output()?;
|
||
return serde_json::from_slice(&output.stdout)?;
|
||
}
|
||
|
||
if let Ok(path) = std::env::var("KB_CONFIG") {
|
||
return Self::from_file(&path);
|
||
}
|
||
|
||
Ok(Self::default()) // Embedded defaults
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="integration-with-justfile"><a class="header" href="#integration-with-justfile">Integration with justfile</a></h2>
|
||
<p>The <code>justfile</code> integrates configuration validation:</p>
|
||
<pre><code class="language-just"># Validate all Nickel configs
|
||
nickel-validate-all:
|
||
@echo "Validating Nickel schemas..."
|
||
nickel typecheck schemas/kb/contracts.ncl
|
||
nickel typecheck schemas/kb/defaults.ncl
|
||
nickel typecheck schemas/kb/helpers.ncl
|
||
nickel typecheck schemas/kb/modes/dev.ncl
|
||
nickel typecheck schemas/kb/modes/prod.ncl
|
||
nickel typecheck schemas/kb/modes/test.ncl
|
||
nickel typecheck .kb-config/core/kb.ncl
|
||
|
||
# Export all platform configs
|
||
nickel-export-all:
|
||
@echo "Exporting platform configs to JSON..."
|
||
@mkdir -p .kb-config/targets
|
||
nickel export --format json .kb-config/platform/dev.ncl > .kb-config/targets/kb-dev.json
|
||
nickel export --format json .kb-config/platform/prod.ncl > .kb-config/targets/kb-prod.json
|
||
nickel export --format json .kb-config/platform/test.ncl > .kb-config/targets/kb-test.json
|
||
@echo "Exported 3 configurations to .kb-config/targets/"
|
||
</code></pre>
|
||
<p>Usage:</p>
|
||
<pre><code class="language-bash">just nickel::validate-all # Check configs are valid
|
||
just nickel::export-all # Generate JSON for all environments
|
||
</code></pre>
|
||
<h2 id="best-practices-1"><a class="header" href="#best-practices-1">Best Practices</a></h2>
|
||
<h3 id="1-never-hardcode"><a class="header" href="#1-never-hardcode">1. Never Hardcode</a></h3>
|
||
<p>If it's behavior, it's config:</p>
|
||
<ul>
|
||
<li>Storage paths</li>
|
||
<li>API endpoints</li>
|
||
<li>Thresholds</li>
|
||
<li>Timeouts</li>
|
||
<li>Feature flags</li>
|
||
<li>Provider selection</li>
|
||
</ul>
|
||
<h3 id="2-use-modes-for-environment-differences"><a class="header" href="#2-use-modes-for-environment-differences">2. Use Modes for Environment Differences</a></h3>
|
||
<p>Don't put environment-specific values in user config:</p>
|
||
<p><strong>Bad</strong>:</p>
|
||
<pre><code class="language-nickel"># user config with env-specific values
|
||
{
|
||
storage = {
|
||
url = if env == "prod" then "prod-url" else "dev-url" # Don't do this
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Good</strong>:</p>
|
||
<pre><code class="language-nickel"># modes/prod.ncl
|
||
{ storage = { url = "prod-url" } }
|
||
|
||
# modes/dev.ncl
|
||
{ storage = { url = "dev-url" } }
|
||
|
||
# user config is environment-agnostic
|
||
{ graph = { name = "my-project" } }
|
||
</code></pre>
|
||
<h3 id="3-document-complex-fields"><a class="header" href="#3-document-complex-fields">3. Document Complex Fields</a></h3>
|
||
<p>Use Nickel's <code>doc</code> metadata:</p>
|
||
<pre><code class="language-nickel">similarity_threshold | Number
|
||
| doc "Minimum cosine similarity (0-1) for semantic search matches. Higher = stricter."
|
||
| default = 0.6,
|
||
</code></pre>
|
||
<h3 id="4-validate-early"><a class="header" href="#4-validate-early">4. Validate Early</a></h3>
|
||
<p>Run <code>nickel typecheck</code> in CI/CD before building Rust code.</p>
|
||
<h3 id="5-version-configs"><a class="header" href="#5-version-configs">5. Version Configs</a></h3>
|
||
<p>Track <code>.ncl</code> files in git, ignore <code>.kb-config/targets/*.json</code> (generated).</p>
|
||
<h2 id="see-also"><a class="header" href="#see-also">See Also</a></h2>
|
||
<ul>
|
||
<li><strong>Schema Reference</strong>: <a href="architecture/../config/schema.html">Configuration Schema</a></li>
|
||
<li><strong>User Guide</strong>: <a href="architecture/../config/overview.html">Configuration Guide</a></li>
|
||
<li><strong>ADR</strong>: <a href="architecture/adrs/001-nickel-vs-toml.html">Why Nickel vs TOML</a></li>
|
||
<li><strong>Examples</strong>: <code>.kb-config/core/kb.ncl</code>, <code>.kb-config/platform/*.ncl</code></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="storage-architecture-1"><a class="header" href="#storage-architecture-1">Storage Architecture</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="logseq-blocks-support---architecture-design"><a class="header" href="#logseq-blocks-support---architecture-design">Logseq Blocks Support - Architecture Design</a></h1>
|
||
<h2 id="problem-statement"><a class="header" href="#problem-statement">Problem Statement</a></h2>
|
||
<p>Logseq uses <strong>content blocks</strong> as the fundamental unit of information, not full documents. Each block can have:</p>
|
||
<ul>
|
||
<li><strong>Properties</strong>: <code>#card</code>, <code>TODO</code>, <code>DONE</code>, custom properties</li>
|
||
<li><strong>Tags</strong>: Inline tags like <code>#flashcard</code>, <code>#important</code></li>
|
||
<li><strong>References</strong>: Block references <code>((block-id))</code>, page references <code>[[page]]</code></li>
|
||
<li><strong>Nesting</strong>: Outliner-style hierarchy (parent-child blocks)</li>
|
||
<li><strong>Metadata</strong>: Block-level properties (unlike page-level frontmatter)</li>
|
||
</ul>
|
||
<p><strong>Current KB limitation</strong>: Nodes only have <code>content: String</code> (flat markdown). Importing from Logseq loses block structure and properties.</p>
|
||
<p><strong>Requirement</strong>: Support round-trip import/export with full block fidelity:</p>
|
||
<pre><code>Logseq Graph → KOGRAL Import → KOGRAL Storage → KOGRAL Export → Logseq Graph
|
||
(blocks preserved) (blocks preserved)
|
||
</code></pre>
|
||
<h2 id="use-cases-3"><a class="header" href="#use-cases-3">Use Cases</a></h2>
|
||
<h3 id="1-flashcards-card"><a class="header" href="#1-flashcards-card">1. Flashcards (<code>#card</code>)</a></h3>
|
||
<p><strong>Logseq</strong>:</p>
|
||
<pre><code class="language-markdown">- What is Rust's ownership model? #card
|
||
- Rust uses ownership, borrowing, and lifetimes
|
||
- Three rules: one owner, many borrows XOR one mutable
|
||
</code></pre>
|
||
<p><strong>KB needs to preserve</strong>:</p>
|
||
<ul>
|
||
<li>Block with <code>#card</code> property</li>
|
||
<li>Nested answer blocks</li>
|
||
<li>Ability to query all cards</li>
|
||
</ul>
|
||
<h3 id="2-task-tracking-tododone"><a class="header" href="#2-task-tracking-tododone">2. Task Tracking (<code>TODO</code>/<code>DONE</code>)</a></h3>
|
||
<p><strong>Logseq</strong>:</p>
|
||
<pre><code class="language-markdown">- TODO Implement block parser #rust
|
||
- DONE Research block structure
|
||
- TODO Write parser tests
|
||
</code></pre>
|
||
<p><strong>KB needs to preserve</strong>:</p>
|
||
<ul>
|
||
<li>Task status per block</li>
|
||
<li>Hierarchical task breakdown</li>
|
||
<li>Tags on tasks</li>
|
||
</ul>
|
||
<h3 id="3-block-references"><a class="header" href="#3-block-references">3. Block References</a></h3>
|
||
<p><strong>Logseq</strong>:</p>
|
||
<pre><code class="language-markdown">- Core concept: ((block-uuid-123))
|
||
- See also: [[Related Page]]
|
||
</code></pre>
|
||
<p><strong>KB needs to preserve</strong>:</p>
|
||
<ul>
|
||
<li>Block-to-block links (not just page-to-page)</li>
|
||
<li>UUID references</li>
|
||
</ul>
|
||
<h3 id="4-block-properties"><a class="header" href="#4-block-properties">4. Block Properties</a></h3>
|
||
<p><strong>Logseq</strong>:</p>
|
||
<pre><code class="language-markdown">- This is a block with properties
|
||
property1:: value1
|
||
property2:: value2
|
||
</code></pre>
|
||
<p><strong>KB needs to preserve</strong>:</p>
|
||
<ul>
|
||
<li>Custom key-value properties per block</li>
|
||
<li>Property inheritance/override</li>
|
||
</ul>
|
||
<h2 id="design-options"><a class="header" href="#design-options">Design Options</a></h2>
|
||
<h3 id="option-a-blocks-as-first-class-data-structure"><a class="header" href="#option-a-blocks-as-first-class-data-structure">Option A: Blocks as First-Class Data Structure</a></h3>
|
||
<p><strong>Add <code>blocks</code> field to Node</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub struct Node {
|
||
// ... existing fields ...
|
||
pub content: String, // Backward compat: flat markdown
|
||
pub blocks: Option<Vec<Block>>, // NEW: Structured blocks
|
||
}
|
||
|
||
pub struct Block {
|
||
pub id: String, // UUID or auto-generated
|
||
pub content: String, // Block text
|
||
pub properties: BlockProperties, // Tags, status, custom props
|
||
pub children: Vec<Block>, // Nested blocks
|
||
pub created: DateTime<Utc>,
|
||
pub modified: DateTime<Utc>,
|
||
}
|
||
|
||
pub struct BlockProperties {
|
||
pub tags: Vec<String>, // #card, #important
|
||
pub status: Option<TaskStatus>, // TODO, DONE, WAITING
|
||
pub custom: HashMap<String, String>, // property:: value
|
||
}
|
||
|
||
pub enum TaskStatus {
|
||
Todo,
|
||
Doing,
|
||
Done,
|
||
Waiting,
|
||
Cancelled,
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>✅ Type-safe, explicit structure</li>
|
||
<li>✅ Queryable (find all #card blocks)</li>
|
||
<li>✅ Preserves hierarchy</li>
|
||
<li>✅ Supports block-level operations</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>❌ Adds complexity to Node</li>
|
||
<li>❌ Dual representation (content + blocks)</li>
|
||
<li>❌ Requires migration of existing data</li>
|
||
</ul>
|
||
<h3 id="option-b-parser-only-approach"><a class="header" href="#option-b-parser-only-approach">Option B: Parser-Only Approach</a></h3>
|
||
<p><strong>Keep <code>content: String</code>, parse blocks on-demand</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub struct BlockParser;
|
||
|
||
impl BlockParser {
|
||
// Parse markdown content into block structure
|
||
fn parse(content: &str) -> Vec<Block>;
|
||
|
||
// Serialize blocks back to markdown
|
||
fn serialize(blocks: &[Block]) -> String;
|
||
}
|
||
|
||
// Usage
|
||
let blocks = BlockParser::parse(&node.content);
|
||
let filtered = blocks.iter().filter(|b| b.properties.tags.contains("card"));
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>✅ No schema changes</li>
|
||
<li>✅ Backward compatible</li>
|
||
<li>✅ Simple storage (still just String)</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>❌ Parse overhead on every access</li>
|
||
<li>❌ Can't query blocks in database (SurrealDB)</li>
|
||
<li>❌ Harder to index/search blocks</li>
|
||
</ul>
|
||
<h3 id="option-c-hybrid-approach-recommended"><a class="header" href="#option-c-hybrid-approach-recommended">Option C: Hybrid Approach (RECOMMENDED)</a></h3>
|
||
<p><strong>Combine both: structured storage + lazy parsing</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub struct Node {
|
||
// ... existing fields ...
|
||
pub content: String, // Source of truth (markdown)
|
||
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub blocks: Option<Vec<Block>>, // Cached structure (parsed)
|
||
}
|
||
|
||
impl Node {
|
||
// Parse blocks from content if not already cached
|
||
pub fn get_blocks(&mut self) -> &Vec<Block> {
|
||
if self.blocks.is_none() {
|
||
self.blocks = Some(BlockParser::parse(&self.content));
|
||
}
|
||
self.blocks.as_ref().unwrap()
|
||
}
|
||
|
||
// Update content from blocks (when blocks modified)
|
||
pub fn sync_blocks_to_content(&mut self) {
|
||
if let Some(ref blocks) = self.blocks {
|
||
self.content = BlockParser::serialize(blocks);
|
||
}
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Storage Strategy</strong>:</p>
|
||
<ol>
|
||
<li>
|
||
<p><strong>Filesystem</strong> - Store as markdown (Logseq compatible):</p>
|
||
<pre><code class="language-markdown">- Block 1 #card
|
||
- Nested block
|
||
- Block 2 TODO
|
||
</code></pre>
|
||
</li>
|
||
<li>
|
||
<p><strong>SurrealDB</strong> - Store both:</p>
|
||
<pre><code class="language-sql">DEFINE TABLE block SCHEMAFULL;
|
||
DEFINE FIELD node_id ON block TYPE record(node);
|
||
DEFINE FIELD block_id ON block TYPE string;
|
||
DEFINE FIELD content ON block TYPE string;
|
||
DEFINE FIELD properties ON block TYPE object;
|
||
DEFINE FIELD parent_id ON block TYPE option<string>;
|
||
|
||
-- Index for queries
|
||
DEFINE INDEX block_tags ON block COLUMNS properties.tags;
|
||
DEFINE INDEX block_status ON block COLUMNS properties.status;
|
||
</code></pre>
|
||
</li>
|
||
</ol>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>✅ Best of both worlds</li>
|
||
<li>✅ Filesystem stays Logseq-compatible</li>
|
||
<li>✅ SurrealDB can query blocks</li>
|
||
<li>✅ Lazy parsing (only when needed)</li>
|
||
<li>✅ Backward compatible</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>⚠️ Need to keep content/blocks in sync</li>
|
||
<li>⚠️ More complex implementation</li>
|
||
</ul>
|
||
<h2 id="recommended-implementation"><a class="header" href="#recommended-implementation">Recommended Implementation</a></h2>
|
||
<p><strong>Phase 1: Data Model</strong></p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// crates/kb-core/src/models/block.rs
|
||
|
||
use chrono::{DateTime, Utc};
|
||
use serde::{Deserialize, Serialize};
|
||
use std::collections::HashMap;
|
||
|
||
/// A content block (Logseq-style)
|
||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||
pub struct Block {
|
||
/// Unique block identifier (UUID)
|
||
pub id: String,
|
||
|
||
/// Block content (markdown text, excluding nested blocks)
|
||
pub content: String,
|
||
|
||
/// Block properties (tags, status, custom)
|
||
pub properties: BlockProperties,
|
||
|
||
/// Child blocks (nested hierarchy)
|
||
#[serde(default)]
|
||
pub children: Vec<Block>,
|
||
|
||
/// Creation timestamp
|
||
pub created: DateTime<Utc>,
|
||
|
||
/// Last modification timestamp
|
||
pub modified: DateTime<Utc>,
|
||
|
||
/// Parent block ID (if nested)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub parent_id: Option<String>,
|
||
}
|
||
|
||
/// Block-level properties
|
||
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
|
||
pub struct BlockProperties {
|
||
/// Tags (e.g., #card, #important)
|
||
#[serde(default)]
|
||
pub tags: Vec<String>,
|
||
|
||
/// Task status (TODO, DONE, etc.)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub status: Option<TaskStatus>,
|
||
|
||
/// Custom properties (property:: value)
|
||
#[serde(default)]
|
||
pub custom: HashMap<String, String>,
|
||
|
||
/// Block references ((uuid))
|
||
#[serde(default)]
|
||
pub block_refs: Vec<String>,
|
||
|
||
/// Page references ([[page]])
|
||
#[serde(default)]
|
||
pub page_refs: Vec<String>,
|
||
}
|
||
|
||
/// Task status for TODO blocks
|
||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
|
||
#[serde(rename_all = "UPPERCASE")]
|
||
pub enum TaskStatus {
|
||
Todo,
|
||
Doing,
|
||
Done,
|
||
Later,
|
||
Now,
|
||
Waiting,
|
||
Cancelled,
|
||
}
|
||
|
||
impl Block {
|
||
/// Create a new block with content
|
||
pub fn new(content: String) -> Self {
|
||
use uuid::Uuid;
|
||
Self {
|
||
id: Uuid::new_v4().to_string(),
|
||
content,
|
||
properties: BlockProperties::default(),
|
||
children: Vec::new(),
|
||
created: Utc::now(),
|
||
modified: Utc::now(),
|
||
parent_id: None,
|
||
}
|
||
}
|
||
|
||
/// Add a child block
|
||
pub fn add_child(&mut self, mut child: Block) {
|
||
child.parent_id = Some(self.id.clone());
|
||
self.children.push(child);
|
||
self.modified = Utc::now();
|
||
}
|
||
|
||
/// Add a tag to this block
|
||
pub fn add_tag(&mut self, tag: String) {
|
||
if !self.properties.tags.contains(&tag) {
|
||
self.properties.tags.push(tag);
|
||
self.modified = Utc::now();
|
||
}
|
||
}
|
||
|
||
/// Set task status
|
||
pub fn set_status(&mut self, status: TaskStatus) {
|
||
self.properties.status = Some(status);
|
||
self.modified = Utc::now();
|
||
}
|
||
|
||
/// Get all blocks (self + descendants) as flat list
|
||
pub fn flatten(&self) -> Vec<&Block> {
|
||
let mut result = vec![self];
|
||
for child in &self.children {
|
||
result.extend(child.flatten());
|
||
}
|
||
result
|
||
}
|
||
|
||
/// Find block by ID in tree
|
||
pub fn find(&self, id: &str) -> Option<&Block> {
|
||
if self.id == id {
|
||
return Some(self);
|
||
}
|
||
for child in &self.children {
|
||
if let Some(found) = child.find(id) {
|
||
return Some(found);
|
||
}
|
||
}
|
||
None
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Phase 2: Update Node Model</strong></p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// crates/kb-core/src/models.rs (modifications)
|
||
|
||
use crate::models::block::Block;
|
||
|
||
pub struct Node {
|
||
// ... existing fields ...
|
||
pub content: String,
|
||
|
||
/// Structured blocks (optional, parsed from content)
|
||
#[serde(skip_serializing_if = "Option::is_none")]
|
||
pub blocks: Option<Vec<Block>>,
|
||
}
|
||
|
||
impl Node {
|
||
/// Get blocks, parsing from content if needed
|
||
pub fn get_blocks(&mut self) -> Result<&Vec<Block>> {
|
||
if self.blocks.is_none() {
|
||
self.blocks = Some(crate::parser::BlockParser::parse(&self.content)?);
|
||
}
|
||
Ok(self.blocks.as_ref().unwrap())
|
||
}
|
||
|
||
/// Update content from blocks
|
||
pub fn sync_blocks_to_content(&mut self) {
|
||
if let Some(ref blocks) = self.blocks {
|
||
self.content = crate::parser::BlockParser::serialize(blocks);
|
||
}
|
||
}
|
||
|
||
/// Find all blocks with a specific tag
|
||
pub fn find_blocks_by_tag(&mut self, tag: &str) -> Result<Vec<&Block>> {
|
||
let blocks = self.get_blocks()?;
|
||
let mut result = Vec::new();
|
||
for block in blocks {
|
||
for b in block.flatten() {
|
||
if b.properties.tags.iter().any(|t| t == tag) {
|
||
result.push(b);
|
||
}
|
||
}
|
||
}
|
||
Ok(result)
|
||
}
|
||
|
||
/// Find all TODO blocks
|
||
pub fn find_todos(&mut self) -> Result<Vec<&Block>> {
|
||
let blocks = self.get_blocks()?;
|
||
let mut result = Vec::new();
|
||
for block in blocks {
|
||
for b in block.flatten() {
|
||
if matches!(b.properties.status, Some(TaskStatus::Todo)) {
|
||
result.push(b);
|
||
}
|
||
}
|
||
}
|
||
Ok(result)
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Phase 3: Block Parser</strong></p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// crates/kb-core/src/parser/block_parser.rs
|
||
|
||
use crate::models::block::{Block, BlockProperties, TaskStatus};
|
||
use regex::Regex;
|
||
|
||
pub struct BlockParser;
|
||
|
||
impl BlockParser {
|
||
/// Parse markdown content into block structure
|
||
///
|
||
/// Handles:
|
||
/// - Outliner format (- prefix with indentation)
|
||
/// - Tags (#card, #important)
|
||
/// - Task status (TODO, DONE)
|
||
/// - Properties (property:: value)
|
||
/// - Block references (((uuid)))
|
||
/// - Page references ([[page]])
|
||
pub fn parse(content: &str) -> Result<Vec<Block>> {
|
||
let mut blocks = Vec::new();
|
||
let mut stack: Vec<(usize, Block)> = Vec::new(); // (indent_level, block)
|
||
|
||
for line in content.lines() {
|
||
// Detect indentation level
|
||
let indent = count_indent(line);
|
||
let trimmed = line.trim_start();
|
||
|
||
// Skip empty lines
|
||
if trimmed.is_empty() {
|
||
continue;
|
||
}
|
||
|
||
// Parse block line
|
||
if let Some(block_content) = trimmed.strip_prefix("- ") {
|
||
let mut block = Self::parse_block_line(block_content)?;
|
||
|
||
// Pop stack until we find parent level
|
||
while let Some((level, _)) = stack.last() {
|
||
if *level < indent {
|
||
break;
|
||
}
|
||
stack.pop();
|
||
}
|
||
|
||
// Add as child to parent or as root
|
||
if let Some((_, parent)) = stack.last_mut() {
|
||
parent.add_child(block.clone());
|
||
} else {
|
||
blocks.push(block.clone());
|
||
}
|
||
|
||
stack.push((indent, block));
|
||
}
|
||
}
|
||
|
||
Ok(blocks)
|
||
}
|
||
|
||
/// Parse a single block line (after "- " prefix)
|
||
fn parse_block_line(line: &str) -> Result<Block> {
|
||
let mut block = Block::new(String::new());
|
||
let mut properties = BlockProperties::default();
|
||
|
||
// Extract task status (TODO, DONE, etc.)
|
||
let (status, remaining) = Self::extract_task_status(line);
|
||
properties.status = status;
|
||
|
||
// Extract tags (#card, #important)
|
||
let (tags, remaining) = Self::extract_tags(remaining);
|
||
properties.tags = tags;
|
||
|
||
// Extract properties (property:: value)
|
||
let (custom_props, remaining) = Self::extract_properties(remaining);
|
||
properties.custom = custom_props;
|
||
|
||
// Extract block references (((uuid)))
|
||
let (block_refs, remaining) = Self::extract_block_refs(remaining);
|
||
properties.block_refs = block_refs;
|
||
|
||
// Extract page references ([[page]])
|
||
let (page_refs, content) = Self::extract_page_refs(remaining);
|
||
properties.page_refs = page_refs;
|
||
|
||
block.content = content.trim().to_string();
|
||
block.properties = properties;
|
||
|
||
Ok(block)
|
||
}
|
||
|
||
/// Serialize blocks back to markdown
|
||
pub fn serialize(blocks: &[Block]) -> String {
|
||
let mut result = String::new();
|
||
for block in blocks {
|
||
Self::serialize_block(&mut result, block, 0);
|
||
}
|
||
result
|
||
}
|
||
|
||
fn serialize_block(output: &mut String, block: &Block, indent: usize) {
|
||
// Write indent
|
||
for _ in 0..indent {
|
||
output.push_str(" ");
|
||
}
|
||
|
||
// Write prefix
|
||
output.push_str("- ");
|
||
|
||
// Write task status
|
||
if let Some(status) = block.properties.status {
|
||
output.push_str(&format!("{:?} ", status).to_uppercase());
|
||
}
|
||
|
||
// Write content
|
||
output.push_str(&block.content);
|
||
|
||
// Write tags
|
||
for tag in &block.properties.tags {
|
||
output.push_str(&format!(" #{}", tag));
|
||
}
|
||
|
||
// Write properties
|
||
if !block.properties.custom.is_empty() {
|
||
output.push('\n');
|
||
for (key, value) in &block.properties.custom {
|
||
for _ in 0..=indent {
|
||
output.push_str(" ");
|
||
}
|
||
output.push_str(&format!("{}:: {}\n", key, value));
|
||
}
|
||
}
|
||
|
||
output.push('\n');
|
||
|
||
// Write children recursively
|
||
for child in &block.children {
|
||
Self::serialize_block(output, child, indent + 1);
|
||
}
|
||
}
|
||
|
||
// Helper methods for extraction
|
||
fn extract_task_status(line: &str) -> (Option<TaskStatus>, &str) {
|
||
let line = line.trim_start();
|
||
if let Some(rest) = line.strip_prefix("TODO ") {
|
||
(Some(TaskStatus::Todo), rest)
|
||
} else if let Some(rest) = line.strip_prefix("DONE ") {
|
||
(Some(TaskStatus::Done), rest)
|
||
} else if let Some(rest) = line.strip_prefix("DOING ") {
|
||
(Some(TaskStatus::Doing), rest)
|
||
} else if let Some(rest) = line.strip_prefix("LATER ") {
|
||
(Some(TaskStatus::Later), rest)
|
||
} else if let Some(rest) = line.strip_prefix("NOW ") {
|
||
(Some(TaskStatus::Now), rest)
|
||
} else if let Some(rest) = line.strip_prefix("WAITING ") {
|
||
(Some(TaskStatus::Waiting), rest)
|
||
} else if let Some(rest) = line.strip_prefix("CANCELLED ") {
|
||
(Some(TaskStatus::Cancelled), rest)
|
||
} else {
|
||
(None, line)
|
||
}
|
||
}
|
||
|
||
fn extract_tags(line: &str) -> (Vec<String>, String) {
|
||
let tag_regex = Regex::new(r"#(\w+)").unwrap();
|
||
let mut tags = Vec::new();
|
||
let mut result = line.to_string();
|
||
|
||
for cap in tag_regex.captures_iter(line) {
|
||
if let Some(tag) = cap.get(1) {
|
||
tags.push(tag.as_str().to_string());
|
||
result = result.replace(&format!("#{}", tag.as_str()), "");
|
||
}
|
||
}
|
||
|
||
(tags, result.trim().to_string())
|
||
}
|
||
|
||
fn extract_properties(line: &str) -> (HashMap<String, String>, String) {
|
||
let prop_regex = Regex::new(r"(\w+)::\s*([^\n]+)").unwrap();
|
||
let mut props = HashMap::new();
|
||
let mut result = line.to_string();
|
||
|
||
for cap in prop_regex.captures_iter(line) {
|
||
if let (Some(key), Some(value)) = (cap.get(1), cap.get(2)) {
|
||
props.insert(key.as_str().to_string(), value.as_str().trim().to_string());
|
||
result = result.replace(&cap[0], "");
|
||
}
|
||
}
|
||
|
||
(props, result.trim().to_string())
|
||
}
|
||
|
||
fn extract_block_refs(line: &str) -> (Vec<String>, String) {
|
||
let ref_regex = Regex::new(r"\(\(([^)]+)\)\)").unwrap();
|
||
let mut refs = Vec::new();
|
||
let mut result = line.to_string();
|
||
|
||
for cap in ref_regex.captures_iter(line) {
|
||
if let Some(uuid) = cap.get(1) {
|
||
refs.push(uuid.as_str().to_string());
|
||
result = result.replace(&cap[0], "");
|
||
}
|
||
}
|
||
|
||
(refs, result.trim().to_string())
|
||
}
|
||
|
||
fn extract_page_refs(line: &str) -> (Vec<String>, String) {
|
||
let page_regex = Regex::new(r"\[\[([^\]]+)\]\]").unwrap();
|
||
let mut pages = Vec::new();
|
||
let result = line.to_string();
|
||
|
||
for cap in page_regex.captures_iter(line) {
|
||
if let Some(page) = cap.get(1) {
|
||
pages.push(page.as_str().to_string());
|
||
// Keep [[page]] in content for now (backward compat)
|
||
}
|
||
}
|
||
|
||
(pages, result)
|
||
}
|
||
}
|
||
|
||
fn count_indent(line: &str) -> usize {
|
||
line.chars().take_while(|c| c.is_whitespace()).count() / 2
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Phase 4: Logseq Import/Export</strong></p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// crates/kb-core/src/logseq.rs
|
||
|
||
use crate::models::{Node, NodeType};
|
||
use crate::models::block::Block;
|
||
use crate::parser::BlockParser;
|
||
|
||
pub struct LogseqImporter;
|
||
|
||
impl LogseqImporter {
|
||
/// Import a Logseq page (markdown file) as a Node
|
||
pub fn import_page(path: &Path) -> Result<Node> {
|
||
let content = std::fs::read_to_string(path)?;
|
||
|
||
// Extract frontmatter if present
|
||
let (frontmatter, body) = Self::split_frontmatter(&content);
|
||
|
||
// Parse blocks from body
|
||
let blocks = BlockParser::parse(&body)?;
|
||
|
||
// Create node with blocks
|
||
let mut node = Node::new(NodeType::Note, Self::extract_title(path));
|
||
node.content = body;
|
||
node.blocks = Some(blocks);
|
||
|
||
// Apply frontmatter properties
|
||
if let Some(fm) = frontmatter {
|
||
Self::apply_frontmatter(&mut node, &fm)?;
|
||
}
|
||
|
||
Ok(node)
|
||
}
|
||
|
||
fn split_frontmatter(content: &str) -> (Option<String>, String) {
|
||
if content.starts_with("---\n") {
|
||
if let Some(end) = content[4..].find("\n---\n") {
|
||
let frontmatter = content[4..4 + end].to_string();
|
||
let body = content[4 + end + 5..].to_string();
|
||
return (Some(frontmatter), body);
|
||
}
|
||
}
|
||
(None, content.to_string())
|
||
}
|
||
|
||
fn extract_title(path: &Path) -> String {
|
||
path.file_stem()
|
||
.and_then(|s| s.to_str())
|
||
.unwrap_or("Untitled")
|
||
.to_string()
|
||
}
|
||
|
||
fn apply_frontmatter(node: &mut Node, frontmatter: &str) -> Result<()> {
|
||
// Parse YAML frontmatter and apply to node
|
||
// ... implementation ...
|
||
Ok(())
|
||
}
|
||
}
|
||
|
||
pub struct LogseqExporter;
|
||
|
||
impl LogseqExporter {
|
||
/// Export a Node to Logseq page format
|
||
pub fn export_page(node: &Node, path: &Path) -> Result<()> {
|
||
let mut output = String::new();
|
||
|
||
// Generate frontmatter
|
||
output.push_str("---\n");
|
||
output.push_str(&Self::generate_frontmatter(node)?);
|
||
output.push_str("---\n\n");
|
||
|
||
// Serialize blocks or use content
|
||
if let Some(ref blocks) = node.blocks {
|
||
output.push_str(&BlockParser::serialize(blocks));
|
||
} else {
|
||
output.push_str(&node.content);
|
||
}
|
||
|
||
std::fs::write(path, output)?;
|
||
Ok(())
|
||
}
|
||
|
||
fn generate_frontmatter(node: &Node) -> Result<String> {
|
||
let mut fm = String::new();
|
||
fm.push_str(&format!("title: {}\n", node.title));
|
||
fm.push_str(&format!("tags: {}\n", node.tags.join(", ")));
|
||
// ... more frontmatter fields ...
|
||
Ok(fm)
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="query-api-extensions"><a class="header" href="#query-api-extensions">Query API Extensions</a></h2>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// New methods in Graph or Query module
|
||
|
||
impl Graph {
|
||
/// Find all blocks with a specific tag across all nodes
|
||
pub fn find_blocks_by_tag(&mut self, tag: &str) -> Vec<(&Node, &Block)> {
|
||
let mut results = Vec::new();
|
||
for node in self.nodes.values_mut() {
|
||
if let Ok(blocks) = node.find_blocks_by_tag(tag) {
|
||
for block in blocks {
|
||
results.push((node as &Node, block));
|
||
}
|
||
}
|
||
}
|
||
results
|
||
}
|
||
|
||
/// Find all flashcards (#card blocks)
|
||
pub fn find_flashcards(&mut self) -> Vec<(&Node, &Block)> {
|
||
self.find_blocks_by_tag("card")
|
||
}
|
||
|
||
/// Find all TODO items across knowledge base
|
||
pub fn find_all_todos(&mut self) -> Vec<(&Node, &Block)> {
|
||
let mut results = Vec::new();
|
||
for node in self.nodes.values_mut() {
|
||
if let Ok(todos) = node.find_todos() {
|
||
for block in todos {
|
||
results.push((node as &Node, block));
|
||
}
|
||
}
|
||
}
|
||
results
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="mcp-tool-extensions"><a class="header" href="#mcp-tool-extensions">MCP Tool Extensions</a></h2>
|
||
<pre><code class="language-json">{
|
||
"name": "kogral/find_blocks",
|
||
"description": "Find blocks by tag, status, or properties",
|
||
"inputSchema": {
|
||
"type": "object",
|
||
"properties": {
|
||
"tag": { "type": "string", "description": "Filter by tag (e.g., 'card')" },
|
||
"status": { "type": "string", "enum": ["TODO", "DONE", "DOING"] },
|
||
"property": { "type": "string", "description": "Custom property key" },
|
||
"value": { "type": "string", "description": "Property value to match" }
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="configuration-1"><a class="header" href="#configuration-1">Configuration</a></h2>
|
||
<pre><code class="language-nickel"># schemas/kb/contracts.ncl (additions)
|
||
|
||
BlockConfig = {
|
||
enabled | Bool
|
||
| doc "Enable block-level parsing and storage"
|
||
| default = true,
|
||
|
||
preserve_hierarchy | Bool
|
||
| doc "Preserve block nesting on import/export"
|
||
| default = true,
|
||
|
||
parse_on_load | Bool
|
||
| doc "Automatically parse blocks when loading nodes"
|
||
| default = false, # Lazy parsing by default
|
||
|
||
supported_statuses | Array String
|
||
| doc "Supported task statuses"
|
||
| default = ["TODO", "DONE", "DOING", "LATER", "NOW", "WAITING", "CANCELLED"],
|
||
}
|
||
|
||
KbConfig = {
|
||
# ... existing fields ...
|
||
|
||
blocks | BlockConfig
|
||
| doc "Block-level features configuration"
|
||
| default = {},
|
||
}
|
||
</code></pre>
|
||
<h2 id="migration-path"><a class="header" href="#migration-path">Migration Path</a></h2>
|
||
<p><strong>Phase 1</strong>: Add Block models (no behavior change)
|
||
<strong>Phase 2</strong>: Add BlockParser (opt-in via config)
|
||
<strong>Phase 3</strong>: Update Logseq import/export
|
||
<strong>Phase 4</strong>: Add block queries to CLI/MCP
|
||
<strong>Phase 5</strong>: SurrealDB block indexing</p>
|
||
<p><strong>Backward Compatibility</strong>:</p>
|
||
<ul>
|
||
<li>Existing nodes without <code>blocks</code> field work as before</li>
|
||
<li><code>content</code> remains source of truth</li>
|
||
<li><code>blocks</code> is optional cache/structure</li>
|
||
<li>Config flag <code>blocks.enabled</code> to opt-in</li>
|
||
</ul>
|
||
<h2 id="testing-strategy-1"><a class="header" href="#testing-strategy-1">Testing Strategy</a></h2>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>#[cfg(test)]
|
||
mod tests {
|
||
use super::*;
|
||
|
||
#[test]
|
||
fn test_parse_simple_block() {
|
||
let content = "- This is a block #card";
|
||
let blocks = BlockParser::parse(content).unwrap();
|
||
|
||
assert_eq!(blocks.len(), 1);
|
||
assert_eq!(blocks[0].content, "This is a block");
|
||
assert_eq!(blocks[0].properties.tags, vec!["card"]);
|
||
}
|
||
|
||
#[test]
|
||
fn test_parse_nested_blocks() {
|
||
let content = r#"
|
||
- Parent block
|
||
- Child block 1
|
||
- Child block 2
|
||
- Grandchild
|
||
"#;
|
||
let blocks = BlockParser::parse(content).unwrap();
|
||
|
||
assert_eq!(blocks.len(), 1);
|
||
assert_eq!(blocks[0].children.len(), 2);
|
||
assert_eq!(blocks[0].children[1].children.len(), 1);
|
||
}
|
||
|
||
#[test]
|
||
fn test_parse_todo() {
|
||
let content = "- TODO Implement feature #rust";
|
||
let blocks = BlockParser::parse(content).unwrap();
|
||
|
||
assert_eq!(blocks[0].properties.status, Some(TaskStatus::Todo));
|
||
assert_eq!(blocks[0].content, "Implement feature");
|
||
assert_eq!(blocks[0].properties.tags, vec!["rust"]);
|
||
}
|
||
|
||
#[test]
|
||
fn test_roundtrip() {
|
||
let original = r#"- Block 1 #card
|
||
- Nested
|
||
- TODO Block 2
|
||
priority:: high
|
||
"#;
|
||
let blocks = BlockParser::parse(original).unwrap();
|
||
let serialized = BlockParser::serialize(&blocks);
|
||
let reparsed = BlockParser::parse(&serialized).unwrap();
|
||
|
||
assert_eq!(blocks.len(), reparsed.len());
|
||
assert_eq!(blocks[0].properties, reparsed[0].properties);
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="summary-1"><a class="header" href="#summary-1">Summary</a></h2>
|
||
<p><strong>Recommended Approach</strong>: Hybrid (Option C)</p>
|
||
<ul>
|
||
<li><strong>Add</strong> <code>Block</code> struct with properties, hierarchy</li>
|
||
<li><strong>Extend</strong> <code>Node</code> with optional <code>blocks: Option<Vec<Block>></code></li>
|
||
<li><strong>Implement</strong> bidirectional parser (markdown ↔ blocks)</li>
|
||
<li><strong>Preserve</strong> <code>content</code> as source of truth (backward compat)</li>
|
||
<li><strong>Enable</strong> block queries in CLI/MCP</li>
|
||
<li><strong>Support</strong> round-trip Logseq import/export</li>
|
||
</ul>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>✅ Full Logseq compatibility</li>
|
||
<li>✅ Queryable blocks (find #card, TODO, etc.)</li>
|
||
<li>✅ Backward compatible</li>
|
||
<li>✅ Extensible (custom properties)</li>
|
||
<li>✅ Type-safe structure</li>
|
||
</ul>
|
||
<p><strong>Trade-offs</strong>:</p>
|
||
<ul>
|
||
<li>⚠️ Added complexity</li>
|
||
<li>⚠️ Need to sync content ↔ blocks</li>
|
||
<li>⚠️ More storage for SurrealDB backend</li>
|
||
</ul>
|
||
<p><strong>Next Steps</strong>:</p>
|
||
<ol>
|
||
<li>Review and approve design</li>
|
||
<li>Implement Phase 1 (Block models)</li>
|
||
<li>Implement Phase 2 (BlockParser)</li>
|
||
<li>Update Logseq import/export</li>
|
||
<li>Add block queries to MCP/CLI</li>
|
||
</ol>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-001-nickel-vs-toml-for-configuration"><a class="header" href="#adr-001-nickel-vs-toml-for-configuration">ADR-001: Nickel vs TOML for Configuration</a></h1>
|
||
<p><strong>Status</strong>: Accepted</p>
|
||
<p><strong>Date</strong>: 2026-01-17</p>
|
||
<p><strong>Deciders</strong>: Architecture Team</p>
|
||
<p><strong>Context</strong>: Configuration Strategy for Knowledge Base System</p>
|
||
<hr />
|
||
<h2 id="context"><a class="header" href="#context">Context</a></h2>
|
||
<p>The KOGRAL requires a flexible, type-safe configuration format that supports:</p>
|
||
<ol>
|
||
<li><strong>Complex nested structures</strong> (graph settings, storage configs, embedding providers)</li>
|
||
<li><strong>Type validation</strong> (prevent runtime errors from config mistakes)</li>
|
||
<li><strong>Composition and inheritance</strong> (shared configs, environment-specific overrides)</li>
|
||
<li><strong>Documentation</strong> (self-documenting schemas)</li>
|
||
<li><strong>Validation before runtime</strong> (catch errors early)</li>
|
||
</ol>
|
||
<p>We evaluated two primary options:</p>
|
||
<h3 id="option-1-toml-traditional-config-format"><a class="header" href="#option-1-toml-traditional-config-format">Option 1: TOML (Traditional Config Format)</a></h3>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>Widely adopted in Rust ecosystem (<code>Cargo.toml</code>)</li>
|
||
<li>Simple, human-readable syntax</li>
|
||
<li>Native <code>serde</code> support</li>
|
||
<li>IDE support (syntax highlighting, completion)</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>No type system (validation only at runtime)</li>
|
||
<li>Limited composition (no imports, no functions)</li>
|
||
<li>No schema validation (errors discovered during execution)</li>
|
||
<li>Verbose for complex nested structures</li>
|
||
<li>No documentation in config files</li>
|
||
</ul>
|
||
<p><strong>Example TOML</strong>:</p>
|
||
<pre><code class="language-toml">[graph]
|
||
name = "my-project"
|
||
version = "1.0.0"
|
||
|
||
[storage]
|
||
primary = "filesystem" # String, not validated as enum
|
||
|
||
[storage.secondary]
|
||
enabled = true
|
||
type = "surrealdb" # Typo would fail at runtime
|
||
url = "ws://localhost:8000"
|
||
|
||
[embeddings]
|
||
enabled = true
|
||
provider = "openai" # No validation of valid providers
|
||
model = "text-embedding-3-small"
|
||
</code></pre>
|
||
<p><strong>Problems</strong>:</p>
|
||
<ul>
|
||
<li>Typos in enum values (<code>"surrealdb"</code> vs <code>"surealdb"</code>) fail at runtime</li>
|
||
<li>No validation that <code>provider = "openai"</code> requires <code>api_key_env</code></li>
|
||
<li>No documentation of valid options</li>
|
||
<li>No way to compose configs (e.g., base config + environment override)</li>
|
||
</ul>
|
||
<h3 id="option-2-nickel-functional-configuration-language"><a class="header" href="#option-2-nickel-functional-configuration-language">Option 2: Nickel (Functional Configuration Language)</a></h3>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li><strong>Type system</strong> with contracts (validate before runtime)</li>
|
||
<li><strong>Composition</strong> via imports and merging</li>
|
||
<li><strong>Documentation</strong> in schemas (self-documenting)</li>
|
||
<li><strong>Validation</strong> at export time (catch errors early)</li>
|
||
<li><strong>Functions</strong> for conditional logic</li>
|
||
<li><strong>Default values</strong> in schema definitions</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>Less familiar to Rust developers</li>
|
||
<li>Requires separate <code>nickel</code> CLI tool</li>
|
||
<li>Smaller ecosystem</li>
|
||
<li>Steeper learning curve</li>
|
||
</ul>
|
||
<p><strong>Example Nickel</strong>:</p>
|
||
<pre><code class="language-nickel"># schemas/kb-config.ncl
|
||
{
|
||
KbConfig = {
|
||
graph | GraphConfig,
|
||
storage | StorageConfig,
|
||
embeddings | EmbeddingConfig,
|
||
},
|
||
|
||
StorageConfig = {
|
||
primary | [| 'filesystem, 'memory |], # Enum validated at export
|
||
|
||
secondary | {
|
||
enabled | Bool,
|
||
type | [| 'surrealdb, 'sqlite |], # Typos caught immediately
|
||
url | String,
|
||
} | optional,
|
||
},
|
||
|
||
EmbeddingConfig = {
|
||
enabled | Bool,
|
||
provider | [| 'openai, 'claude, 'fastembed |], # Valid providers enforced
|
||
model | String,
|
||
api_key_env | String | doc "Environment variable for API key",
|
||
},
|
||
}
|
||
</code></pre>
|
||
<p><strong>Benefits</strong>:</p>
|
||
<ul>
|
||
<li>Typos in enum values caught at <code>nickel export</code> time</li>
|
||
<li>Schema enforces required fields based on provider</li>
|
||
<li>Documentation embedded in schema</li>
|
||
<li>Config can be composed: <code>import "base.ncl" & { /* overrides */ }</code></li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="decision"><a class="header" href="#decision">Decision</a></h2>
|
||
<p><strong>We will use Nickel for configuration.</strong></p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<ol>
|
||
<li>Define schemas in <code>schemas/*.ncl</code> with type contracts</li>
|
||
<li>Users write configs in <code>.kogral/config.ncl</code></li>
|
||
<li>Export to JSON via CLI: <code>nickel export --format json config.ncl</code></li>
|
||
<li>Load JSON in Rust via <code>serde_json</code> into typed structs</li>
|
||
</ol>
|
||
<p><strong>Pattern</strong> (double validation):</p>
|
||
<pre><code>Nickel Config (.ncl)
|
||
↓ [nickel export]
|
||
JSON (validated by Nickel contracts)
|
||
↓ [serde_json::from_str]
|
||
Rust Struct (validated by serde)
|
||
↓
|
||
Runtime (guaranteed valid config)
|
||
</code></pre>
|
||
<p><strong>Bridge Code</strong> (<code>kb-core/src/config/nickel.rs</code>):</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub fn load_config<P: AsRef<Path>>(path: P) -> Result<KbConfig> {
|
||
// Export Nickel to JSON
|
||
let json = export_nickel_to_json(path)?;
|
||
|
||
// Deserialize to Rust struct
|
||
let config: KbConfig = serde_json::from_str(&json)?;
|
||
|
||
Ok(config)
|
||
}
|
||
|
||
fn export_nickel_to_json<P: AsRef<Path>>(path: P) -> Result<String> {
|
||
let output = Command::new("nickel")
|
||
.arg("export")
|
||
.arg("--format").arg("json")
|
||
.arg(path.as_ref())
|
||
.output()?;
|
||
|
||
Ok(String::from_utf8(output.stdout)?)
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<hr />
|
||
<h2 id="consequences"><a class="header" href="#consequences">Consequences</a></h2>
|
||
<h3 id="positive"><a class="header" href="#positive">Positive</a></h3>
|
||
<p>✅ <strong>Type Safety</strong>: Config errors caught before runtime</p>
|
||
<ul>
|
||
<li>Invalid enum values fail at export: <code>'filesystm</code> → error</li>
|
||
<li>Missing required fields detected: no <code>graph.name</code> → error</li>
|
||
<li>Type mismatches prevented: <code>enabled = "yes"</code> → error (expects Bool)</li>
|
||
</ul>
|
||
<p>✅ <strong>Self-Documenting</strong>: Schemas serve as documentation</p>
|
||
<ul>
|
||
<li><code>| doc "Environment variable for API key"</code> describes fields</li>
|
||
<li>Enum options visible in schema: <code>[| 'openai, 'claude, 'fastembed |]</code></li>
|
||
<li>Default values explicit: <code>| default = 'filesystem</code></li>
|
||
</ul>
|
||
<p>✅ <strong>Composition</strong>: Config reuse and overrides</p>
|
||
<pre><code class="language-nickel"># base.ncl
|
||
{ graph = { version = "1.0.0" } }
|
||
|
||
# project.ncl
|
||
import "base.ncl" & { graph = { name = "my-project" } }
|
||
</code></pre>
|
||
<p>✅ <strong>Validation Before Deployment</strong>: Catch errors in CI</p>
|
||
<pre><code class="language-bash"># CI pipeline
|
||
nickel typecheck config.ncl
|
||
nickel export --format json config.ncl > /dev/null
|
||
</code></pre>
|
||
<p>✅ <strong>Conditional Logic</strong>: Environment-specific configs</p>
|
||
<pre><code class="language-nickel">let is_prod = std.string.is_match "prod" (std.env.get "ENV") in
|
||
{
|
||
embeddings = {
|
||
provider = if is_prod then 'openai else 'fastembed,
|
||
},
|
||
}
|
||
</code></pre>
|
||
<h3 id="negative"><a class="header" href="#negative">Negative</a></h3>
|
||
<p>❌ <strong>Learning Curve</strong>: Team must learn Nickel syntax</p>
|
||
<ul>
|
||
<li><strong>Mitigation</strong>: Provide comprehensive examples in <code>config/</code> directory</li>
|
||
<li><strong>Mitigation</strong>: Document common patterns in <code>docs/config/</code></li>
|
||
</ul>
|
||
<p>❌ <strong>Tool Dependency</strong>: Requires <code>nickel</code> CLI installed</p>
|
||
<ul>
|
||
<li><strong>Mitigation</strong>: Document installation in setup guide</li>
|
||
<li><strong>Mitigation</strong>: Check <code>nickel</code> availability in <code>kb init</code> command</li>
|
||
</ul>
|
||
<p>❌ <strong>IDE Support</strong>: Limited compared to TOML</p>
|
||
<ul>
|
||
<li><strong>Mitigation</strong>: Use LSP (nickel-lang-lsp) for VSCode/Neovim</li>
|
||
<li><strong>Mitigation</strong>: Syntax highlighting available for major editors</li>
|
||
</ul>
|
||
<p>❌ <strong>Ecosystem Size</strong>: Smaller than TOML</p>
|
||
<ul>
|
||
<li><strong>Mitigation</strong>: Nickel actively developed by Tweag</li>
|
||
<li><strong>Mitigation</strong>: Stable language specification (v1.0+)</li>
|
||
</ul>
|
||
<h3 id="neutral"><a class="header" href="#neutral">Neutral</a></h3>
|
||
<p>⚪ <strong>Two-Stage Loading</strong>: Nickel → JSON → Rust</p>
|
||
<ul>
|
||
<li>Not a performance concern (config loaded once at startup)</li>
|
||
<li>Adds resilience (double validation)</li>
|
||
<li>Allows runtime config inspection (read JSON directly)</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="alternatives-considered"><a class="header" href="#alternatives-considered">Alternatives Considered</a></h2>
|
||
<h3 id="json-schema"><a class="header" href="#json-schema">JSON Schema</a></h3>
|
||
<p><strong>Rejected</strong>: Not ergonomic for humans to write</p>
|
||
<ul>
|
||
<li>No comments</li>
|
||
<li>Verbose syntax (<code>{"key": "value"}</code> vs <code>key = value</code>)</li>
|
||
<li>JSON Schema separate from config (duplication)</li>
|
||
</ul>
|
||
<h3 id="yaml"><a class="header" href="#yaml">YAML</a></h3>
|
||
<p><strong>Rejected</strong>: No type system, ambiguous parsing</p>
|
||
<ul>
|
||
<li>Boolean confusion: <code>yes</code>/<code>no</code>/<code>on</code>/<code>off</code>/<code>true</code>/<code>false</code></li>
|
||
<li>Indentation-sensitive (error-prone)</li>
|
||
<li>No validation without external tools</li>
|
||
</ul>
|
||
<h3 id="dhall"><a class="header" href="#dhall">Dhall</a></h3>
|
||
<p><strong>Rejected</strong>: More complex than needed</p>
|
||
<ul>
|
||
<li>Turing-incomplete by design (limits use cases)</li>
|
||
<li>Smaller ecosystem than Nickel</li>
|
||
<li>Steeper learning curve</li>
|
||
</ul>
|
||
<h3 id="kcl-kusionstack-configuration-language"><a class="header" href="#kcl-kusionstack-configuration-language">KCL (KusionStack Configuration Language)</a></h3>
|
||
<p><strong>Rejected</strong>: Kubernetes-focused, less general-purpose</p>
|
||
<ul>
|
||
<li>Designed for K8s manifests</li>
|
||
<li>Less mature than Nickel for general config</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="implementation-timeline"><a class="header" href="#implementation-timeline">Implementation Timeline</a></h2>
|
||
<ol>
|
||
<li>✅ Define base schemas (<code>schemas/kb-config.ncl</code>)</li>
|
||
<li>✅ Implement Nickel loader (<code>kb-core/src/config/nickel.rs</code>)</li>
|
||
<li>✅ Create example configs (<code>config/defaults.ncl</code>, <code>config/production.ncl</code>)</li>
|
||
<li>✅ Document Nickel usage (<code>docs/config/nickel-schemas.md</code>)</li>
|
||
<li>⏳ Add LSP recommendations to setup guide</li>
|
||
<li>⏳ Create Nickel → TOML migration tool (for existing users)</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="monitoring"><a class="header" href="#monitoring">Monitoring</a></h2>
|
||
<p><strong>Success Criteria</strong>:</p>
|
||
<ul>
|
||
<li>Config errors caught at export time (not runtime)</li>
|
||
<li>Users can compose configs for different environments</li>
|
||
<li>Team comfortable with Nickel syntax within 2 weeks</li>
|
||
</ul>
|
||
<p><strong>Metrics</strong>:</p>
|
||
<ul>
|
||
<li>Number of config validation errors caught before runtime</li>
|
||
<li>Time to diagnose config issues (should decrease)</li>
|
||
<li>User feedback on config complexity</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="references"><a class="header" href="#references">References</a></h2>
|
||
<ul>
|
||
<li><a href="https://nickel-lang.org/">Nickel Language</a></li>
|
||
<li><a href="https://nickel-lang.org/user-manual/introduction">Nickel User Manual</a></li>
|
||
<li><a href="architecture/adrs/../../crates/kb-core/src/config/README.html">platform-config pattern</a> (reference implementation)</li>
|
||
<li><a href="https://toml.io/">TOML Specification</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="revision-history"><a class="header" href="#revision-history">Revision History</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Date</th><th>Author</th><th>Change</th></tr></thead><tbody>
|
||
<tr><td>2026-01-17</td><td>Architecture Team</td><td>Initial decision</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<hr />
|
||
<p><strong>Next ADR</strong>: <a href="architecture/adrs/002-fastembed-ai-providers.html">ADR-002: FastEmbed via AI Providers</a></p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-002-fastembed-via-ai-providers-for-embeddings"><a class="header" href="#adr-002-fastembed-via-ai-providers-for-embeddings">ADR-002: FastEmbed via AI Providers for Embeddings</a></h1>
|
||
<p><strong>Status</strong>: Accepted</p>
|
||
<p><strong>Date</strong>: 2026-01-17</p>
|
||
<p><strong>Deciders</strong>: Architecture Team</p>
|
||
<p><strong>Context</strong>: Embedding Strategy for Semantic Search</p>
|
||
<hr />
|
||
<h2 id="context-1"><a class="header" href="#context-1">Context</a></h2>
|
||
<p>The KOGRAL requires embedding generation for semantic search capabilities. Embeddings convert text into numerical vectors that capture semantic meaning, enabling "find concepts" rather than just "find keywords".</p>
|
||
<p><strong>Requirements</strong>:</p>
|
||
<ol>
|
||
<li><strong>Local-First Option</strong>: Must work offline without external API dependencies</li>
|
||
<li><strong>Production Scalability</strong>: Support cloud AI providers for large-scale deployments</li>
|
||
<li><strong>Multiple Providers</strong>: Flexibility to choose based on cost, quality, privacy</li>
|
||
<li><strong>Cost-Effective Development</strong>: Free local embeddings for development and testing</li>
|
||
<li><strong>Quality</strong>: Good enough embeddings for finding related concepts</li>
|
||
</ol>
|
||
<p><strong>Options Evaluated</strong>:</p>
|
||
<h3 id="option-1-only-local-embeddings-fastembed"><a class="header" href="#option-1-only-local-embeddings-fastembed">Option 1: Only Local Embeddings (fastembed)</a></h3>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>No API costs</li>
|
||
<li>Works offline</li>
|
||
<li>Privacy-preserving (no data leaves machine)</li>
|
||
<li>Fast (local GPU acceleration possible)</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>Limited model quality compared to cloud providers</li>
|
||
<li>Resource-intensive (requires download ~100MB models)</li>
|
||
<li>Single provider lock-in (fastembed library)</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>use fastembed::{TextEmbedding, InitOptions};
|
||
|
||
let model = TextEmbedding::try_new(InitOptions {
|
||
model_name: "BAAI/bge-small-en-v1.5",
|
||
..Default::default()
|
||
})?;
|
||
|
||
let embeddings = model.embed(vec!["Hello world"], None)?;
|
||
// Output: Vec<Vec<f32>> with 384 dimensions
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="option-2-only-cloud-ai-providers-openai-claude-etc"><a class="header" href="#option-2-only-cloud-ai-providers-openai-claude-etc">Option 2: Only Cloud AI Providers (OpenAI, Claude, etc.)</a></h3>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>State-of-the-art embedding quality</li>
|
||
<li>No local resource usage</li>
|
||
<li>Latest models available</li>
|
||
<li>Scalable to millions of documents</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>Requires API keys (cost per embedding)</li>
|
||
<li>Network dependency (no offline mode)</li>
|
||
<li>Privacy concerns (data sent to third parties)</li>
|
||
<li>Vendor lock-in risk</li>
|
||
</ul>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>use rig::providers::openai;
|
||
|
||
let client = openai::Client::new("sk-...");
|
||
let embeddings = client.embeddings("text-embedding-3-small")
|
||
.embed_documents(vec!["Hello world"]).await?;
|
||
// Output: Vec<Vec<f32>> with 1536 dimensions
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="option-3-hybrid-strategy-fastembed--ai-providers-via-rig-core"><a class="header" href="#option-3-hybrid-strategy-fastembed--ai-providers-via-rig-core">Option 3: Hybrid Strategy (fastembed + AI providers via rig-core)</a></h3>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>✅ Best of both worlds: local dev, cloud production</li>
|
||
<li>✅ User choice: privacy-first or quality-first</li>
|
||
<li>✅ Cost flexibility: free for small projects, paid for scale</li>
|
||
<li>✅ Unified interface via <code>rig-core</code> library</li>
|
||
<li>✅ Easy provider switching (config-driven)</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>❌ More complex implementation (multiple providers)</li>
|
||
<li>❌ Dimension mismatch between providers (384 vs 1536)</li>
|
||
<li>❌ Additional dependencies (<code>rig-core</code>, <code>fastembed</code>)</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="decision-1"><a class="header" href="#decision-1">Decision</a></h2>
|
||
<p><strong>We will use a hybrid strategy: fastembed (local) + AI providers (via rig-core).</strong></p>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<ol>
|
||
<li><strong>Default</strong>: <code>fastembed</code> with <code>BAAI/bge-small-en-v1.5</code> (384 dimensions)</li>
|
||
<li><strong>Optional</strong>: OpenAI, Claude, Ollama via <code>rig-core</code> (configurable)</li>
|
||
<li><strong>Interface</strong>: <code>EmbeddingProvider</code> trait abstracts provider details</li>
|
||
<li><strong>Config-Driven</strong>: Provider selection via Nickel configuration</li>
|
||
</ol>
|
||
<p><strong>Architecture</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>#[async_trait]
|
||
pub trait EmbeddingProvider: Send + Sync {
|
||
async fn embed(&self, texts: Vec<String>) -> Result<Vec<Vec<f32>>>;
|
||
fn dimensions(&self) -> usize;
|
||
fn model_name(&self) -> &str;
|
||
}
|
||
|
||
// Local implementation
|
||
pub struct FastEmbedProvider {
|
||
model: TextEmbedding,
|
||
}
|
||
|
||
impl FastEmbedProvider {
|
||
pub fn new(model_name: &str) -> Result<Self> {
|
||
let model = TextEmbedding::try_new(InitOptions {
|
||
model_name: model_name.into(),
|
||
..Default::default()
|
||
})?;
|
||
Ok(Self { model })
|
||
}
|
||
}
|
||
|
||
#[async_trait]
|
||
impl EmbeddingProvider for FastEmbedProvider {
|
||
async fn embed(&self, texts: Vec<String>) -> Result<Vec<Vec<f32>>> {
|
||
Ok(self.model.embed(texts, None)?)
|
||
}
|
||
|
||
fn dimensions(&self) -> usize { 384 }
|
||
fn model_name(&self) -> &str { "BAAI/bge-small-en-v1.5" }
|
||
}
|
||
|
||
// Cloud provider implementation (via rig-core)
|
||
pub struct RigEmbeddingProvider {
|
||
client: rig::Client,
|
||
model: String,
|
||
dimensions: usize,
|
||
}
|
||
|
||
#[async_trait]
|
||
impl EmbeddingProvider for RigEmbeddingProvider {
|
||
async fn embed(&self, texts: Vec<String>) -> Result<Vec<Vec<f32>>> {
|
||
let embeddings = self.client
|
||
.embeddings(&self.model)
|
||
.embed_documents(texts)
|
||
.await?;
|
||
Ok(embeddings)
|
||
}
|
||
|
||
fn dimensions(&self) -> usize { self.dimensions }
|
||
fn model_name(&self) -> &str { &self.model }
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p><strong>Configuration</strong> (Nickel):</p>
|
||
<pre><code class="language-nickel"># Local development (default)
|
||
{
|
||
embeddings = {
|
||
enabled = true,
|
||
provider = 'fastembed,
|
||
model = "BAAI/bge-small-en-v1.5",
|
||
dimensions = 384,
|
||
},
|
||
}
|
||
|
||
# Production with OpenAI
|
||
{
|
||
embeddings = {
|
||
enabled = true,
|
||
provider = 'openai,
|
||
model = "text-embedding-3-small",
|
||
dimensions = 1536,
|
||
api_key_env = "OPENAI_API_KEY",
|
||
},
|
||
}
|
||
|
||
# Self-hosted with Ollama
|
||
{
|
||
embeddings = {
|
||
enabled = true,
|
||
provider = 'ollama,
|
||
model = "nomic-embed-text",
|
||
dimensions = 768,
|
||
},
|
||
}
|
||
</code></pre>
|
||
<p><strong>Provider Selection</strong> (<code>kb-core/src/embeddings/mod.rs</code>):</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub fn create_provider(config: &EmbeddingConfig) -> Result<Box<dyn EmbeddingProvider>> {
|
||
match config.provider {
|
||
EmbeddingProviderType::FastEmbed => {
|
||
Ok(Box::new(FastEmbedProvider::new(&config.model)?))
|
||
}
|
||
EmbeddingProviderType::OpenAI => {
|
||
let api_key = std::env::var(&config.api_key_env)?;
|
||
Ok(Box::new(RigEmbeddingProvider::new_openai(api_key, &config.model)?))
|
||
}
|
||
EmbeddingProviderType::Claude => {
|
||
let api_key = std::env::var(&config.api_key_env)?;
|
||
Ok(Box::new(RigEmbeddingProvider::new_claude(api_key, &config.model)?))
|
||
}
|
||
EmbeddingProviderType::Ollama => {
|
||
Ok(Box::new(RigEmbeddingProvider::new_ollama(&config.model)?))
|
||
}
|
||
}
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<hr />
|
||
<h2 id="consequences-1"><a class="header" href="#consequences-1">Consequences</a></h2>
|
||
<h3 id="positive-1"><a class="header" href="#positive-1">Positive</a></h3>
|
||
<p>✅ <strong>Development Flexibility</strong>:</p>
|
||
<ul>
|
||
<li>Developers can use <code>fastembed</code> without API keys</li>
|
||
<li>Fast feedback loop (local embeddings, no network calls)</li>
|
||
<li>Works offline (train trips, flights)</li>
|
||
</ul>
|
||
<p>✅ <strong>Production Quality</strong>:</p>
|
||
<ul>
|
||
<li>Production deployments can use OpenAI/Claude for better quality</li>
|
||
<li>Latest embedding models available</li>
|
||
<li>Scalable to millions of documents</li>
|
||
</ul>
|
||
<p>✅ <strong>Privacy Control</strong>:</p>
|
||
<ul>
|
||
<li>Privacy-sensitive projects use local embeddings</li>
|
||
<li>Public projects can use cloud providers</li>
|
||
<li>User choice via configuration</li>
|
||
</ul>
|
||
<p>✅ <strong>Cost Optimization</strong>:</p>
|
||
<ul>
|
||
<li>Small projects: free (fastembed)</li>
|
||
<li>Large projects: pay for quality (cloud providers)</li>
|
||
<li>Hybrid: important docs via cloud, bulk via local</li>
|
||
</ul>
|
||
<p>✅ <strong>Unified Interface</strong>:</p>
|
||
<ul>
|
||
<li><code>EmbeddingProvider</code> trait abstracts provider details</li>
|
||
<li>Query code doesn't know/care about provider</li>
|
||
<li>Easy to add new providers</li>
|
||
</ul>
|
||
<h3 id="negative-1"><a class="header" href="#negative-1">Negative</a></h3>
|
||
<p>❌ <strong>Dimension Mismatch</strong>:</p>
|
||
<ul>
|
||
<li>fastembed: 384 dimensions</li>
|
||
<li>OpenAI: 1536 dimensions</li>
|
||
<li>Cannot mix in same index</li>
|
||
</ul>
|
||
<p><strong>Mitigation</strong>:</p>
|
||
<ul>
|
||
<li>Store provider + dimensions in node metadata</li>
|
||
<li>Rebuild index when changing providers</li>
|
||
<li>Document dimension constraints</li>
|
||
</ul>
|
||
<p>❌ <strong>Model Download</strong>:</p>
|
||
<ul>
|
||
<li>First use of fastembed downloads ~100MB model</li>
|
||
<li>Slow initial startup</li>
|
||
</ul>
|
||
<p><strong>Mitigation</strong>:</p>
|
||
<ul>
|
||
<li>Pre-download in Docker images</li>
|
||
<li>Document model download in setup guide</li>
|
||
<li>Cache models in <code>~/.cache/fastembed</code></li>
|
||
</ul>
|
||
<p>❌ <strong>Complex Configuration</strong>:</p>
|
||
<ul>
|
||
<li>Multiple provider options may confuse users</li>
|
||
</ul>
|
||
<p><strong>Mitigation</strong>:</p>
|
||
<ul>
|
||
<li>Sane default (fastembed)</li>
|
||
<li>Clear examples for each provider</li>
|
||
<li>Validation errors explain misconfigurations</li>
|
||
</ul>
|
||
<h3 id="neutral-1"><a class="header" href="#neutral-1">Neutral</a></h3>
|
||
<p>⚪ <strong>Dependency Trade-off</strong>:</p>
|
||
<ul>
|
||
<li><code>fastembed</code> adds ~5MB to binary</li>
|
||
<li><code>rig-core</code> adds ~2MB</li>
|
||
<li>Total: ~7MB overhead</li>
|
||
</ul>
|
||
<p>Not a concern for CLI/MCP server use case.</p>
|
||
<hr />
|
||
<h2 id="provider-comparison"><a class="header" href="#provider-comparison">Provider Comparison</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Provider</th><th>Dimensions</th><th>Quality</th><th>Cost</th><th>Privacy</th><th>Offline</th></tr></thead><tbody>
|
||
<tr><td><strong>fastembed</strong></td><td>384</td><td>Good</td><td>Free</td><td>✅ Local</td><td>✅ Yes</td></tr>
|
||
<tr><td><strong>OpenAI</strong></td><td>1536</td><td>Excellent</td><td>$0.0001/1K</td><td>❌ Cloud</td><td>❌ No</td></tr>
|
||
<tr><td><strong>Claude</strong></td><td>1024</td><td>Excellent</td><td>$0.00025/1K</td><td>❌ Cloud</td><td>❌ No</td></tr>
|
||
<tr><td><strong>Ollama</strong></td><td>768</td><td>Very Good</td><td>Free</td><td>✅ Local</td><td>✅ Yes</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<p><strong>Recommendation by Use Case</strong>:</p>
|
||
<ul>
|
||
<li><strong>Development</strong>: fastembed (fast, free, offline)</li>
|
||
<li><strong>Small Teams</strong>: fastembed or Ollama (privacy, no costs)</li>
|
||
<li><strong>Enterprise</strong>: OpenAI or Claude (best quality, scalable)</li>
|
||
<li><strong>Self-Hosted</strong>: Ollama (good quality, local control)</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="implementation-timeline-1"><a class="header" href="#implementation-timeline-1">Implementation Timeline</a></h2>
|
||
<ol>
|
||
<li>✅ Define <code>EmbeddingProvider</code> trait</li>
|
||
<li>✅ Implement FastEmbedProvider (stub, feature-gated)</li>
|
||
<li>✅ Implement RigEmbeddingProvider (stub, feature-gated)</li>
|
||
<li>⏳ Complete FastEmbed integration with model download</li>
|
||
<li>⏳ Complete rig-core integration (OpenAI, Claude, Ollama)</li>
|
||
<li>⏳ Add query engine with similarity search</li>
|
||
<li>⏳ Document provider selection and trade-offs</li>
|
||
</ol>
|
||
<hr />
|
||
<h2 id="monitoring-1"><a class="header" href="#monitoring-1">Monitoring</a></h2>
|
||
<p><strong>Success Criteria</strong>:</p>
|
||
<ul>
|
||
<li>Users can switch providers via config change</li>
|
||
<li>Local embeddings work without API keys</li>
|
||
<li>Production deployments use cloud providers successfully</li>
|
||
<li>Query quality acceptable for both local and cloud embeddings</li>
|
||
</ul>
|
||
<p><strong>Metrics</strong>:</p>
|
||
<ul>
|
||
<li>Embedding generation latency (local vs cloud)</li>
|
||
<li>Query accuracy (precision@10 for semantic search)</li>
|
||
<li>API costs (cloud providers)</li>
|
||
<li>User satisfaction (feedback on search quality)</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="references-1"><a class="header" href="#references-1">References</a></h2>
|
||
<ul>
|
||
<li><a href="https://github.com/Anush008/fastembed-rs">fastembed Documentation</a></li>
|
||
<li><a href="https://github.com/0xPlaygrounds/rig">rig-core Documentation</a></li>
|
||
<li><a href="https://platform.openai.com/docs/guides/embeddings">OpenAI Embeddings API</a></li>
|
||
<li><a href="https://huggingface.co/BAAI/bge-small-en-v1.5">BAAI/bge Models</a></li>
|
||
<li><a href="https://ollama.com/blog/embedding-models">Ollama Embeddings</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="revision-history-1"><a class="header" href="#revision-history-1">Revision History</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Date</th><th>Author</th><th>Change</th></tr></thead><tbody>
|
||
<tr><td>2026-01-17</td><td>Architecture Team</td><td>Initial decision</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<hr />
|
||
<p><strong>Previous ADR</strong>: <a href="architecture/adrs/001-nickel-vs-toml.html">ADR-001: Nickel vs TOML</a>
|
||
<strong>Next ADR</strong>: <a href="architecture/adrs/003-hybrid-storage.html">ADR-003: Hybrid Storage Strategy</a></p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-003-hybrid-storage-strategy"><a class="header" href="#adr-003-hybrid-storage-strategy">ADR-003: Hybrid Storage Strategy</a></h1>
|
||
<p><strong>Status</strong>: Accepted</p>
|
||
<p><strong>Date</strong>: 2026-01-17</p>
|
||
<p><strong>Deciders</strong>: Architecture Team</p>
|
||
<p><strong>Context</strong>: Storage Backend Strategy for Knowledge Base</p>
|
||
<hr />
|
||
<h2 id="context-2"><a class="header" href="#context-2">Context</a></h2>
|
||
<p>The KOGRAL needs to store knowledge graphs with these requirements:</p>
|
||
<ol>
|
||
<li><strong>Git-Friendly</strong>: Knowledge should version alongside code</li>
|
||
<li><strong>Scalable</strong>: Support small projects (10s of nodes) to large organizations (10,000+ nodes)</li>
|
||
<li><strong>Queryable</strong>: Efficient graph queries and relationship traversal</li>
|
||
<li><strong>Offline-Capable</strong>: Work without network access</li>
|
||
<li><strong>Collaborative</strong>: Support shared organizational knowledge</li>
|
||
<li><strong>Cost-Effective</strong>: Free for small projects, reasonable cost at scale</li>
|
||
</ol>
|
||
<p><strong>Constraints</strong>:</p>
|
||
<ul>
|
||
<li>Developers want to edit knowledge in text editors</li>
|
||
<li>Organizations want centralized guideline management</li>
|
||
<li>Git workflows essential for code-adjacent knowledge</li>
|
||
<li>Large graphs need database performance</li>
|
||
</ul>
|
||
<h3 id="option-1-filesystem-only"><a class="header" href="#option-1-filesystem-only">Option 1: Filesystem Only</a></h3>
|
||
<p><strong>Approach</strong>: Store everything as markdown files</p>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>✅ Git-native (perfect for versioning)</li>
|
||
<li>✅ Text editor friendly</li>
|
||
<li>✅ No dependencies</li>
|
||
<li>✅ Works offline</li>
|
||
<li>✅ Free</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>❌ Poor performance for large graphs (100 0+ nodes)</li>
|
||
<li>❌ No efficient graph queries</li>
|
||
<li>❌ Difficult to share across projects</li>
|
||
<li>❌ Manual sync for collaboration</li>
|
||
</ul>
|
||
<p><strong>Scalability</strong>: Good for < 100 nodes, poor beyond</p>
|
||
<h3 id="option-2-database-only-surrealdb"><a class="header" href="#option-2-database-only-surrealdb">Option 2: Database Only (SurrealDB)</a></h3>
|
||
<p><strong>Approach</strong>: Store all knowledge in SurrealDB graph database</p>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>✅ Excellent query performance</li>
|
||
<li>✅ Native graph relationships</li>
|
||
<li>✅ Scalable to millions of nodes</li>
|
||
<li>✅ Centralized for collaboration</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>❌ Not git-trackable</li>
|
||
<li>❌ Requires running database server</li>
|
||
<li>❌ Can't edit with text editor</li>
|
||
<li>❌ Network dependency</li>
|
||
<li>❌ Infrastructure cost</li>
|
||
</ul>
|
||
<p><strong>Scalability</strong>: Excellent, but loses developer workflow benefits</p>
|
||
<h3 id="option-3-hybrid-filesystem--surrealdb"><a class="header" href="#option-3-hybrid-filesystem--surrealdb">Option 3: Hybrid (Filesystem + SurrealDB)</a></h3>
|
||
<p><strong>Approach</strong>: Filesystem for local project knowledge, SurrealDB for shared organizational knowledge</p>
|
||
<p><strong>Pros</strong>:</p>
|
||
<ul>
|
||
<li>✅ Git-friendly for project knowledge</li>
|
||
<li>✅ Text editor friendly</li>
|
||
<li>✅ Scalable for shared knowledge</li>
|
||
<li>✅ Works offline (local graph)</li>
|
||
<li>✅ Collaborative (shared graph)</li>
|
||
<li>✅ Cost-effective (DB only for shared)</li>
|
||
</ul>
|
||
<p><strong>Cons</strong>:</p>
|
||
<ul>
|
||
<li>❌ More complex implementation</li>
|
||
<li>❌ Sync mechanism needed</li>
|
||
<li>❌ Two storage systems to manage</li>
|
||
</ul>
|
||
<p><strong>Scalability</strong>: Excellent - best of both worlds</p>
|
||
<hr />
|
||
<h2 id="decision-2"><a class="header" href="#decision-2">Decision</a></h2>
|
||
<p><strong>We will use a hybrid storage strategy: Filesystem (local) + SurrealDB (shared).</strong></p>
|
||
<p><strong>Architecture</strong>:</p>
|
||
<pre><code>┌─────────────────────────────────────────────────────────────┐
|
||
│ Project A (.kogral/) │
|
||
│ Storage: Filesystem (git-tracked) │
|
||
│ Scope: Project-specific notes, decisions, patterns │
|
||
│ Access: Local only │
|
||
└──────────────────┬──────────────────────────────────────────┘
|
||
│
|
||
│ [inherits]
|
||
↓
|
||
┌─────────────────────────────────────────────────────────────┐
|
||
│ Shared KB (SurrealDB or synced filesystem) │
|
||
│ Storage: SurrealDB (scalable) or filesystem (synced) │
|
||
│ Scope: Organization-wide guidelines, patterns │
|
||
│ Access: All projects │
|
||
└─────────────────────────────────────────────────────────────┘
|
||
</code></pre>
|
||
<p><strong>Implementation</strong>:</p>
|
||
<pre><code class="language-nickel"># Project config
|
||
{
|
||
storage = {
|
||
primary = 'filesystem, # Local project knowledge
|
||
secondary = {
|
||
enabled = true,
|
||
type = 'surrealdb, # Shared knowledge
|
||
url = "ws://kb-central.company.com:8000",
|
||
namespace = "organization",
|
||
database = "shared-kb",
|
||
},
|
||
},
|
||
|
||
inheritance = {
|
||
base = "surrealdb://organization/shared-kb", # Inherit from shared
|
||
priority = 100, # Project overrides shared
|
||
},
|
||
}
|
||
</code></pre>
|
||
<p><strong>Sync Strategy</strong>:</p>
|
||
<pre><code>.kogral/ (Filesystem)
|
||
↓ [on save]
|
||
Watch for changes
|
||
↓ [debounced]
|
||
Sync to SurrealDB
|
||
↓
|
||
Shared graph updated
|
||
↓ [on query]
|
||
Merge local + shared results
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="consequences-2"><a class="header" href="#consequences-2">Consequences</a></h2>
|
||
<h3 id="positive-2"><a class="header" href="#positive-2">Positive</a></h3>
|
||
<p>✅ <strong>Developer Workflow Preserved</strong>:</p>
|
||
<pre><code class="language-bash"># Local knowledge workflow (unchanged)
|
||
vim .kogral/notes/my-note.md
|
||
git add .kogral/notes/my-note.md
|
||
git commit -m "Add implementation note"
|
||
git push
|
||
</code></pre>
|
||
<p>✅ <strong>Git Integration</strong>:</p>
|
||
<ul>
|
||
<li>Project knowledge versioned with code</li>
|
||
<li>Branches include relevant knowledge</li>
|
||
<li>Merges resolve knowledge conflicts</li>
|
||
<li>PR reviews include knowledge changes</li>
|
||
</ul>
|
||
<p>✅ <strong>Offline Development</strong>:</p>
|
||
<ul>
|
||
<li>Full functionality without network</li>
|
||
<li>Shared guidelines cached locally</li>
|
||
<li>Sync when reconnected</li>
|
||
</ul>
|
||
<p>✅ <strong>Scalability</strong>:</p>
|
||
<ul>
|
||
<li>Projects: filesystem (100s of nodes, fine performance)</li>
|
||
<li>Organization: SurrealDB (10,000+ nodes, excellent performance)</li>
|
||
</ul>
|
||
<p>✅ <strong>Collaboration</strong>:</p>
|
||
<ul>
|
||
<li>Shared guidelines accessible to all projects</li>
|
||
<li>Updates to shared knowledge propagate automatically</li>
|
||
<li>Consistent practices across organization</li>
|
||
</ul>
|
||
<p>✅ <strong>Cost-Effective</strong>:</p>
|
||
<ul>
|
||
<li>Small projects: free (filesystem only)</li>
|
||
<li>Organizations: SurrealDB for shared only (not all project knowledge)</li>
|
||
</ul>
|
||
<p>✅ <strong>Gradual Adoption</strong>:</p>
|
||
<ul>
|
||
<li>Start with filesystem only</li>
|
||
<li>Add SurrealDB when needed</li>
|
||
<li>Feature-gated (<code>--features surrealdb</code>)</li>
|
||
</ul>
|
||
<h3 id="negative-2"><a class="header" href="#negative-2">Negative</a></h3>
|
||
<p>❌ <strong>Complexity</strong>:</p>
|
||
<ul>
|
||
<li>Two storage implementations</li>
|
||
<li>Sync mechanism required</li>
|
||
<li>Conflict resolution needed</li>
|
||
</ul>
|
||
<p><strong>Mitigation</strong>:</p>
|
||
<ul>
|
||
<li>Storage trait abstracts differences</li>
|
||
<li>Sync is optional (can disable)</li>
|
||
<li>Conflicts rare (guidelines change infrequently)</li>
|
||
</ul>
|
||
<p>❌ <strong>Sync Latency</strong>:</p>
|
||
<ul>
|
||
<li>Changes to shared KB not instant in all projects</li>
|
||
</ul>
|
||
<p><strong>Mitigation</strong>:</p>
|
||
<ul>
|
||
<li>Acceptable latency (guidelines don't change rapidly)</li>
|
||
<li>Manual sync command available (<code>kb sync</code>)</li>
|
||
<li>Auto-sync on query (fetch latest)</li>
|
||
</ul>
|
||
<p>❌ <strong>Infrastructure Requirement</strong>:</p>
|
||
<ul>
|
||
<li>SurrealDB server needed for shared KB</li>
|
||
</ul>
|
||
<p><strong>Mitigation</strong>:</p>
|
||
<ul>
|
||
<li>Optional (can use synced filesystem instead)</li>
|
||
<li>Docker Compose for easy setup</li>
|
||
<li>Managed SurrealDB Cloud option</li>
|
||
</ul>
|
||
<h3 id="neutral-2"><a class="header" href="#neutral-2">Neutral</a></h3>
|
||
<p>⚪ <strong>Storage Trait Implementation</strong>:</p>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>#[async_trait]
|
||
pub trait Storage {
|
||
async fn save_graph(&self, graph: &Graph) -> Result<()>;
|
||
async fn load_graph(&self, name: &str) -> Result<Graph>;
|
||
async fn list_graphs(&self) -> Result<Vec<String>>;
|
||
}
|
||
|
||
// Three implementations
|
||
impl Storage for FilesystemStorage { /* ... */ }
|
||
impl Storage for SurrealDbStorage { /* ... */ }
|
||
impl Storage for MemoryStorage { /* ... */ }
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<p>Abstraction makes multi-backend manageable.</p>
|
||
<hr />
|
||
<h2 id="use-cases-4"><a class="header" href="#use-cases-4">Use Cases</a></h2>
|
||
<h3 id="small-project-solo-developer"><a class="header" href="#small-project-solo-developer">Small Project (Solo Developer)</a></h3>
|
||
<p><strong>Config</strong>:</p>
|
||
<pre><code class="language-nickel">{ storage = { primary = 'filesystem } }
|
||
</code></pre>
|
||
<p><strong>Behavior</strong>:</p>
|
||
<ul>
|
||
<li>All knowledge in <code>.kogral/</code> directory</li>
|
||
<li>Git-tracked with code</li>
|
||
<li>No database required</li>
|
||
<li>Works offline</li>
|
||
</ul>
|
||
<h3 id="medium-project-team"><a class="header" href="#medium-project-team">Medium Project (Team)</a></h3>
|
||
<p><strong>Config</strong>:</p>
|
||
<pre><code class="language-nickel">{
|
||
storage = {
|
||
primary = 'filesystem,
|
||
secondary = {
|
||
enabled = true,
|
||
type = 'surrealdb,
|
||
url = "ws://team-kb.local:8000",
|
||
},
|
||
},
|
||
}
|
||
</code></pre>
|
||
<p><strong>Behavior</strong>:</p>
|
||
<ul>
|
||
<li>Project knowledge in <code>.kogral/</code> (git-tracked)</li>
|
||
<li>Shared team patterns in SurrealDB</li>
|
||
<li>Automatic sync</li>
|
||
<li>Offline fallback to cached</li>
|
||
</ul>
|
||
<h3 id="large-organization"><a class="header" href="#large-organization">Large Organization</a></h3>
|
||
<p><strong>Config</strong>:</p>
|
||
<pre><code class="language-nickel">{
|
||
storage = {
|
||
primary = 'filesystem,
|
||
secondary = {
|
||
enabled = true,
|
||
type = 'surrealdb,
|
||
url = "ws://kb.company.com:8000",
|
||
namespace = "engineering",
|
||
database = "shared-kb",
|
||
},
|
||
},
|
||
|
||
inheritance = {
|
||
base = "surrealdb://engineering/shared-kb",
|
||
guidelines = [
|
||
"surrealdb://engineering/rust-guidelines",
|
||
"surrealdb://engineering/security-policies",
|
||
],
|
||
},
|
||
}
|
||
</code></pre>
|
||
<p><strong>Behavior</strong>:</p>
|
||
<ul>
|
||
<li>Project-specific in <code>.kogral/</code></li>
|
||
<li>Organization guidelines in SurrealDB</li>
|
||
<li>Security policies enforced</li>
|
||
<li>Automatic guideline updates</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="sync-mechanism"><a class="header" href="#sync-mechanism">Sync Mechanism</a></h2>
|
||
<h3 id="filesystem--surrealdb"><a class="header" href="#filesystem--surrealdb">Filesystem → SurrealDB</a></h3>
|
||
<p><strong>Trigger</strong>: File changes detected (via <code>notify</code> crate)</p>
|
||
<p><strong>Process</strong>:</p>
|
||
<ol>
|
||
<li>Parse changed markdown file</li>
|
||
<li>Convert to Node struct</li>
|
||
<li>Upsert to SurrealDB</li>
|
||
<li>Update relationships</li>
|
||
</ol>
|
||
<p><strong>Debouncing</strong>: 500ms (configurable)</p>
|
||
<h3 id="surrealdb--filesystem"><a class="header" href="#surrealdb--filesystem">SurrealDB → Filesystem</a></h3>
|
||
<p><strong>Trigger</strong>: Query for shared knowledge</p>
|
||
<p><strong>Process</strong>:</p>
|
||
<ol>
|
||
<li>Query SurrealDB for shared nodes</li>
|
||
<li>Cache locally (in-memory or filesystem)</li>
|
||
<li>Merge with local results</li>
|
||
<li>Return combined</li>
|
||
</ol>
|
||
<p><strong>Caching</strong>: TTL-based (5 minutes default)</p>
|
||
<h3 id="conflict-resolution"><a class="header" href="#conflict-resolution">Conflict Resolution</a></h3>
|
||
<p><strong>Strategy</strong>: Last-write-wins with version tracking</p>
|
||
<p><strong>Example</strong>:</p>
|
||
<pre><code>Project A: Updates shared guideline (v1 → v2)
|
||
Project B: Has cached v1
|
||
|
||
On Project B query:
|
||
- Detects v2 available
|
||
- Fetches v2
|
||
- Updates cache
|
||
- Uses v2 going forward
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="alternatives-considered-1"><a class="header" href="#alternatives-considered-1">Alternatives Considered</a></h2>
|
||
<h3 id="git-submodules-for-shared-knowledge"><a class="header" href="#git-submodules-for-shared-knowledge">Git Submodules for Shared Knowledge</a></h3>
|
||
<p><strong>Rejected</strong>: Cumbersome workflow</p>
|
||
<ul>
|
||
<li>Requires manual submodule update</li>
|
||
<li>Merge conflicts in shared submodule</li>
|
||
<li>Not discoverable (need to know submodule exists)</li>
|
||
</ul>
|
||
<h3 id="syncthing-for-filesystem-sync"><a class="header" href="#syncthing-for-filesystem-sync">Syncthing for Filesystem Sync</a></h3>
|
||
<p><strong>Rejected</strong>: Not designed for this use case</p>
|
||
<ul>
|
||
<li>No query optimization</li>
|
||
<li>No relationship indexes</li>
|
||
<li>Sync conflicts difficult to resolve</li>
|
||
</ul>
|
||
<h3 id="postgresql-with-json"><a class="header" href="#postgresql-with-json">PostgreSQL with JSON</a></h3>
|
||
<p><strong>Rejected</strong>: Not a graph database</p>
|
||
<ul>
|
||
<li>Poor graph query performance</li>
|
||
<li>Relationship traversal requires complex SQL joins</li>
|
||
<li>No native graph features</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="migration-path-1"><a class="header" href="#migration-path-1">Migration Path</a></h2>
|
||
<h3 id="phase-1-filesystem-only-current"><a class="header" href="#phase-1-filesystem-only-current">Phase 1: Filesystem Only (Current)</a></h3>
|
||
<ul>
|
||
<li>All storage via filesystem</li>
|
||
<li>Git-tracked</li>
|
||
<li>No database required</li>
|
||
</ul>
|
||
<h3 id="phase-2-optional-surrealdb"><a class="header" href="#phase-2-optional-surrealdb">Phase 2: Optional SurrealDB</a></h3>
|
||
<ul>
|
||
<li>Add SurrealDB support (feature-gated)</li>
|
||
<li>Manual sync command</li>
|
||
<li>Shared KB opt-in</li>
|
||
</ul>
|
||
<h3 id="phase-3-automatic-sync"><a class="header" href="#phase-3-automatic-sync">Phase 3: Automatic Sync</a></h3>
|
||
<ul>
|
||
<li>File watching</li>
|
||
<li>Auto-sync on changes</li>
|
||
<li>Background sync</li>
|
||
</ul>
|
||
<h3 id="phase-4-multi-tenant-surrealdb"><a class="header" href="#phase-4-multi-tenant-surrealdb">Phase 4: Multi-Tenant SurrealDB</a></h3>
|
||
<ul>
|
||
<li>Organization namespaces</li>
|
||
<li>Access control</li>
|
||
<li>Audit logs</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="monitoring-2"><a class="header" href="#monitoring-2">Monitoring</a></h2>
|
||
<p><strong>Success Criteria</strong>:</p>
|
||
<ul>
|
||
<li>Developers don't notice hybrid complexity</li>
|
||
<li>Sync completes < 1 second for typical changes</li>
|
||
<li>Shared guidelines accessible in < 100ms</li>
|
||
<li>Zero data loss in sync</li>
|
||
</ul>
|
||
<p><strong>Metrics</strong>:</p>
|
||
<ul>
|
||
<li>Sync latency (P50, P95, P99)</li>
|
||
<li>Cache hit rate (shared knowledge)</li>
|
||
<li>Conflict rate (expect < 0.1%)</li>
|
||
<li>User satisfaction</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="references-2"><a class="header" href="#references-2">References</a></h2>
|
||
<ul>
|
||
<li><a href="https://surrealdb.com/docs">SurrealDB Documentation</a></li>
|
||
<li><a href="architecture/adrs/../../crates/kb-core/src/storage/mod.rs">Storage Trait Implementation</a></li>
|
||
<li><a href="architecture/adrs/../../crates/kb-core/src/storage/filesystem.rs">FilesystemStorage</a></li>
|
||
<li><a href="architecture/adrs/../../crates/kb-core/src/storage/surrealdb.rs">SurrealDbStorage</a></li>
|
||
<li><a href="architecture/adrs/../../scripts/kb-sync.nu">Sync Mechanism</a></li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="revision-history-2"><a class="header" href="#revision-history-2">Revision History</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Date</th><th>Author</th><th>Change</th></tr></thead><tbody>
|
||
<tr><td>2026-01-17</td><td>Architecture Team</td><td>Initial decision</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<hr />
|
||
<p><strong>Previous ADR</strong>: <a href="architecture/adrs/002-fastembed-ai-providers.html">ADR-002: FastEmbed via AI Providers</a>
|
||
<strong>Next ADR</strong>: <a href="architecture/adrs/004-logseq-compatibility.html">ADR-004: Logseq Compatibility</a></p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-004-logseq-blocks-support"><a class="header" href="#adr-004-logseq-blocks-support">ADR-004: Logseq Blocks Support</a></h1>
|
||
<h2 id="status"><a class="header" href="#status">Status</a></h2>
|
||
<p><strong>Proposed</strong> (Design phase)</p>
|
||
<h2 id="context-3"><a class="header" href="#context-3">Context</a></h2>
|
||
<p>Logseq uses <strong>content blocks</strong> as the fundamental unit of information, not full documents. KB currently treats <code>Node.content</code> as flat markdown string, which loses block-level features on import/export:</p>
|
||
<p><strong>Lost features</strong>:</p>
|
||
<ul>
|
||
<li>Block properties (<code>#card</code>, <code>TODO</code>, custom properties)</li>
|
||
<li>Block hierarchy (outliner nesting)</li>
|
||
<li>Block references (<code>((block-uuid))</code>)</li>
|
||
<li>Block-level queries (find all flashcards, TODOs)</li>
|
||
</ul>
|
||
<p><strong>User requirement</strong>: Round-trip Logseq import/export with full fidelity:</p>
|
||
<pre><code>Logseq → KOGRAL Import → KOGRAL Storage → KOGRAL Export → Logseq
|
||
(blocks preserved at every step)
|
||
</code></pre>
|
||
<h2 id="decision-3"><a class="header" href="#decision-3">Decision</a></h2>
|
||
<p>Implement <strong>Hybrid Block Support</strong> (structured + markdown):</p>
|
||
<h3 id="1-add-block-data-structure"><a class="header" href="#1-add-block-data-structure">1. Add Block Data Structure</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub struct Block {
|
||
pub id: String, // UUID
|
||
pub content: String, // Block text
|
||
pub properties: BlockProperties, // Tags, status, custom
|
||
pub children: Vec<Block>, // Nested blocks
|
||
// ... timestamps ...
|
||
}
|
||
|
||
pub struct BlockProperties {
|
||
pub tags: Vec<String>, // #card, #important
|
||
pub status: Option<TaskStatus>, // TODO, DONE, etc.
|
||
pub custom: HashMap<String, String>, // property:: value
|
||
pub block_refs: Vec<String>, // ((uuid))
|
||
pub page_refs: Vec<String>, // [[page]]
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="2-extend-node-model"><a class="header" href="#2-extend-node-model">2. Extend Node Model</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>pub struct Node {
|
||
// ... existing fields ...
|
||
pub content: String, // Source of truth (markdown)
|
||
pub blocks: Option<Vec<Block>>, // Cached structure (optional)
|
||
}
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h3 id="3-bidirectional-parser"><a class="header" href="#3-bidirectional-parser">3. Bidirectional Parser</a></h3>
|
||
<ul>
|
||
<li><strong>Parse</strong>: Markdown → <code>Vec<Block></code> (lazy, on-demand)</li>
|
||
<li><strong>Serialize</strong>: <code>Vec<Block></code> → Markdown (for export)</li>
|
||
</ul>
|
||
<h3 id="4-storage-strategy"><a class="header" href="#4-storage-strategy">4. Storage Strategy</a></h3>
|
||
<p><strong>Filesystem</strong> (git-friendly, Logseq-compatible):</p>
|
||
<pre><code class="language-markdown">- Block 1 #card
|
||
- Nested answer
|
||
- TODO Block 2
|
||
priority:: high
|
||
</code></pre>
|
||
<p><strong>SurrealDB</strong> (queryable):</p>
|
||
<pre><code class="language-sql">DEFINE TABLE block;
|
||
DEFINE FIELD node_id ON block TYPE record(node);
|
||
DEFINE FIELD block_id ON block TYPE string;
|
||
DEFINE FIELD properties ON block TYPE object;
|
||
DEFINE INDEX block_tags ON block COLUMNS properties.tags;
|
||
</code></pre>
|
||
<h3 id="5-query-extensions"><a class="header" href="#5-query-extensions">5. Query Extensions</a></h3>
|
||
<pre><pre class="playground"><code class="language-rust"><span class="boring">#![allow(unused)]
|
||
</span><span class="boring">fn main() {
|
||
</span>// Find all flashcards
|
||
graph.find_blocks_by_tag("card")
|
||
|
||
// Find all TODOs
|
||
graph.find_all_todos()
|
||
|
||
// Find blocks with custom property
|
||
node.find_blocks_by_property("priority", "high")
|
||
<span class="boring">}</span></code></pre></pre>
|
||
<h2 id="consequences-3"><a class="header" href="#consequences-3">Consequences</a></h2>
|
||
<h3 id="positive-3"><a class="header" href="#positive-3">Positive</a></h3>
|
||
<p>✅ <strong>Full Logseq Compatibility</strong> - Import/export preserves all block features
|
||
✅ <strong>Queryable Blocks</strong> - Find #card, TODO, custom properties across KOGRAL
|
||
✅ <strong>Backward Compatible</strong> - Existing nodes without blocks still work
|
||
✅ <strong>Type-Safe</strong> - Structured data instead of regex parsing everywhere
|
||
✅ <strong>Extensible</strong> - Custom block properties supported
|
||
✅ <strong>Hierarchy Preserved</strong> - Nested blocks maintain parent-child relationships</p>
|
||
<h3 id="negative-3"><a class="header" href="#negative-3">Negative</a></h3>
|
||
<p>⚠️ <strong>Added Complexity</strong> - New data structures, parser, sync logic
|
||
⚠️ <strong>Dual Representation</strong> - Must keep <code>content</code> and <code>blocks</code> in sync
|
||
⚠️ <strong>Storage Overhead</strong> - SurrealDB stores both markdown and structure
|
||
⚠️ <strong>Migration Required</strong> - Existing data needs parsing to populate blocks</p>
|
||
<h3 id="neutral-3"><a class="header" href="#neutral-3">Neutral</a></h3>
|
||
<p>⚙️ <strong>Lazy Parsing</strong> - Blocks parsed on-demand (not stored by default)
|
||
⚙️ <strong>Opt-In</strong> - Config flag <code>blocks.enabled</code> to activate features
|
||
⚙️ <strong>Gradual Adoption</strong> - Can implement in phases</p>
|
||
<h2 id="implementation-phases"><a class="header" href="#implementation-phases">Implementation Phases</a></h2>
|
||
<p><strong>Phase 1: Foundation</strong> (No behavior change)</p>
|
||
<ul>
|
||
<li>Add <code>Block</code> struct to <code>models/block.rs</code></li>
|
||
<li>Add optional <code>blocks</code> field to <code>Node</code></li>
|
||
<li>Add config: <code>blocks.enabled = false</code> (default off)</li>
|
||
</ul>
|
||
<p><strong>Phase 2: Parser</strong></p>
|
||
<ul>
|
||
<li>Implement <code>BlockParser::parse()</code> (markdown → blocks)</li>
|
||
<li>Implement <code>BlockParser::serialize()</code> (blocks → markdown)</li>
|
||
<li>Add <code>Node::get_blocks()</code> method (lazy parsing)</li>
|
||
</ul>
|
||
<p><strong>Phase 3: Logseq Integration</strong></p>
|
||
<ul>
|
||
<li>Update <code>LogseqImporter</code> to parse blocks</li>
|
||
<li>Update <code>LogseqExporter</code> to serialize blocks</li>
|
||
<li>Test round-trip (Logseq → KB → Logseq)</li>
|
||
</ul>
|
||
<p><strong>Phase 4: Query API</strong></p>
|
||
<ul>
|
||
<li>Add <code>Graph::find_blocks_by_tag()</code></li>
|
||
<li>Add <code>Graph::find_all_todos()</code></li>
|
||
<li>Add <code>Node::find_blocks_by_property()</code></li>
|
||
</ul>
|
||
<p><strong>Phase 5: MCP/CLI Integration</strong></p>
|
||
<ul>
|
||
<li>Add <code>kb/find_blocks</code> MCP tool</li>
|
||
<li>Add <code>kb find-cards</code> CLI command</li>
|
||
<li>Add <code>kb find-todos</code> CLI command</li>
|
||
</ul>
|
||
<p><strong>Phase 6: SurrealDB Backend</strong></p>
|
||
<ul>
|
||
<li>Create <code>block</code> table schema</li>
|
||
<li>Index on tags, status, properties</li>
|
||
<li>Store blocks alongside nodes</li>
|
||
</ul>
|
||
<h2 id="alternatives-considered-2"><a class="header" href="#alternatives-considered-2">Alternatives Considered</a></h2>
|
||
<h3 id="alternative-1-blocks-as-first-class-nodes"><a class="header" href="#alternative-1-blocks-as-first-class-nodes">Alternative 1: Blocks as First-Class Nodes</a></h3>
|
||
<p>Convert each Logseq block to a separate KOGRAL Node.</p>
|
||
<p><strong>Rejected</strong>: Too granular, explosion of nodes, loses document context.</p>
|
||
<h3 id="alternative-2-parser-only-no-storage"><a class="header" href="#alternative-2-parser-only-no-storage">Alternative 2: Parser-Only (No Storage)</a></h3>
|
||
<p>Keep <code>content: String</code>, parse blocks on every access.</p>
|
||
<p><strong>Rejected</strong>: Can't query blocks in database, parse overhead, can't index.</p>
|
||
<h3 id="alternative-3-metadata-field"><a class="header" href="#alternative-3-metadata-field">Alternative 3: Metadata Field</a></h3>
|
||
<p>Store blocks in <code>metadata: HashMap<String, Value></code>.</p>
|
||
<p><strong>Rejected</strong>: Not type-safe, harder to query, no schema validation.</p>
|
||
<h2 id="references-3"><a class="header" href="#references-3">References</a></h2>
|
||
<ul>
|
||
<li><a href="https://docs.logseq.com/#/page/blocks">Logseq Block Format</a></li>
|
||
<li><a href="architecture/adrs/../logseq-blocks-design.html">Full Design Document</a></li>
|
||
<li><a href="https://github.com/.../issues/XXX">Implementation Tracking</a></li>
|
||
</ul>
|
||
<h2 id="notes"><a class="header" href="#notes">Notes</a></h2>
|
||
<p><strong>Backward Compatibility Strategy</strong>:</p>
|
||
<ul>
|
||
<li><code>content</code> remains source of truth</li>
|
||
<li><code>blocks</code> is optional enhancement</li>
|
||
<li>Old code works unchanged</li>
|
||
<li>New features opt-in via config</li>
|
||
</ul>
|
||
<p><strong>Migration Path</strong>:</p>
|
||
<ul>
|
||
<li>Existing users: blocks disabled by default</li>
|
||
<li>New users: blocks enabled, parsed on import</li>
|
||
<li>Manual: <code>kb reindex --parse-blocks</code> to populate</li>
|
||
</ul>
|
||
<hr />
|
||
<p><strong>Decision Date</strong>: 2026-01-17
|
||
<strong>Approvers</strong>: TBD
|
||
<strong>Review Date</strong>: After Phase 2 implementation</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="adr-005-mcp-protocol-for-ai-integration"><a class="header" href="#adr-005-mcp-protocol-for-ai-integration">ADR-005: MCP Protocol for AI Integration</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="prerequisites-2"><a class="header" href="#prerequisites-2">Prerequisites</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="installation-methods-1"><a class="header" href="#installation-methods-1">Installation Methods</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="project-initialization"><a class="header" href="#project-initialization">Project Initialization</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="environment-configuration"><a class="header" href="#environment-configuration">Environment Configuration</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="verification"><a class="header" href="#verification">Verification</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="configuration-overview"><a class="header" href="#configuration-overview">Configuration Overview</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="nickel-schemas"><a class="header" href="#nickel-schemas">Nickel Schemas</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="graph-settings"><a class="header" href="#graph-settings">Graph Settings</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="inheritance"><a class="header" href="#inheritance">Inheritance</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="examples"><a class="header" href="#examples">Examples</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="storage-backends"><a class="header" href="#storage-backends">Storage Backends</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="filesystem-storage"><a class="header" href="#filesystem-storage">Filesystem Storage</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="surrealdb-storage"><a class="header" href="#surrealdb-storage">SurrealDB Storage</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="in-memory-storage"><a class="header" href="#in-memory-storage">In-Memory Storage</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="sync-strategies"><a class="header" href="#sync-strategies">Sync Strategies</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="embeddings-overview"><a class="header" href="#embeddings-overview">Embeddings Overview</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="provider-configuration"><a class="header" href="#provider-configuration">Provider Configuration</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="fastembed-local"><a class="header" href="#fastembed-local">FastEmbed Local</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="openai-integration"><a class="header" href="#openai-integration">OpenAI Integration</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="claude-integration"><a class="header" href="#claude-integration">Claude Integration</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="ollama-integration"><a class="header" href="#ollama-integration">Ollama Integration</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="semantic-search-2"><a class="header" href="#semantic-search-2">Semantic Search</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="template-system-1"><a class="header" href="#template-system-1">Template System</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="document-templates"><a class="header" href="#document-templates">Document Templates</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="export-templates"><a class="header" href="#export-templates">Export Templates</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="customization"><a class="header" href="#customization">Customization</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="cli-overview"><a class="header" href="#cli-overview">CLI Overview</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="commands-reference"><a class="header" href="#commands-reference">Commands Reference</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="workflows"><a class="header" href="#workflows">Workflows</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="nushell-scripts-1"><a class="header" href="#nushell-scripts-1">NuShell Scripts</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="mcp-quick-guide"><a class="header" href="#mcp-quick-guide">MCP Quick Guide</a></h1>
|
||
<p>Fast-track guide to integrating KOGRAL with Claude Code via the Model Context Protocol (MCP).</p>
|
||
<h2 id="what-is-mcp"><a class="header" href="#what-is-mcp">What is MCP?</a></h2>
|
||
<p>MCP (Model Context Protocol) is a protocol that allows Claude Code to interact with external tools and data sources. The kogral-mcp server exposes your KOGRAL knowledge base to Claude Code for AI-assisted knowledge management.</p>
|
||
<h2 id="quick-setup-5-minutes"><a class="header" href="#quick-setup-5-minutes">Quick Setup (5 Minutes)</a></h2>
|
||
<h3 id="step-1-build-mcp-server"><a class="header" href="#step-1-build-mcp-server">Step 1: Build MCP Server</a></h3>
|
||
<pre><code class="language-bash"># Build kogral-mcp server
|
||
cargo build --package kb-mcp --release
|
||
|
||
# Verify binary
|
||
ls -lh target/release/kb-mcp
|
||
</code></pre>
|
||
<h3 id="step-2-configure-claude-code"><a class="header" href="#step-2-configure-claude-code">Step 2: Configure Claude Code</a></h3>
|
||
<p>Add to <code>~/.config/claude/config.json</code>:</p>
|
||
<pre><code class="language-json">{
|
||
"mcpServers": {
|
||
"kogral-mcp": {
|
||
"command": "/path/to/knowledge-base/target/release/kb-mcp",
|
||
"args": ["serve"],
|
||
"env": {
|
||
"KOGRAL_DIR": "/path/to/your/project/.kogral"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p>Replace <code>/path/to/knowledge-base</code> and <code>/path/to/your/project/.kogral</code> with your actual paths.</p>
|
||
<h3 id="step-3-initialize-kogral"><a class="header" href="#step-3-initialize-kogral">Step 3: Initialize KOGRAL</a></h3>
|
||
<pre><code class="language-bash"># Navigate to your project
|
||
cd /path/to/your/project
|
||
|
||
# Initialize .kb directory
|
||
kb init
|
||
</code></pre>
|
||
<h3 id="step-4-start-claude-code"><a class="header" href="#step-4-start-claude-code">Step 4: Start Claude Code</a></h3>
|
||
<pre><code class="language-bash"># Start Claude Code (will auto-connect to kb-mcp)
|
||
claude-code
|
||
</code></pre>
|
||
<h3 id="step-5-test-connection"><a class="header" href="#step-5-test-connection">Step 5: Test Connection</a></h3>
|
||
<p>In Claude Code, try:</p>
|
||
<pre><code>Search for "error handling"
|
||
</code></pre>
|
||
<p>Claude will use the <code>kogral/search</code> tool to query your knowledge base.</p>
|
||
<hr />
|
||
<h2 id="essential-commands"><a class="header" href="#essential-commands">Essential Commands</a></h2>
|
||
<h3 id="search-knowledge-base"><a class="header" href="#search-knowledge-base">Search Knowledge Base</a></h3>
|
||
<p><strong>Natural language</strong>:</p>
|
||
<pre><code>Find notes about Rust error handling
|
||
</code></pre>
|
||
<p><strong>Claude translates to</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/search",
|
||
"params": {
|
||
"query": "error handling",
|
||
"type": "note",
|
||
"semantic": true
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="add-note"><a class="header" href="#add-note">Add Note</a></h3>
|
||
<p><strong>Natural language</strong>:</p>
|
||
<pre><code>Add a note about async Rust patterns with tags rust, async, patterns
|
||
</code></pre>
|
||
<p><strong>Claude translates to</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/add_note",
|
||
"params": {
|
||
"title": "Async Rust Patterns",
|
||
"content": "...",
|
||
"tags": ["rust", "async", "patterns"]
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="add-decision"><a class="header" href="#add-decision">Add Decision</a></h3>
|
||
<p><strong>Natural language</strong>:</p>
|
||
<pre><code>Document decision to use SurrealDB for storage
|
||
</code></pre>
|
||
<p><strong>Claude translates to</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/add_decision",
|
||
"params": {
|
||
"title": "Use SurrealDB for Storage",
|
||
"context": "Need scalable graph database",
|
||
"decision": "Adopt SurrealDB as primary storage backend",
|
||
"consequences": ["Better query performance", "Additional infrastructure"]
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="get-guidelines"><a class="header" href="#get-guidelines">Get Guidelines</a></h3>
|
||
<p><strong>Natural language</strong>:</p>
|
||
<pre><code>What are our Rust error handling guidelines?
|
||
</code></pre>
|
||
<p><strong>Claude translates to</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/get_guidelines",
|
||
"params": {
|
||
"language": "rust",
|
||
"category": "error-handling"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="link-concepts"><a class="header" href="#link-concepts">Link Concepts</a></h3>
|
||
<p><strong>Natural language</strong>:</p>
|
||
<pre><code>Link note-async-patterns to decision-use-tokio as implements
|
||
</code></pre>
|
||
<p><strong>Claude translates to</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"tool": "kogral/link",
|
||
"params": {
|
||
"from": "note-async-patterns",
|
||
"to": "decision-use-tokio",
|
||
"relation": "implements"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="common-workflows-1"><a class="header" href="#common-workflows-1">Common Workflows</a></h2>
|
||
<h3 id="capture-meeting-notes"><a class="header" href="#capture-meeting-notes">Capture Meeting Notes</a></h3>
|
||
<p><strong>You</strong>:</p>
|
||
<pre><code>We just decided in our meeting to migrate from REST to GraphQL.
|
||
Context: current REST API is too chatty, requires multiple round trips.
|
||
Consequences: better client performance, but team needs to learn GraphQL.
|
||
Create a decision record.
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (uses <code>kb/add_decision</code>):</p>
|
||
<pre><code>Created decision: decision-migrate-to-graphql
|
||
|
||
Status: proposed
|
||
Context: Current REST API requires multiple round trips (chatty)
|
||
Decision: Migrate from REST to GraphQL API
|
||
Consequences:
|
||
- ✅ Better client performance
|
||
- ❌ Team learning curve for GraphQL
|
||
</code></pre>
|
||
<h3 id="find-related-knowledge"><a class="header" href="#find-related-knowledge">Find Related Knowledge</a></h3>
|
||
<p><strong>You</strong>:</p>
|
||
<pre><code>I'm working on error handling. What related notes and patterns do we have?
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (uses <code>kogral/search</code> with semantic=true):</p>
|
||
<pre><code>Found 5 related items:
|
||
|
||
1. **Error Handling with thiserror** (pattern)
|
||
Tags: rust, error-handling
|
||
Created: 2026-01-15
|
||
|
||
2. **Result Type Best Practices** (guideline)
|
||
Tags: rust, error-handling, best-practices
|
||
Created: 2026-01-10
|
||
|
||
3. **Decision: Use anyhow for Application Errors** (decision)
|
||
Status: accepted
|
||
Created: 2026-01-08
|
||
|
||
4. **Custom Error Types** (note)
|
||
Tags: rust, error-handling
|
||
Created: 2026-01-05
|
||
|
||
5. **Error Propagation Patterns** (pattern)
|
||
Tags: rust, error-handling
|
||
Created: 2026-01-03
|
||
|
||
Would you like me to link your current work to any of these?
|
||
</code></pre>
|
||
<h3 id="document-implementation"><a class="header" href="#document-implementation">Document Implementation</a></h3>
|
||
<p><strong>You</strong>:</p>
|
||
<pre><code>I just implemented the user authentication module following our security guidelines.
|
||
Add a note and link it to our security guideline.
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (uses <code>kb/add_note</code> + <code>kb/link</code>):</p>
|
||
<pre><code>Created note: note-user-auth-implementation
|
||
Tags: security, authentication, implementation
|
||
|
||
Linked:
|
||
note-user-auth-implementation --[implements]--> guideline-security-practices
|
||
|
||
Done! Your implementation is now documented and connected to our guidelines.
|
||
</code></pre>
|
||
<h3 id="review-project-knowledge"><a class="header" href="#review-project-knowledge">Review Project Knowledge</a></h3>
|
||
<p><strong>You</strong>:</p>
|
||
<pre><code>Summarize our project knowledge: what decisions have we made, what patterns are we using?
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (uses <code>kb/summarize_project</code> prompt):</p>
|
||
<pre><code># Project Knowledge Summary
|
||
|
||
**Total Nodes**: 42
|
||
|
||
## Recent Decisions (5)
|
||
- Use SurrealDB for Storage (accepted, 2 days ago)
|
||
- Adopt MCP for AI Integration (accepted, 5 days ago)
|
||
- Nickel for Configuration (accepted, 1 week ago)
|
||
|
||
## Active Patterns (8)
|
||
- Error Handling with thiserror
|
||
- Repository Pattern for DB
|
||
- Builder Pattern for Complex Objects
|
||
- ...
|
||
|
||
## Top Tags
|
||
1. rust (23 nodes)
|
||
2. architecture (12 nodes)
|
||
3. error-handling (8 nodes)
|
||
4. patterns (7 nodes)
|
||
|
||
## Guideline Coverage
|
||
- Rust: 5 guidelines
|
||
- Testing: 3 guidelines
|
||
- Security: 2 guidelines
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="advanced-usage"><a class="header" href="#advanced-usage">Advanced Usage</a></h2>
|
||
<h3 id="conditional-queries"><a class="header" href="#conditional-queries">Conditional Queries</a></h3>
|
||
<p><strong>You</strong>:</p>
|
||
<pre><code>Find all decisions made in the last week that are still proposed (not accepted)
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (combines <code>kogral/search</code> with filtering):</p>
|
||
<pre><code>Found 2 proposed decisions from last week:
|
||
|
||
1. **Migrate to GraphQL** (proposed)
|
||
Created: 3 days ago
|
||
Context: REST API is too chatty...
|
||
|
||
2. **Add Redis Caching** (proposed)
|
||
Created: 5 days ago
|
||
Context: Database queries are slow...
|
||
|
||
Would you like to review these and update their status?
|
||
</code></pre>
|
||
<h3 id="cross-reference-check"><a class="header" href="#cross-reference-check">Cross-Reference Check</a></h3>
|
||
<p><strong>You</strong>:</p>
|
||
<pre><code>Before I implement X, check if we have any decisions or guidelines about it
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (uses <code>kogral/search</code> + <code>kogral/get_guidelines</code>):</p>
|
||
<pre><code>Checking existing knowledge about X...
|
||
|
||
**Decisions**: None found
|
||
**Guidelines**: 1 relevant guideline found
|
||
- guideline-module-structure: "Keep modules focused and small"
|
||
|
||
**Related Notes**: 2 notes mention similar topics
|
||
- note-module-design-patterns
|
||
- note-separation-of-concerns
|
||
|
||
Recommendation: Your implementation of X looks aligned with our guidelines.
|
||
Consider linking it to guideline-module-structure when done.
|
||
</code></pre>
|
||
<h3 id="export-for-sharing"><a class="header" href="#export-for-sharing">Export for Sharing</a></h3>
|
||
<p><strong>You</strong>:</p>
|
||
<pre><code>Export our architectural decisions to Logseq so I can review them visually
|
||
</code></pre>
|
||
<p><strong>Claude</strong> (uses <code>kb/export</code>):</p>
|
||
<pre><code>Exporting to Logseq format...
|
||
|
||
✓ Exported 12 decisions to /Users/akasha/logseq-export/
|
||
- 12 decision records
|
||
- Preserved wikilinks
|
||
- Added Logseq properties
|
||
|
||
You can now open /Users/akasha/logseq-export/ in Logseq to visualize the decision graph.
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="tips-and-tricks-1"><a class="header" href="#tips-and-tricks-1">Tips and Tricks</a></h2>
|
||
<h3 id="1-use-natural-language"><a class="header" href="#1-use-natural-language">1. Use Natural Language</a></h3>
|
||
<p>Don't worry about exact tool syntax. Claude understands:</p>
|
||
<p>❌ <strong>Don't say</strong>: "Use kb/search with query='rust' and type='pattern'"
|
||
✅ <strong>Do say</strong>: "Find Rust patterns in KOGRAL"</p>
|
||
<h3 id="2-be-specific-with-tags"><a class="header" href="#2-be-specific-with-tags">2. Be Specific with Tags</a></h3>
|
||
<p>When adding notes, use consistent tags:</p>
|
||
<p>✅ <strong>Good</strong>: <code>tags: rust, error-handling, pattern</code>
|
||
❌ <strong>Bad</strong>: <code>tags: Rust, ErrorHandling, patterns</code></p>
|
||
<h3 id="3-link-as-you-go"><a class="header" href="#3-link-as-you-go">3. Link as You Go</a></h3>
|
||
<p>After creating notes, ask Claude to link them:</p>
|
||
<pre><code>Link this note to our error handling guideline as 'implements'
|
||
</code></pre>
|
||
<h3 id="4-review-regularly"><a class="header" href="#4-review-regularly">4. Review Regularly</a></h3>
|
||
<p>Ask Claude for summaries:</p>
|
||
<pre><code>What have we documented this week?
|
||
</code></pre>
|
||
<h3 id="5-use-semantic-search"><a class="header" href="#5-use-semantic-search">5. Use Semantic Search</a></h3>
|
||
<p>For conceptual queries:</p>
|
||
<pre><code>Find anything related to "making code maintainable"
|
||
</code></pre>
|
||
<p>Not just keyword "maintainable", but concepts like refactoring, clean code, patterns, etc.</p>
|
||
<hr />
|
||
<h2 id="troubleshooting-2"><a class="header" href="#troubleshooting-2">Troubleshooting</a></h2>
|
||
<h3 id="mcp-server-not-responding-1"><a class="header" href="#mcp-server-not-responding-1">"MCP server not responding"</a></h3>
|
||
<pre><code class="language-bash"># Check kb-mcp is built
|
||
ls target/release/kb-mcp
|
||
|
||
# Test manually
|
||
echo '{"jsonrpc":"2.0","id":1,"method":"kogral/search","params":{"query":"test"}}' \
|
||
| target/release/kb-mcp serve
|
||
</code></pre>
|
||
<h3 id="kb-directory-not-found-1"><a class="header" href="#kb-directory-not-found-1">"KB directory not found"</a></h3>
|
||
<pre><code class="language-bash"># Verify .kb exists
|
||
ls -la /path/to/project/.kogral
|
||
|
||
# Initialize if missing
|
||
cd /path/to/project
|
||
kb init
|
||
</code></pre>
|
||
<h3 id="permission-denied"><a class="header" href="#permission-denied">"Permission denied"</a></h3>
|
||
<pre><code class="language-bash"># Make binary executable
|
||
chmod +x target/release/kb-mcp
|
||
|
||
# Check environment variable
|
||
echo $KOGRAL_DIR
|
||
</code></pre>
|
||
<h3 id="empty-search-results"><a class="header" href="#empty-search-results">"Empty search results"</a></h3>
|
||
<pre><code class="language-bash"># Add some test content
|
||
kb add note "Test Note" --content "Test content"
|
||
|
||
# Try search again in Claude Code
|
||
</code></pre>
|
||
<hr />
|
||
<h2 id="next-steps-7"><a class="header" href="#next-steps-7">Next Steps</a></h2>
|
||
<ul>
|
||
<li><strong>Read</strong>: <a href="apps/../api/mcp-tools.html">MCP Tools API Reference</a> for all available tools</li>
|
||
<li><strong>Explore</strong>: <a href="apps/../guides/use-cases.html">Use Cases</a> for more examples</li>
|
||
<li><strong>Configure</strong>: <a href="apps/../config/overview.html">Configuration Reference</a> to customize behavior</li>
|
||
<li><strong>Integrate</strong>: <a href="apps/claude-code.html">Claude Code Integration</a> for advanced setup</li>
|
||
</ul>
|
||
<hr />
|
||
<h2 id="quick-reference-card"><a class="header" href="#quick-reference-card">Quick Reference Card</a></h2>
|
||
<div class="table-wrapper"><table><thead><tr><th>Task</th><th>Say to Claude</th></tr></thead><tbody>
|
||
<tr><td>Search</td><td>"Find notes about X"</td></tr>
|
||
<tr><td>Add note</td><td>"Add a note about X with tags Y, Z"</td></tr>
|
||
<tr><td>Add decision</td><td>"Document decision to use X for Y"</td></tr>
|
||
<tr><td>Get guidelines</td><td>"What are our X guidelines?"</td></tr>
|
||
<tr><td>Link nodes</td><td>"Link A to B as implements"</td></tr>
|
||
<tr><td>Summarize</td><td>"Summarize project knowledge"</td></tr>
|
||
<tr><td>Export</td><td>"Export to Logseq format"</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<hr />
|
||
<p><strong>Remember</strong>: Claude Code with MCP turns KOGRAL into an active participant in your development workflow. Ask questions, capture decisions, and let AI help you maintain your project's knowledge graph.</p>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="claude-code-integration-1"><a class="header" href="#claude-code-integration-1">Claude Code Integration</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="logseq-integration"><a class="header" href="#logseq-integration">Logseq Integration</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="obsidian-compatibility"><a class="header" href="#obsidian-compatibility">Obsidian Compatibility</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="git-workflows"><a class="header" href="#git-workflows">Git Workflows</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="mcp-protocol"><a class="header" href="#mcp-protocol">MCP Protocol</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="mcp-tools-api-reference"><a class="header" href="#mcp-tools-api-reference">MCP Tools API Reference</a></h1>
|
||
<p>Complete reference for the Model Context Protocol (MCP) server tools and resources.</p>
|
||
<h2 id="overview"><a class="header" href="#overview">Overview</a></h2>
|
||
<p>The kogral-mcp server implements the MCP protocol (JSON-RPC 2.0) for Claude Code integration. It provides:</p>
|
||
<ul>
|
||
<li><strong>10 Tools</strong>: Operations for querying and modifying knowledge base (7 core + 3 block tools)</li>
|
||
<li><strong>6 Resources</strong>: Access to knowledge graph content via URIs</li>
|
||
<li><strong>2 Prompts</strong>: Guided workflows for common tasks</li>
|
||
</ul>
|
||
<h2 id="server-configuration"><a class="header" href="#server-configuration">Server Configuration</a></h2>
|
||
<h3 id="start-mcp-server"><a class="header" href="#start-mcp-server">Start MCP Server</a></h3>
|
||
<pre><code class="language-bash"># Stdio transport (local use)
|
||
kb serve
|
||
|
||
# Or run directly
|
||
kb-mcp serve
|
||
</code></pre>
|
||
<h3 id="claude-code-configuration"><a class="header" href="#claude-code-configuration">Claude Code Configuration</a></h3>
|
||
<p>Add to <code>~/.config/claude/config.json</code>:</p>
|
||
<pre><code class="language-json">{
|
||
"mcpServers": {
|
||
"kogral-mcp": {
|
||
"command": "/path/to/kb-mcp",
|
||
"args": ["serve"],
|
||
"env": {
|
||
"KOGRAL_DIR": "/path/to/project/.kogral"
|
||
}
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="tools"><a class="header" href="#tools">Tools</a></h2>
|
||
<h3 id="kbsearch"><a class="header" href="#kbsearch">kb/search</a></h3>
|
||
<p>Search the knowledge base using text and/or semantic similarity.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"query": {
|
||
"type": "string",
|
||
"description": "Search query"
|
||
},
|
||
"type": {
|
||
"type": "string",
|
||
"enum": ["note", "decision", "guideline", "pattern", "journal", "execution", "all"],
|
||
"description": "Filter by node type",
|
||
"default": "all"
|
||
},
|
||
"project": {
|
||
"type": "string",
|
||
"description": "Limit search to specific project graph"
|
||
},
|
||
"semantic": {
|
||
"type": "boolean",
|
||
"description": "Enable semantic similarity search",
|
||
"default": true
|
||
},
|
||
"threshold": {
|
||
"type": "number",
|
||
"description": "Minimum similarity threshold (0-1)",
|
||
"default": 0.4
|
||
},
|
||
"limit": {
|
||
"type": "integer",
|
||
"description": "Maximum number of results",
|
||
"default": 10
|
||
}
|
||
},
|
||
"required": ["query"]
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 1,
|
||
"method": "kogral/search",
|
||
"params": {
|
||
"query": "error handling patterns in Rust",
|
||
"type": "pattern",
|
||
"semantic": true,
|
||
"threshold": 0.6,
|
||
"limit": 5
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 1,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Found 3 result(s):\n\n1. Error Handling with thiserror (pattern, score: 0.85)\n Tags: rust, error-handling\n Created: 2026-01-15\n \n2. Result Type Best Practices (guideline, score: 0.72)\n Tags: rust, error-handling, best-practices\n Created: 2026-01-10\n\n3. Custom Error Types (note, score: 0.65)\n Tags: rust, error-handling\n Created: 2026-01-08"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="kbadd_note"><a class="header" href="#kbadd_note">kb/add_note</a></h3>
|
||
<p>Add a new note to the knowledge base.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"title": {
|
||
"type": "string",
|
||
"description": "Note title"
|
||
},
|
||
"content": {
|
||
"type": "string",
|
||
"description": "Note content (markdown)"
|
||
},
|
||
"tags": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"description": "Tags for categorization",
|
||
"default": []
|
||
},
|
||
"relates_to": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"description": "Related node IDs",
|
||
"default": []
|
||
},
|
||
"project": {
|
||
"type": "string",
|
||
"description": "Project graph name",
|
||
"default": "default"
|
||
}
|
||
},
|
||
"required": ["title", "content"]
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 2,
|
||
"method": "kogral/add_note",
|
||
"params": {
|
||
"title": "Async Trait Patterns",
|
||
"content": "Common patterns for using async traits in Rust:\n\n1. Use `async-trait` crate\n2. Box return types for flexibility\n3. Consider Send + Sync bounds",
|
||
"tags": ["rust", "async", "patterns"],
|
||
"relates_to": ["pattern-error-handling"]
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 2,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Note added successfully: note-async-trait-patterns (ID: note-abc123)"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="kbadd_decision"><a class="header" href="#kbadd_decision">kb/add_decision</a></h3>
|
||
<p>Create an Architectural Decision Record (ADR).</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"title": {
|
||
"type": "string",
|
||
"description": "Decision title"
|
||
},
|
||
"context": {
|
||
"type": "string",
|
||
"description": "Decision context and background"
|
||
},
|
||
"decision": {
|
||
"type": "string",
|
||
"description": "The decision made"
|
||
},
|
||
"consequences": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"description": "List of consequences",
|
||
"default": []
|
||
},
|
||
"status": {
|
||
"type": "string",
|
||
"enum": ["proposed", "accepted", "rejected", "deprecated", "superseded"],
|
||
"default": "proposed"
|
||
},
|
||
"tags": {
|
||
"type": "array",
|
||
"items": { "type": "string" },
|
||
"default": []
|
||
}
|
||
},
|
||
"required": ["title", "context", "decision"]
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 3,
|
||
"method": "kogral/add_decision",
|
||
"params": {
|
||
"title": "Use SurrealDB for Knowledge Graph Storage",
|
||
"context": "Need a scalable storage solution that supports graph queries and can handle growing knowledge base",
|
||
"decision": "Adopt SurrealDB as the primary storage backend for production deployments",
|
||
"consequences": [
|
||
"Better query performance for graph traversal",
|
||
"Native support for relationships",
|
||
"Additional infrastructure dependency",
|
||
"Team needs to learn SurrealDB query language"
|
||
],
|
||
"status": "accepted",
|
||
"tags": ["architecture", "storage", "surrealdb"]
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 3,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Decision added: decision-use-surrealdb (ID: decision-xyz789)\nStatus: accepted"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="kblink"><a class="header" href="#kblink">kb/link</a></h3>
|
||
<p>Create a relationship between two nodes.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"from": {
|
||
"type": "string",
|
||
"description": "Source node ID"
|
||
},
|
||
"to": {
|
||
"type": "string",
|
||
"description": "Target node ID"
|
||
},
|
||
"relation": {
|
||
"type": "string",
|
||
"enum": ["relates_to", "depends_on", "implements", "extends", "supersedes", "explains"],
|
||
"description": "Relationship type"
|
||
},
|
||
"strength": {
|
||
"type": "number",
|
||
"description": "Relationship strength (0-1)",
|
||
"minimum": 0,
|
||
"maximum": 1,
|
||
"default": 1.0
|
||
}
|
||
},
|
||
"required": ["from", "to", "relation"]
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 4,
|
||
"method": "kogral/link",
|
||
"params": {
|
||
"from": "note-async-trait-patterns",
|
||
"to": "pattern-error-handling",
|
||
"relation": "relates_to",
|
||
"strength": 0.8
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 4,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Link created: note-async-trait-patterns --[relates_to]--> pattern-error-handling (strength: 0.8)"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Relationship Types</strong>:</p>
|
||
<ul>
|
||
<li><code>relates_to</code>: General conceptual relationship</li>
|
||
<li><code>depends_on</code>: Dependency (from depends on to)</li>
|
||
<li><code>implements</code>: Implementation of concept/pattern</li>
|
||
<li><code>extends</code>: Inherits or extends another node</li>
|
||
<li><code>supersedes</code>: Replaces an older version</li>
|
||
<li><code>explains</code>: Provides documentation/clarification</li>
|
||
</ul>
|
||
<h3 id="kbget_guidelines"><a class="header" href="#kbget_guidelines">kb/get_guidelines</a></h3>
|
||
<p>Retrieve guidelines for current project with inheritance resolution.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"language": {
|
||
"type": "string",
|
||
"description": "Programming language (e.g., rust, nushell)"
|
||
},
|
||
"category": {
|
||
"type": "string",
|
||
"description": "Guideline category (e.g., error-handling, testing)"
|
||
},
|
||
"include_base": {
|
||
"type": "boolean",
|
||
"description": "Include shared/base guidelines",
|
||
"default": true
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 5,
|
||
"method": "kogral/get_guidelines",
|
||
"params": {
|
||
"language": "rust",
|
||
"category": "error-handling",
|
||
"include_base": true
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 5,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Guidelines for rust/error-handling:\n\n## Project Guidelines (priority: 150)\n\n1. Custom Error Types (guideline-custom-errors)\n - Use thiserror for error definitions\n - Implement From traits for conversions\n - Source: .kogral/guidelines/rust-errors.md\n\n## Shared Guidelines (priority: 50)\n\n1. Result Type Best Practices (guideline-result-best-practices)\n - Always use Result<T> for fallible operations\n - Never use unwrap() in production\n - Source: ~/Tools/.kogral-shared/guidelines/rust-errors.md\n\n2. Error Propagation (guideline-error-propagation)\n - Use ? operator for error propagation\n - Add context with .context()\n - Source: ~/Tools/.kogral-shared/guidelines/rust-errors.md"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="kblist_graphs"><a class="header" href="#kblist_graphs">kb/list_graphs</a></h3>
|
||
<p>List available knowledge graphs.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 6,
|
||
"method": "kb/list_graphs"
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 6,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Available graphs:\n\n- default (Current project)\n Path: /path/to/project/.kogral\n Nodes: 42\n Last modified: 2026-01-17T10:30:00Z\n\n- shared (Shared guidelines)\n Path: ~/Tools/.kogral-shared\n Nodes: 156\n Last modified: 2026-01-15T14:20:00Z"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="kbexport"><a class="header" href="#kbexport">kb/export</a></h3>
|
||
<p>Export knowledge base to various formats.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"format": {
|
||
"type": "string",
|
||
"enum": ["logseq", "json", "markdown"],
|
||
"description": "Export format"
|
||
},
|
||
"output_path": {
|
||
"type": "string",
|
||
"description": "Output file or directory path"
|
||
},
|
||
"include_types": {
|
||
"type": "array",
|
||
"items": {
|
||
"type": "string",
|
||
"enum": ["note", "decision", "guideline", "pattern", "journal", "execution"]
|
||
},
|
||
"description": "Node types to include",
|
||
"default": ["note", "decision", "guideline", "pattern"]
|
||
},
|
||
"skip_journals": {
|
||
"type": "boolean",
|
||
"default": true
|
||
}
|
||
},
|
||
"required": ["format", "output_path"]
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 7,
|
||
"method": "kogral/export",
|
||
"params": {
|
||
"format": "logseq",
|
||
"output_path": "/Users/akasha/logseq-graph",
|
||
"include_types": ["note", "decision", "guideline"],
|
||
"skip_journals": true
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 7,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Export completed:\n\nFormat: Logseq\nOutput: /Users/akasha/logseq-graph\nExported: 42 nodes\n - 23 notes\n - 12 decisions\n - 7 guidelines\n\nLogseq graph ready to open."
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="block-tools"><a class="header" href="#block-tools">Block Tools</a></h2>
|
||
<p>Tools for querying Logseq content blocks. Requires <code>blocks.enable_mcp_tools = true</code> in configuration.</p>
|
||
<h3 id="kbfind_blocks"><a class="header" href="#kbfind_blocks">kb/find_blocks</a></h3>
|
||
<p>Find blocks by tag, task status, or custom property across the knowledge base.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"tag": {
|
||
"type": "string",
|
||
"description": "Find blocks with this tag (e.g., 'card', 'important')"
|
||
},
|
||
"status": {
|
||
"type": "string",
|
||
"enum": ["TODO", "DOING", "DONE", "LATER", "NOW", "WAITING", "CANCELLED"],
|
||
"description": "Find blocks with this task status"
|
||
},
|
||
"property_key": {
|
||
"type": "string",
|
||
"description": "Custom property key to search for"
|
||
},
|
||
"property_value": {
|
||
"type": "string",
|
||
"description": "Custom property value to match"
|
||
},
|
||
"limit": {
|
||
"type": "integer",
|
||
"description": "Maximum number of results",
|
||
"default": 20
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Note</strong>: Provide one of: <code>tag</code>, <code>status</code>, or both <code>property_key</code> and <code>property_value</code>.</p>
|
||
<p><strong>Example Request</strong> (find blocks by tag):</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 8,
|
||
"method": "kogral/find_blocks",
|
||
"params": {
|
||
"tag": "high-priority",
|
||
"limit": 10
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 8,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Found 3 blocks with tag '#high-priority':\n\n**Project Tasks** (project-tasks-123)\n - TODO Implement authentication #high-priority\n - TODO Fix security vulnerability #high-priority\n\n**Sprint Planning** (sprint-planning-456)\n - DOING Refactor database layer #high-priority"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong> (find blocks by property):</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 9,
|
||
"method": "kogral/find_blocks",
|
||
"params": {
|
||
"property_key": "priority",
|
||
"property_value": "high",
|
||
"limit": 15
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="kbfind_todos"><a class="header" href="#kbfind_todos">kb/find_todos</a></h3>
|
||
<p>Find all TODO blocks across the knowledge base.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"limit": {
|
||
"type": "integer",
|
||
"description": "Maximum number of results",
|
||
"default": 20
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 10,
|
||
"method": "kogral/find_todos",
|
||
"params": {
|
||
"limit": 25
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 10,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Found 8 TODO blocks:\n\n**Project Tasks** (project-tasks-123)\n - TODO Implement authentication\n - TODO Write integration tests\n - TODO Update documentation\n\n**Bug Fixes** (bug-fixes-456)\n - TODO Fix race condition in cache\n - TODO Address memory leak\n\n**Research** (research-789)\n - TODO Evaluate GraphQL alternatives\n - TODO Benchmark new approach\n - TODO Document findings"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="kbfind_cards"><a class="header" href="#kbfind_cards">kb/find_cards</a></h3>
|
||
<p>Find all flashcard blocks (blocks tagged with #card) for spaced repetition learning.</p>
|
||
<p><strong>Input Schema</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"type": "object",
|
||
"properties": {
|
||
"limit": {
|
||
"type": "integer",
|
||
"description": "Maximum number of flashcards",
|
||
"default": 10
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 11,
|
||
"method": "kogral/find_cards",
|
||
"params": {
|
||
"limit": 5
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Response</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 11,
|
||
"result": {
|
||
"type": "text",
|
||
"text": "Found 3 flashcards:\n\n**Rust Learning** (rust-learning-123)\n - What is Rust's ownership model? #card #rust\n - Ownership prevents data races at compile time\n - Each value has a single owner\n\n**System Design** (system-design-456)\n - What is the CAP theorem? #card #distributed-systems\n - Consistency, Availability, Partition tolerance\n - Can only guarantee 2 of 3\n\n**Algorithms** (algorithms-789)\n - What is the time complexity of quicksort? #card #algorithms\n - Average: O(n log n)\n - Worst case: O(n²)"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Use Cases</strong>:</p>
|
||
<ul>
|
||
<li><strong>kb/find_blocks</strong>: General block search by metadata</li>
|
||
<li><strong>kb/find_todos</strong>: Task management and tracking</li>
|
||
<li><strong>kb/find_cards</strong>: Spaced repetition learning system</li>
|
||
</ul>
|
||
<p><strong>See Also</strong>:</p>
|
||
<ul>
|
||
<li><a href="api/../kb/core-concepts.html#logseq-content-blocks">Logseq Blocks Support</a></li>
|
||
<li><a href="api/../architecture/adrs/004-logseq-blocks-support.html">ADR-004: Logseq Blocks Support</a></li>
|
||
</ul>
|
||
<h2 id="resources"><a class="header" href="#resources">Resources</a></h2>
|
||
<p>Resources provide read-only access to knowledge graph content via URIs.</p>
|
||
<h3 id="kogralprojectnotes"><a class="header" href="#kogralprojectnotes">kogral://project/notes</a></h3>
|
||
<p>Access project notes.</p>
|
||
<p><strong>URI</strong>: <code>kogral://project/notes</code> or <code>kogral://project/notes/{note-id}</code></p>
|
||
<p><strong>Example</strong>: Read all project notes</p>
|
||
<pre><code>kogral://project/notes
|
||
</code></pre>
|
||
<p><strong>Example</strong>: Read specific note</p>
|
||
<pre><code>kogral://project/notes/async-trait-patterns
|
||
</code></pre>
|
||
<h3 id="kogralprojectdecisions"><a class="header" href="#kogralprojectdecisions">kogral://project/decisions</a></h3>
|
||
<p>Access project decisions (ADRs).</p>
|
||
<p><strong>URI</strong>: <code>kogral://project/decisions</code> or <code>kogral://project/decisions/{decision-id}</code></p>
|
||
<h3 id="kogralprojectguidelines"><a class="header" href="#kogralprojectguidelines">kogral://project/guidelines</a></h3>
|
||
<p>Access project-specific guidelines.</p>
|
||
<p><strong>URI</strong>: <code>kogral://project/guidelines</code> or <code>kogral://project/guidelines/{guideline-id}</code></p>
|
||
<h3 id="kogralprojectpatterns"><a class="header" href="#kogralprojectpatterns">kogral://project/patterns</a></h3>
|
||
<p>Access project patterns.</p>
|
||
<p><strong>URI</strong>: <code>kogral://project/patterns</code> or <code>kogral://project/patterns/{pattern-id}</code></p>
|
||
<h3 id="kogralsharedguidelines"><a class="header" href="#kogralsharedguidelines">kogral://shared/guidelines</a></h3>
|
||
<p>Access shared guidelines (inherited).</p>
|
||
<p><strong>URI</strong>: <code>kogral://shared/guidelines</code> or <code>kogral://shared/guidelines/{guideline-id}</code></p>
|
||
<h3 id="kogralsharedpatterns"><a class="header" href="#kogralsharedpatterns">kogral://shared/patterns</a></h3>
|
||
<p>Access shared patterns (inherited).</p>
|
||
<p><strong>URI</strong>: <code>kogral://shared/patterns</code> or <code>kogral://shared/patterns/{pattern-id}</code></p>
|
||
<h2 id="prompts"><a class="header" href="#prompts">Prompts</a></h2>
|
||
<p>Prompts provide guided workflows for common tasks.</p>
|
||
<h3 id="kbsummarize_project"><a class="header" href="#kbsummarize_project">kb/summarize_project</a></h3>
|
||
<p>Generate a comprehensive project knowledge summary.</p>
|
||
<p><strong>Arguments</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"project": {
|
||
"type": "string",
|
||
"description": "Project graph name",
|
||
"default": "default"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 8,
|
||
"method": "kogral/summarize_project",
|
||
"params": {
|
||
"project": "default"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Returns</strong>: Prompt messages with project summary including:</p>
|
||
<ul>
|
||
<li>Total node counts by type</li>
|
||
<li>Recent additions</li>
|
||
<li>Top tags</li>
|
||
<li>Key decisions</li>
|
||
<li>Active patterns</li>
|
||
</ul>
|
||
<h3 id="kbfind_related"><a class="header" href="#kbfind_related">kb/find_related</a></h3>
|
||
<p>Find nodes related to a specific topic or node.</p>
|
||
<p><strong>Arguments</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"node_id": {
|
||
"type": "string",
|
||
"description": "Node ID to find relations for"
|
||
},
|
||
"depth": {
|
||
"type": "integer",
|
||
"description": "Maximum traversal depth",
|
||
"default": 2
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Example Request</strong>:</p>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 9,
|
||
"method": "kb/find_related",
|
||
"params": {
|
||
"node_id": "pattern-error-handling",
|
||
"depth": 2
|
||
}
|
||
}
|
||
</code></pre>
|
||
<p><strong>Returns</strong>: Prompt messages with:</p>
|
||
<ul>
|
||
<li>Direct relationships</li>
|
||
<li>Indirect relationships (depth 2+)</li>
|
||
<li>Common tags</li>
|
||
<li>Related guidelines</li>
|
||
</ul>
|
||
<h2 id="error-handling-1"><a class="header" href="#error-handling-1">Error Handling</a></h2>
|
||
<h3 id="error-codes"><a class="header" href="#error-codes">Error Codes</a></h3>
|
||
<p>Standard JSON-RPC 2.0 error codes:</p>
|
||
<div class="table-wrapper"><table><thead><tr><th>Code</th><th>Meaning</th><th>Description</th></tr></thead><tbody>
|
||
<tr><td>-32700</td><td>Parse error</td><td>Invalid JSON</td></tr>
|
||
<tr><td>-32600</td><td>Invalid Request</td><td>Missing required fields</td></tr>
|
||
<tr><td>-32601</td><td>Method not found</td><td>Unknown tool/resource</td></tr>
|
||
<tr><td>-32602</td><td>Invalid params</td><td>Parameter validation failed</td></tr>
|
||
<tr><td>-32603</td><td>Internal error</td><td>Server-side error</td></tr>
|
||
</tbody></table>
|
||
</div>
|
||
<h3 id="example-error-response"><a class="header" href="#example-error-response">Example Error Response</a></h3>
|
||
<pre><code class="language-json">{
|
||
"jsonrpc": "2.0",
|
||
"id": 1,
|
||
"error": {
|
||
"code": -32602,
|
||
"message": "Invalid params",
|
||
"data": {
|
||
"details": "Field 'query' is required but missing",
|
||
"field": "query"
|
||
}
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="best-practices-2"><a class="header" href="#best-practices-2">Best Practices</a></h2>
|
||
<h3 id="1-use-semantic-search-for-discovery"><a class="header" href="#1-use-semantic-search-for-discovery">1. Use Semantic Search for Discovery</a></h3>
|
||
<pre><code class="language-json">{
|
||
"method": "kogral/search",
|
||
"params": {
|
||
"query": "how to handle database connection errors",
|
||
"semantic": true,
|
||
"threshold": 0.5
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="2-link-related-concepts"><a class="header" href="#2-link-related-concepts">2. Link Related Concepts</a></h3>
|
||
<pre><code class="language-json">{
|
||
"method": "kogral/link",
|
||
"params": {
|
||
"from": "note-new-discovery",
|
||
"to": "pattern-related-pattern",
|
||
"relation": "implements"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="3-query-guidelines-before-implementation"><a class="header" href="#3-query-guidelines-before-implementation">3. Query Guidelines Before Implementation</a></h3>
|
||
<pre><code class="language-json">{
|
||
"method": "kogral/get_guidelines",
|
||
"params": {
|
||
"language": "rust",
|
||
"category": "testing"
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h3 id="4-document-decisions-with-adrs"><a class="header" href="#4-document-decisions-with-adrs">4. Document Decisions with ADRs</a></h3>
|
||
<pre><code class="language-json">{
|
||
"method": "kogral/add_decision",
|
||
"params": {
|
||
"title": "Use X for Y",
|
||
"context": "Background...",
|
||
"decision": "We will...",
|
||
"consequences": ["Pro 1", "Con 1"]
|
||
}
|
||
}
|
||
</code></pre>
|
||
<h2 id="see-also-1"><a class="header" href="#see-also-1">See Also</a></h2>
|
||
<ul>
|
||
<li><a href="https://modelcontextprotocol.io/docs">MCP Specification</a></li>
|
||
<li><a href="api/../user-guide/quickstart.html">Quick Start Guide</a></li>
|
||
<li><a href="api/../user-guide/configuration.html">Configuration Reference</a></li>
|
||
<li><a href="api/../../crates/kb-mcp/src/">kb-mcp Source Code</a></li>
|
||
</ul>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="resources-1"><a class="header" href="#resources-1">Resources</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="rust-api"><a class="header" href="#rust-api">Rust API</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="development-setup"><a class="header" href="#development-setup">Development Setup</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="code-standards"><a class="header" href="#code-standards">Code Standards</a></h1>
|
||
<div style="break-before: page; page-break-before: always;"></div><h1 id="testing"><a class="header" href="#testing">Testing</a></h1>
|
||
|
||
</main>
|
||
|
||
<nav class="nav-wrapper" aria-label="Page navigation">
|
||
<!-- Mobile navigation buttons -->
|
||
|
||
|
||
<div style="clear: both"></div>
|
||
</nav>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="nav-wide-wrapper" aria-label="Page navigation">
|
||
|
||
</nav>
|
||
|
||
</div>
|
||
|
||
|
||
|
||
|
||
<script>
|
||
window.playground_copyable = true;
|
||
</script>
|
||
|
||
|
||
<script src="elasticlunr.min.js"></script>
|
||
<script src="mark.min.js"></script>
|
||
<script src="searcher.js"></script>
|
||
|
||
<script src="clipboard.min.js"></script>
|
||
<script src="highlight.js"></script>
|
||
<script src="book.js"></script>
|
||
|
||
<!-- Custom JS scripts -->
|
||
|
||
<script>
|
||
window.addEventListener('load', function() {
|
||
window.setTimeout(window.print, 100);
|
||
});
|
||
</script>
|
||
|
||
</div>
|
||
</body>
|
||
</html>
|