chore: add docs

This commit is contained in:
Jesús Pérez 2026-01-23 16:11:07 +00:00
parent 44cd68f2ed
commit 22e9473e69
Signed by: jesus
GPG Key ID: 9F243E355E0BC939
206 changed files with 54161 additions and 0 deletions

336
CHANGELOG.md Normal file
View File

@ -0,0 +1,336 @@
# Changelog
All notable changes to the Knowledge Base project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Changed
#### BREAKING: MCP Protocol Rebranding
- **All MCP URIs, tool names, and prompts updated to KOGRAL namespace**
- **URI Scheme**: `kb://``kogral://`
- `kb://project/notes``kogral://project/notes`
- `kb://project/decisions``kogral://project/decisions`
- `kb://project/guidelines``kogral://project/guidelines`
- `kb://project/patterns``kogral://project/patterns`
- `kb://shared/guidelines``kogral://shared/guidelines`
- `kb://shared/patterns``kogral://shared/patterns`
- **Tool Names**: `kb/*``kogral/*`
- `kb/search``kogral/search`
- `kb/add_note``kogral/add_note`
- `kb/add_decision``kogral/add_decision`
- `kb/link``kogral/link`
- `kb/get_guidelines``kogral/get_guidelines`
- `kb/list_graphs``kogral/list_graphs`
- `kb/export``kogral/export`
- `kb/find_blocks``kogral/find_blocks`
- `kb/find_todos``kogral/find_todos`
- `kb/find_cards``kogral/find_cards`
- **Prompt Names**: `kb/*``kogral/*`
- `kb/summarize_project``kogral/summarize_project`
- `kb/find_related``kogral/find_related`
- **Server Name**: `kb-mcp``kogral-mcp`
- **Migration Required**: Users must update `~/.config/claude/config.json`:
```diff
{
"mcpServers": {
- "kb-mcp": {
- "command": "/path/to/kb-mcp",
+ "kogral-mcp": {
+ "command": "/path/to/kogral-mcp",
- "env": { "KB_DIR": "/path/to/.kb" }
+ "env": { "KOGRAL_DIR": "/path/to/.kogral" }
}
}
}
```
- **Rationale**: Complete branding consistency across all interfaces. Pre-release status (v0.x) allows breaking changes without user pain.
- **Files updated**:
- `crates/kb-mcp/src/resources.rs`: All URI definitions and parsing
- `crates/kb-mcp/src/tools.rs`: All tool name definitions and dispatch
- `crates/kb-mcp/src/prompts.rs`: All prompt name definitions and dispatch
- `crates/kb-mcp/src/main.rs`: Server name registration
- `crates/kb-mcp/tests/integration_test.rs`: Test assertions updated
- `docs/apps/mcp-quickguide.md`: All examples and references
- `docs/kogral/core-concepts.md`: MCP protocol documentation
- `docs/api/mcp-tools.md`: API reference documentation
- `README.md`: MCP integration examples
#### Branding
- **Rebranded to KOGRAL** (**KO**wledge **GRA**phs, **L**ocal-first)
- Updated project name from "Knowledge Base" to "KOGRAL" across all documentation
- New tagline: "Git-native knowledge graphs for developer teams"
- Updated description emphasizing structured knowledge management for developers
- Documentation folder renamed: `docs/kb/``docs/kogral/`
- Documentation files renamed:
- `docs/kogral/what-is-kb.md``what-is-kogral.md`
- `docs/kogral/why-kb.md``why-kogral.md`
- Technical names maintained for compatibility: `kb-core`, `kb-cli`, `kb-mcp` (crate names), `kb` (CLI binary)
- Hybrid naming strategy: KOGRAL as primary brand, `kb.vapora.dev` as future alias
- Files updated:
- `README.md`: Title, description, and branding
- `Cargo.toml` (workspace): Homepage, description, repository metadata
- `crates/kb-core/Cargo.toml`: Package description
- `crates/kb-cli/Cargo.toml`: Package description
- `crates/kb-mcp/Cargo.toml`: Package description
- `crates/kb-core/src/lib.rs`: Module documentation comments
- `crates/kb-core/src/models.rs`: Type documentation comments
- `crates/kb-cli/src/main.rs`: CLI description and messages
- `docs/kogral/what-is-kogral.md`: Full KOGRAL branding with acronym explanation
- `docs/kogral/core-concepts.md`: All "KB" references replaced with "KOGRAL"
- `docs/README.md`: Title and section headers
- `docs/book.toml`: mdBook title and description
- `docs/SUMMARY.md`: Navigation titles
- `.claude/CLAUDE.md`: Project description and documentation links
#### BREAKING: Filesystem Paths and Environment Variables
- **Directory structure renamed from `.kb/` to `.kogral/`**
- Project directory: `.kb/``.kogral/`
- Shared directory: `.kb-shared/``.kogral-shared/`
- Configuration paths updated throughout codebase
- All storage paths use `.kogral/` prefix
- **Environment variable renamed from `KB_DIR` to `KOGRAL_DIR`**
- MCP server configuration requires `KOGRAL_DIR` environment variable
- CLI tools respect `KOGRAL_DIR` for custom directory location
- **Migration Required**:
1. Rename existing directory: `mv .kb .kogral`
2. Update environment variables: `KB_DIR``KOGRAL_DIR`
3. Update config files referencing `.kb/` paths
- **Rationale**: Complete branding consistency across all filesystem operations. Pre-release status allows breaking changes.
- **Files updated**:
- All Rust source files in `crates/`: Path constants and variable names
- `crates/kb-core/src/config/loader.rs`: Configuration path discovery
- `crates/kb-core/src/storage/filesystem.rs`: Storage paths
- `crates/kb-cli/src/main.rs`: CLI messages and examples
- `crates/kb-mcp/src/main.rs`: Environment variable handling
- All documentation in `docs/`: Examples and instructions
- `README.md`: Setup and usage examples
- `.claude/CLAUDE.md`: Project configuration paths
### Added
#### Core Infrastructure
- Initial project structure with Cargo workspace (kb-core, kb-cli, kb-mcp)
- Core graph models (Node, Edge, Graph) with 6 node types and 6 relationship types
- Config-driven architecture using Nickel schemas (NCL → JSON → Rust)
- Markdown parser with Logseq compatibility (YAML frontmatter + wikilinks)
- Storage backends: Filesystem (default), In-Memory, SurrealDB (feature-gated)
- Embedding integration via rig-core (OpenAI, Claude, Ollama) + fastembed local fallback
- Tera template engine for document generation and export
#### Logseq Blocks Support (ADR-004)
- **Block Data Structures** (`crates/kb-core/src/models.rs`):
- `TaskStatus` enum: TODO, DOING, DONE, LATER, NOW, WAITING, CANCELLED
- `BlockProperties` struct: tags, task status, custom properties, block/page references
- `Block` struct: hierarchical content blocks with properties and children
- `Node.blocks` field: Optional cached block structure (lazy parsing)
- **BlockParser** (`crates/kb-core/src/block_parser.rs`):
- Bidirectional parser: Markdown outliner ↔ Block structures
- Supports: task markers, #tags, custom properties (key:: value), ((block-refs)), [[page-refs]]
- Hierarchical parsing based on indentation
- Round-trip fidelity for Logseq import/export
- 15 comprehensive tests covering parsing, serialization, and round-trips
- **Block Query API**:
- `Node::get_blocks()`: Lazy parsing from content with caching
- `Node::find_blocks_by_tag()`: Find blocks with specific tag
- `Node::find_all_todos()`: Find all task blocks
- `Node::find_blocks_by_status()`: Find blocks by task status
- `Node::find_blocks_by_property()`: Find blocks with custom property
- `Graph::find_blocks_by_tag()`: Search blocks across all nodes
- `Graph::find_all_todos()`: Find TODOs across entire graph
- 6 integration tests for Node and Graph block queries
- **SurrealDB Schema** (`schemas/surrealdb/blocks.surql`):
- `block` table with indexes on node_id, block_id, tags, status, parent_id
- Full-text search index on content
- Support for hierarchical queries and tag-based filtering
- Migration queries and example usage patterns
- **Logseq Import/Export** (`crates/kb-core/src/import/logseq.rs`, `crates/kb-core/src/export/logseq.rs`):
- Round-trip import: Logseq markdown → KB Node with blocks
- Property parsing: key:: value format, tags ([[tag]] and #tag), references, timestamps
- Export to Logseq page/journal formats with block serialization
- 12 comprehensive tests covering import/export round-trips
- **MCP Block Tools** (`crates/kb-mcp/src/tools.rs`):
- `kb/find_blocks`: Search blocks by tag, task status, or custom property
- `kb/find_todos`: Find all TODO blocks across KB
- `kb/find_cards`: Find all flashcard blocks (#card tag)
- Fixed borrowing issues with two-phase data collection pattern
- **Configuration Schema** (`schemas/kb-config.ncl`, `config/*.ncl`):
- Added `BlocksConfig` schema with 4 settings:
- `enabled`: Enable/disable Logseq blocks parsing (default: false, opt-in)
- `parse_on_import`: Auto-parse blocks from Logseq imports (default: true)
- `serialize_on_export`: Serialize blocks to outliner format on export (default: true)
- `enable_mcp_tools`: Enable block-related MCP tools (default: true)
- Updated all config files (defaults.ncl, production.ncl, minimal.ncl)
- Validated: All configs export to JSON successfully
- **Documentation**:
- Complete technical design: `docs/architecture/logseq-blocks-design.md`
- Architectural Decision Record: `docs/architecture/adrs/004-logseq-blocks-support.md`
- 6-phase implementation plan documented
- Use cases and examples for flashcards, task tracking, and block references
#### Applications
- CLI tool (kb-cli) with 13 commands (init, add, search, link, sync, serve, etc.)
- MCP server (kb-mcp) with 10 tools (7 core + 3 block tools), 6 resources, 2 prompts for Claude Code integration
- NuShell maintenance scripts (6 scripts: sync, backup, reindex, import/export Logseq, stats)
#### Configuration System (Nickel)
- Complete Nickel configuration schema following typedialog/provisioning pattern
- Schema contracts (`schemas/kb/contracts.ncl`) - Type definitions with validation
- Default values (`schemas/kb/defaults.ncl`) - Base configuration values
- Composition helpers (`schemas/kb/helpers.ncl`) - merge_with_override, compose_config
- Mode overlays (`schemas/kb/modes/{dev,prod,test}.ncl`) - Environment-specific tuning
- Runtime configurations (`.kb-config/core/kb.ncl`, `.kb-config/platform/*.ncl`)
- Double validation pattern: Nickel contracts + serde deserialization
- Config export to JSON for Rust consumption (`.kb-config/targets/*.json`)
#### Templates
- Tera templates for 6 document types (note, decision, guideline, pattern, journal, execution)
- 4 export format templates (Logseq page/journal, summary, JSON)
- Customizable template system with frontmatter generation
#### Documentation (mdBook)
- Complete mdBook documentation structure with SUMMARY.md navigation
- 4 SVG diagrams for visual guides:
- `architecture-overview.svg` - Complete system architecture
- `core-concepts.svg` - Node types and relationships
- `config-composition.svg` - Configuration flow (Schema → Defaults → Mode → User → JSON)
- `storage-architecture.svg` - Hybrid storage strategy
- 11 major documentation sections (kb/, guides/, architecture/, setup/, config/, storage/, ai/, templates/, cli/, apps/, api/, contributing/)
- ADR documentation (ADR-001: Nickel vs TOML, ADR-002: FastEmbed + AI providers, ADR-003: Hybrid storage)
- Use cases documentation (8 real-world scenarios)
- MCP quick guide (5-minute setup for Claude Code)
- Config-driven architecture deep dive
- Storage architecture deep dive with code examples
- docs/README.md - Comprehensive guide for using and building documentation
#### Build System
- Modular justfile system (7 modules: build, test, dev, ci, nickel, docs, scripts)
- Nickel validation and export recipes (`just nickel::validate-all`, `just nickel::export-all`)
- mdBook documentation recipes (`just docs::serve`, `just docs::build`, `just docs::test`)
- CI/CD pipeline recipes (format, lint, test, build validation)
- NuShell script validation and execution recipes
#### Project Documentation
- Enhanced README.md with complete documentation links and visual diagram references
- Directory structure documentation with detailed explanations
- Quick start guide with MCP integration
- Integration guides (Vapora, Logseq, Claude Code)
- CHANGELOG.md following Keep a Changelog format
### Changed
- Updated README.md with comprehensive documentation links (mdBook, guides, architecture, config)
- Enhanced directory structure documentation with .kb-config/ and schemas/kb/ details
- Improved documentation section with visual diagrams and just recipe references
- **Blocks Feature Documentation**:
- README.md: Added Logseq blocks section with examples, features, and configuration
- README.md: Updated architecture diagram to include BlockParser
- README.md: Added 3 new MCP tools to integration section
- docs/kb/core-concepts.md: Added comprehensive "Logseq Content Blocks" section with examples, use cases, and queries
- docs/api/mcp-tools.md: Added "Block Tools" section documenting kb/find_blocks, kb/find_todos, kb/find_cards
- docs/api/mcp-tools.md: Updated overview from 7 to 10 tools
### Infrastructure
- Cargo workspace with 3 crates
- Feature flags: `filesystem` (default), `surrealdb`, `fastembed`, `full`
- 90 tests across workspace (81 kb-core + 5 kb-mcp + 4 kb-cli)
- 15 tests for block parser
- 12 tests for Logseq import/export
- 6 tests for block queries
- mdBook integration for documentation (serves on [http://localhost:3000](http://localhost:3000))
- Nickel → JSON → Rust configuration pipeline
- Justfile modules: build, test, dev, ci, nickel, docs, scripts
- CI/CD pipeline recipes (format, lint, test, build validation)
- mdBook documentation structure with SUMMARY.md
- NuShell script validation recipes
## [1.0.0] - TBD
Initial release (planned).
### Planned Features
- Full FilesystemStorage implementation with file watching
- Complete Query Engine with semantic search
- Guideline inheritance resolution (shared → local override priority)
- SurrealDB schema and complete backend implementation
- Sync mechanism: bidirectional Filesystem ↔ SurrealDB
- FastEmbed local embeddings integration
- API provider integrations (OpenAI, Claude, Ollama) for embeddings
- Cross-graph queries (project + shared knowledge bases)
- Template customization system
- Export to multiple formats (Logseq, JSON, Markdown)
- Import from Logseq graphs
- MCP protocol compliance testing
- Production-ready CLI tool
- Performance benchmarks
- Security audit
---
## Version History
### Version Numbering
This project follows semantic versioning (MAJOR.MINOR.PATCH):
- **MAJOR**: Incompatible API changes
- **MINOR**: New features (backward compatible)
- **PATCH**: Bug fixes (backward compatible)
### Release Schedule
- **Alpha** (0.x.x): API unstable, features incomplete
- **Beta** (1.0.0-beta.x): API stable, features complete, testing phase
- **Stable** (1.0.0+): Production ready
### Feature Roadmap
#### Phase 1: Foundation ✅ (Completed)
- [x] Core models and graph structure
- [x] Nickel configuration system
- [x] Markdown parser with Logseq compatibility
- [x] Storage trait abstraction
- [x] CLI structure with clap
- [x] MCP server protocol implementation
- [x] NuShell maintenance scripts
- [x] Documentation structure
#### Phase 2: Integration (In Progress)
- [ ] Full FilesystemStorage with file watching
- [ ] SurrealDB backend implementation
- [ ] Query engine with text + semantic search
- [ ] Embedding provider integrations
- [ ] Guideline inheritance resolution
- [ ] Sync mechanism implementation
#### Phase 3: Polish (Planned)
- [ ] Performance optimization
- [ ] Comprehensive error handling
- [ ] Production testing
- [ ] Security audit
- [ ] API stabilization
- [ ] Release preparation
---
## Migration Guides
### Upgrading from 0.x to 1.0
(To be documented when 1.0 is released)
---
## Contributing
See [CONTRIBUTING.md](docs/contributing/development.md) for development setup and guidelines.
---
## License
(To be determined)

105
CODE_OF_CONDUCT.md Normal file
View File

@ -0,0 +1,105 @@
# Code of Conduct
## Our Pledge
We, as members, contributors, and leaders, pledge to make participation in our project and
community a harassment-free experience for everyone, regardless of:
- Age
- Body size
- Visible or invisible disability
- Ethnicity
- Sex characteristics
- Gender identity and expression
- Level of experience
- Education
- Socioeconomic status
- Nationality
- Personal appearance
- Race
- Caste
- Color
- Religion
- Sexual identity and orientation
We pledge to act and interact in ways that contribute to an open, welcoming, diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by mistakes
- Focusing on what is best not just for us as individuals, but for the overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery
- Trolling, insulting, or derogatory comments
- Personal or political attacks
- Public or private harassment
- Publishing others' private information (doxing)
- Other conduct which could reasonably be considered inappropriate in a professional setting
## Enforcement Responsibilities
Project maintainers are responsible for clarifying and enforcing our standards of acceptable
behavior and will take appropriate corrective action in response to unacceptable behavior.
Maintainers have the right and responsibility to:
- Remove, edit, or reject comments, commits, code, and other contributions
- Ban contributors for behavior they deem inappropriate, threatening, or harmful
## Scope
This Code of Conduct applies to:
- All community spaces (GitHub, forums, chat, events, etc.)
- Official project channels and representations
- Interactions between community members related to the project
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be reported to project maintainers:
- Email: [project contact]
- GitHub: Private security advisory
- Issues: Report with `conduct` label (public discussions only)
All complaints will be reviewed and investigated promptly and fairly.
### Enforcement Guidelines
**1. Correction**
- Community impact: Use of inappropriate language or unwelcoming behavior
- Action: Private written warning with explanation and clarity on impact
- Consequence: Warning and no further violations
**2. Warning**
- Community impact: Violation through single incident or series of actions
- Action: Written warning with severity consequences for continued behavior
- Consequence: Suspension from community interaction
**3. Temporary Ban**
- Community impact: Serious violation of standards
- Action: Temporary ban from community interaction
- Consequence: Revocation of ban after reflection period
**4. Permanent Ban**
- Community impact: Pattern of violating community standards
- Action: Permanent ban from community interaction
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org), version 2.1.
For answers to common questions about this code of conduct, see the [FAQ](https://www.contributor-covenant.org/faq).
---
**Thank you for being part of our community!**
We believe in creating a welcoming and inclusive space where everyone can contribute their best work. Together, we make this project better.

130
CONTRIBUTING.md Normal file
View File

@ -0,0 +1,130 @@
# Contributing
Thank you for your interest in contributing! This document provides guidelines and instructions for contributing to this project.
## Code of Conduct
This project adheres to a Code of Conduct. By participating, you are expected to uphold this code.
Please see [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for details.
## Getting Started
### Prerequisites
- Rust 1.70+ (if project uses Rust)
- NuShell (if project uses Nushell scripts)
- Git
### Development Setup
1. Fork the repository
2. Clone your fork: `git clone <your-fork-url>`
3. Add upstream: `git remote add upstream <upstream-url>`
4. Create a branch: `git checkout -b feature/your-feature`
## Development Workflow
### Before You Code
- Check existing issues and pull requests to avoid duplication
- Create an issue to discuss major changes before implementing
- Assign yourself to let others know you're working on it
### Code Standards
#### Rust
- Run `cargo fmt --all` before committing
- All code must pass `cargo clippy -- -D warnings`
- Write tests for new functionality
- Maintain 100% documentation coverage for public APIs
#### Nushell
- Validate scripts with `nu --ide-check 100 script.nu`
- Follow consistent naming conventions
- Use type hints where applicable
#### Nickel
- Type check schemas with `nickel typecheck`
- Document schema fields with comments
- Test schema validation
### Commit Guidelines
- Write clear, descriptive commit messages
- Reference issues with `Fixes #123` or `Related to #123`
- Keep commits focused on a single concern
- Use imperative mood: "Add feature" not "Added feature"
### Testing
All changes must include tests:
```bash
# Run all tests
cargo test --workspace
# Run with coverage
cargo llvm-cov --all-features --lcov
# Run locally before pushing
just ci-full
```
### Pull Request Process
1. Update documentation for any changed functionality
2. Add tests for new code
3. Ensure all CI checks pass
4. Request review from maintainers
5. Be responsive to feedback and iterate quickly
## Review Process
- Maintainers will review your PR within 3-5 business days
- Feedback is constructive and meant to improve the code
- All discussions should be respectful and professional
- Once approved, maintainers will merge the PR
## Reporting Bugs
Found a bug? Please file an issue with:
- **Title**: Clear, descriptive title
- **Description**: What happened and what you expected
- **Steps to reproduce**: Minimal reproducible example
- **Environment**: OS, Rust version, etc.
- **Screenshots**: If applicable
## Suggesting Enhancements
Have an idea? Please file an issue with:
- **Title**: Clear feature title
- **Description**: What, why, and how
- **Use cases**: Real-world scenarios where this would help
- **Alternative approaches**: If you've considered any
## Documentation
- Keep README.md up to date
- Document public APIs with rustdoc comments
- Add examples for non-obvious functionality
- Update CHANGELOG.md with your changes
## Release Process
Maintainers handle releases following semantic versioning:
- MAJOR: Breaking changes
- MINOR: New features (backward compatible)
- PATCH: Bug fixes
## Questions
- Check existing documentation and issues
- Ask in discussions or open an issue
- Join our community channels
Thank you for contributing!

98
SECURITY.md Normal file
View File

@ -0,0 +1,98 @@
# Security Policy
## Supported Versions
This project provides security updates for the following versions:
| Version | Supported |
|---------|-----------|
| 1.x | ✅ Yes |
| 0.x | ❌ No |
Only the latest major version receives security patches. Users are encouraged to upgrade to the latest version.
## Reporting a Vulnerability
**Do not open public GitHub issues for security vulnerabilities.**
Instead, please report security issues to the maintainers privately:
### Reporting Process
1. Email security details to the maintainers (see project README for contact)
2. Include:
- Description of the vulnerability
- Steps to reproduce (if possible)
- Potential impact
- Suggested fix (if you have one)
3. Expect acknowledgment within 48 hours
4. We will work on a fix and coordinate disclosure timing
### Responsible Disclosure
- Allow reasonable time for a fix before public disclosure
- Work with us to understand and validate the issue
- Maintain confidentiality until the fix is released
## Security Best Practices
### For Users
- Keep dependencies up to date
- Use the latest version of this project
- Review security advisories regularly
- Report vulnerabilities responsibly
### For Contributors
- Run `cargo audit` before submitting PRs
- Use `cargo deny` to check license compliance
- Follow secure coding practices
- Don't hardcode secrets or credentials
- Validate all external inputs
## Dependency Security
We use automated tools to monitor dependencies:
- **cargo-audit**: Scans for known security vulnerabilities
- **cargo-deny**: Checks licenses and bans unsafe dependencies
These run in CI on every push and PR.
## Code Review
All code changes go through review before merging:
- At least one maintainer review required
- Security implications considered
- Tests required for all changes
- CI checks must pass
## Known Vulnerabilities
We maintain transparency about known issues:
- Documented in GitHub security advisories
- Announced in release notes
- Tracked in issues with `security` label
## Security Contact
For security inquiries, please contact:
- Email: [project maintainers]
- Issue: Open a private security advisory on GitHub
## Changelog
Security fixes are highlighted in CHANGELOG.md with [SECURITY] prefix.
## Resources
- [OWASP Top 10](https://owasp.org/www-project-top-ten/)
- [CWE: Common Weakness Enumeration](https://cwe.mitre.org/)
- [Rust Security](https://www.rust-lang.org/governance/security-disclosures)
- [npm Security](https://docs.npmjs.com/about-npm/security)
## Questions
If you have security questions (not vulnerabilities), open a discussion or issue with the `security` label.

147
assets/branding/README.md Normal file
View File

@ -0,0 +1,147 @@
# KOGRAL Branding Assets
Complete branding system for KOGRAL (KOwledge GRAphs, Local-first) - Git-native knowledge graphs for developer teams.
## Directory Structure
```text
branding/
├── README.md # This file
├── index.html # Quick reference showcase
└── kogral-assets-showcase.html # Comprehensive assets catalog
```
## Quick Start
Open either HTML file in your browser to view the interactive branding showcase:
- **`index.html`** - Compact version with dark/light mode toggle
- **`kogral-assets-showcase.html`** - Full-featured showcase with navigation, comparison tables, and detailed guidelines
## Logo Variants
All logos are available in the `../logos/` directory with these suffixes:
| Suffix | Variant | Dimensions | Use Case |
|--------|---------|------------|----------|
| (none) | Full logo | 610×200px | Primary branding |
| `-h` | Horizontal | 1000×300px | Banners, headers |
| `-s` | Square | 200×200px | Apps, UI, icons |
| `-h-s` | Horizontal small | 500×150px | Navbars, sidebars |
| `-bn` | Black & white | 610×200px | Print, documents |
## Icon Variants
All icons are available in the `../icons/` directory:
| Variant | Dimensions | Use Case |
|---------|------------|----------|
| `kogral-icon.svg` | 200×200px | Standard favicon, UI elements |
| `kogral-icon-s.svg` | 100×100px | Compact spaces, mobile UI |
| `kogral-icon-bn.svg` | 200×200px | Print, monochrome contexts |
## Color Palette
Core brand colors with semantic meaning:
- **Primary Blue** (`#4a9eff`) - Knowledge, connectivity, main interactions
- **Accent Green** (`#3dd68d`) - Active states, growth, highlights
- **Gold** (`#fbbf24`) - Important elements, central focal point
- **Slate Secondary** (`#64748b`) - Secondary elements, borders
- **Dark Primary** (`#1a2744`) - UI backgrounds, primary dark
- **Dark Secondary** (`#2a3f6a`) - UI emphasis, secondary dark
## Usage Guidelines
### Logo Sizing
- Use the full logo for primary brand identification
- Maintain minimum 20px whitespace around all sides
- Never distort or rotate the logo
- For small spaces, prefer square or horizontal-small variants
### Color Usage
- Primary Blue for main interactions and hierarchy
- Accent Green highlights active states and positive actions
- Gold emphasizes central knowledge nodes and critical elements
- Use monochrome variants on dark/light backgrounds for accessibility
### Digital Applications
- All assets are SVG format for infinite scalability
- Use in web apps, mobile interfaces, and digital signage
- Animations are preserved and scale responsively
- Export to PNG at 2x resolution for high-DPI displays
### Print Production
- Always use black & white variants for print materials
- Ensure minimum 1/4" clear space around logo
- Test colors on actual materials before final production
- Monochrome is preferred for single-color prints
### Social Media
- Use square logo (200×200px) for profile pictures
- Horizontal variants (1000×300px) for banners and covers
- Export PNG at 2x scale for high-DPI displays
- Use full logo for primary brand identification
### Accessibility
- All color combinations meet WCAG AA standards
- Monochrome variants ensure colorblind accessibility
- Use descriptive alt-text: "KOGRAL - Git-native knowledge graphs"
## Asset Features
✨ **Interactive HTML Showcases**
- Dark/light mode toggle with persistent storage
- Responsive grid layouts
- One-click filename copy
- Sticky navigation (showcase version)
- Color palette visualization
- Comparison tables
🎨 **SVG Animations**
- Pulsing central node
- Flowing connection lines
- Rotating hexagon outlines
- Synchronized glow effects
📊 **Format & Scalability**
- All assets in SVG format
- Infinite scalability without quality loss
- Animations preserved across all sizes
- Print-ready monochrome variants
## File Sizes
- Full logos: ~3-5KB each
- Icons: ~2-3KB each
- HTML showcases: ~20-29KB
## Version Information
- **Last Updated:** 2026-01-18
- **Version:** 1.0
- **Format:** SVG + HTML5
- **Compatibility:** All modern browsers
## Brand Identity
**KOGRAL** represents:
- 🧠 Knowledge graphs for intelligent organization
- 🔗 Connected nodes and relationships
- 💾 Git-native, local-first architecture
- 🌍 Distributed, team-friendly knowledge management
- ✨ Elegant, animated visual identity
## Related Assets
- Logos: `/assets/logos/`
- Icons: `/assets/icons/`
- Main logo file: `/assets/kogral-logo.svg`
## Contact & Updates
For asset requests or updates, refer to the KOGRAL project documentation.
---
**All assets in KOGRAL branding system are optimized for scalability, accessibility, and brand consistency.**

1304
assets/branding/index.html Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,221 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<defs>
<radialGradient id="nodeCore-icon-bn" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4f4f4f"/>
<stop offset="100%" stop-color="#2d2d2d"/>
</radialGradient>
<radialGradient id="nodeAccent-icon-bn" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#7a7a7a"/>
<stop offset="100%" stop-color="#555555"/>
</radialGradient>
<radialGradient id="nodeSecondary-icon-bn" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#999999"/>
<stop offset="100%" stop-color="#6b6b6b"/>
</radialGradient>
<radialGradient id="nodeGold-icon-bn" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#f0f0f0"/>
<stop offset="40%" stop-color="#d0d0d0"/>
<stop offset="100%" stop-color="#a0a0a0"/>
</radialGradient>
<filter id="glow-icon-bn" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-icon-bn" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#d0d0d0" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.connection-base {
stroke: #909090;
stroke-width: 1;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #4f4f4f;
stroke-width: 1.5;
stroke-linecap: round;
stroke-dasharray: 6 25;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #7a7a7a;
stroke-width: 1.5;
stroke-linecap: round;
stroke-dasharray: 6 25;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #4f4f4f;
stroke-width: 0.8;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #7a7a7a;
stroke-width: 0.6;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #4f4f4f;
stroke-width: 0.8;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #7a7a7a;
stroke-width: 0.8;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
</style>
<g id="icon" transform="translate(50, 50) scale(0.25)">
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<g id="nodes">
<g class="node-center" filter="url(#glowGold-icon-bn)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-icon-bn)"/>
<circle cx="256" cy="256" r="16" fill="#f0f0f0" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<g class="node-1" filter="url(#glow-icon-bn)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-icon-bn)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-2" filter="url(#glow-icon-bn)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-icon-bn)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-3" filter="url(#glow-icon-bn)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-icon-bn)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-4" filter="url(#glow-icon-bn)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-icon-bn)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-5" filter="url(#glow-icon-bn)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-icon-bn)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-6" filter="url(#glow-icon-bn)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-icon-bn)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1,221 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100" width="100" height="100">
<defs>
<radialGradient id="nodeCore-icon-s" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent-icon-s" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary-icon-s" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold-icon-s" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow-icon-s" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="1.5" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-icon-s" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.08); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 20; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.connection-base {
stroke: #475569;
stroke-width: 0.6;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #4a9eff;
stroke-width: 0.8;
stroke-linecap: round;
stroke-dasharray: 4 15;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #3dd68d;
stroke-width: 0.8;
stroke-linecap: round;
stroke-dasharray: 4 15;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #4a9eff;
stroke-width: 0.5;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #3dd68d;
stroke-width: 0.4;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #4a9eff;
stroke-width: 0.5;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #3dd68d;
stroke-width: 0.5;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
</style>
<g id="icon" transform="translate(25, 25) scale(0.125)">
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<g id="nodes">
<g class="node-center" filter="url(#glowGold-icon-s)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-icon-s)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<g class="node-1" filter="url(#glow-icon-s)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-icon-s)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-2" filter="url(#glow-icon-s)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-icon-s)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-3" filter="url(#glow-icon-s)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-icon-s)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-4" filter="url(#glow-icon-s)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-icon-s)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-5" filter="url(#glow-icon-s)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-icon-s)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-6" filter="url(#glow-icon-s)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-icon-s)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.4 KiB

View File

@ -0,0 +1,228 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 200 200" width="200" height="200">
<defs>
<!-- Gradientes -->
<radialGradient id="nodeCore-icon" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent-icon" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary-icon" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold-icon" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow-icon" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-icon" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.1); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.connection-base {
stroke: #475569;
stroke-width: 1;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #4a9eff;
stroke-width: 1.5;
stroke-linecap: round;
stroke-dasharray: 6 25;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #3dd68d;
stroke-width: 1.5;
stroke-linecap: round;
stroke-dasharray: 6 25;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #4a9eff;
stroke-width: 0.8;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #3dd68d;
stroke-width: 0.6;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #4a9eff;
stroke-width: 0.8;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #3dd68d;
stroke-width: 0.8;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
</style>
<!-- ICON HEXAGON -->
<g id="icon" transform="translate(50, 50) scale(0.25)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes">
<g class="node-center" filter="url(#glowGold-icon)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-icon)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<g class="node-1" filter="url(#glow-icon)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-icon)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-2" filter="url(#glow-icon)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-icon)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-3" filter="url(#glow-icon)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-icon)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-4" filter="url(#glow-icon)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-icon)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-5" filter="url(#glow-icon)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-icon)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-6" filter="url(#glow-icon)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-icon)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 8.6 KiB

354
assets/kogral-logo.svg Normal file
View File

@ -0,0 +1,354 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 200" width="610" height="200">
<defs>
<!-- Gradientes -->
<radialGradient id="nodeCore" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.node-primary { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.connection-base {
stroke: #475569;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #4a9eff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #3dd68d;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #4a9eff;
stroke-width: 1;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #3dd68d;
stroke-width: 0.75;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #4a9eff;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #3dd68d;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base {
fill: none;
stroke: #cbd5e1;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border {
fill: none;
stroke: #475569;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON (escalado) ==================== -->
<g id="logo" transform="translate(5, 5) scale(0.37)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes">
<!-- Centro - DORADO -->
<g class="node-center" filter="url(#glowGold)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g class="node-1" filter="url(#glow)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g class="node-2" filter="url(#glow)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g class="node-3" filter="url(#glow)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g class="node-4" filter="url(#glow)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g class="node-5" filter="url(#glow)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g class="node-6" filter="url(#glow)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- ==================== FONDO CONSTELACIÓN ==================== -->
<g id="constellation" transform="translate(175, 25)" opacity="0.35">
<!-- Conexiones de red - punteadas -->
<line x1="20" y1="75" x2="100" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="100" y1="85" x2="170" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="170" y1="85" x2="220" y2="75" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="220" y1="75" x2="300" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="300" y1="85" x2="353" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Línea prolongada hasta el punto final -->
<line x1="353" y1="70" x2="400" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Conexiones diagonales -->
<line x1="60" y1="30" x2="100" y2="57" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="60" y1="120" x2="100" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="170" y1="135" x2="220" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="320" y1="60" x2="353" y2="30" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<!-- Pequeños nodos de constelación -->
<circle cx="-15" cy="50" r="2" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="3s" repeatCount="indefinite"/>
</circle>
<circle cx="-10" cy="100" r="1.5" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="2.5s" repeatCount="indefinite" begin="0.5s"/>
</circle>
<circle cx="373" cy="15" r="1.5" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="3.2s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<!-- Nodos intermedios -->
<circle cx="135" cy="70" r="1.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2s" repeatCount="indefinite"/>
</circle>
<circle cx="195" cy="60" r="1" fill="#3dd68d">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.3s" repeatCount="indefinite" begin="0.4s"/>
</circle>
<circle cx="260" cy="90" r="1.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.6s" repeatCount="indefinite" begin="0.8s"/>
</circle>
<circle cx="330" cy="50" r="1" fill="#3dd68d">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.1s" repeatCount="indefinite" begin="1.2s"/>
</circle>
</g>
<!-- ==================== TEXTO kogral (solo tubos, sin flujo interno) ==================== -->
<g id="text" transform="translate(175, 25)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-primary" style="transform-origin: 400px 70px" filter="url(#glowGold)">
<circle cx="400" cy="70" r="10" fill="url(#nodeGold)"/>
<circle cx="400" cy="70" r="6" fill="#fef3c7" opacity="0.5"/>
<circle cx="400" cy="70" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,34 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="16" height="16">
<defs>
<radialGradient id="fav16-gold" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<radialGradient id="fav16-blue" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="fav16-green" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
</defs>
<!-- Simplified hexagon for favicon -->
<polygon points="8,2 13,5 13,11 8,14 3,11 3,5" fill="none" stroke="#475569" stroke-width="0.5" opacity="0.3"/>
<!-- Center gold node -->
<circle cx="8" cy="8" r="3" fill="url(#fav16-gold)"/>
<circle cx="8" cy="8" r="1.5" fill="#fef3c7" opacity="0.6"/>
<!-- Key outer nodes (simplified to 3) -->
<circle cx="8" cy="3.5" r="1.2" fill="url(#fav16-blue)"/>
<circle cx="12" cy="10.5" r="1.2" fill="url(#fav16-green)"/>
<circle cx="4" cy="10.5" r="1.2" fill="url(#fav16-blue)"/>
<!-- Connection lines -->
<line x1="8" y1="8" x2="8" y2="3.5" stroke="#4a9eff" stroke-width="0.7" opacity="0.4"/>
<line x1="8" y1="8" x2="12" y2="10.5" stroke="#3dd68d" stroke-width="0.7" opacity="0.4"/>
<line x1="8" y1="8" x2="4" y2="10.5" stroke="#4a9eff" stroke-width="0.7" opacity="0.4"/>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

View File

@ -0,0 +1,58 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32">
<defs>
<radialGradient id="fav32-gold" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<radialGradient id="fav32-blue" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="fav32-green" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="fav32-slate" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
</defs>
<!-- Hexagon outline -->
<polygon points="16,4 26,10 26,22 16,28 6,22 6,10" fill="none" stroke="#4a9eff" stroke-width="0.7" opacity="0.2"/>
<!-- Connection lines to center -->
<line x1="16" y1="16" x2="16" y2="6" stroke="#475569" stroke-width="1" opacity="0.3"/>
<line x1="16" y1="16" x2="24" y2="11" stroke="#475569" stroke-width="1" opacity="0.3"/>
<line x1="16" y1="16" x2="24" y2="21" stroke="#475569" stroke-width="1" opacity="0.3"/>
<line x1="16" y1="16" x2="16" y2="26" stroke="#475569" stroke-width="1" opacity="0.3"/>
<line x1="16" y1="16" x2="8" y2="21" stroke="#475569" stroke-width="1" opacity="0.3"/>
<line x1="16" y1="16" x2="8" y2="11" stroke="#475569" stroke-width="1" opacity="0.3"/>
<!-- Flow lines -->
<line x1="16" y1="16" x2="16" y2="6" stroke="#4a9eff" stroke-width="1.2" stroke-dasharray="2 4" opacity="0.6"/>
<line x1="16" y1="16" x2="24" y2="11" stroke="#3dd68d" stroke-width="1.2" stroke-dasharray="2 4" opacity="0.6"/>
<line x1="16" y1="16" x2="16" y2="26" stroke="#4a9eff" stroke-width="1.2" stroke-dasharray="2 4" opacity="0.6"/>
<!-- Center gold node -->
<circle cx="16" cy="16" r="4.5" fill="url(#fav32-gold)"/>
<circle cx="16" cy="16" r="2.5" fill="#fef3c7" opacity="0.5"/>
<circle cx="16" cy="16" r="1" fill="#fff" opacity="0.7"/>
<!-- 6 outer nodes -->
<circle cx="16" cy="6" r="2.2" fill="url(#fav32-green)"/>
<circle cx="24" cy="11" r="2.2" fill="url(#fav32-blue)"/>
<circle cx="24" cy="21" r="2.2" fill="url(#fav32-slate)"/>
<circle cx="16" cy="26" r="2.2" fill="url(#fav32-green)"/>
<circle cx="8" cy="21" r="2.2" fill="url(#fav32-blue)"/>
<circle cx="8" cy="11" r="2.2" fill="url(#fav32-slate)"/>
<!-- Highlights on outer nodes -->
<circle cx="16" cy="6" r="0.7" fill="#fff" opacity="0.3"/>
<circle cx="24" cy="11" r="0.7" fill="#fff" opacity="0.3"/>
<circle cx="24" cy="21" r="0.7" fill="#fff" opacity="0.3"/>
<circle cx="16" cy="26" r="0.7" fill="#fff" opacity="0.3"/>
<circle cx="8" cy="21" r="0.7" fill="#fff" opacity="0.3"/>
<circle cx="8" cy="11" r="0.7" fill="#fff" opacity="0.3"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -0,0 +1,296 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 200" width="610" height="200">
<defs>
<!-- Gradientes -->
<radialGradient id="nodeCore-hs" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent-hs" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary-hs" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold-hs" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow-hs" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong-hs" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-hs" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
.connection-base-hs {
stroke: #475569;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow-hs {
stroke: #4a9eff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
}
.connection-flow-accent-hs {
stroke: #3dd68d;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
}
.hex-outline-hs {
stroke: #4a9eff;
stroke-width: 1;
fill: none;
opacity: 0.15;
}
.hex-outline-inner-hs {
stroke: #3dd68d;
stroke-width: 0.75;
fill: none;
opacity: 0.15;
}
.glow-ring-hs {
fill: none;
stroke: #4a9eff;
stroke-width: 1;
opacity: 0.4;
}
.glow-ring-accent-hs {
fill: none;
stroke: #3dd68d;
stroke-width: 1;
opacity: 0.4;
}
.text-tube-base-hs {
fill: none;
stroke: #cbd5e1;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border-hs {
fill: none;
stroke: #475569;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON (escalado) ==================== -->
<g id="logo" transform="translate(5, 5) scale(0.37)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline-hs" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner-hs" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial">
<line class="connection-base-hs" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base-hs" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base-hs" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base-hs" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base-hs" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base-hs" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow-hs" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent-hs" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow-hs" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent-hs" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow-hs" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent-hs" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter">
<line class="connection-base-hs" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base-hs" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base-hs" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base-hs" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base-hs" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base-hs" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes">
<!-- Centro - DORADO -->
<g filter="url(#glowGold-hs)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-hs)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g filter="url(#glow-hs)">
<circle class="glow-ring-accent-hs" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-hs)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g filter="url(#glow-hs)">
<circle class="glow-ring-hs" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-hs)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g filter="url(#glow-hs)">
<circle class="glow-ring-accent-hs" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-hs)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g filter="url(#glow-hs)">
<circle class="glow-ring-hs" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-hs)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g filter="url(#glow-hs)">
<circle class="glow-ring-accent-hs" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-hs)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g filter="url(#glow-hs)">
<circle class="glow-ring-hs" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-hs)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff" opacity="0.6"/>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d" opacity="0.6"/>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff" opacity="0.6"/>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d" opacity="0.6"/>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff" opacity="0.6"/>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d" opacity="0.6"/>
</g>
</g>
<!-- ==================== FONDO CONSTELACIÓN ==================== -->
<g id="constellation" transform="translate(175, 25)" opacity="0.35">
<!-- Conexiones de red - punteadas -->
<line x1="20" y1="75" x2="100" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="100" y1="85" x2="170" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="170" y1="85" x2="220" y2="75" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="220" y1="75" x2="300" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="300" y1="85" x2="353" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="353" y1="70" x2="400" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Conexiones diagonales -->
<line x1="60" y1="30" x2="100" y2="57" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="60" y1="120" x2="100" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="170" y1="135" x2="220" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="320" y1="60" x2="353" y2="30" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<!-- Pequeños nodos de constelación -->
<circle cx="-15" cy="50" r="2" fill="#64748b" opacity="0.6"/>
<circle cx="-10" cy="100" r="1.5" fill="#64748b" opacity="0.6"/>
<circle cx="373" cy="15" r="1.5" fill="#64748b" opacity="0.6"/>
<!-- Nodos intermedios -->
<circle cx="135" cy="70" r="1.5" fill="#4a9eff" opacity="0.5"/>
<circle cx="195" cy="60" r="1" fill="#3dd68d" opacity="0.5"/>
<circle cx="260" cy="90" r="1.5" fill="#4a9eff" opacity="0.5"/>
<circle cx="330" cy="50" r="1" fill="#3dd68d" opacity="0.5"/>
</g>
<!-- ==================== TEXTO kogral (solo tubos, sin flujo interno) ==================== -->
<g id="text" transform="translate(175, 25)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border-hs"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border-hs"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border-hs"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base-hs"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base-hs"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base-hs"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border-hs"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base-hs"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border-hs"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border-hs" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base-hs"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base-hs" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border-hs"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border-hs" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base-hs"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base-hs" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border-hs"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border-hs"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base-hs"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base-hs"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border-hs"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base-hs"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g filter="url(#glowGold-hs)">
<circle cx="400" cy="70" r="10" fill="url(#nodeGold-hs)"/>
<circle cx="400" cy="70" r="6" fill="#fef3c7" opacity="0.5"/>
<circle cx="400" cy="70" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

354
assets/logos/kogral-h.svg Normal file
View File

@ -0,0 +1,354 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 200" width="610" height="200">
<defs>
<!-- Gradientes -->
<radialGradient id="nodeCore" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.node-primary { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.connection-base {
stroke: #475569;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #4a9eff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #3dd68d;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #4a9eff;
stroke-width: 1;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #3dd68d;
stroke-width: 0.75;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #4a9eff;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #3dd68d;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base {
fill: none;
stroke: #cbd5e1;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border {
fill: none;
stroke: #475569;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON (escalado) ==================== -->
<g id="logo" transform="translate(5, 5) scale(0.37)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes">
<!-- Centro - DORADO -->
<g class="node-center" filter="url(#glowGold)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g class="node-1" filter="url(#glow)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g class="node-2" filter="url(#glow)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g class="node-3" filter="url(#glow)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g class="node-4" filter="url(#glow)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g class="node-5" filter="url(#glow)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g class="node-6" filter="url(#glow)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- ==================== FONDO CONSTELACIÓN ==================== -->
<g id="constellation" transform="translate(175, 25)" opacity="0.35">
<!-- Conexiones de red - punteadas -->
<line x1="20" y1="75" x2="100" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="100" y1="85" x2="170" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="170" y1="85" x2="220" y2="75" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="220" y1="75" x2="300" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="300" y1="85" x2="353" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Línea prolongada hasta el punto final -->
<line x1="353" y1="70" x2="400" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Conexiones diagonales -->
<line x1="60" y1="30" x2="100" y2="57" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="60" y1="120" x2="100" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="170" y1="135" x2="220" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="320" y1="60" x2="353" y2="30" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<!-- Pequeños nodos de constelación -->
<circle cx="-15" cy="50" r="2" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="3s" repeatCount="indefinite"/>
</circle>
<circle cx="-10" cy="100" r="1.5" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="2.5s" repeatCount="indefinite" begin="0.5s"/>
</circle>
<circle cx="373" cy="15" r="1.5" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="3.2s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<!-- Nodos intermedios -->
<circle cx="135" cy="70" r="1.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2s" repeatCount="indefinite"/>
</circle>
<circle cx="195" cy="60" r="1" fill="#3dd68d">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.3s" repeatCount="indefinite" begin="0.4s"/>
</circle>
<circle cx="260" cy="90" r="1.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.6s" repeatCount="indefinite" begin="0.8s"/>
</circle>
<circle cx="330" cy="50" r="1" fill="#3dd68d">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.1s" repeatCount="indefinite" begin="1.2s"/>
</circle>
</g>
<!-- ==================== TEXTO kogral (solo tubos, sin flujo interno) ==================== -->
<g id="text" transform="translate(175, 25)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-primary" style="transform-origin: 400px 70px" filter="url(#glowGold)">
<circle cx="400" cy="70" r="10" fill="url(#nodeGold)"/>
<circle cx="400" cy="70" r="6" fill="#fef3c7" opacity="0.5"/>
<circle cx="400" cy="70" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

View File

@ -0,0 +1,293 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 200" width="610" height="200">
<defs>
<!-- Gradientes en escala de grises -->
<radialGradient id="nodeCore-bn" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4f4f4f"/>
<stop offset="100%" stop-color="#2d2d2d"/>
</radialGradient>
<radialGradient id="nodeAccent-bn" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#7a7a7a"/>
<stop offset="100%" stop-color="#555555"/>
</radialGradient>
<radialGradient id="nodeSecondary-bn" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#999999"/>
<stop offset="100%" stop-color="#6b6b6b"/>
</radialGradient>
<radialGradient id="nodeGold-bn" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#f0f0f0"/>
<stop offset="40%" stop-color="#d0d0d0"/>
<stop offset="100%" stop-color="#a0a0a0"/>
</radialGradient>
<filter id="glow-bn" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-bn" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#d0d0d0" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.node-primary { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.connection-base {
stroke: #909090;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #4f4f4f;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #7a7a7a;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #4f4f4f;
stroke-width: 1;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #7a7a7a;
stroke-width: 0.75;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #4f4f4f;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #7a7a7a;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base {
fill: none;
stroke: #e0e0e0;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border {
fill: none;
stroke: #909090;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- LOGO HEXAGON (escalado) -->
<g id="logo" transform="translate(5, 5) scale(0.37)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes">
<g class="node-center" filter="url(#glowGold-bn)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-bn)"/>
<circle cx="256" cy="256" r="16" fill="#f0f0f0" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<g class="node-1" filter="url(#glow-bn)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-bn)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-2" filter="url(#glow-bn)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-bn)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-3" filter="url(#glow-bn)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-bn)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-4" filter="url(#glow-bn)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-bn)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-5" filter="url(#glow-bn)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-bn)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-6" filter="url(#glow-bn)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-bn)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- TEXTO kogral (escala de grises) -->
<g id="text" transform="translate(175, 25)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-primary" style="transform-origin: 400px 70px" filter="url(#glowGold-bn)">
<circle cx="400" cy="70" r="10" fill="url(#nodeGold-bn)"/>
<circle cx="400" cy="70" r="6" fill="#f0f0f0" opacity="0.5"/>
<circle cx="400" cy="70" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,309 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 0 450 315" width="450" height="315">
<defs>
<!-- Gradientes en escala de grises negro -->
<radialGradient id="nodeCore-bv" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4f4f4f"/>
<stop offset="100%" stop-color="#2d2d2d"/>
</radialGradient>
<radialGradient id="nodeAccent-bv" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#7a7a7a"/>
<stop offset="100%" stop-color="#555555"/>
</radialGradient>
<radialGradient id="nodeSecondary-bv" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#999999"/>
<stop offset="100%" stop-color="#6b6b6b"/>
</radialGradient>
<radialGradient id="nodeGold-bv" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#f0f0f0"/>
<stop offset="40%" stop-color="#d0d0d0"/>
<stop offset="100%" stop-color="#a0a0a0"/>
</radialGradient>
<filter id="glow-bv" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong-bv" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-bv" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#d0d0d0" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse-bv {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow-bv {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter-bv {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate-bv {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center-bv { transform-origin: center; animation: pulse-bv 2.5s ease-in-out infinite; }
.node-1-bv { transform-origin: center; animation: pulse-bv 2.5s ease-in-out infinite 0s; }
.node-2-bv { transform-origin: center; animation: pulse-bv 2.5s ease-in-out infinite 0.4s; }
.node-3-bv { transform-origin: center; animation: pulse-bv 2.5s ease-in-out infinite 0.8s; }
.node-4-bv { transform-origin: center; animation: pulse-bv 2.5s ease-in-out infinite 1.2s; }
.node-5-bv { transform-origin: center; animation: pulse-bv 2.5s ease-in-out infinite 1.6s; }
.node-6-bv { transform-origin: center; animation: pulse-bv 2.5s ease-in-out infinite 2s; }
.connection-base-bv {
stroke: #909090;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow-bv {
stroke: #4f4f4f;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter-bv 1.8s linear infinite;
}
.connection-flow-accent-bv {
stroke: #7a7a7a;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter-bv 1.8s linear infinite 0.9s;
}
.hex-outline-bv {
stroke: #4f4f4f;
stroke-width: 1;
fill: none;
animation: hexRotate-bv 3s ease-in-out infinite;
}
.hex-outline-inner-bv {
stroke: #7a7a7a;
stroke-width: 0.75;
fill: none;
animation: hexRotate-bv 3s ease-in-out infinite 1.5s;
}
.glow-ring-bv {
fill: none;
stroke: #4f4f4f;
stroke-width: 1;
animation: pulseGlow-bv 2.5s ease-in-out infinite;
}
.glow-ring-accent-bv {
fill: none;
stroke: #7a7a7a;
stroke-width: 1;
animation: pulseGlow-bv 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base-bv {
fill: none;
stroke: #e0e0e0;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border-bv {
fill: none;
stroke: #909090;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON ==================== -->
<g id="logo-bv" transform="translate(45, 20) scale(0.42)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline-bv" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner-bv" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial-bv">
<line class="connection-base-bv" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base-bv" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base-bv" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base-bv" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base-bv" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base-bv" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow-bv" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent-bv" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow-bv" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent-bv" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow-bv" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent-bv" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter-bv">
<line class="connection-base-bv" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base-bv" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base-bv" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base-bv" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base-bv" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base-bv" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes-bv">
<!-- Centro - DORADO -->
<g class="node-center-bv" filter="url(#glowGold-bv)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-bv)"/>
<circle cx="256" cy="256" r="16" fill="#f0f0f0" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g class="node-1-bv" filter="url(#glow-bv)">
<circle class="glow-ring-accent-bv" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-bv)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g class="node-2-bv" filter="url(#glow-bv)">
<circle class="glow-ring-bv" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-bv)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g class="node-3-bv" filter="url(#glow-bv)">
<circle class="glow-ring-accent-bv" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-bv)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g class="node-4-bv" filter="url(#glow-bv)">
<circle class="glow-ring-bv" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-bv)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g class="node-5-bv" filter="url(#glow-bv)">
<circle class="glow-ring-accent-bv" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-bv)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g class="node-6-bv" filter="url(#glow-bv)">
<circle class="glow-ring-bv" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-bv)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots-bv" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4f4f4f">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#7a7a7a">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- ==================== TEXTO kogral (centered below logo) ==================== -->
<g id="text-bv" transform="translate(-45, 175)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border-bv"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border-bv"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border-bv"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base-bv"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base-bv"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base-bv"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border-bv"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base-bv"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border-bv"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border-bv" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base-bv"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base-bv" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border-bv"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border-bv" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base-bv"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base-bv" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border-bv"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border-bv"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base-bv"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base-bv"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border-bv"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base-bv"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-center-bv" filter="url(#glowGold-bv)">
<circle cx="393" cy="75" r="10" fill="url(#nodeGold-bv)"/>
<circle cx="393" cy="75" r="6" fill="#f0f0f0" opacity="0.5"/>
<circle cx="393" cy="75" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,293 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 200" width="610" height="200">
<defs>
<!-- Gradientes en escala de grises blanco -->
<radialGradient id="nodeCore-wh" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#f5f5f5"/>
<stop offset="100%" stop-color="#e0e0e0"/>
</radialGradient>
<radialGradient id="nodeAccent-wh" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="100%" stop-color="#f5f5f5"/>
</radialGradient>
<radialGradient id="nodeSecondary-wh" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fafafa"/>
<stop offset="100%" stop-color="#f0f0f0"/>
</radialGradient>
<radialGradient id="nodeGold-wh" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="40%" stop-color="#fefefe"/>
<stop offset="100%" stop-color="#fcfcfc"/>
</radialGradient>
<filter id="glow-wh" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-wh" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fefefe" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.node-primary { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.connection-base {
stroke: #f0f0f0;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #f5f5f5;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #ffffff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #f5f5f5;
stroke-width: 1;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #ffffff;
stroke-width: 0.75;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #f5f5f5;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #ffffff;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base {
fill: none;
stroke: #ffffff;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border {
fill: none;
stroke: #f0f0f0;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- LOGO HEXAGON (escalado) -->
<g id="logo" transform="translate(5, 5) scale(0.37)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes">
<g class="node-center" filter="url(#glowGold-wh)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-wh)"/>
<circle cx="256" cy="256" r="16" fill="#ffffff" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<g class="node-1" filter="url(#glow-wh)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-wh)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-2" filter="url(#glow-wh)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-wh)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-3" filter="url(#glow-wh)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-wh)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-4" filter="url(#glow-wh)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-wh)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-5" filter="url(#glow-wh)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-wh)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<g class="node-6" filter="url(#glow-wh)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-wh)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#f5f5f5">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#ffffff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#f5f5f5">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#ffffff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#f5f5f5">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#ffffff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- TEXTO kogral (escala de grises blanco) -->
<g id="text" transform="translate(175, 25)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-primary" style="transform-origin: 400px 70px" filter="url(#glowGold-wh)">
<circle cx="400" cy="70" r="10" fill="url(#nodeGold-wh)"/>
<circle cx="400" cy="70" r="6" fill="#ffffff" opacity="0.5"/>
<circle cx="400" cy="70" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 11 KiB

View File

@ -0,0 +1,309 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 0 450 315" width="450" height="315">
<defs>
<!-- Gradientes en escala de grises blanco -->
<radialGradient id="nodeCore-wv" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#f5f5f5"/>
<stop offset="100%" stop-color="#e0e0e0"/>
</radialGradient>
<radialGradient id="nodeAccent-wv" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="100%" stop-color="#f5f5f5"/>
</radialGradient>
<radialGradient id="nodeSecondary-wv" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fafafa"/>
<stop offset="100%" stop-color="#f0f0f0"/>
</radialGradient>
<radialGradient id="nodeGold-wv" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#ffffff"/>
<stop offset="40%" stop-color="#fefefe"/>
<stop offset="100%" stop-color="#fcfcfc"/>
</radialGradient>
<filter id="glow-wv" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong-wv" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-wv" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fefefe" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse-wv {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow-wv {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter-wv {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate-wv {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center-wv { transform-origin: center; animation: pulse-wv 2.5s ease-in-out infinite; }
.node-1-wv { transform-origin: center; animation: pulse-wv 2.5s ease-in-out infinite 0s; }
.node-2-wv { transform-origin: center; animation: pulse-wv 2.5s ease-in-out infinite 0.4s; }
.node-3-wv { transform-origin: center; animation: pulse-wv 2.5s ease-in-out infinite 0.8s; }
.node-4-wv { transform-origin: center; animation: pulse-wv 2.5s ease-in-out infinite 1.2s; }
.node-5-wv { transform-origin: center; animation: pulse-wv 2.5s ease-in-out infinite 1.6s; }
.node-6-wv { transform-origin: center; animation: pulse-wv 2.5s ease-in-out infinite 2s; }
.connection-base-wv {
stroke: #f0f0f0;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow-wv {
stroke: #f5f5f5;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter-wv 1.8s linear infinite;
}
.connection-flow-accent-wv {
stroke: #ffffff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter-wv 1.8s linear infinite 0.9s;
}
.hex-outline-wv {
stroke: #f5f5f5;
stroke-width: 1;
fill: none;
animation: hexRotate-wv 3s ease-in-out infinite;
}
.hex-outline-inner-wv {
stroke: #ffffff;
stroke-width: 0.75;
fill: none;
animation: hexRotate-wv 3s ease-in-out infinite 1.5s;
}
.glow-ring-wv {
fill: none;
stroke: #f5f5f5;
stroke-width: 1;
animation: pulseGlow-wv 2.5s ease-in-out infinite;
}
.glow-ring-accent-wv {
fill: none;
stroke: #ffffff;
stroke-width: 1;
animation: pulseGlow-wv 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base-wv {
fill: none;
stroke: #ffffff;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border-wv {
fill: none;
stroke: #f0f0f0;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON ==================== -->
<g id="logo-wv" transform="translate(45, 20) scale(0.42)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline-wv" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner-wv" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial-wv">
<line class="connection-base-wv" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base-wv" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base-wv" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base-wv" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base-wv" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base-wv" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow-wv" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent-wv" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow-wv" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent-wv" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow-wv" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent-wv" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter-wv">
<line class="connection-base-wv" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base-wv" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base-wv" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base-wv" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base-wv" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base-wv" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes-wv">
<!-- Centro - DORADO -->
<g class="node-center-wv" filter="url(#glowGold-wv)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-wv)"/>
<circle cx="256" cy="256" r="16" fill="#ffffff" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g class="node-1-wv" filter="url(#glow-wv)">
<circle class="glow-ring-accent-wv" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-wv)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g class="node-2-wv" filter="url(#glow-wv)">
<circle class="glow-ring-wv" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-wv)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g class="node-3-wv" filter="url(#glow-wv)">
<circle class="glow-ring-accent-wv" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-wv)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g class="node-4-wv" filter="url(#glow-wv)">
<circle class="glow-ring-wv" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-wv)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g class="node-5-wv" filter="url(#glow-wv)">
<circle class="glow-ring-accent-wv" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-wv)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g class="node-6-wv" filter="url(#glow-wv)">
<circle class="glow-ring-wv" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-wv)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots-wv" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#f5f5f5">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#ffffff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#f5f5f5">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#ffffff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#f5f5f5">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#ffffff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- ==================== TEXTO kogral (centered below logo) ==================== -->
<g id="text-wv" transform="translate(-45, 175)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border-wv"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border-wv"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border-wv"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base-wv"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base-wv"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base-wv"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border-wv"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base-wv"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border-wv"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border-wv" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base-wv"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base-wv" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border-wv"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border-wv" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base-wv"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base-wv" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border-wv"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border-wv"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base-wv"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base-wv"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border-wv"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base-wv"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-center-wv" filter="url(#glowGold-wv)">
<circle cx="393" cy="75" r="10" fill="url(#nodeGold-wv)"/>
<circle cx="393" cy="75" r="6" fill="#ffffff" opacity="0.5"/>
<circle cx="393" cy="75" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -0,0 +1,267 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 0 450 315" width="450" height="315">
<defs>
<!-- Gradientes -->
<radialGradient id="nodeCore-vs" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent-vs" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary-vs" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold-vs" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow-vs" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong-vs" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-vs" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
.connection-base-vs {
stroke: #475569;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow-vs {
stroke: #4a9eff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
}
.connection-flow-accent-vs {
stroke: #3dd68d;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
}
.hex-outline-vs {
stroke: #4a9eff;
stroke-width: 1;
fill: none;
opacity: 0.15;
}
.hex-outline-inner-vs {
stroke: #3dd68d;
stroke-width: 0.75;
fill: none;
opacity: 0.15;
}
.glow-ring-vs {
fill: none;
stroke: #4a9eff;
stroke-width: 1;
opacity: 0.4;
}
.glow-ring-accent-vs {
fill: none;
stroke: #3dd68d;
stroke-width: 1;
opacity: 0.4;
}
.text-tube-base-vs {
fill: none;
stroke: #cbd5e1;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border-vs {
fill: none;
stroke: #475569;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON ==================== -->
<g id="logo-vs" transform="translate(45, 20) scale(0.42)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline-vs" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner-vs" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial-vs">
<line class="connection-base-vs" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base-vs" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base-vs" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base-vs" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base-vs" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base-vs" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow-vs" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent-vs" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow-vs" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent-vs" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow-vs" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent-vs" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter-vs">
<line class="connection-base-vs" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base-vs" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base-vs" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base-vs" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base-vs" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base-vs" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes-vs">
<!-- Centro - DORADO -->
<g filter="url(#glowGold-vs)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-vs)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g filter="url(#glow-vs)">
<circle class="glow-ring-accent-vs" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-vs)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g filter="url(#glow-vs)">
<circle class="glow-ring-vs" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-vs)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g filter="url(#glow-vs)">
<circle class="glow-ring-accent-vs" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-vs)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g filter="url(#glow-vs)">
<circle class="glow-ring-vs" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-vs)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g filter="url(#glow-vs)">
<circle class="glow-ring-accent-vs" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-vs)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g filter="url(#glow-vs)">
<circle class="glow-ring-vs" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-vs)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots-vs" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff" opacity="0.6"/>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d" opacity="0.6"/>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff" opacity="0.6"/>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d" opacity="0.6"/>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff" opacity="0.6"/>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d" opacity="0.6"/>
</g>
</g>
<!-- ==================== TEXTO kogral (centered below logo) ==================== -->
<g id="text-vs" transform="translate(-45, 175)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border-vs"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border-vs"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border-vs"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base-vs"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base-vs"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base-vs"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border-vs"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base-vs"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border-vs"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border-vs" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base-vs"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base-vs" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border-vs"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border-vs" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base-vs"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base-vs" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border-vs"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border-vs"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base-vs"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base-vs"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border-vs"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base-vs"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g filter="url(#glowGold-vs)">
<circle cx="393" cy="75" r="10" fill="url(#nodeGold-vs)"/>
<circle cx="393" cy="75" r="6" fill="#fef3c7" opacity="0.5"/>
<circle cx="393" cy="75" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 9.7 KiB

309
assets/logos/kogral-v.svg Normal file
View File

@ -0,0 +1,309 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-50 0 450 315" width="450" height="315">
<defs>
<!-- Gradientes -->
<radialGradient id="nodeCore-v" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent-v" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary-v" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold-v" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow-v" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong-v" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold-v" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse-v {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow-v {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter-v {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate-v {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center-v { transform-origin: center; animation: pulse-v 2.5s ease-in-out infinite; }
.node-1-v { transform-origin: center; animation: pulse-v 2.5s ease-in-out infinite 0s; }
.node-2-v { transform-origin: center; animation: pulse-v 2.5s ease-in-out infinite 0.4s; }
.node-3-v { transform-origin: center; animation: pulse-v 2.5s ease-in-out infinite 0.8s; }
.node-4-v { transform-origin: center; animation: pulse-v 2.5s ease-in-out infinite 1.2s; }
.node-5-v { transform-origin: center; animation: pulse-v 2.5s ease-in-out infinite 1.6s; }
.node-6-v { transform-origin: center; animation: pulse-v 2.5s ease-in-out infinite 2s; }
.connection-base-v {
stroke: #475569;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow-v {
stroke: #4a9eff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter-v 1.8s linear infinite;
}
.connection-flow-accent-v {
stroke: #3dd68d;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter-v 1.8s linear infinite 0.9s;
}
.hex-outline-v {
stroke: #4a9eff;
stroke-width: 1;
fill: none;
animation: hexRotate-v 3s ease-in-out infinite;
}
.hex-outline-inner-v {
stroke: #3dd68d;
stroke-width: 0.75;
fill: none;
animation: hexRotate-v 3s ease-in-out infinite 1.5s;
}
.glow-ring-v {
fill: none;
stroke: #4a9eff;
stroke-width: 1;
animation: pulseGlow-v 2.5s ease-in-out infinite;
}
.glow-ring-accent-v {
fill: none;
stroke: #3dd68d;
stroke-width: 1;
animation: pulseGlow-v 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base-v {
fill: none;
stroke: #cbd5e1;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border-v {
fill: none;
stroke: #475569;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON ==================== -->
<g id="logo-v" transform="translate(45, 20) scale(0.42)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline-v" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner-v" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial-v">
<line class="connection-base-v" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base-v" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base-v" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base-v" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base-v" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base-v" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow-v" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent-v" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow-v" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent-v" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow-v" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent-v" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter-v">
<line class="connection-base-v" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base-v" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base-v" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base-v" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base-v" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base-v" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes-v">
<!-- Centro - DORADO -->
<g class="node-center-v" filter="url(#glowGold-v)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold-v)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g class="node-1-v" filter="url(#glow-v)">
<circle class="glow-ring-accent-v" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent-v)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g class="node-2-v" filter="url(#glow-v)">
<circle class="glow-ring-v" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore-v)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g class="node-3-v" filter="url(#glow-v)">
<circle class="glow-ring-accent-v" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary-v)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g class="node-4-v" filter="url(#glow-v)">
<circle class="glow-ring-v" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent-v)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g class="node-5-v" filter="url(#glow-v)">
<circle class="glow-ring-accent-v" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore-v)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g class="node-6-v" filter="url(#glow-v)">
<circle class="glow-ring-v" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary-v)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots-v" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- ==================== TEXTO kogral (centered below logo) ==================== -->
<g id="text-v" transform="translate(-45, 175)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border-v"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border-v"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border-v"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base-v"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base-v"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base-v"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border-v"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base-v"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border-v"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border-v" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base-v"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base-v" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border-v"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border-v" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base-v"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base-v" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border-v"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border-v"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base-v"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base-v"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border-v"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base-v"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-center-v" filter="url(#glowGold-v)">
<circle cx="393" cy="75" r="10" fill="url(#nodeGold-v)"/>
<circle cx="393" cy="75" r="6" fill="#fef3c7" opacity="0.5"/>
<circle cx="393" cy="75" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 12 KiB

328
assets/web/README.md Normal file
View File

@ -0,0 +1,328 @@
# KOGRAL Web Assets
Web-based landing page and static content for KOGRAL.
## Directory Structure
```text
assets/web/
├── src/
│ ├── index.html # Source HTML (readable)
│ └── kogral.svg # Logo for landing page
├── index.html # Minified/Production HTML
├── kogral.svg # Logo for landing page
├── minify.sh # Minification script
└── README.md # This file
```
## Files
### `src/index.html` - Source Version
- **Purpose**: Development and maintenance
- **Content**:
- Full formatting and indentation
- Inline CSS and JavaScript
- Bilingual (English/Spanish) content
- Language-aware dynamic switching
- Knowledge graph showcase
- Technology stack display
- Core components grid
**Use for:**
- Editing content
- Understanding structure
- Version control
- Making translation updates
### `index.html` - Production Version
- **Purpose**: Served to browsers (fast loading)
- **Optimizations**:
- Removed all comments
- Compressed CSS (removed spaces, combined rules)
- Minified JavaScript (single line)
- Removed whitespace between tags
- Preserved all functionality
**Use for:**
- Production web server
- CDN distribution
- Browser caching
- Fast load times
### `kogral.svg` - Logo
- **Purpose**: Landing page branding
- **Source**: Copy of `../logos/kogral-h.svg` (horizontal logo)
- **Dimensions**: 610×200px (viewBox 0 0 610 200)
- **Features**: Animated knowledge graph nodes, connections, and text
## How to Use
### Development
Edit `src/index.html`:
```bash
# Edit source file
nano assets/web/src/index.html
# Regenerate minified version (see below)
./assets/web/minify.sh
```
### Update Minified Version
When you update `src/index.html`, regenerate `index.html`:
```bash
# Using minify.sh script
cd /path/to/kogral
./assets/web/minify.sh
# Or manually
cd /path/to/kogral
perl -e '
use strict;
use warnings;
open(my $fh, "<", "assets/web/src/index.html") or die $!;
my $content = do { local $/; <$fh> };
close($fh);
# Remove comments
$content =~ s/<!--.*?-->//gs;
# Compress whitespace in style tags
$content =~ s/(<style[^>]*>)(.*?)(<\/style>)/
my $before = $1;
my $style = $2;
my $after = $3;
$style =~ s{\/\*.*?\*\/}{}gs;
$style =~ s{\s+}{ }gs;
$style =~ s{\s*([{}:;,>+~])\s*}{$1}gs;
$before . $style . $after;
/gies;
# Compress whitespace in script tags
$content =~ s/(<script[^>]*>)(.*?)(<\/script>)/
my $before = $1;
my $script = $2;
my $after = $3;
$script =~ s{\/\/.*$}{}gm;
$script =~ s{\s+}{ }gs;
$script =~ s{\s*([{}();,])\s*}{$1}gs;
$before . $script . $after;
/gies;
# Remove whitespace between tags
$content =~ s/>\s+</></gs;
$content =~ s/\s+/ /gs;
$content =~ s/^\s+|\s+$//g;
open(my $out, ">", "assets/web/index.html") or die $!;
print $out $content;
close($out);
print "✅ Minified version created\n";
'
```
### Deployment
Serve `index.html` from your web server:
```bash
# Using Rust
cargo install static-web-server
static-web-server -d assets/web/
# Using Python
python3 -m http.server --directory assets/web
# Using Node.js
npx http-server assets/web
# Using nginx
# Point root to assets/web/
# Serve index.html as default
```
## Features
### Responsive Design
- Mobile-first approach
- Flexbox and Grid layouts
- Media queries for mobile
- Touch-friendly navigation
### Performance
- Inline CSS (no separate requests)
- Inline JavaScript (no blocking external scripts)
- Minimal dependencies (no frameworks)
- Optimized minified size
### Bilingual
- English and Spanish
- LocalStorage persistence
- Data attributes for translations
- Dynamic language switching
### Modern CSS
- CSS Gradients
- Animations (fadeInUp)
- Hover effects
- Grid and Flexbox layouts
### Styling
- KOGRAL color scheme (Blue #4a9eff, Green #3dd68d, Gold #fbbf24)
- Gradient backgrounds
- Inter font family
- Smooth transitions
## Content Sections
1. **Hero** - Title, tagline, logo, version badge
2. **Problems** - 4 knowledge management problems KOGRAL solves
- Scattered documentation
- No version control for decisions
- Lost context over time
- Isolated team knowledge
3. **How It Works** - Feature overview (Markdown-native, Semantic Search, Config-driven, Claude Code)
4. **Technology Stack** - Tech badges (Rust, Nickel, SurrealDB, fastembed, MCP, Logseq, etc.)
5. **Core Components** - Component showcase (kogral-core, kogral-cli, kogral-mcp, etc.)
6. **CTA** - Call-to-action button linking to GitHub
7. **Footer** - Credits and tagline
## Translations
All text content is bilingual. Edit data attributes in `src/index.html`:
```html
<!-- English/Spanish example -->
<span data-en="Knowledge Graphs" data-es="Grafos de Conocimiento">Knowledge Graphs</span>
```
The JavaScript automatically updates based on selected language.
## Color Scheme
KOGRAL branding colors:
- **Primary Blue**: `#4a9eff` - Main brand color
- **Secondary Green**: `#3dd68d` - Highlights and active states
- **Gold Accent**: `#fbbf24` - Gradient accents
- **Slate**: `#64748b` - Secondary text
- **Dark Background**: `#0a0a14` - Main background
## Maintenance
- Source edits go in `src/index.html`
- Regenerate `index.html` when source changes
- Both files are versioned in git
- Keep them in sync
## Git Workflow
```bash
# Edit source
nano assets/web/src/index.html
# Regenerate minified version
./assets/web/minify.sh
# Commit changes
git add assets/web/src/index.html
git add assets/web/index.html
git commit -m "Update landing page content"
git push
```
## File Sizes
Source and production versions:
| File | Type |
|------|------|
| `src/index.html` | Source (readable, formatted) |
| `index.html` | Production (minified, optimized) |
| `kogral.svg` | ~15KB (animated horizontal logo) |
## Version Information
- **Last Updated**: 2026-01-23
- **Version**: 0.1.0
- **Format**: HTML5 + CSS3 + ES6
- **Compatibility**: All modern browsers
- **Languages**: English, Spanish
## Technology Focus
**KOGRAL** landing page emphasizes:
- 📝 Git-native knowledge management
- 🔍 Semantic search with embeddings
- ⚙️ Config-driven architecture (Nickel)
- 🤖 Claude Code integration (MCP)
- 📊 6 node types, 6 relationships
- 🗄️ Multi-backend storage (Filesystem, SurrealDB, Memory)
## Features Highlighted
### 4 Core Problems Solved
1. **Scattered Documentation** - Unifies notes, decisions, guidelines across tools
2. **No Version Control** - Git-tracked ADRs with full history
3. **Lost Context** - Semantic search and relationship tracking
4. **Isolated Knowledge** - Multi-graph architecture with inheritance
### How It Works
1. **Markdown-Native** - YAML frontmatter, wikilinks, code references
2. **Semantic Search** - Vector embeddings, local or cloud
3. **Config-Driven** - Nickel schemas, 3 modes (dev/prod/test)
4. **Claude Code** - MCP server with 7 tools, 6 resources, 2 prompts
### Technology Stack
- Rust Edition 2021
- Nickel Config
- SurrealDB
- fastembed (local embeddings)
- rig-core (cloud embeddings)
- MCP Protocol
- Logseq Compatible
- Nushell Scripts
### Core Components
- **kogral-core** - Core library
- **kogral-cli** - 13 commands
- **kogral-mcp** - MCP server
- **Config System** - Nickel schemas
- **3 Storage Backends** - Filesystem, SurrealDB, Memory
- **2 Embedding Providers** - FastEmbed, rig-core
- **6 Node Types** - Note, Decision, Guideline, Pattern, Journal, Execution
- **6 Relationships** - relates_to, depends_on, implements, extends, supersedes, explains
## Quality Metrics
- 3 Rust crates
- 56 tests passing
- 15K lines of code
- 0 unsafe blocks
- 100% documentation coverage
- 0 clippy warnings
---
**Last Updated**: 2026-01-23
**Version**: 0.1.0 (matches KOGRAL release)

1
assets/web/index.html Normal file

File diff suppressed because one or more lines are too long

354
assets/web/kogral.svg Normal file
View File

@ -0,0 +1,354 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 200" width="610" height="200">
<defs>
<!-- Gradientes -->
<radialGradient id="nodeCore" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.node-primary { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.connection-base {
stroke: #475569;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #4a9eff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #3dd68d;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #4a9eff;
stroke-width: 1;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #3dd68d;
stroke-width: 0.75;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #4a9eff;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #3dd68d;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base {
fill: none;
stroke: #cbd5e1;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border {
fill: none;
stroke: #475569;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON (escalado) ==================== -->
<g id="logo" transform="translate(5, 5) scale(0.37)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes">
<!-- Centro - DORADO -->
<g class="node-center" filter="url(#glowGold)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g class="node-1" filter="url(#glow)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g class="node-2" filter="url(#glow)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g class="node-3" filter="url(#glow)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g class="node-4" filter="url(#glow)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g class="node-5" filter="url(#glow)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g class="node-6" filter="url(#glow)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- ==================== FONDO CONSTELACIÓN ==================== -->
<g id="constellation" transform="translate(175, 25)" opacity="0.35">
<!-- Conexiones de red - punteadas -->
<line x1="20" y1="75" x2="100" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="100" y1="85" x2="170" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="170" y1="85" x2="220" y2="75" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="220" y1="75" x2="300" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="300" y1="85" x2="353" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Línea prolongada hasta el punto final -->
<line x1="353" y1="70" x2="400" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Conexiones diagonales -->
<line x1="60" y1="30" x2="100" y2="57" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="60" y1="120" x2="100" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="170" y1="135" x2="220" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="320" y1="60" x2="353" y2="30" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<!-- Pequeños nodos de constelación -->
<circle cx="-15" cy="50" r="2" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="3s" repeatCount="indefinite"/>
</circle>
<circle cx="-10" cy="100" r="1.5" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="2.5s" repeatCount="indefinite" begin="0.5s"/>
</circle>
<circle cx="373" cy="15" r="1.5" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="3.2s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<!-- Nodos intermedios -->
<circle cx="135" cy="70" r="1.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2s" repeatCount="indefinite"/>
</circle>
<circle cx="195" cy="60" r="1" fill="#3dd68d">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.3s" repeatCount="indefinite" begin="0.4s"/>
</circle>
<circle cx="260" cy="90" r="1.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.6s" repeatCount="indefinite" begin="0.8s"/>
</circle>
<circle cx="330" cy="50" r="1" fill="#3dd68d">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.1s" repeatCount="indefinite" begin="1.2s"/>
</circle>
</g>
<!-- ==================== TEXTO kogral (solo tubos, sin flujo interno) ==================== -->
<g id="text" transform="translate(175, 25)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-primary" style="transform-origin: 400px 70px" filter="url(#glowGold)">
<circle cx="400" cy="70" r="10" fill="url(#nodeGold)"/>
<circle cx="400" cy="70" r="6" fill="#fef3c7" opacity="0.5"/>
<circle cx="400" cy="70" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

87
assets/web/minify.sh Executable file
View File

@ -0,0 +1,87 @@
#!/bin/bash
# Minify index.html from src/ to production version
# Usage: ./minify.sh
set -e
SRC_FILE="$(dirname "$0")/src/index.html"
OUT_FILE="$(dirname "$0")/index.html"
TEMP_FILE="${OUT_FILE}.tmp"
if [ ! -f "$SRC_FILE" ]; then
echo "❌ Source file not found: $SRC_FILE"
exit 1
fi
echo "🔨 Minifying HTML..."
echo " Input: $SRC_FILE"
echo " Output: $OUT_FILE"
perl -e "
use strict;
use warnings;
open(my \$fh, '<', '$SRC_FILE') or die \$!;
my \$content = do { local \$/; <\$fh> };
close(\$fh);
# Remove HTML comments
\$content =~ s/<!--.*?-->//gs;
# Compress CSS (remove spaces and comments)
\$content =~ s/(<style[^>]*>)(.*?)(<\/style>)/
my \$before = \$1;
my \$style = \$2;
my \$after = \$3;
\$style =~ s{\/\*.*?\*\/}{}gs;
\$style =~ s{\s+}{ }gs;
\$style =~ s{\s*([{}:;,>+~])\s*}{\$1}gs;
\$before . \$style . \$after;
/gies;
# Compress JavaScript (remove comments and extra spaces)
\$content =~ s/(<script[^>]*>)(.*?)(<\/script>)/
my \$before = \$1;
my \$script = \$2;
my \$after = \$3;
\$script =~ s{\/\/.*\$}{}gm;
\$script =~ s{\s+}{ }gs;
\$script =~ s{\s*([{}();,])\s*}{\$1}gs;
\$before . \$script . \$after;
/gies;
# Remove whitespace between tags
\$content =~ s/>\s+</></gs;
# Compress general whitespace
\$content =~ s/\s+/ /gs;
# Trim
\$content =~ s/^\s+|\s+\$//g;
open(my \$out, '>', '$TEMP_FILE') or die \$!;
print \$out \$content;
close(\$out);
" || {
echo "❌ Minification failed"
rm -f "$TEMP_FILE"
exit 1
}
mv "$TEMP_FILE" "$OUT_FILE"
# Show statistics
original=$(wc -c < "$SRC_FILE")
minified=$(wc -c < "$OUT_FILE")
saved=$((original - minified))
percent=$((saved * 100 / original))
echo ""
echo "✅ Minification complete!"
echo ""
echo "📊 Compression statistics:"
printf " Original: %6d bytes\n" "$original"
printf " Minified: %6d bytes\n" "$minified"
printf " Saved: %6d bytes (%d%%)\n" "$saved" "$percent"
echo ""
echo "$OUT_FILE is ready for production"

956
assets/web/src/index.html Normal file
View File

@ -0,0 +1,956 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title
data-en="KOGRAL - Git-Native Knowledge Graphs"
data-es="KOGRAL - Grafos de Conocimiento Git-Native"
>
KOGRAL
</title>
<link
href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap"
rel="stylesheet"
/>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: "Inter", -apple-system, BlinkMacSystemFont, sans-serif;
background: #0a0a14;
color: #ffffff;
overflow-x: hidden;
}
.gradient-bg {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
background:
radial-gradient(
circle at 20% 50%,
rgba(74, 158, 255, 0.15) 0%,
transparent 50%
),
radial-gradient(
circle at 80% 80%,
rgba(61, 214, 141, 0.15) 0%,
transparent 50%
),
radial-gradient(
circle at 40% 90%,
rgba(251, 191, 36, 0.1) 0%,
transparent 50%
);
}
.language-toggle {
position: fixed;
top: 2rem;
right: 2rem;
z-index: 100;
display: flex;
gap: 0.5rem;
background: rgba(255, 255, 255, 0.05);
border: 1px solid rgba(74, 158, 255, 0.3);
border-radius: 20px;
padding: 0.3rem 0.3rem;
}
.lang-btn {
background: transparent;
border: none;
color: #94a3b8;
padding: 0.5rem 1rem;
border-radius: 18px;
cursor: pointer;
font-weight: 700;
font-size: 0.85rem;
text-transform: uppercase;
transition: all 0.3s ease;
font-family: "Inter", sans-serif;
}
.lang-btn.active {
background: linear-gradient(135deg, #4a9eff 0%, #3dd68d 100%);
color: #fff;
}
.lang-btn:hover {
color: #3dd68d;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 2rem;
position: relative;
}
header {
text-align: center;
padding: 5rem 0 4rem;
animation: fadeInUp 0.8s ease-out;
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.status-badge {
display: inline-block;
background: rgba(61, 214, 141, 0.2);
border: 1px solid #3dd68d;
color: #3dd68d;
padding: 0.5rem 1.5rem;
border-radius: 50px;
font-size: 0.85rem;
font-weight: 700;
margin-bottom: 1.5rem;
}
.logo-container {
margin-bottom: 2rem;
}
.logo-container img {
max-width: 500px;
width: 100%;
height: auto;
filter: drop-shadow(0 0 30px rgba(74, 158, 255, 0.4));
}
.tagline {
font-size: 0.95rem;
color: #3dd68d;
font-weight: 600;
letter-spacing: 0.1em;
text-transform: uppercase;
margin-bottom: 1rem;
}
h1 {
font-size: 2.8rem;
font-weight: 800;
line-height: 1.2;
margin-bottom: 1.5rem;
background: linear-gradient(
135deg,
#4a9eff 0%,
#3dd68d 50%,
#fbbf24 100%
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero-subtitle {
font-size: 1.15rem;
color: #cbd5e1;
max-width: 800px;
margin: 0 auto 2rem;
line-height: 1.8;
}
.highlight {
color: #3dd68d;
font-weight: 700;
}
.section {
margin: 4rem 0;
animation: fadeInUp 0.8s ease-out;
}
.section-title {
font-size: 2rem;
font-weight: 800;
margin-bottom: 2rem;
color: #3dd68d;
text-align: center;
}
.section-title span {
background: linear-gradient(135deg, #4a9eff 0%, #3dd68d 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.problems-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 1.5rem;
margin-top: 2rem;
}
.problem-card {
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(74, 158, 255, 0.3);
border-radius: 12px;
padding: 2rem;
transition: all 0.3s ease;
position: relative;
overflow: hidden;
}
.problem-card:hover {
transform: translateY(-5px);
background: rgba(255, 255, 255, 0.05);
border-color: rgba(61, 214, 141, 0.5);
}
.problem-number {
font-size: 2rem;
font-weight: 800;
background: linear-gradient(135deg, #4a9eff 0%, #3dd68d 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
line-height: 1;
margin-bottom: 0.5rem;
}
.problem-card h3 {
color: #4a9eff;
font-size: 1.05rem;
margin-bottom: 0.7rem;
font-weight: 700;
}
.problem-card p {
color: #cbd5e1;
font-size: 0.9rem;
line-height: 1.6;
}
.problem-card ul {
color: #cbd5e1;
font-size: 0.9rem;
line-height: 1.6;
list-style: none;
padding: 0;
margin: 0;
}
.problem-card ul li {
padding-left: 1.2rem;
margin-bottom: 0.5rem;
position: relative;
}
.problem-card ul li:before {
content: "•";
color: #4a9eff;
font-weight: bold;
position: absolute;
left: 0;
}
.tech-stack {
display: flex;
flex-wrap: wrap;
gap: 1rem;
margin-top: 2rem;
justify-content: center;
}
.tech-badge {
background: rgba(61, 214, 141, 0.15);
border: 1px solid #3dd68d;
padding: 0.5rem 1rem;
border-radius: 20px;
font-size: 0.8rem;
color: #3dd68d;
font-weight: 700;
}
.features-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.feature-box {
background: linear-gradient(
135deg,
rgba(74, 158, 255, 0.1) 0%,
rgba(61, 214, 141, 0.1) 100%
);
border-radius: 12px;
padding: 2rem;
border-left: 4px solid #4a9eff;
transition: all 0.3s ease;
}
.feature-box:hover {
background: linear-gradient(
135deg,
rgba(74, 158, 255, 0.15) 0%,
rgba(61, 214, 141, 0.15) 100%
);
transform: translateY(-3px);
}
.feature-icon {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.feature-title {
font-size: 1.15rem;
font-weight: 700;
color: #4a9eff;
margin-bottom: 0.7rem;
}
.feature-text {
color: #cbd5e1;
font-size: 0.95rem;
line-height: 1.7;
}
.feature-text.feature-text {
list-style: none;
padding: 0;
margin: 0;
}
.feature-text li {
padding-left: 1.2rem;
margin-bottom: 0.6rem;
position: relative;
}
.feature-text li:before {
content: "▸";
color: #3dd68d;
font-weight: bold;
position: absolute;
left: 0;
}
.components-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
gap: 1rem;
margin-top: 2rem;
}
.component-item {
background: rgba(74, 158, 255, 0.1);
padding: 1.2rem;
border-radius: 8px;
font-size: 0.9rem;
border: 1px solid rgba(74, 158, 255, 0.3);
transition: all 0.2s ease;
text-align: center;
}
.component-item:hover {
background: rgba(74, 158, 255, 0.15);
transform: translateY(-2px);
}
.component-name {
color: #4a9eff;
font-weight: 700;
display: block;
margin-bottom: 0.3rem;
}
.component-role {
color: #94a3b8;
font-size: 0.85rem;
}
.cta-section {
text-align: center;
margin: 5rem 0 3rem;
padding: 4rem 2rem;
background: linear-gradient(
135deg,
rgba(74, 158, 255, 0.1) 0%,
rgba(61, 214, 141, 0.1) 100%
);
border-radius: 20px;
border: 1px solid rgba(74, 158, 255, 0.3);
}
.cta-title {
font-size: 2rem;
font-weight: 800;
margin-bottom: 1rem;
background: linear-gradient(135deg, #4a9eff 0%, #3dd68d 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.cta-button {
display: inline-block;
background: linear-gradient(
135deg,
#4a9eff 0%,
#3dd68d 50%,
#fbbf24 100%
);
color: #fff;
padding: 1.1rem 2.8rem;
border-radius: 50px;
text-decoration: none;
font-weight: 800;
font-size: 1rem;
transition: all 0.3s ease;
box-shadow: 0 10px 30px rgba(74, 158, 255, 0.3);
text-transform: uppercase;
letter-spacing: 0.05em;
border: none;
cursor: pointer;
}
.cta-button:hover {
transform: translateY(-3px) scale(1.05);
box-shadow: 0 20px 50px rgba(74, 158, 255, 0.5);
}
footer {
text-align: center;
padding: 3rem 0 2rem;
color: #64748b;
border-top: 1px solid rgba(255, 255, 255, 0.1);
margin-top: 4rem;
font-size: 0.9rem;
}
footer p:first-child {
font-weight: 700;
color: #94a3b8;
}
footer p:last-child {
margin-top: 0.5rem;
font-size: 0.85rem;
}
.hidden {
display: none;
}
@media (max-width: 768px) {
h1 {
font-size: 2rem;
}
.hero-subtitle {
font-size: 1rem;
}
.logo-container img {
max-width: 320px;
}
.section-title {
font-size: 1.6rem;
}
.cta-title {
font-size: 1.6rem;
}
.language-toggle {
top: 1rem;
right: 1rem;
}
}
</style>
</head>
<body>
<div class="gradient-bg"></div>
<div class="language-toggle">
<button
class="lang-btn active"
data-lang="en"
onclick="switchLanguage('en')"
>
EN
</button>
<button class="lang-btn" data-lang="es" onclick="switchLanguage('es')">
ES
</button>
</div>
<div class="container">
<header>
<span class="status-badge" data-en="✅ v0.1.0" data-es="✅ v0.1.0"
>✅ v0.1.0</span
>
<div class="logo-container">
<img src="/kogral.svg" alt="KOGRAL - Git-Native Knowledge Graphs" />
</div>
<p class="tagline">Git-Native Knowledge Management</p>
<h1
data-en="Knowledge Graphs<br>That Live in Git"
data-es="Grafos de Conocimiento<br>Que Viven en Git"
>
Knowledge Graphs
</h1>
<p class="hero-subtitle">
<span
class="highlight"
data-en="Structured knowledge management"
data-es="Gestión estructurada de conocimiento"
>Structured knowledge management</span
><span
data-en=" that scales from solo projects to organizations. Config-driven architecture for capturing architectural decisions, coding guidelines, and reusable patterns in version-controlled markdown."
data-es=" que escala desde proyectos individuales a organizaciones. Arquitectura basada en configuración para capturar decisiones arquitectónicas, guías de código y patrones reutilizables en markdown versionado."
> that scales from solo projects to organizations. Config-driven architecture for capturing architectural decisions, coding guidelines, and reusable patterns in version-controlled markdown.
</span>
<br><span><strong data-en="100% Rust. Zero compromises." data-es="100% Rust. Sin compromisos."
>100% Rust. Zero compromises.</strong
>
</span>
</p>
</header>
<section class="section">
<h2 class="section-title">
<span
data-en="The 4 Problems It Solves"
data-es="Los 4 Problemas que Resuelve"
>The 4 Problems It Solves</span
>
</h2>
<div class="problems-grid">
<div class="problem-card">
<div class="problem-number">01</div>
<h3 data-en="Scattered Documentation" data-es="Documentación Dispersa">
Scattered Documentation
</h3>
<ul
data-en="<li>Notes in Notion</li><li>Decisions in Slack</li><li>Guidelines in wikis—all disconnected</li><li>KOGRAL unifies with git-native markdown + MCP</li>"
data-es="<li>Notas en Notion</li><li>Decisiones en Slack</li><li>Guías en wikis—todo desconectado</li><li>KOGRAL unifica con markdown git-native + MCP</li>"
>
<li>Notes in Notion</li>
<li>Decisions in Slack</li>
<li>Guidelines in wikis—all disconnected</li>
<li>KOGRAL unifies with git-native markdown + MCP</li>
</ul>
</div>
<div class="problem-card">
<div class="problem-number">02</div>
<h3
data-en="No Version Control for Decisions"
data-es="Sin Control de Versiones para Decisiones"
>
No Version Control for Decisions
</h3>
<ul
data-en="<li>Architectural decisions lost in chat history</li><li>No traceability for why code exists</li><li>KOGRAL: Git-tracked ADRs with full history</li><li>Link decisions directly to code with @file:line</li>"
data-es="<li>Decisiones arquitectónicas perdidas en chat</li><li>Sin trazabilidad de por qué existe el código</li><li>KOGRAL: ADRs rastreados en Git con historial completo</li><li>Vincula decisiones al código con @file:line</li>"
>
<li>Architectural decisions lost in chat history</li>
<li>No traceability for why code exists</li>
<li>KOGRAL: Git-tracked ADRs with full history</li>
<li>Link decisions directly to code with @file:line</li>
</ul>
</div>
<div class="problem-card">
<div class="problem-number">03</div>
<h3 data-en="Lost Context Over Time" data-es="Contexto Perdido con el Tiempo">
Lost Context Over Time
</h3>
<ul
data-en="<li>Team members join and can't find past decisions</li><li>Outdated documentation causes repeated mistakes</li><li>KOGRAL: Semantic search across your knowledge base</li><li>Relationship tracking shows why patterns exist</li>"
data-es="<li>Nuevos miembros no encuentran decisiones pasadas</li><li>Documentación desactualizada causa errores repetidos</li><li>KOGRAL: Búsqueda semántica en tu base de conocimiento</li><li>Rastreo de relaciones muestra por qué existen patrones</li>"
>
<li>Team members join and can't find past decisions</li>
<li>Outdated documentation causes repeated mistakes</li>
<li>KOGRAL: Semantic search across your knowledge base</li>
<li>Relationship tracking shows why patterns exist</li>
</ul>
</div>
<div class="problem-card">
<div class="problem-number">04</div>
<h3 data-en="Isolated Team Knowledge" data-es="Conocimiento Aislado del Equipo">
Isolated Team Knowledge
</h3>
<ul
data-en="<li>Every project reinvents the wheel</li><li>Patterns can't be shared across teams</li><li>KOGRAL: Multi-graph architecture</li><li>Shared organizational knowledge + project-specific overrides</li><li>Inheritance system for guidelines</li>"
data-es="<li>Cada proyecto reinventa la rueda</li><li>Los patrones no se comparten entre equipos</li><li>KOGRAL: Arquitectura multi-grafo</li><li>Conocimiento organizacional compartido + sobrescrituras por proyecto</li><li>Sistema de herencia para guías</li>"
>
<li>Every project reinvents the wheel</li>
<li>Patterns can't be shared across teams</li>
<li>KOGRAL: Multi-graph architecture</li>
<li>Shared organizational knowledge + project-specific overrides</li>
<li>Inheritance system for guidelines</li>
</ul>
</div>
</div>
</section>
<section class="section">
<h2 class="section-title">
<span data-en="How It Works" data-es="Cómo Funciona"
>How It Works</span
>
</h2>
<div class="features-grid">
<div class="feature-box">
<div class="feature-icon">📝</div>
<h3
class="feature-title"
data-en="Markdown-Native"
data-es="Markdown Nativo"
>
Markdown-Native
</h3>
<ul
class="feature-text"
data-en="<li>YAML frontmatter for metadata</li><li>Wikilinks [[like-this]] for relationships</li><li>Code references @file.rs:42</li><li>Logseq-compatible format</li><li>Human-readable, Git-friendly</li>"
data-es="<li>YAML frontmatter para metadatos</li><li>Wikilinks [[así]] para relaciones</li><li>Referencias de código @file.rs:42</li><li>Formato compatible con Logseq</li><li>Legible por humanos, amigable con Git</li>"
>
<li>YAML frontmatter for metadata</li>
<li>Wikilinks [[like-this]] for relationships</li>
<li>Code references @file.rs:42</li>
<li>Logseq-compatible format</li>
<li>Human-readable, Git-friendly</li>
</ul>
</div>
<div class="feature-box" style="border-left-color: #3dd68d">
<div class="feature-icon">🔍</div>
<h3
class="feature-title"
style="color: #3dd68d"
data-en="Semantic Search"
data-es="Búsqueda Semántica"
>
Semantic Search
</h3>
<ul
class="feature-text"
data-en="<li>Text search across all nodes</li><li>Vector embeddings for semantic queries</li><li>Local embeddings (fastembed, no API costs)</li><li>Cloud APIs supported (OpenAI, Claude, Ollama)</li><li>Find related knowledge automatically</li>"
data-es="<li>Búsqueda de texto en todos los nodos</li><li>Embeddings vectoriales para consultas semánticas</li><li>Embeddings locales (fastembed, sin costos API)</li><li>APIs en la nube soportadas (OpenAI, Claude, Ollama)</li><li>Encuentra conocimiento relacionado automáticamente</li>"
>
<li>Text search across all nodes</li>
<li>Vector embeddings for semantic queries</li>
<li>Local embeddings (fastembed, no API costs)</li>
<li>Cloud APIs supported (OpenAI, Claude, Ollama)</li>
<li>Find related knowledge automatically</li>
</ul>
</div>
<div class="feature-box" style="border-left-color: #fbbf24">
<div class="feature-icon">⚙️</div>
<h3
class="feature-title"
style="color: #fbbf24"
data-en="Config-Driven"
data-es="Basado en Configuración"
>
Config-Driven
</h3>
<ul
class="feature-text"
data-en="<li>Nickel schemas with validation</li><li>3 modes: dev, prod, test</li><li>Storage backend selection (filesystem, SurrealDB, in-memory)</li><li>Embedding provider configuration</li><li>No hardcoded paths or settings</li>"
data-es="<li>Esquemas Nickel con validación</li><li>3 modos: dev, prod, test</li><li>Selección de backend de almacenamiento (filesystem, SurrealDB, memoria)</li><li>Configuración de proveedor de embeddings</li><li>Sin rutas o configuraciones hardcodeadas</li>"
>
<li>Nickel schemas with validation</li>
<li>3 modes: dev, prod, test</li>
<li>Storage backend selection (filesystem, SurrealDB, in-memory)</li>
<li>Embedding provider configuration</li>
<li>No hardcoded paths or settings</li>
</ul>
</div>
<div class="feature-box" style="border-left-color: #64748b">
<div class="feature-icon">🤖</div>
<h3
class="feature-title"
style="color: #64748b"
data-en="Claude Code Integration"
data-es="Integración Claude Code"
>
Claude Code Integration
</h3>
<ul
class="feature-text"
data-en="<li>MCP server (Model Context Protocol)</li><li>7 tools: search, add notes/decisions, link, export</li><li>6 resources: project/shared graphs</li><li>2 prompts: summarize project, find related</li><li>JSON-RPC 2.0 over stdio</li>"
data-es="<li>Servidor MCP (Model Context Protocol)</li><li>7 herramientas: buscar, añadir notas/decisiones, vincular, exportar</li><li>6 recursos: grafos proyecto/compartidos</li><li>2 prompts: resumir proyecto, encontrar relacionados</li><li>JSON-RPC 2.0 sobre stdio</li>"
>
<li>MCP server (Model Context Protocol)</li>
<li>7 tools: search, add notes/decisions, link, export</li>
<li>6 resources: project/shared graphs</li>
<li>2 prompts: summarize project, find related</li>
<li>JSON-RPC 2.0 over stdio</li>
</ul>
</div>
</div>
</section>
<section class="section">
<h2 class="section-title">
<span data-en="Technology Stack" data-es="Stack Tecnológico"
>Technology Stack</span
>
</h2>
<div class="tech-stack">
<span class="tech-badge">Rust Edition 2021</span>
<span class="tech-badge">Nickel Config</span>
<span class="tech-badge">SurrealDB</span>
<span class="tech-badge">fastembed</span>
<span class="tech-badge">rig-core</span>
<span class="tech-badge">MCP Protocol</span>
<span class="tech-badge">Logseq Compatible</span>
<span class="tech-badge">Tera Templates</span>
<span class="tech-badge">Clap CLI</span>
<span class="tech-badge">DashMap</span>
<span class="tech-badge">Nushell Scripts</span>
<span class="tech-badge">mdBook Docs</span>
</div>
</section>
<section class="section">
<h2 class="section-title">
<span data-en="Core Components" data-es="Componentes Principales"
>Core Components</span
>
</h2>
<div class="components-grid">
<div class="component-item">
<span class="component-name" data-en="kogral-core" data-es="kogral-core"
>kogral-core</span
><span
class="component-role"
data-en="Core library"
data-es="Biblioteca núcleo"
>Core library</span
>
</div>
<div class="component-item">
<span class="component-name" data-en="kogral-cli" data-es="kogral-cli"
>kogral-cli</span
><span
class="component-role"
data-en="13 commands"
data-es="13 comandos"
>13 commands</span
>
</div>
<div class="component-item">
<span
class="component-name"
data-en="kogral-mcp"
data-es="kogral-mcp"
>kogral-mcp</span
><span
class="component-role"
data-en="MCP server"
data-es="Servidor MCP"
>MCP server</span
>
</div>
<div class="component-item">
<span class="component-name" data-en="Config System" data-es="Sistema Config"
>Config System</span
><span
class="component-role"
data-en="Nickel schemas"
data-es="Esquemas Nickel"
>Nickel schemas</span
>
</div>
<div class="component-item">
<span class="component-name" data-en="3 Storage Backends" data-es="3 Backends Storage"
>3 Storage Backends</span
><span
class="component-role"
data-en="Filesystem, SurrealDB, Memory"
data-es="Filesystem, SurrealDB, Memoria"
>Filesystem, SurrealDB, Memory</span
>
</div>
<div class="component-item">
<span class="component-name" data-en="2 Embedding Providers" data-es="2 Proveedores Embeddings"
>2 Embedding Providers</span
><span
class="component-role"
data-en="FastEmbed, rig-core"
data-es="FastEmbed, rig-core"
>FastEmbed, rig-core</span
>
</div>
<div class="component-item">
<span
class="component-name"
data-en="6 Node Types"
data-es="6 Tipos de Nodos"
>6 Node Types</span
><span
class="component-role"
data-en="Note, Decision, Guideline, Pattern, Journal, Execution"
data-es="Nota, Decisión, Guía, Patrón, Diario, Ejecución"
>Note, Decision, Guideline, Pattern, Journal, Execution</span
>
</div>
<div class="component-item">
<span
class="component-name"
data-en="6 Relationships"
data-es="6 Relaciones"
>6 Relationships</span
><span
class="component-role"
data-en="relates_to, depends_on, implements, extends, supersedes, explains"
data-es="relates_to, depends_on, implements, extends, supersedes, explains"
>relates_to, depends_on, implements, extends, supersedes, explains</span
>
</div>
<div class="component-item">
<span
class="component-name"
data-en="Multi-Graph"
data-es="Multi-Grafo"
>Multi-Graph</span
><span
class="component-role"
data-en="Project + Shared"
data-es="Proyecto + Compartido"
>Project + Shared</span
>
</div>
<div class="component-item">
<span class="component-name" data-en="56 Tests Passing" data-es="56 Tests Pasando"
>56 Tests Passing</span
><span
class="component-role"
data-en="Quality assurance"
data-es="Aseguramiento calidad"
>Quality assurance</span
>
</div>
<div class="component-item">
<span class="component-name" data-en="0 Unsafe Code" data-es="0 Código Unsafe"
>0 Unsafe Code</span
><span
class="component-role"
data-en="Memory safety"
data-es="Seguridad memoria"
>Memory safety</span
>
</div>
<div class="component-item">
<span
class="component-name"
data-en="100% Doc Coverage"
data-es="100% Cobertura Doc"
>100% Doc Coverage</span
><span
class="component-role"
data-en="Public APIs documented"
data-es="APIs públicas documentadas"
>Public APIs documented</span
>
</div>
</div>
</section>
<div class="cta-section">
<h2
class="cta-title"
data-en="Ready to organize your knowledge?"
data-es="¿Listo para organizar tu conocimiento?"
>
Ready to organize your knowledge?
</h2>
<p
style="color: #94a3b8; margin-bottom: 2rem; font-size: 1.05rem"
data-en="3 Crates | 56 Tests | 15K LOC | 100% Rust 🦀"
data-es="3 Crates | 56 Tests | 15K LOC | 100% Rust 🦀"
>
3 Crates | 56 Tests | 15K LOC | 100% Rust 🦀
</p>
<a
href="https://github.com/jesusperezlorenzo/kogral"
class="cta-button"
data-en="Get Started →"
data-es="Comenzar →"
>Get Started →</a
>
</div>
<footer>
<p data-en="KOGRAL v0.1.0" data-es="KOGRAL v0.1.0">KOGRAL v0.1.0</p>
<p
data-en="Built with Rust • Configuration with Nickel"
data-es="Construido con Rust • Configuración con Nickel"
>
Built with Rust • Configuration with Nickel
</p>
<p
style="margin-top: 1rem; font-size: 0.8rem"
data-en="Git-Native Knowledge Graphs for Developer Teams"
data-es="Grafos de Conocimiento Git-Native para Equipos de Desarrollo"
>
Git-Native Knowledge Graphs for Developer Teams
</p>
</footer>
</div>
<script>
// Language management
const LANG_KEY = "kogral-lang";
function getCurrentLanguage() {
return localStorage.getItem(LANG_KEY) || "en";
}
function switchLanguage(lang) {
localStorage.setItem(LANG_KEY, lang);
// Update language buttons
document.querySelectorAll(".lang-btn").forEach((btn) => {
btn.classList.remove("active");
if (btn.dataset.lang === lang) {
btn.classList.add("active");
}
});
// Update all translatable elements
document.querySelectorAll("[data-en][data-es]").forEach((el) => {
const content = el.dataset[lang];
// Use innerHTML for headings (might contain <br>) and lists (contain <li>)
if (
el.tagName === "H1" ||
el.tagName === "H2" ||
el.tagName === "H3" ||
el.tagName === "UL"
) {
el.innerHTML = content;
} else {
el.textContent = content;
}
});
document.documentElement.lang = lang;
}
// Initialize language on page load
document.addEventListener("DOMContentLoaded", () => {
const currentLang = getCurrentLanguage();
switchLanguage(currentLang);
});
</script>
</body>
</html>

354
assets/web/src/kogral.svg Normal file
View File

@ -0,0 +1,354 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 610 200" width="610" height="200">
<defs>
<!-- Gradientes -->
<radialGradient id="nodeCore" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#4a9eff"/>
<stop offset="100%" stop-color="#2d7ad6"/>
</radialGradient>
<radialGradient id="nodeAccent" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#3dd68d"/>
<stop offset="100%" stop-color="#28a968"/>
</radialGradient>
<radialGradient id="nodeSecondary" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#64748b"/>
<stop offset="100%" stop-color="#475569"/>
</radialGradient>
<radialGradient id="nodeGold" cx="50%" cy="50%" r="50%">
<stop offset="0%" stop-color="#fef3c7"/>
<stop offset="40%" stop-color="#fbbf24"/>
<stop offset="100%" stop-color="#d97706"/>
</radialGradient>
<filter id="glow" x="-50%" y="-50%" width="200%" height="200%">
<feGaussianBlur stdDeviation="2" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowStrong" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feMerge>
<feMergeNode in="blur"/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
<filter id="glowGold" x="-100%" y="-100%" width="300%" height="300%">
<feGaussianBlur stdDeviation="4" result="blur"/>
<feFlood flood-color="#fbbf24" flood-opacity="0.6"/>
<feComposite in2="blur" operator="in"/>
<feMerge>
<feMergeNode/>
<feMergeNode in="SourceGraphic"/>
</feMerge>
</filter>
</defs>
<style>
@keyframes pulse {
0%, 100% { transform: scale(1); opacity: 1; }
50% { transform: scale(1.12); opacity: 0.85; }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.4; }
50% { opacity: 0.8; }
}
@keyframes flowToCenter {
0% { stroke-dashoffset: 30; }
100% { stroke-dashoffset: 0; }
}
@keyframes hexRotate {
0%, 100% { opacity: 0.15; }
50% { opacity: 0.3; }
}
.node-center { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.node-1 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0s; }
.node-2 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.4s; }
.node-3 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 0.8s; }
.node-4 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.2s; }
.node-5 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 1.6s; }
.node-6 { transform-origin: center; animation: pulse 2.5s ease-in-out infinite 2s; }
.node-primary { transform-origin: center; animation: pulse 2.5s ease-in-out infinite; }
.connection-base {
stroke: #475569;
stroke-width: 1.5;
stroke-linecap: round;
fill: none;
opacity: 0.5;
}
.connection-flow {
stroke: #4a9eff;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite;
}
.connection-flow-accent {
stroke: #3dd68d;
stroke-width: 2;
stroke-linecap: round;
stroke-dasharray: 8 30;
fill: none;
animation: flowToCenter 1.8s linear infinite 0.9s;
}
.hex-outline {
stroke: #4a9eff;
stroke-width: 1;
fill: none;
animation: hexRotate 3s ease-in-out infinite;
}
.hex-outline-inner {
stroke: #3dd68d;
stroke-width: 0.75;
fill: none;
animation: hexRotate 3s ease-in-out infinite 1.5s;
}
.glow-ring {
fill: none;
stroke: #4a9eff;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite;
}
.glow-ring-accent {
fill: none;
stroke: #3dd68d;
stroke-width: 1;
animation: pulseGlow 2.5s ease-in-out infinite 0.5s;
}
.text-tube-base {
fill: none;
stroke: #cbd5e1;
stroke-width: 10;
stroke-linecap: round;
stroke-linejoin: round;
}
.text-tube-border {
fill: none;
stroke: #475569;
stroke-width: 12;
stroke-linecap: round;
stroke-linejoin: round;
}
</style>
<!-- ==================== LOGO HEXAGON (escalado) ==================== -->
<g id="logo" transform="translate(5, 5) scale(0.37)">
<!-- Hexágono exterior sutil -->
<polygon class="hex-outline" points="256,96 388,176 388,336 256,416 124,336 124,176"/>
<polygon class="hex-outline-inner" points="256,136 352,196 352,316 256,376 160,316 160,196"/>
<!-- Conexiones al centro -->
<g id="connections-radial">
<line class="connection-base" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-base" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-base" x1="256" y1="256" x2="135" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="256" y2="116"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="377" y2="186"/>
<line class="connection-flow" x1="256" y1="256" x2="377" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="256" y2="396"/>
<line class="connection-flow" x1="256" y1="256" x2="135" y2="326"/>
<line class="connection-flow-accent" x1="256" y1="256" x2="135" y2="186"/>
</g>
<!-- Conexiones perimetrales -->
<g id="connections-perimeter">
<line class="connection-base" x1="256" y1="116" x2="377" y2="186"/>
<line class="connection-base" x1="377" y1="186" x2="377" y2="326"/>
<line class="connection-base" x1="377" y1="326" x2="256" y2="396"/>
<line class="connection-base" x1="256" y1="396" x2="135" y2="326"/>
<line class="connection-base" x1="135" y1="326" x2="135" y2="186"/>
<line class="connection-base" x1="135" y1="186" x2="256" y2="116"/>
</g>
<!-- Nodos -->
<g id="nodes">
<!-- Centro - DORADO -->
<g class="node-center" filter="url(#glowGold)">
<circle cx="256" cy="256" r="28" fill="url(#nodeGold)"/>
<circle cx="256" cy="256" r="16" fill="#fef3c7" opacity="0.5"/>
<circle cx="256" cy="256" r="7" fill="#fff" opacity="0.7"/>
</g>
<!-- Nodo 1 - Top -->
<g class="node-1" filter="url(#glow)">
<circle class="glow-ring-accent" cx="256" cy="116" r="20" opacity="0.3"/>
<circle cx="256" cy="116" r="15" fill="url(#nodeAccent)"/>
<circle cx="256" cy="116" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 2 - Top Right -->
<g class="node-2" filter="url(#glow)">
<circle class="glow-ring" cx="377" cy="186" r="20" opacity="0.3"/>
<circle cx="377" cy="186" r="15" fill="url(#nodeCore)"/>
<circle cx="377" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 3 - Bottom Right -->
<g class="node-3" filter="url(#glow)">
<circle class="glow-ring-accent" cx="377" cy="326" r="20" opacity="0.3"/>
<circle cx="377" cy="326" r="15" fill="url(#nodeSecondary)"/>
<circle cx="377" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 4 - Bottom -->
<g class="node-4" filter="url(#glow)">
<circle class="glow-ring" cx="256" cy="396" r="20" opacity="0.3"/>
<circle cx="256" cy="396" r="15" fill="url(#nodeAccent)"/>
<circle cx="256" cy="396" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 5 - Bottom Left -->
<g class="node-5" filter="url(#glow)">
<circle class="glow-ring-accent" cx="135" cy="326" r="20" opacity="0.3"/>
<circle cx="135" cy="326" r="15" fill="url(#nodeCore)"/>
<circle cx="135" cy="326" r="6" fill="#fff" opacity="0.2"/>
</g>
<!-- Nodo 6 - Top Left -->
<g class="node-6" filter="url(#glow)">
<circle class="glow-ring" cx="135" cy="186" r="20" opacity="0.3"/>
<circle cx="135" cy="186" r="15" fill="url(#nodeSecondary)"/>
<circle cx="135" cy="186" r="6" fill="#fff" opacity="0.2"/>
</g>
</g>
<!-- Puntos de sincronización -->
<g id="sync-dots" opacity="0.7">
<circle cx="256" cy="186" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite"/>
</circle>
<circle cx="316" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<circle cx="316" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.6s"/>
</circle>
<circle cx="256" cy="326" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="0.9s"/>
</circle>
<circle cx="196" cy="291" r="2.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.2s"/>
</circle>
<circle cx="196" cy="221" r="2.5" fill="#3dd68d">
<animate attributeName="opacity" values="0.3;1;0.3" dur="1.8s" repeatCount="indefinite" begin="1.5s"/>
</circle>
</g>
</g>
<!-- ==================== FONDO CONSTELACIÓN ==================== -->
<g id="constellation" transform="translate(175, 25)" opacity="0.35">
<!-- Conexiones de red - punteadas -->
<line x1="20" y1="75" x2="100" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="100" y1="85" x2="170" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="170" y1="85" x2="220" y2="75" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="220" y1="75" x2="300" y2="85" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<line x1="300" y1="85" x2="353" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Línea prolongada hasta el punto final -->
<line x1="353" y1="70" x2="400" y2="70" stroke="#94a3b8" stroke-width="1.5" stroke-dasharray="4 8"/>
<!-- Conexiones diagonales -->
<line x1="60" y1="30" x2="100" y2="57" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="60" y1="120" x2="100" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="170" y1="135" x2="220" y2="113" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<line x1="320" y1="60" x2="353" y2="30" stroke="#94a3b8" stroke-width="1" stroke-dasharray="3 6"/>
<!-- Pequeños nodos de constelación -->
<circle cx="-15" cy="50" r="2" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="3s" repeatCount="indefinite"/>
</circle>
<circle cx="-10" cy="100" r="1.5" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="2.5s" repeatCount="indefinite" begin="0.5s"/>
</circle>
<circle cx="373" cy="15" r="1.5" fill="#64748b">
<animate attributeName="opacity" values="0.3;0.8;0.3" dur="3.2s" repeatCount="indefinite" begin="0.3s"/>
</circle>
<!-- Nodos intermedios -->
<circle cx="135" cy="70" r="1.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2s" repeatCount="indefinite"/>
</circle>
<circle cx="195" cy="60" r="1" fill="#3dd68d">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.3s" repeatCount="indefinite" begin="0.4s"/>
</circle>
<circle cx="260" cy="90" r="1.5" fill="#4a9eff">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.6s" repeatCount="indefinite" begin="0.8s"/>
</circle>
<circle cx="330" cy="50" r="1" fill="#3dd68d">
<animate attributeName="opacity" values="0.2;0.7;0.2" dur="2.1s" repeatCount="indefinite" begin="1.2s"/>
</circle>
</g>
<!-- ==================== TEXTO kogral (solo tubos, sin flujo interno) ==================== -->
<g id="text" transform="translate(175, 25)">
<!-- K -->
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-border"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-border"/>
<line x1="20" y1="30" x2="20" y2="120" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="30" class="text-tube-base"/>
<line x1="20" y1="75" x2="60" y2="120" class="text-tube-base"/>
<!-- o -->
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-border"/>
<ellipse cx="100" cy="85" rx="20" ry="28" class="text-tube-base"/>
<!-- g -->
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-border"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-border" fill="none"/>
<ellipse cx="170" cy="85" rx="20" ry="28" class="text-tube-base"/>
<path d="M190 85 L190 115 Q190 135 170 135 Q152 135 148 122" class="text-tube-base" fill="none"/>
<!-- r -->
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-border"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-border" fill="none"/>
<line x1="220" y1="60" x2="220" y2="113" class="text-tube-base"/>
<path d="M220 75 Q220 58 242 58 Q258 58 262 70" class="text-tube-base" fill="none"/>
<!-- a -->
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-border"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-border"/>
<ellipse cx="300" cy="85" rx="20" ry="28" class="text-tube-base"/>
<line x1="320" y1="60" x2="320" y2="113" class="text-tube-base"/>
<!-- l -->
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-border"/>
<line x1="353" y1="30" x2="353" y2="113" class="text-tube-base"/>
<!-- Punto dorado final (nodo final de la línea central) -->
<g class="node-primary" style="transform-origin: 400px 70px" filter="url(#glowGold)">
<circle cx="400" cy="70" r="10" fill="url(#nodeGold)"/>
<circle cx="400" cy="70" r="6" fill="#fef3c7" opacity="0.5"/>
<circle cx="400" cy="70" r="2.5" fill="#fff" opacity="0.7"/>
</g>
</g>
</svg>

After

Width:  |  Height:  |  Size: 14 KiB

354
docs/README.md Normal file
View File

@ -0,0 +1,354 @@
# KOGRAL Documentation
<div align="center"> <img src="../assets/kogral-logo.svg" alt="KoGraL Logo" width="600" /> </div>
Welcome to the KOGRAL documentation! This directory contains comprehensive documentation for KOGRAL (**KO**wledge **GRA**phs, **L**ocal-first), built with [mdBook](https://rust-lang.github.io/mdBook/).
## 📚 Reading the Documentation
You have several options for reading the documentation:
### Option 1: Serve Locally with mdBook (Recommended)
The best reading experience with navigation, search, and live reload:
```bash
# Serve documentation at http://localhost:3000
just docs::serve
```
This will:
- Build the mdBook
- Start a local web server on port 3000
- Open your browser automatically
- Watch for changes and auto-reload
### Option 2: Build Static HTML
Generate static HTML files you can browse offline:
```bash
# Build mdBook to docs/book/
just docs::build
```
Then open `docs/book/index.html` in your browser.
### Option 3: Read Markdown Files Directly
All documentation is written in Markdown and can be read directly:
- Browse via GitHub/GitLab web interface
- Use your editor's Markdown preview
- Read from terminal with `bat`, `glow`, or similar tools
**Navigation**: See [SUMMARY.md](SUMMARY.md) for the complete table of contents.
## 🛠️ Documentation Commands
We use `just` recipes for documentation tasks. All commands assume you're in the project root directory.
### Build and Serve
```bash
# Serve documentation locally (recommended)
just docs::serve
# Build static HTML
just docs::build
# Watch and rebuild on file changes
just docs::watch
```
### Validation
```bash
# Test code examples in documentation
just docs::test
# Check for broken links
just docs::check-links
```
### Cleanup
```bash
# Remove build artifacts
just docs::clean
```
### View All Documentation Commands
```bash
just docs::help
```
## 📦 Installing mdBook
mdBook is required to build and serve the documentation.
### Install via Cargo
```bash
cargo install mdbook
```
### Install Optional Tools
For enhanced functionality:
```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
```
### Verify Installation
```bash
mdbook --version
# Should output: mdbook v0.4.x or later
```
## 📖 Documentation Structure
The documentation is organized into the following sections:
### 1. **KOGRAL Definition** (`kogral/`)
- What is KOGRAL and why it exists
- Core concepts (nodes, edges, graphs)
- Design philosophy
### 2. **Guides** (`guides/`)
- Quick start (5 minutes)
- Installation guide
- Use cases with examples
### 3. **Architecture** (`architecture/`)
- System overview with diagrams
- Config-driven architecture
- Graph model details
- ADRs (Architectural Decision Records)
### 4. **Setup** (`setup/`)
- Initial setup
- Development environment
- Production deployment
- Testing environment
- CI/CD integration
### 5. **Configuration** (`config/`)
- Configuration overview
- Nickel schema reference
- Runtime configuration
- Environment modes (dev/prod/test)
### 6. **Storage** (`storage/`)
- Storage architecture (hybrid strategy)
- Filesystem backend
- SurrealDB backend
- In-memory backend
- Sync mechanism
### 7. **AI & Embeddings** (`ai/`)
- Semantic search
- Embedding providers
- Provider comparison
- Configuration examples
### 8. **Templates** (`templates/`)
- Template system (Tera)
- Document templates
- Export templates
- Custom templates
### 9. **CLI Reference** (`cli/`)
- All kogral-cli commands
- Common workflows
- Advanced usage
- Troubleshooting
### 10. **Apps & Integrations** (`apps/`)
- MCP quick guide (Claude Code)
- Logseq integration
- Vapora integration
### 11. **API Reference** (`api/`)
- MCP tools specification
- Storage trait
- Embedding trait
- REST API (future)
### 12. **Contributing** (`contributing/`)
- Development setup
- Code guidelines
- Testing standards
- Documentation guidelines
## 🎨 Visual Diagrams
The documentation includes SVG diagrams for visual understanding:
- **[architecture-overview.svg](diagrams/architecture-overview.svg)** - Complete system architecture
- **[core-concepts.svg](diagrams/core-concepts.svg)** - Node types and relationships
- **[config-composition.svg](diagrams/config-composition.svg)** - Configuration flow (Nickel → JSON → Rust)
- **[storage-architecture.svg](diagrams/storage-architecture.svg)** - Hybrid storage strategy
These diagrams are embedded in relevant documentation pages and can be viewed standalone in a browser.
## 🔍 Searching the Documentation
When using `just docs::serve`, you get a built-in search feature:
1. Click the search icon (🔍) in the top-left corner
2. Type your query
3. Press Enter to navigate results
The search indexes all documentation content including:
- Page titles
- Headers
- Body text
- Code examples (optionally)
## ✏️ Editing Documentation
### File Format
All documentation is written in **GitHub Flavored Markdown** with mdBook extensions.
See [contributing/documentation.md](contributing/documentation.md) for detailed editing guidelines.
### Adding a New Page
1. Create the markdown file in the appropriate directory
2. Add it to `SUMMARY.md` for navigation
3. Build to verify: `just docs::build`
### Adding a New Section
1. Create the directory
2. Add a `README.md` for the section landing page
3. Add section to `SUMMARY.md`
## 🧪 Testing Documentation
### Test Code Examples
```bash
just docs::test
```
This runs all Rust code examples in the documentation to ensure they compile.
### Check Links
```bash
just docs::check-links
```
This validates all internal and external links.
## 📝 Documentation Standards
When contributing to documentation:
1. **Use clear, concise language** - Write for developers and AI agents
2. **Include code examples** - Show, don't just tell
3. **Add diagrams where helpful** - Visual aids improve understanding
4. **Link related concepts** - Help readers discover related content
5. **Test code examples** - Ensure code compiles and works
6. **Use consistent formatting** - Follow existing page structure
7. **Update SUMMARY.md** - New pages must be in navigation
8. **Run checks before committing**:
```bash
just docs::build
just docs::test
just docs::check-links
```
## 💡 Tips
### Live Reload While Writing
```bash
just docs::watch
```
This watches for changes and rebuilds automatically. Open <http://localhost:3000> in your browser to see updates in real-time.
### Markdown Preview in Editor
Most editors have Markdown preview:
- **VS Code**: `Ctrl+Shift+V` (Cmd+Shift+V on Mac)
- **IntelliJ/CLion**: Preview pane (right side)
- **Vim/Neovim**: Use plugins like `markdown-preview.nvim`
### Quick Reference
- **SUMMARY.md** - Table of contents (edit to add/remove pages)
- **book.toml** - mdBook configuration
- **theme/** - Custom CSS/JS (if needed)
- **diagrams/** - SVG diagrams
## 🐛 Troubleshooting
### mdbook command not found
```bash
# Install mdBook
cargo install mdbook
# Verify installation
mdbook --version
```
### Port 3000 already in use
```bash
# Serve on different port
cd docs
mdbook serve --port 3001
```
### Links broken after moving files
```bash
# Check all links
just docs::check-links
# Update internal links in affected files
# Then rebuild
just docs::build
```
## 📚 Resources
- [mdBook User Guide](https://rust-lang.github.io/mdBook/)
- [GitHub Flavored Markdown Spec](https://github.github.com/gfm/)
- [Markdown Guide](https://www.markdownguide.org/)
## 🤝 Contributing to Documentation
Documentation improvements are always welcome! To contribute:
1. Fork the repository
2. Create a feature branch
3. Make your changes
4. Test with `just docs::build` and `just docs::test`
5. Submit a pull request
See [contributing/documentation.md](contributing/documentation.md) for detailed guidelines.
---
**Happy documenting! 📖**
If you have questions or need help, please open an issue or reach out to the maintainers.

103
docs/SUMMARY.md Normal file
View File

@ -0,0 +1,103 @@
# Summary
[Introduction](README.md)
# Project Definition
- [What is KOGRAL?](kogral/what-is-kogral.md)
- [Core Concepts](kogral/core-concepts.md)
- [Why KOGRAL?](kogral/why-kogral.md)
- [Design Philosophy](kogral/design-philosophy.md)
# Guides
- [Installation](guides/installation.md)
- [Quick Start](guides/quickstart.md)
- [Daily Workflows](guides/daily-workflows.md)
- [Use Cases](guides/use-cases.md)
- [Best Practices](guides/best-practices.md)
# Architecture
- [System Overview](architecture/overview.md)
- [Graph Model](architecture/graph-model.md)
- [Config-Driven Design](architecture/config-driven.md)
- [Storage Architecture](architecture/storage-architecture.md)
- [Logseq Blocks Design](architecture/logseq-blocks-design.md)
## ADRs
- [ADR-001: Nickel vs TOML for Configuration](architecture/adrs/001-nickel-vs-toml.md)
- [ADR-002: FastEmbed via AI Providers](architecture/adrs/002-fastembed-ai-providers.md)
- [ADR-003: Hybrid Storage Strategy](architecture/adrs/003-hybrid-storage.md)
- [ADR-004: Logseq Blocks Support](architecture/adrs/004-logseq-blocks-support.md)
- [ADR-005: MCP Protocol for AI Integration](architecture/adrs/005-mcp-protocol.md)
# Setup
- [Prerequisites](setup/prerequisites.md)
- [Installation Methods](setup/installation.md)
- [Project Initialization](setup/project-init.md)
- [Environment Configuration](setup/environment.md)
- [Verification](setup/verification.md)
# Configuration
- [Configuration Overview](config/overview.md)
- [Nickel Schemas](config/nickel-schemas.md)
- [Graph Settings](config/graph-settings.md)
- [Inheritance](config/inheritance.md)
- [Examples](config/examples.md)
# Storage
- [Storage Backends](storage/backends.md)
- [Filesystem Storage](storage/filesystem.md)
- [SurrealDB Storage](storage/surrealdb.md)
- [In-Memory Storage](storage/memory.md)
- [Sync Strategies](storage/sync.md)
# AI & Embeddings
- [Embeddings Overview](ai/embeddings-overview.md)
- [Provider Configuration](ai/providers.md)
- [FastEmbed Local](ai/fastembed.md)
- [OpenAI Integration](ai/openai.md)
- [Claude Integration](ai/claude.md)
- [Ollama Integration](ai/ollama.md)
- [Semantic Search](ai/semantic-search.md)
# Templates
- [Template System](templates/overview.md)
- [Document Templates](templates/documents.md)
- [Export Templates](templates/export.md)
- [Customization](templates/customization.md)
# CLI
- [CLI Overview](cli/overview.md)
- [Commands Reference](cli/commands.md)
- [Workflows](cli/workflows.md)
- [NuShell Scripts](cli/nushell-scripts.md)
# Apps & Connections
- [MCP Quick Guide](apps/mcp-quickguide.md)
- [Claude Code Integration](apps/claude-code.md)
- [Logseq Integration](apps/logseq.md)
- [Obsidian Compatibility](apps/obsidian.md)
- [Git Workflows](apps/git.md)
# API Reference
- [MCP Protocol](api/mcp-protocol.md)
- [Tools Reference](api/mcp-tools.md)
- [Resources](api/mcp-resources.md)
- [Rust API](api/rust-api.md)
# Contributing
- [Development Setup](contributing/development.md)
- [Code Standards](contributing/standards.md)
- [Testing](contributing/testing.md)

View File

@ -0,0 +1 @@
# Embeddings Overview

1
docs/ai/fastembed.md Normal file
View File

@ -0,0 +1 @@
# FastEmbed Local

1
docs/ai/ollama.md Normal file
View File

@ -0,0 +1 @@
# Ollama Integration

1
docs/ai/openai.md Normal file
View File

@ -0,0 +1 @@
# OpenAI Integration

1
docs/ai/providers.md Normal file
View File

@ -0,0 +1 @@
# Provider Configuration

View File

@ -0,0 +1 @@
# Semantic Search

229
docs/api/README.md Normal file
View File

@ -0,0 +1,229 @@
# API Reference
API documentation for the Knowledge Base system.
## Available APIs
- [MCP Tools](mcp-tools.md) - Model Context Protocol tools and resources for Claude Code integration
- [Rust API](rust-api.md) - kogral-core library API for programmatic access
## Overview
The Knowledge Base system provides multiple API layers:
### MCP Protocol
JSON-RPC 2.0 protocol for Claude Code integration:
- 7 Tools for interacting with knowledge base
- 6 Resources for accessing knowledge graphs
- 2 Prompts for guided workflows
See [MCP Tools](mcp-tools.md) for complete reference.
### Rust Library
Direct programmatic access via kogral-core:
```rust
use kb_core::prelude::*;
// Load a graph
let storage = FilesystemStorage::new(".kogral")?;
let graph = storage.load_graph("default").await?;
// Query nodes
let results = graph.nodes.values()
.filter(|n| n.tags.contains(&"rust".to_string()))
.collect::<Vec<_>>();
```
See [Rust API](rust-api.md) for complete reference.
## Quick Reference
### Common MCP Operations
```json
// Search knowledge base
{
"jsonrpc": "2.0",
"method": "kogral/search",
"params": {
"query": "error handling",
"type": "guideline",
"semantic": true
}
}
// Add a note
{
"jsonrpc": "2.0",
"method": "kogral/add_note",
"params": {
"title": "New Discovery",
"content": "Interesting finding about...",
"tags": ["research", "rust"]
}
}
// Get guidelines
{
"jsonrpc": "2.0",
"method": "kogral/get_guidelines",
"params": {
"language": "rust",
"category": "error-handling"
}
}
```
### Common Rust Operations
```rust
// Create a new graph
let mut graph = Graph::new("my-project".to_string());
// Add a node
let node = Node {
id: "note-1".to_string(),
node_type: NodeType::Note,
title: "Example Note".to_string(),
content: "Note content".to_string(),
tags: vec!["rust".to_string()],
status: NodeStatus::Active,
// ...
};
graph.add_node(node)?;
// Create a relationship
let edge = Edge::new(
"note-1".to_string(),
"note-2".to_string(),
EdgeType::RelatesTo,
);
graph.add_edge(edge)?;
// Save graph
storage.save_graph(&graph).await?;
```
## Authentication
### MCP Server
No authentication required for stdio transport (local use).
For remote access (SSE transport), configure API keys in environment:
```bash
export KB_API_KEY="your-api-key"
```
### Rust Library
No authentication needed for local filesystem access.
For SurrealDB backend, configure credentials:
```rust
let config = SurrealDbConfig {
url: "ws://localhost:8000".to_string(),
namespace: "kb".to_string(),
database: "default".to_string(),
username: Some("root".to_string()),
password: Some("root".to_string()),
};
let storage = SurrealDbStorage::new(config).await?;
```
## Error Handling
All APIs use structured error types:
### MCP Errors
JSON-RPC error responses:
```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32600,
"message": "Invalid request",
"data": {
"details": "Missing required parameter: query"
}
}
}
```
Error codes:
- `-32700`: Parse error
- `-32600`: Invalid request
- `-32601`: Method not found
- `-32602`: Invalid params
- `-32603`: Internal error
### Rust Errors
`KbError` enum with thiserror:
```rust
pub enum KbError {
#[error("Storage error: {0}")]
Storage(String),
#[error("Node not found: {0}")]
NodeNotFound(String),
#[error("Invalid configuration: {0}")]
Config(String),
#[error("Parse error: {0}")]
Parse(String),
}
```
## Rate Limiting
MCP server has no rate limiting for local stdio use.
For production deployments with remote access, configure rate limits:
```nickel
mcp = {
rate_limit = {
enabled = true,
requests_per_minute = 60,
},
}
```
## Versioning
API follows semantic versioning:
- **Major version**: Breaking changes to API
- **Minor version**: New features, backward compatible
- **Patch version**: Bug fixes, backward compatible
Current version: `1.0.0`
## Deprecation Policy
- Deprecated features marked in documentation
- Minimum 1 major version before removal
- Migration guide provided
## Support
- File issues on GitHub
- Check documentation updates
- Join community discussions
## See Also
- [MCP Specification](https://modelcontextprotocol.io/docs)
- [kogral-core Documentation](../../crates/kogral-core/README.md)
- [Configuration Reference](../user-guide/configuration.md)

1
docs/api/mcp-protocol.md Normal file
View File

@ -0,0 +1 @@
# MCP Protocol

View File

@ -0,0 +1 @@
# Resources

902
docs/api/mcp-tools.md Normal file
View File

@ -0,0 +1,902 @@
# MCP Tools API Reference
Complete reference for the Model Context Protocol (MCP) server tools and resources.
## Overview
The kogral-mcp server implements the MCP protocol (JSON-RPC 2.0) for Claude Code integration. It provides:
- **10 Tools**: Operations for querying and modifying knowledge base (7 core + 3 block tools)
- **6 Resources**: Access to knowledge graph content via URIs
- **2 Prompts**: Guided workflows for common tasks
## Server Configuration
### Start MCP Server
```bash
# Stdio transport (local use)
kogral serve
# Or run directly
kogral-mcp serve
```
### Claude Code Configuration
Add to `~/.config/claude/config.json`:
```json
{
"mcpServers": {
"kogral-mcp": {
"command": "/path/to/kogral-mcp",
"args": ["serve"],
"env": {
"KOGRAL_DIR": "/path/to/project/.kogral"
}
}
}
}
```
## Tools
### kogral/search
Search the knowledge base using text and/or semantic similarity.
**Input Schema**:
```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"]
}
```
**Example Request**:
```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
}
}
```
**Example Response**:
```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"
}
}
```
### kogral/add_note
Add a new note to the knowledge base.
**Input Schema**:
```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"]
}
```
**Example Request**:
```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"]
}
}
```
**Example Response**:
```json
{
"jsonrpc": "2.0",
"id": 2,
"result": {
"type": "text",
"text": "Note added successfully: note-async-trait-patterns (ID: note-abc123)"
}
}
```
### kogral/add_decision
Create an Architectural Decision Record (ADR).
**Input Schema**:
```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"]
}
```
**Example Request**:
```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"]
}
}
```
**Example Response**:
```json
{
"jsonrpc": "2.0",
"id": 3,
"result": {
"type": "text",
"text": "Decision added: decision-use-surrealdb (ID: decision-xyz789)\nStatus: accepted"
}
}
```
### kogral/link
Create a relationship between two nodes.
**Input Schema**:
```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"]
}
```
**Example Request**:
```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
}
}
```
**Example Response**:
```json
{
"jsonrpc": "2.0",
"id": 4,
"result": {
"type": "text",
"text": "Link created: note-async-trait-patterns --[relates_to]--> pattern-error-handling (strength: 0.8)"
}
}
```
**Relationship Types**:
- `relates_to`: General conceptual relationship
- `depends_on`: Dependency (from depends on to)
- `implements`: Implementation of concept/pattern
- `extends`: Inherits or extends another node
- `supersedes`: Replaces an older version
- `explains`: Provides documentation/clarification
### kogral/get_guidelines
Retrieve guidelines for current project with inheritance resolution.
**Input Schema**:
```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
}
}
}
```
**Example Request**:
```json
{
"jsonrpc": "2.0",
"id": 5,
"method": "kogral/get_guidelines",
"params": {
"language": "rust",
"category": "error-handling",
"include_base": true
}
}
```
**Example Response**:
```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"
}
}
```
### kogral/list_graphs
List available knowledge graphs.
**Input Schema**:
```json
{
"type": "object",
"properties": {}
}
```
**Example Request**:
```json
{
"jsonrpc": "2.0",
"id": 6,
"method": "kogral/list_graphs"
}
```
**Example Response**:
```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"
}
}
```
### kogral/export
Export knowledge base to various formats.
**Input Schema**:
```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"]
}
```
**Example Request**:
```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
}
}
```
**Example Response**:
```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."
}
}
```
## Block Tools
Tools for querying Logseq content blocks. Requires `blocks.enable_mcp_tools = true` in configuration.
### kogral/find_blocks
Find blocks by tag, task status, or custom property across the knowledge base.
**Input Schema**:
```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
}
}
}
```
**Note**: Provide one of: `tag`, `status`, or both `property_key` and `property_value`.
**Example Request** (find blocks by tag):
```json
{
"jsonrpc": "2.0",
"id": 8,
"method": "kogral/find_blocks",
"params": {
"tag": "high-priority",
"limit": 10
}
}
```
**Example Response**:
```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"
}
}
```
**Example Request** (find blocks by property):
```json
{
"jsonrpc": "2.0",
"id": 9,
"method": "kogral/find_blocks",
"params": {
"property_key": "priority",
"property_value": "high",
"limit": 15
}
}
```
### kogral/find_todos
Find all TODO blocks across the knowledge base.
**Input Schema**:
```json
{
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of results",
"default": 20
}
}
}
```
**Example Request**:
```json
{
"jsonrpc": "2.0",
"id": 10,
"method": "kogral/find_todos",
"params": {
"limit": 25
}
}
```
**Example Response**:
```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"
}
}
```
### kogral/find_cards
Find all flashcard blocks (blocks tagged with #card) for spaced repetition learning.
**Input Schema**:
```json
{
"type": "object",
"properties": {
"limit": {
"type": "integer",
"description": "Maximum number of flashcards",
"default": 10
}
}
}
```
**Example Request**:
```json
{
"jsonrpc": "2.0",
"id": 11,
"method": "kogral/find_cards",
"params": {
"limit": 5
}
}
```
**Example Response**:
```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²)"
}
}
```
**Use Cases**:
- **kogral/find_blocks**: General block search by metadata
- **kogral/find_todos**: Task management and tracking
- **kogral/find_cards**: Spaced repetition learning system
**See Also**:
- [Logseq Blocks Support](../kogral/core-concepts.md#logseq-content-blocks)
- [ADR-004: Logseq Blocks Support](../architecture/adrs/004-logseq-blocks-support.md)
## Resources
Resources provide read-only access to knowledge graph content via URIs.
### kogral://project/notes
Access project notes.
**URI**: `kogral://project/notes` or `kogral://project/notes/{note-id}`
**Example**: Read all project notes
```text
kogral://project/notes
```
**Example**: Read specific note
```text
kogral://project/notes/async-trait-patterns
```
### kogral://project/decisions
Access project decisions (ADRs).
**URI**: `kogral://project/decisions` or `kogral://project/decisions/{decision-id}`
### kogral://project/guidelines
Access project-specific guidelines.
**URI**: `kogral://project/guidelines` or `kogral://project/guidelines/{guideline-id}`
### kogral://project/patterns
Access project patterns.
**URI**: `kogral://project/patterns` or `kogral://project/patterns/{pattern-id}`
### kogral://shared/guidelines
Access shared guidelines (inherited).
**URI**: `kogral://shared/guidelines` or `kogral://shared/guidelines/{guideline-id}`
### kogral://shared/patterns
Access shared patterns (inherited).
**URI**: `kogral://shared/patterns` or `kogral://shared/patterns/{pattern-id}`
## Prompts
Prompts provide guided workflows for common tasks.
### kogral/summarize_project
Generate a comprehensive project knowledge summary.
**Arguments**:
```json
{
"project": {
"type": "string",
"description": "Project graph name",
"default": "default"
}
}
```
**Example Request**:
```json
{
"jsonrpc": "2.0",
"id": 8,
"method": "kogral/summarize_project",
"params": {
"project": "default"
}
}
```
**Returns**: Prompt messages with project summary including:
- Total node counts by type
- Recent additions
- Top tags
- Key decisions
- Active patterns
### kogral/find_related
Find nodes related to a specific topic or node.
**Arguments**:
```json
{
"node_id": {
"type": "string",
"description": "Node ID to find relations for"
},
"depth": {
"type": "integer",
"description": "Maximum traversal depth",
"default": 2
}
}
```
**Example Request**:
```json
{
"jsonrpc": "2.0",
"id": 9,
"method": "kogral/find_related",
"params": {
"node_id": "pattern-error-handling",
"depth": 2
}
}
```
**Returns**: Prompt messages with:
- Direct relationships
- Indirect relationships (depth 2+)
- Common tags
- Related guidelines
## Error Handling
### Error Codes
Standard JSON-RPC 2.0 error codes:
| Code | Meaning | Description |
| ------ | --------- | ------------- |
| -32700 | Parse error | Invalid JSON |
| -32600 | Invalid Request | Missing required fields |
| -32601 | Method not found | Unknown tool/resource |
| -32602 | Invalid params | Parameter validation failed |
| -32603 | Internal error | Server-side error |
### Example Error Response
```json
{
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32602,
"message": "Invalid params",
"data": {
"details": "Field 'query' is required but missing",
"field": "query"
}
}
}
```
## Best Practices
### 1. Use Semantic Search for Discovery
```json
{
"method": "kogral/search",
"params": {
"query": "how to handle database connection errors",
"semantic": true,
"threshold": 0.5
}
}
```
### 2. Link Related Concepts
```json
{
"method": "kogral/link",
"params": {
"from": "note-new-discovery",
"to": "pattern-related-pattern",
"relation": "implements"
}
}
```
### 3. Query Guidelines Before Implementation
```json
{
"method": "kogral/get_guidelines",
"params": {
"language": "rust",
"category": "testing"
}
}
```
### 4. Document Decisions with ADRs
```json
{
"method": "kogral/add_decision",
"params": {
"title": "Use X for Y",
"context": "Background...",
"decision": "We will...",
"consequences": ["Pro 1", "Con 1"]
}
}
```
## See Also
- [MCP Specification](https://modelcontextprotocol.io/docs)
- [Quick Start Guide](../user-guide/quickstart.md)
- [Configuration Reference](../user-guide/configuration.md)
- [kogral-mcp Source Code](../../crates/kogral-mcp/src/)

1
docs/api/rust-api.md Normal file
View File

@ -0,0 +1 @@
# Rust API

1
docs/apps/claude-code.md Normal file
View File

@ -0,0 +1 @@
# Claude Code Integration

1
docs/apps/git.md Normal file
View File

@ -0,0 +1 @@
# Git Workflows

1
docs/apps/logseq.md Normal file
View File

@ -0,0 +1 @@
# Logseq Integration

488
docs/apps/mcp-quickguide.md Normal file
View File

@ -0,0 +1,488 @@
# MCP Quick Guide
Fast-track guide to integrating KOGRAL with Claude Code via the Model Context Protocol (MCP).
## What is MCP
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.
## Quick Setup (5 Minutes)
### Step 1: Build MCP Server
```bash
# Build kogral-mcp server
cargo build --package kogral-mcp --release
# Verify binary
ls -lh target/release/kogral-mcp
```
### Step 2: Configure Claude Code
Add to `~/.config/claude/config.json`:
```json
{
"mcpServers": {
"kogral-mcp": {
"command": "/path/to/knowledge-base/target/release/kogral-mcp",
"args": ["serve"],
"env": {
"KOGRAL_DIR": "/path/to/your/project/.kogral"
}
}
}
}
```
Replace `/path/to/knowledge-base` and `/path/to/your/project/.kogral` with your actual paths.
### Step 3: Initialize KOGRAL
```bash
# Navigate to your project
cd /path/to/your/project
# Initialize .kogral directory
kogral init
```
### Step 4: Start Claude Code
```bash
# Start Claude Code (will auto-connect to kogral-mcp)
claude-code
```
### Step 5: Test Connection
In Claude Code, try:
```text
Search for "error handling"
```
Claude will use the `kogral/search` tool to query your knowledge base.
---
## Essential Commands
### Search Knowledge Base
**Natural language**:
```text
Find notes about Rust error handling
```
**Claude translates to**:
```json
{
"tool": "kogral/search",
"params": {
"query": "error handling",
"type": "note",
"semantic": true
}
}
```
### Add Note
**Natural language**:
```text
Add a note about async Rust patterns with tags rust, async, patterns
```
**Claude translates to**:
```json
{
"tool": "kogral/add_note",
"params": {
"title": "Async Rust Patterns",
"content": "...",
"tags": ["rust", "async", "patterns"]
}
}
```
### Add Decision
**Natural language**:
```text
Document decision to use SurrealDB for storage
```
**Claude translates to**:
```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"]
}
}
```
### Get Guidelines
**Natural language**:
```text
What are our Rust error handling guidelines?
```
**Claude translates to**:
```json
{
"tool": "kogral/get_guidelines",
"params": {
"language": "rust",
"category": "error-handling"
}
}
```
### Link Concepts
**Natural language**:
```text
Link note-async-patterns to decision-use-tokio as implements
```
**Claude translates to**:
```json
{
"tool": "kogral/link",
"params": {
"from": "note-async-patterns",
"to": "decision-use-tokio",
"relation": "implements"
}
}
```
---
## Common Workflows
### Capture Meeting Notes
**You**:
```text
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.
```
**Claude** (uses `kogral/add_decision`):
```text
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
```
### Find Related Knowledge
**You**:
```text
I'm working on error handling. What related notes and patterns do we have?
```
**Claude** (uses `kogral/search` with semantic=true):
```text
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?
```
### Document Implementation
**You**:
```text
I just implemented the user authentication module following our security guidelines.
Add a note and link it to our security guideline.
```
**Claude** (uses `kogral/add_note` + `kogral/link`):
```text
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.
```
### Review Project Knowledge
**You**:
```text
Summarize our project knowledge: what decisions have we made, what patterns are we using?
```
**Claude** (uses `kogral/summarize_project` prompt):
```text
# 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
```
---
## Advanced Usage
### Conditional Queries
**You**:
```text
Find all decisions made in the last week that are still proposed (not accepted)
```
**Claude** (combines `kogral/search` with filtering):
```text
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?
```
### Cross-Reference Check
**You**:
```text
Before I implement X, check if we have any decisions or guidelines about it
```
**Claude** (uses `kogral/search` + `kogral/get_guidelines`):
```text
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.
```
### Export for Sharing
**You**:
```text
Export our architectural decisions to Logseq so I can review them visually
```
**Claude** (uses `kogral/export`):
```text
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.
```
---
## Tips and Tricks
### 1. Use Natural Language
Don't worry about exact tool syntax. Claude understands:
**Don't say**: "Use kogral/search with query='rust' and type='pattern'"
**Do say**: "Find Rust patterns in KOGRAL"
### 2. Be Specific with Tags
When adding notes, use consistent tags:
**Good**: `tags: rust, error-handling, pattern`
**Bad**: `tags: Rust, ErrorHandling, patterns`
### 3. Link as You Go
After creating notes, ask Claude to link them:
```text
Link this note to our error handling guideline as 'implements'
```
### 4. Review Regularly
Ask Claude for summaries:
```text
What have we documented this week?
```
### 5. Use Semantic Search
For conceptual queries:
```text
Find anything related to "making code maintainable"
```
Not just keyword "maintainable", but concepts like refactoring, clean code, patterns, etc.
---
## Troubleshooting
### "MCP server not responding"
```bash
# Check kogral-mcp is built
ls target/release/kogral-mcp
# Test manually
echo '{"jsonrpc":"2.0","id":1,"method":"kogral/search","params":{"query":"test"}}' \
| target/release/kogral-mcp serve
```
### "KB directory not found"
```bash
# Verify .kogral exists
ls -la /path/to/project/.kogral
# Initialize if missing
cd /path/to/project
kogral init
```
### "Permission denied"
```bash
# Make binary executable
chmod +x target/release/kogral-mcp
# Check environment variable
echo $KOGRAL_DIR
```
### "Empty search results"
```bash
# Add some test content
kogral add note "Test Note" --content "Test content"
# Try search again in Claude Code
```
---
## Next Steps
- **Read**: [MCP Tools API Reference](../api/mcp-tools.md) for all available tools
- **Explore**: [Use Cases](../guides/use-cases.md) for more examples
- **Configure**: [Configuration Reference](../config/overview.md) to customize behavior
- **Integrate**: [Claude Code Integration](claude-code.md) for advanced setup
---
## Quick Reference Card
| Task | Say to Claude |
| ------ | --------------- |
| Search | "Find notes about X" |
| Add note | "Add a note about X with tags Y, Z" |
| Add decision | "Document decision to use X for Y" |
| Get guidelines | "What are our X guidelines?" |
| Link nodes | "Link A to B as implements" |
| Summarize | "Summarize project knowledge" |
| Export | "Export to Logseq format" |
---
**Remember**: 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.

1
docs/apps/obsidian.md Normal file
View File

@ -0,0 +1 @@
# Obsidian Compatibility

View File

@ -0,0 +1,313 @@
# ADR-001: Nickel vs TOML for Configuration
**Status**: Accepted
**Date**: 2026-01-17
**Deciders**: Architecture Team
**Context**: Configuration Strategy for Knowledge Base System
---
## Context
The KOGRAL requires a flexible, type-safe configuration format that supports:
1. **Complex nested structures** (graph settings, storage configs, embedding providers)
2. **Type validation** (prevent runtime errors from config mistakes)
3. **Composition and inheritance** (shared configs, environment-specific overrides)
4. **Documentation** (self-documenting schemas)
5. **Validation before runtime** (catch errors early)
We evaluated two primary options:
### Option 1: TOML (Traditional Config Format)
**Pros**:
- Widely adopted in Rust ecosystem (`Cargo.toml`)
- Simple, human-readable syntax
- Native `serde` support
- IDE support (syntax highlighting, completion)
**Cons**:
- No type system (validation only at runtime)
- Limited composition (no imports, no functions)
- No schema validation (errors discovered during execution)
- Verbose for complex nested structures
- No documentation in config files
**Example TOML**:
```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"
```
**Problems**:
- Typos in enum values (`"surrealdb"` vs `"surealdb"`) fail at runtime
- No validation that `provider = "openai"` requires `api_key_env`
- No documentation of valid options
- No way to compose configs (e.g., base config + environment override)
### Option 2: Nickel (Functional Configuration Language)
**Pros**:
- **Type system** with contracts (validate before runtime)
- **Composition** via imports and merging
- **Documentation** in schemas (self-documenting)
- **Validation** at export time (catch errors early)
- **Functions** for conditional logic
- **Default values** in schema definitions
**Cons**:
- Less familiar to Rust developers
- Requires separate `nickel` CLI tool
- Smaller ecosystem
- Steeper learning curve
**Example Nickel**:
```nickel
# schemas/kogral-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",
},
}
```
**Benefits**:
- Typos in enum values caught at `nickel export` time
- Schema enforces required fields based on provider
- Documentation embedded in schema
- Config can be composed: `import "base.ncl" & { /* overrides */ }`
---
## Decision
**We will use Nickel for configuration.**
**Implementation**:
1. Define schemas in `schemas/*.ncl` with type contracts
2. Users write configs in `.kogral/config.ncl`
3. Export to JSON via CLI: `nickel export --format json config.ncl`
4. Load JSON in Rust via `serde_json` into typed structs
**Pattern** (double validation):
```text
Nickel Config (.ncl)
↓ [nickel export]
JSON (validated by Nickel contracts)
↓ [serde_json::from_str]
Rust Struct (validated by serde)
Runtime (guaranteed valid config)
```
**Bridge Code** (`kogral-core/src/config/nickel.rs`):
```rust
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)?)
}
```
---
## Consequences
### Positive
**Type Safety**: Config errors caught before runtime
- Invalid enum values fail at export: `'filesystm` → error
- Missing required fields detected: no `graph.name` → error
- Type mismatches prevented: `enabled = "yes"` → error (expects Bool)
**Self-Documenting**: Schemas serve as documentation
- `| doc "Environment variable for API key"` describes fields
- Enum options visible in schema: `[| 'openai, 'claude, 'fastembed |]`
- Default values explicit: `| default = 'filesystem`
**Composition**: Config reuse and overrides
```nickel
# base.ncl
{ graph = { version = "1.0.0" } }
# project.ncl
import "base.ncl" & { graph = { name = "my-project" } }
```
**Validation Before Deployment**: Catch errors in CI
```bash
# CI pipeline
nickel typecheck config.ncl
nickel export --format json config.ncl > /dev/null
```
**Conditional Logic**: Environment-specific configs
```nickel
let is_prod = std.string.is_match "prod" (std.env.get "ENV") in
{
embeddings = {
provider = if is_prod then 'openai else 'fastembed,
},
}
```
### Negative
**Learning Curve**: Team must learn Nickel syntax
- **Mitigation**: Provide comprehensive examples in `config/` directory
- **Mitigation**: Document common patterns in `docs/config/`
**Tool Dependency**: Requires `nickel` CLI installed
- **Mitigation**: Document installation in setup guide
- **Mitigation**: Check `nickel` availability in `kogral init` command
**IDE Support**: Limited compared to TOML
- **Mitigation**: Use LSP (nickel-lang-lsp) for VSCode/Neovim
- **Mitigation**: Syntax highlighting available for major editors
**Ecosystem Size**: Smaller than TOML
- **Mitigation**: Nickel actively developed by Tweag
- **Mitigation**: Stable language specification (v1.0+)
### Neutral
**Two-Stage Loading**: Nickel → JSON → Rust
- Not a performance concern (config loaded once at startup)
- Adds resilience (double validation)
- Allows runtime config inspection (read JSON directly)
---
## Alternatives Considered
### JSON Schema
**Rejected**: Not ergonomic for humans to write
- No comments
- Verbose syntax (`{"key": "value"}` vs `key = value`)
- JSON Schema separate from config (duplication)
### YAML
**Rejected**: No type system, ambiguous parsing
- Boolean confusion: `yes`/`no`/`on`/`off`/`true`/`false`
- Indentation-sensitive (error-prone)
- No validation without external tools
### Dhall
**Rejected**: More complex than needed
- Turing-incomplete by design (limits use cases)
- Smaller ecosystem than Nickel
- Steeper learning curve
### KCL (KusionStack Configuration Language)
**Rejected**: Kubernetes-focused, less general-purpose
- Designed for K8s manifests
- Less mature than Nickel for general config
---
## Implementation Timeline
1. ✅ Define base schemas (`schemas/kogral-config.ncl`)
2. ✅ Implement Nickel loader (`kogral-core/src/config/nickel.rs`)
3. ✅ Create example configs (`config/defaults.ncl`, `config/production.ncl`)
4. ✅ Document Nickel usage (`docs/config/nickel-schemas.md`)
5. ⏳ Add LSP recommendations to setup guide
6. ⏳ Create Nickel → TOML migration tool (for existing users)
---
## Monitoring
**Success Criteria**:
- Config errors caught at export time (not runtime)
- Users can compose configs for different environments
- Team comfortable with Nickel syntax within 2 weeks
**Metrics**:
- Number of config validation errors caught before runtime
- Time to diagnose config issues (should decrease)
- User feedback on config complexity
---
## References
- [Nickel Language](https://nickel-lang.org/)
- [Nickel User Manual](https://nickel-lang.org/user-manual/introduction)
- [platform-config pattern](../../crates/kogral-core/src/config/README.md) (reference implementation)
- [TOML Specification](https://toml.io/)
---
## Revision History
| Date | Author | Change |
| ---------- | ------------------ | ---------------- |
| 2026-01-17 | Architecture Team | Initial decision |
---
**Next ADR**: [ADR-002: FastEmbed via AI Providers](002-fastembed-ai-providers.md)

View File

@ -0,0 +1,357 @@
# ADR-002: FastEmbed via AI Providers for Embeddings
**Status**: Accepted
**Date**: 2026-01-17
**Deciders**: Architecture Team
**Context**: Embedding Strategy for Semantic Search
---
## Context
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".
**Requirements**:
1. **Local-First Option**: Must work offline without external API dependencies
2. **Production Scalability**: Support cloud AI providers for large-scale deployments
3. **Multiple Providers**: Flexibility to choose based on cost, quality, privacy
4. **Cost-Effective Development**: Free local embeddings for development and testing
5. **Quality**: Good enough embeddings for finding related concepts
**Options Evaluated**:
### Option 1: Only Local Embeddings (fastembed)
**Pros**:
- No API costs
- Works offline
- Privacy-preserving (no data leaves machine)
- Fast (local GPU acceleration possible)
**Cons**:
- Limited model quality compared to cloud providers
- Resource-intensive (requires download ~100MB models)
- Single provider lock-in (fastembed library)
**Example**:
```rust
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
```
### Option 2: Only Cloud AI Providers (OpenAI, Claude, etc.)
**Pros**:
- State-of-the-art embedding quality
- No local resource usage
- Latest models available
- Scalable to millions of documents
**Cons**:
- Requires API keys (cost per embedding)
- Network dependency (no offline mode)
- Privacy concerns (data sent to third parties)
- Vendor lock-in risk
**Example**:
```rust
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
```
### Option 3: Hybrid Strategy (fastembed + AI providers via rig-core)
**Pros**:
- ✅ Best of both worlds: local dev, cloud production
- ✅ User choice: privacy-first or quality-first
- ✅ Cost flexibility: free for small projects, paid for scale
- ✅ Unified interface via `rig-core` library
- ✅ Easy provider switching (config-driven)
**Cons**:
- ❌ More complex implementation (multiple providers)
- ❌ Dimension mismatch between providers (384 vs 1536)
- ❌ Additional dependencies (`rig-core`, `fastembed`)
---
## Decision
**We will use a hybrid strategy: fastembed (local) + AI providers (via rig-core).**
**Implementation**:
1. **Default**: `fastembed` with `BAAI/bge-small-en-v1.5` (384 dimensions)
2. **Optional**: OpenAI, Claude, Ollama via `rig-core` (configurable)
3. **Interface**: `EmbeddingProvider` trait abstracts provider details
4. **Config-Driven**: Provider selection via Nickel configuration
**Architecture**:
```rust
#[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 }
}
```
**Configuration** (Nickel):
```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,
},
}
```
**Provider Selection** (`kogral-core/src/embeddings/mod.rs`):
```rust
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)?))
}
}
}
```
---
## Consequences
### Positive
**Development Flexibility**:
- Developers can use `fastembed` without API keys
- Fast feedback loop (local embeddings, no network calls)
- Works offline (train trips, flights)
**Production Quality**:
- Production deployments can use OpenAI/Claude for better quality
- Latest embedding models available
- Scalable to millions of documents
**Privacy Control**:
- Privacy-sensitive projects use local embeddings
- Public projects can use cloud providers
- User choice via configuration
**Cost Optimization**:
- Small projects: free (fastembed)
- Large projects: pay for quality (cloud providers)
- Hybrid: important docs via cloud, bulk via local
**Unified Interface**:
- `EmbeddingProvider` trait abstracts provider details
- Query code doesn't know/care about provider
- Easy to add new providers
### Negative
**Dimension Mismatch**:
- fastembed: 384 dimensions
- OpenAI: 1536 dimensions
- Cannot mix in same index
**Mitigation**:
- Store provider + dimensions in node metadata
- Rebuild index when changing providers
- Document dimension constraints
**Model Download**:
- First use of fastembed downloads ~100MB model
- Slow initial startup
**Mitigation**:
- Pre-download in Docker images
- Document model download in setup guide
- Cache models in `~/.cache/fastembed`
**Complex Configuration**:
- Multiple provider options may confuse users
**Mitigation**:
- Sane default (fastembed)
- Clear examples for each provider
- Validation errors explain misconfigurations
### Neutral
**Dependency Trade-off**:
- `fastembed` adds ~5MB to binary
- `rig-core` adds ~2MB
- Total: ~7MB overhead
Not a concern for CLI/MCP server use case.
---
## Provider Comparison
| Provider | Dimensions | Quality | Cost | Privacy | Offline |
| -------------- | ---------- | --------- | ------------- | --------- | ------- |
| **fastembed** | 384 | Good | Free | ✅ Local | ✅ Yes |
| **OpenAI** | 1536 | Excellent | $0.0001/1K | ❌ Cloud | ❌ No |
| **Claude** | 1024 | Excellent | $0.00025/1K | ❌ Cloud | ❌ No |
| **Ollama** | 768 | Very Good | Free | ✅ Local | ✅ Yes |
**Recommendation by Use Case**:
- **Development**: fastembed (fast, free, offline)
- **Small Teams**: fastembed or Ollama (privacy, no costs)
- **Enterprise**: OpenAI or Claude (best quality, scalable)
- **Self-Hosted**: Ollama (good quality, local control)
---
## Implementation Timeline
1. ✅ Define `EmbeddingProvider` trait
2. ✅ Implement FastEmbedProvider (stub, feature-gated)
3. ✅ Implement RigEmbeddingProvider (stub, feature-gated)
4. ⏳ Complete FastEmbed integration with model download
5. ⏳ Complete rig-core integration (OpenAI, Claude, Ollama)
6. ⏳ Add query engine with similarity search
7. ⏳ Document provider selection and trade-offs
---
## Monitoring
**Success Criteria**:
- Users can switch providers via config change
- Local embeddings work without API keys
- Production deployments use cloud providers successfully
- Query quality acceptable for both local and cloud embeddings
**Metrics**:
- Embedding generation latency (local vs cloud)
- Query accuracy (precision@10 for semantic search)
- API costs (cloud providers)
- User satisfaction (feedback on search quality)
---
## References
- [fastembed Documentation](https://github.com/Anush008/fastembed-rs)
- [rig-core Documentation](https://github.com/0xPlaygrounds/rig)
- [OpenAI Embeddings API](https://platform.openai.com/docs/guides/embeddings)
- [BAAI/bge Models](https://huggingface.co/BAAI/bge-small-en-v1.5)
- [Ollama Embeddings](https://ollama.com/blog/embedding-models)
---
## Revision History
| Date | Author | Change |
| ---------- | ------------------ | ---------------- |
| 2026-01-17 | Architecture Team | Initial decision |
---
**Previous ADR**: [ADR-001: Nickel vs TOML](001-nickel-vs-toml.md)
**Next ADR**: [ADR-003: Hybrid Storage Strategy](003-hybrid-storage.md)

View File

@ -0,0 +1,449 @@
# ADR-003: Hybrid Storage Strategy
**Status**: Accepted
**Date**: 2026-01-17
**Deciders**: Architecture Team
**Context**: Storage Backend Strategy for Knowledge Base
---
## Context
The KOGRAL needs to store knowledge graphs with these requirements:
1. **Git-Friendly**: Knowledge should version alongside code
2. **Scalable**: Support small projects (10s of nodes) to large organizations (10,000+ nodes)
3. **Queryable**: Efficient graph queries and relationship traversal
4. **Offline-Capable**: Work without network access
5. **Collaborative**: Support shared organizational knowledge
6. **Cost-Effective**: Free for small projects, reasonable cost at scale
**Constraints**:
- Developers want to edit knowledge in text editors
- Organizations want centralized guideline management
- Git workflows essential for code-adjacent knowledge
- Large graphs need database performance
### Option 1: Filesystem Only
**Approach**: Store everything as markdown files
**Pros**:
- ✅ Git-native (perfect for versioning)
- ✅ Text editor friendly
- ✅ No dependencies
- ✅ Works offline
- ✅ Free
**Cons**:
- ❌ Poor performance for large graphs (100 0+ nodes)
- ❌ No efficient graph queries
- ❌ Difficult to share across projects
- ❌ Manual sync for collaboration
**Scalability**: Good for < 100 nodes, poor beyond
### Option 2: Database Only (SurrealDB)
**Approach**: Store all knowledge in SurrealDB graph database
**Pros**:
- ✅ Excellent query performance
- ✅ Native graph relationships
- ✅ Scalable to millions of nodes
- ✅ Centralized for collaboration
**Cons**:
- ❌ Not git-trackable
- ❌ Requires running database server
- ❌ Can't edit with text editor
- ❌ Network dependency
- ❌ Infrastructure cost
**Scalability**: Excellent, but loses developer workflow benefits
### Option 3: Hybrid (Filesystem + SurrealDB)
**Approach**: Filesystem for local project knowledge, SurrealDB for shared organizational knowledge
**Pros**:
- ✅ Git-friendly for project knowledge
- ✅ Text editor friendly
- ✅ Scalable for shared knowledge
- ✅ Works offline (local graph)
- ✅ Collaborative (shared graph)
- ✅ Cost-effective (DB only for shared)
**Cons**:
- ❌ More complex implementation
- ❌ Sync mechanism needed
- ❌ Two storage systems to manage
**Scalability**: Excellent - best of both worlds
---
## Decision
**We will use a hybrid storage strategy: Filesystem (local) + SurrealDB (shared).**
**Architecture**:
```text
┌─────────────────────────────────────────────────────────────┐
│ 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 │
└─────────────────────────────────────────────────────────────┘
```
**Implementation**:
```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
},
}
```
**Sync Strategy**:
```text
.kogral/ (Filesystem)
↓ [on save]
Watch for changes
↓ [debounced]
Sync to SurrealDB
Shared graph updated
↓ [on query]
Merge local + shared results
```
---
## Consequences
### Positive
**Developer Workflow Preserved**:
```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
```
**Git Integration**:
- Project knowledge versioned with code
- Branches include relevant knowledge
- Merges resolve knowledge conflicts
- PR reviews include knowledge changes
**Offline Development**:
- Full functionality without network
- Shared guidelines cached locally
- Sync when reconnected
**Scalability**:
- Projects: filesystem (100s of nodes, fine performance)
- Organization: SurrealDB (10,000+ nodes, excellent performance)
**Collaboration**:
- Shared guidelines accessible to all projects
- Updates to shared knowledge propagate automatically
- Consistent practices across organization
**Cost-Effective**:
- Small projects: free (filesystem only)
- Organizations: SurrealDB for shared only (not all project knowledge)
**Gradual Adoption**:
- Start with filesystem only
- Add SurrealDB when needed
- Feature-gated (`--features surrealdb`)
### Negative
**Complexity**:
- Two storage implementations
- Sync mechanism required
- Conflict resolution needed
**Mitigation**:
- Storage trait abstracts differences
- Sync is optional (can disable)
- Conflicts rare (guidelines change infrequently)
**Sync Latency**:
- Changes to shared KOGRAL not instant in all projects
**Mitigation**:
- Acceptable latency (guidelines don't change rapidly)
- Manual sync command available (`kogral sync`)
- Auto-sync on query (fetch latest)
**Infrastructure Requirement**:
- SurrealDB server needed for shared KOGRAL
**Mitigation**:
- Optional (can use synced filesystem instead)
- Docker Compose for easy setup
- Managed SurrealDB Cloud option
### Neutral
**Storage Trait Implementation**:
```rust
#[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 { /* ... */ }
```
Abstraction makes multi-backend manageable.
---
## Use Cases
### Small Project (Solo Developer)
**Config**:
```nickel
{ storage = { primary = 'filesystem } }
```
**Behavior**:
- All knowledge in `.kogral/` directory
- Git-tracked with code
- No database required
- Works offline
### Medium Project (Team)
**Config**:
```nickel
{
storage = {
primary = 'filesystem,
secondary = {
enabled = true,
type = 'surrealdb,
url = "ws://team-kb.local:8000",
},
},
}
```
**Behavior**:
- Project knowledge in `.kogral/` (git-tracked)
- Shared team patterns in SurrealDB
- Automatic sync
- Offline fallback to cached
### Large Organization
**Config**:
```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",
],
},
}
```
**Behavior**:
- Project-specific in `.kogral/`
- Organization guidelines in SurrealDB
- Security policies enforced
- Automatic guideline updates
---
## Sync Mechanism
### Filesystem → SurrealDB
**Trigger**: File changes detected (via `notify` crate)
**Process**:
1. Parse changed markdown file
2. Convert to Node struct
3. Upsert to SurrealDB
4. Update relationships
**Debouncing**: 500ms (configurable)
### SurrealDB → Filesystem
**Trigger**: Query for shared knowledge
**Process**:
1. Query SurrealDB for shared nodes
2. Cache locally (in-memory or filesystem)
3. Merge with local results
4. Return combined
**Caching**: TTL-based (5 minutes default)
### Conflict Resolution
**Strategy**: Last-write-wins with version tracking
**Example**:
```text
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
```
---
## Alternatives Considered
### Git Submodules for Shared Knowledge
**Rejected**: Cumbersome workflow
- Requires manual submodule update
- Merge conflicts in shared submodule
- Not discoverable (need to know submodule exists)
### Syncthing for Filesystem Sync
**Rejected**: Not designed for this use case
- No query optimization
- No relationship indexes
- Sync conflicts difficult to resolve
### PostgreSQL with JSON
**Rejected**: Not a graph database
- Poor graph query performance
- Relationship traversal requires complex SQL joins
- No native graph features
---
## Migration Path
### Phase 1: Filesystem Only (Current)
- All storage via filesystem
- Git-tracked
- No database required
### Phase 2: Optional SurrealDB
- Add SurrealDB support (feature-gated)
- Manual sync command
- Shared KB opt-in
### Phase 3: Automatic Sync
- File watching
- Auto-sync on changes
- Background sync
### Phase 4: Multi-Tenant SurrealDB
- Organization namespaces
- Access control
- Audit logs
---
## Monitoring
**Success Criteria**:
- Developers don't notice hybrid complexity
- Sync completes < 1 second for typical changes
- Shared guidelines accessible in < 100ms
- Zero data loss in sync
**Metrics**:
- Sync latency (P50, P95, P99)
- Cache hit rate (shared knowledge)
- Conflict rate (expect < 0.1%)
- User satisfaction
---
## References
- [SurrealDB Documentation](https://surrealdb.com/docs)
- [Storage Trait Implementation](../../crates/kogral-core/src/storage/mod.rs)
- [FilesystemStorage](../../crates/kogral-core/src/storage/filesystem.rs)
- [SurrealDbStorage](../../crates/kogral-core/src/storage/surrealdb.rs)
- [Sync Mechanism](../../scripts/kogral-sync.nu)
---
## Revision History
| Date | Author | Change |
| ---------- | ------------------ | ---------------- |
| 2026-01-17 | Architecture Team | Initial decision |
---
**Previous ADR**: [ADR-002: FastEmbed via AI Providers](002-fastembed-ai-providers.md)
**Next ADR**: [ADR-004: Logseq Compatibility](004-logseq-compatibility.md)

View File

@ -0,0 +1,196 @@
# ADR-004: Logseq Blocks Support
## Status
**Proposed** (Design phase)
## Context
Logseq uses **content blocks** as the fundamental unit of information, not full documents. KB currently treats `Node.content` as flat markdown string, which loses block-level features on import/export:
**Lost features**:
- Block properties (`#card`, `TODO`, custom properties)
- Block hierarchy (outliner nesting)
- Block references (`((block-uuid))`)
- Block-level queries (find all flashcards, TODOs)
**User requirement**: Round-trip Logseq import/export with full fidelity:
```text
Logseq → KOGRAL Import → KOGRAL Storage → KOGRAL Export → Logseq
(blocks preserved at every step)
```
## Decision
Implement **Hybrid Block Support** (structured + markdown):
### 1. Add Block Data Structure
```rust
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]]
}
```
### 2. Extend Node Model
```rust
pub struct Node {
// ... existing fields ...
pub content: String, // Source of truth (markdown)
pub blocks: Option<Vec<Block>>, // Cached structure (optional)
}
```
### 3. Bidirectional Parser
- **Parse**: Markdown → `Vec<Block>` (lazy, on-demand)
- **Serialize**: `Vec<Block>` → Markdown (for export)
### 4. Storage Strategy
**Filesystem** (git-friendly, Logseq-compatible):
```markdown
- Block 1 #card
- Nested answer
- TODO Block 2
priority:: high
```
**SurrealDB** (queryable):
```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;
```
### 5. Query Extensions
```rust
// 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")
```
## Consequences
### Positive
**Full Logseq Compatibility** - Import/export preserves all block features
**Queryable Blocks** - Find #card, TODO, custom properties across KOGRAL
**Backward Compatible** - Existing nodes without blocks still work
**Type-Safe** - Structured data instead of regex parsing everywhere
**Extensible** - Custom block properties supported
**Hierarchy Preserved** - Nested blocks maintain parent-child relationships
### Negative
⚠️ **Added Complexity** - New data structures, parser, sync logic
⚠️ **Dual Representation** - Must keep `content` and `blocks` in sync
⚠️ **Storage Overhead** - SurrealDB stores both markdown and structure
⚠️ **Migration Required** - Existing data needs parsing to populate blocks
### Neutral
⚙️ **Lazy Parsing** - Blocks parsed on-demand (not stored by default)
⚙️ **Opt-In** - Config flag `blocks.enabled` to activate features
⚙️ **Gradual Adoption** - Can implement in phases
## Implementation Phases
**Phase 1: Foundation** (No behavior change)
- Add `Block` struct to `models/block.rs`
- Add optional `blocks` field to `Node`
- Add config: `blocks.enabled = false` (default off)
**Phase 2: Parser**
- Implement `BlockParser::parse()` (markdown → blocks)
- Implement `BlockParser::serialize()` (blocks → markdown)
- Add `Node::get_blocks()` method (lazy parsing)
**Phase 3: Logseq Integration**
- Update `LogseqImporter` to parse blocks
- Update `LogseqExporter` to serialize blocks
- Test round-trip (Logseq → KB → Logseq)
**Phase 4: Query API**
- Add `Graph::find_blocks_by_tag()`
- Add `Graph::find_all_todos()`
- Add `Node::find_blocks_by_property()`
**Phase 5: MCP/CLI Integration**
- Add `kb/find_blocks` MCP tool
- Add `kogral find-cards` CLI command
- Add `kogral find-todos` CLI command
**Phase 6: SurrealDB Backend**
- Create `block` table schema
- Index on tags, status, properties
- Store blocks alongside nodes
## Alternatives Considered
### Alternative 1: Blocks as First-Class Nodes
Convert each Logseq block to a separate KOGRAL Node.
**Rejected**: Too granular, explosion of nodes, loses document context.
### Alternative 2: Parser-Only (No Storage)
Keep `content: String`, parse blocks on every access.
**Rejected**: Can't query blocks in database, parse overhead, can't index.
### Alternative 3: Metadata Field
Store blocks in `metadata: HashMap<String, Value>`.
**Rejected**: Not type-safe, harder to query, no schema validation.
## References
- [Logseq Block Format](https://docs.logseq.com/#/page/blocks)
- [Full Design Document](../logseq-blocks-design.md)
- [Implementation Tracking](https://github.com/.../issues/XXX)
## Notes
**Backward Compatibility Strategy**:
- `content` remains source of truth
- `blocks` is optional enhancement
- Old code works unchanged
- New features opt-in via config
**Migration Path**:
- Existing users: blocks disabled by default
- New users: blocks enabled, parsed on import
- Manual: `kogral reindex --parse-blocks` to populate
---
**Decision Date**: 2026-01-17
**Approvers**: TBD
**Review Date**: After Phase 2 implementation

View File

@ -0,0 +1 @@
# ADR-005: MCP Protocol for AI Integration

View File

@ -0,0 +1,539 @@
# Config-Driven Architecture
The KOGRAL follows a **config-driven architecture** where all behavior is defined through Nickel configuration files rather than hardcoded in Rust.
## Philosophy
**"Configuration, not code, defines behavior"**
Instead of hardcoding storage backends, embedding providers, or query parameters, KOGRAL uses a layered configuration system that composes settings from multiple sources:
1. **Schema contracts** (type definitions)
2. **Defaults** (base values)
3. **Mode overlays** (dev/prod/test optimizations)
4. **User customizations** (project-specific overrides)
This approach provides:
- ✅ **Type safety** - Nickel contracts validate configuration before runtime
- ✅ **Composability** - Mix and match configurations for different environments
- ✅ **Discoverability** - Self-documenting schemas with inline documentation
- ✅ **Hot-reload** - Change behavior without recompiling Rust code
- ✅ **Double validation** - Nickel contracts + serde ensure correctness
## Configuration Composition Flow
![Configuration Composition](../diagrams/config-composition.svg)
The configuration system uses a **four-layer composition pattern**:
### Layer 1: Schema Contracts
**Location**: `schemas/contracts.ncl`
**Purpose**: Define types and validation rules using Nickel contracts.
**Example**:
```nickel
{
StorageType = [| 'filesystem, 'memory, 'surrealdb |],
StorageConfig = {
primary | StorageType
| doc "Primary storage backend"
| default = 'filesystem,
secondary | SecondaryStorageConfig
| doc "Optional secondary storage"
| default = { enabled = false },
},
}
```
**Benefits**:
- Enum validation (only valid storage types accepted)
- Required vs optional fields
- Default values for optional fields
- Documentation attached to types
### Layer 2: Defaults
**Location**: `schemas/defaults.ncl`
**Purpose**: Provide sensible base values for all configuration options.
**Example**:
```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,
}
```
**Validated by**: `contracts.KbConfig` contract ensures defaults are valid.
### Layer 3: Mode Overlays
**Location**: `schemas/modes/{dev,prod,test}.ncl`
**Purpose**: Environment-specific optimizations and tuning.
#### Development Mode (`dev.ncl`)
Optimized for: Fast iteration, local development, debugging
```nickel
{
storage = {
primary = 'filesystem,
secondary = { enabled = false }, # No database overhead
},
embeddings = {
provider = 'fastembed, # Local, no API costs
},
sync = {
auto_index = false, # Manual control
},
}
```
#### Production Mode (`prod.ncl`)
Optimized for: Performance, reliability, scalability
```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
},
}
```
#### Test Mode (`test.ncl`)
Optimized for: Fast tests, isolation, determinism
```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
},
}
```
### Layer 4: User Customizations
**Location**: `.kogral-config/core/kogral.ncl` or `.kogral-config/platform/{dev,prod,test}.ncl`
**Purpose**: Project-specific or deployment-specific overrides.
**Example** (user project config):
```nickel
let mode = import "../../schemas/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
```
## Composition Mechanism
The `helpers.ncl` module provides the composition function:
```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,
}
```
**Merge behavior**:
- Records are merged recursively
- Override values take precedence over base values
- Arrays are not merged, override replaces base
- Null in override keeps base value
**Example merge**:
```nickel
base = { storage = { primary = 'filesystem }, embeddings = { enabled = true } }
override = { storage = { primary = 'memory } }
# Result: { storage = { primary = 'memory }, embeddings = { enabled = true } }
```
## Export to JSON
Once composed, the Nickel configuration is exported to JSON for Rust consumption:
```bash
nickel export --format json .kogral-config/core/kogral.ncl > .kogral-config/targets/kogral-core.json
```
**Output** (`.kogral-config/targets/kogral-core.json`):
```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
}
}
```
## Rust Integration
The Rust code deserializes the JSON into typed structs using serde:
```rust
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)
}
}
```
**Usage in kogral-core**:
```rust
let config = KbConfig::from_file(".kogral-config/targets/kogral-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)?),
};
```
## Double Validation
Configuration is validated **twice**:
### 1. Nickel Contract Validation
At export time, Nickel validates:
- ✅ Types match contracts (e.g., `primary | StorageType`)
- ✅ Required fields are present
- ✅ Enums have valid values
- ✅ Nested structure is correct
**Error example**:
```text
error: contract broken by a value
┌─ .kogral-config/core/kogral.ncl:15:5
15│ primary = 'invalid,
│ ^^^^^^^^^^^^^^^^^^^ applied to this expression
= This value is not in the enum ['filesystem, 'memory, 'surrealdb]
```
### 2. Serde Deserialization Validation
At runtime, serde validates:
- ✅ JSON structure matches Rust types
- ✅ Field names match (with rename support)
- ✅ Values can be converted to Rust types
- ✅ Required fields are not null
**Error example**:
```rust
Error: missing field `graph` at line 1 column 123
```
## Benefits of Config-Driven Architecture
### 1. Zero Hardcoding
**Bad** (hardcoded):
```rust
// Hardcoded - requires recompilation to change
let storage = FilesystemStorage::new("/fixed/path");
let threshold = 0.6;
```
**Good** (config-driven):
```rust
// Config-driven - change via .ncl file
let storage = create_storage(&config)?;
let threshold = config.query.similarity_threshold;
```
### 2. Environment Flexibility
Same codebase, different behavior:
```bash
# Development
nickel export .kogral-config/platform/dev.ncl > targets/kogral-core.json
# → Filesystem storage, fastembed, no auto-sync
# Production
nickel export .kogral-config/platform/prod.ncl > targets/kogral-core.json
# → SurrealDB enabled, OpenAI embeddings, auto-sync
# Testing
nickel export .kogral-config/platform/test.ncl > targets/kogral-core.json
# → In-memory storage, no embeddings, isolated
```
### 3. Self-Documenting
Nickel contracts include inline documentation:
```nickel
StorageType = [| 'filesystem, 'memory, 'surrealdb |]
| doc "Storage backend type: filesystem (git-tracked), memory (ephemeral), surrealdb (scalable)",
```
IDEs can show this documentation when editing `.ncl` files.
### 4. Type-Safe Evolution
When adding new features:
1. Update contract in `contracts.ncl`
2. Add default in `defaults.ncl`
3. Export validates existing configs
4. Rust compilation validates deserialization
Breaking changes are caught **before runtime**.
### 5. Testability
Different test scenarios without code changes:
```nickel
# test-semantic-search.ncl
let test_config = defaults.base & {
embeddings = { enabled = true, provider = 'fastembed },
query = { similarity_threshold = 0.3 },
} in test_config
```
```rust
#[test]
fn test_semantic_search() {
let config = KbConfig::from_file("test-semantic-search.json")?;
// Config drives test behavior
}
```
## Configuration Discovery
KOGRAL tools automatically discover configuration:
1. **Check `.kogral-config/targets/kogral-core.json`** (pre-exported)
2. **Check `.kogral-config/core/kogral.ncl`** (export on-demand)
3. **Check environment variable `KOGRAL_CONFIG`**
4. **Fall back to embedded defaults**
```rust
impl KbConfig {
pub fn discover() -> Result<Self> {
if let Ok(config) = Self::from_file(".kogral-config/targets/kogral-core.json") {
return Ok(config);
}
if Path::new(".kogral-config/core/kogral.ncl").exists() {
// Export and load
let output = Command::new("nickel")
.args(["export", "--format", "json", ".kogral-config/core/kogral.ncl"])
.output()?;
return serde_json::from_slice(&output.stdout)?;
}
if let Ok(path) = std::env::var("KOGRAL_CONFIG") {
return Self::from_file(&path);
}
Ok(Self::default()) // Embedded defaults
}
}
```
## Integration with justfile
The `justfile` integrates configuration validation:
```just
# Validate all Nickel configs
nickel-validate-all:
@echo "Validating Nickel schemas..."
nickel typecheck schemas/contracts.ncl
nickel typecheck schemas/defaults.ncl
nickel typecheck schemas/helpers.ncl
nickel typecheck schemas/modes/dev.ncl
nickel typecheck schemas/modes/prod.ncl
nickel typecheck schemas/modes/test.ncl
nickel typecheck .kogral-config/core/kogral.ncl
# Export all platform configs
nickel-export-all:
@echo "Exporting platform configs to JSON..."
@mkdir -p .kogral-config/targets
nickel export --format json .kogral-config/platform/dev.ncl > .kogral-config/targets/kogral-dev.json
nickel export --format json .kogral-config/platform/prod.ncl > .kogral-config/targets/kogral-prod.json
nickel export --format json .kogral-config/platform/test.ncl > .kogral-config/targets/kogral-test.json
@echo "Exported 3 configurations to .kogral-config/targets/"
```
Usage:
```bash
just nickel::validate-all # Check configs are valid
just nickel::export-all # Generate JSON for all environments
```
## Best Practices
### 1. Never Hardcode
If it's behavior, it's config:
- Storage paths
- API endpoints
- Thresholds
- Timeouts
- Feature flags
- Provider selection
### 2. Use Modes for Environment Differences
Don't put environment-specific values in user config:
**Bad**:
```nickel
# user config with env-specific values
{
storage = {
url = if env == "prod" then "prod-url" else "dev-url" # Don't do this
}
}
```
**Good**:
```nickel
# modes/prod.ncl
{ storage = { url = "prod-url" } }
# modes/dev.ncl
{ storage = { url = "dev-url" } }
# user config is environment-agnostic
{ graph = { name = "my-project" } }
```
### 3. Document Complex Fields
Use Nickel's `doc` metadata:
```nickel
similarity_threshold | Number
| doc "Minimum cosine similarity (0-1) for semantic search matches. Higher = stricter."
| default = 0.6,
```
### 4. Validate Early
Run `nickel typecheck` in CI/CD before building Rust code.
### 5. Version Configs
Track `.ncl` files in git, ignore `.kogral-config/targets/*.json` (generated).
## See Also
- **Schema Reference**: [Configuration Schema](../config/schema.md)
- **User Guide**: [Configuration Guide](../config/overview.md)
- **ADR**: [Why Nickel vs TOML](adrs/001-nickel-vs-toml.md)
- **Examples**: `.kogral-config/core/kogral.ncl`, `.kogral-config/platform/*.ncl`

View File

@ -0,0 +1 @@
# Graph Model

View File

@ -0,0 +1,935 @@
# Logseq Blocks Support - Architecture Design
## Problem Statement
Logseq uses **content blocks** as the fundamental unit of information, not full documents. Each block can have:
- **Properties**: `#card`, `TODO`, `DONE`, custom properties
- **Tags**: Inline tags like `#flashcard`, `#important`
- **References**: Block references `((block-id))`, page references `[[page]]`
- **Nesting**: Outliner-style hierarchy (parent-child blocks)
- **Metadata**: Block-level properties (unlike page-level frontmatter)
**Current KB limitation**: Nodes only have `content: String` (flat markdown). Importing from Logseq loses block structure and properties.
**Requirement**: Support round-trip import/export with full block fidelity:
```text
Logseq Graph → KOGRAL Import → KOGRAL Storage → KOGRAL Export → Logseq Graph
(blocks preserved) (blocks preserved)
```
## Use Cases
### 1. Flashcards (`#card`)
**Logseq**:
```markdown
- What is Rust's ownership model? #card
- Rust uses ownership, borrowing, and lifetimes
- Three rules: one owner, many borrows XOR one mutable
```
**KB needs to preserve**:
- Block with `#card` property
- Nested answer blocks
- Ability to query all cards
### 2. Task Tracking (`TODO`/`DONE`)
**Logseq**:
```markdown
- TODO Implement block parser #rust
- DONE Research block structure
- TODO Write parser tests
```
**KB needs to preserve**:
- Task status per block
- Hierarchical task breakdown
- Tags on tasks
### 3. Block References
**Logseq**:
```markdown
- Core concept: ((block-uuid-123))
- See also: [[Related Page]]
```
**KB needs to preserve**:
- Block-to-block links (not just page-to-page)
- UUID references
### 4. Block Properties
**Logseq**:
```markdown
- This is a block with properties
property1:: value1
property2:: value2
```
**KB needs to preserve**:
- Custom key-value properties per block
- Property inheritance/override
## Design Options
### Option A: Blocks as First-Class Data Structure
**Add `blocks` field to Node**:
```rust
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,
}
```
**Pros**:
- ✅ Type-safe, explicit structure
- ✅ Queryable (find all #card blocks)
- ✅ Preserves hierarchy
- ✅ Supports block-level operations
**Cons**:
- ❌ Adds complexity to Node
- ❌ Dual representation (content + blocks)
- ❌ Requires migration of existing data
### Option B: Parser-Only Approach
**Keep `content: String`, parse blocks on-demand**:
```rust
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"));
```
**Pros**:
- ✅ No schema changes
- ✅ Backward compatible
- ✅ Simple storage (still just String)
**Cons**:
- ❌ Parse overhead on every access
- ❌ Can't query blocks in database (SurrealDB)
- ❌ Harder to index/search blocks
### Option C: Hybrid Approach (RECOMMENDED)
**Combine both: structured storage + lazy parsing**:
```rust
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);
}
}
}
```
**Storage Strategy**:
1. **Filesystem** - Store as markdown (Logseq compatible):
```markdown
- Block 1 #card
- Nested block
- Block 2 TODO
```
2. **SurrealDB** - Store both:
```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;
```
**Pros**:
- ✅ Best of both worlds
- ✅ Filesystem stays Logseq-compatible
- ✅ SurrealDB can query blocks
- ✅ Lazy parsing (only when needed)
- ✅ Backward compatible
**Cons**:
- ⚠️ Need to keep content/blocks in sync
- ⚠️ More complex implementation
## Recommended Implementation
**Phase 1: Data Model**
```rust
// crates/kogral-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
}
}
```
**Phase 2: Update Node Model**
```rust
// crates/kogral-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)
}
}
```
**Phase 3: Block Parser**
```rust
// crates/kogral-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
}
```
**Phase 4: Logseq Import/Export**
```rust
// crates/kogral-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)
}
}
```
## Query API Extensions
```rust
// 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
}
}
```
## MCP Tool Extensions
```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" }
}
}
}
```
## Configuration
```nickel
# schemas/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 = {},
}
```
## Migration Path
**Phase 1**: Add Block models (no behavior change)
**Phase 2**: Add BlockParser (opt-in via config)
**Phase 3**: Update Logseq import/export
**Phase 4**: Add block queries to CLI/MCP
**Phase 5**: SurrealDB block indexing
**Backward Compatibility**:
- Existing nodes without `blocks` field work as before
- `content` remains source of truth
- `blocks` is optional cache/structure
- Config flag `blocks.enabled` to opt-in
## Testing Strategy
```rust
#[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);
}
}
```
## Summary
**Recommended Approach**: Hybrid (Option C)
- **Add** `Block` struct with properties, hierarchy
- **Extend** `Node` with optional `blocks: Option<Vec<Block>>`
- **Implement** bidirectional parser (markdown ↔ blocks)
- **Preserve** `content` as source of truth (backward compat)
- **Enable** block queries in CLI/MCP
- **Support** round-trip Logseq import/export
**Benefits**:
- ✅ Full Logseq compatibility
- ✅ Queryable blocks (find #card, TODO, etc.)
- ✅ Backward compatible
- ✅ Extensible (custom properties)
- ✅ Type-safe structure
**Trade-offs**:
- ⚠️ Added complexity
- ⚠️ Need to sync content ↔ blocks
- ⚠️ More storage for SurrealDB backend
**Next Steps**:
1. Review and approve design
2. Implement Phase 1 (Block models)
3. Implement Phase 2 (BlockParser)
4. Update Logseq import/export
5. Add block queries to MCP/CLI

View File

@ -0,0 +1,486 @@
# System Architecture
Comprehensive overview of the KOGRAL architecture.
## High-Level Architecture
![Architecture Overview](../diagrams/architecture-overview.svg)
The KOGRAL consists of three main layers:
1. **User Interfaces**: kogral-cli (terminal), kogral-mcp (AI integration), NuShell scripts (automation)
2. **Core Library (kogral-core)**: Rust library with graph engine, storage abstraction, embeddings, query engine
3. **Storage Backends**: Filesystem (git-friendly), SurrealDB (scalable), In-Memory (cache/testing)
## Component Details
### kogral-cli (Command-Line Interface)
**Purpose**: Primary user interface for local knowledge management.
**Commands** (13 total):
- `init`: Initialize `.kogral/` directory
- `add`: Create nodes (note, decision, guideline, pattern, journal)
- `search`: Text and semantic search
- `link`: Create relationships between nodes
- `list`: List all nodes
- `show`: Display node details
- `delete`: Remove nodes
- `graph`: Visualize knowledge graph
- `sync`: Sync filesystem ↔ SurrealDB
- `serve`: Start MCP server
- `import`: Import from Logseq
- `export`: Export to Logseq/JSON
- `config`: Manage configuration
**Technology**: Rust + clap (derive API)
**Features**:
- Colored terminal output
- Interactive prompts
- Dry-run modes
- Validation before operations
### kogral-mcp (MCP Server)
**Purpose**: AI integration via Model Context Protocol.
**Protocol**: JSON-RPC 2.0 over stdio
**Components**:
1. **Tools** (7):
- `kogral/search`: Query knowledge base
- `kogral/add_note`: Create notes
- `kogral/add_decision`: Create ADRs
- `kogral/link`: Create relationships
- `kogral/get_guidelines`: Retrieve guidelines with inheritance
- `kb/list_graphs`: List available graphs
- `kogral/export`: Export to formats
2. **Resources** (6 URIs):
- `kogral://project/notes`
- `kogral://project/decisions`
- `kogral://project/guidelines`
- `kogral://project/patterns`
- `kogral://shared/guidelines`
- `kogral://shared/patterns`
3. **Prompts** (2):
- `kogral/summarize_project`: Generate project summary
- `kogral/find_related`: Find related nodes
**Integration**: Claude Code via `~/.config/claude/config.json`
### NuShell Scripts
**Purpose**: Automation and maintenance tasks.
**Scripts** (6):
- `kogral-sync.nu`: Filesystem ↔ SurrealDB sync
- `kogral-backup.nu`: Archive knowledge base
- `kogral-reindex.nu`: Rebuild embeddings
- `kogral-import-logseq.nu`: Import from Logseq
- `kogral-export-logseq.nu`: Export to Logseq
- `kogral-stats.nu`: Graph statistics
**Features**:
- Colored output
- Dry-run modes
- Progress indicators
- Error handling
## Core Library (kogral-core)
### Models
**Graph**:
```rust
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>,
}
```
**Node**:
```rust
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
}
```
**Edge**:
```rust
pub struct Edge {
pub from: String,
pub to: String,
pub relation: EdgeType,
pub strength: f32,
pub created: DateTime<Utc>,
}
```
### Storage Trait
```rust
#[async_trait]
pub trait Storage: Send + Sync {
/// Save a complete graph to storage
async fn save_graph(&mut self, graph: &Graph) -> Result<()>;
/// Load a graph from storage
async fn load_graph(&self, name: &str) -> Result<Graph>;
/// Save a single node to storage
async fn save_node(&mut self, node: &Node) -> Result<()>;
/// Load a node by ID
async fn load_node(&self, graph_name: &str, node_id: &str) -> Result<Node>;
/// Delete a node
async fn delete_node(&mut self, graph_name: &str, node_id: &str) -> Result<()>;
/// List all graphs
async fn list_graphs(&self) -> Result<Vec<String>>;
/// List nodes in a graph, optionally filtered by type
async fn list_nodes(&self, graph_name: &str, node_type: Option<&str>) -> Result<Vec<Node>>;
}
```
**Implementations**:
1. `FilesystemStorage`: Git-friendly markdown files
2. `MemoryStorage`: In-memory with DashMap
3. `SurrealDbStorage`: Scalable graph database
### Embedding Provider Trait
```rust
#[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;
}
```
**Implementations**:
1. `FastEmbedProvider`: Local fastembed
2. `RigEmbeddingProvider`: OpenAI, Claude, Ollama (via rig-core)
### Parser
**Input**: Markdown file with YAML frontmatter
**Output**: `Node` struct
**Features**:
- YAML frontmatter extraction
- Markdown body parsing
- Wikilink detection (`[[linked-note]]`)
- Code reference parsing (`@file.rs:42`)
**Example**:
```markdown
---
id: note-123
type: note
title: My Note
tags: [rust, async]
---
# My Note
Content with [[other-note]] and @src/main.rs:10
```
```rust
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
}
```
## Configuration System
### Nickel Schema
```nickel
# schemas/kogral-config.ncl
{
KbConfig = {
graph | GraphConfig,
storage | StorageConfig,
embeddings | EmbeddingConfig,
templates | TemplateConfig,
query | QueryConfig,
mcp | McpConfig,
sync | SyncConfig,
},
}
```
### Loading Process
```text
User writes: .kogral/config.ncl
↓ [nickel export --format json]
JSON intermediate
↓ [serde_json::from_str]
KbConfig struct (Rust)
Runtime behavior
```
**Double Validation**:
1. Nickel contracts: Type-safe, enum validation
2. Serde deserialization: Rust type checking
**Benefits**:
- Errors caught at export time
- Runtime guaranteed valid config
- Self-documenting schemas
## Storage Architecture
### Hybrid Strategy
**Local Graph** (per project):
- Storage: Filesystem (`.kogral/` directory)
- Format: Markdown + YAML frontmatter
- Version control: Git
- Scope: Project-specific knowledge
**Shared Graph** (organization):
- Storage: SurrealDB (or synced filesystem)
- Format: Same markdown (for compatibility)
- Version control: Optional
- Scope: Organization-wide guidelines
**Sync**:
```text
Filesystem (.kogral/)
↕ [bidirectional sync]
SurrealDB (central)
```
### File Layout
```text
.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
```
## Query Engine
### Text Search
```rust
let results = graph.nodes.values()
.filter(|node| {
node.title.contains(&query) ||
node.content.contains(&query) ||
node.tags.iter().any(|tag| tag.contains(&query))
})
.collect();
```
### Semantic Search
```rust
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());
```
### Cross-Graph Query
```rust
// 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);
```
## MCP Protocol Flow
```text
Claude Code kogral-mcp kogral-core
│ │ │
├─ JSON-RPC request ───→ │ │
│ kogral/search │ │
│ {"query": "rust"} │ │
│ ├─ search() ──────────→ │
│ │ │
│ │ Query engine
│ │ Text + semantic
│ │ │
│ │ ←──── results ─────────┤
│ │ │
│ ←─ JSON-RPC response ──┤ │
│ {"results": [...]} │ │
```
## Template System
**Engine**: Tera (Jinja2-like)
**Templates**:
1. **Document Templates** (6):
- `note.md.tera`
- `decision.md.tera`
- `guideline.md.tera`
- `pattern.md.tera`
- `journal.md.tera`
- `execution.md.tera`
2. **Export Templates** (4):
- `logseq-page.md.tera`
- `logseq-journal.md.tera`
- `summary.md.tera`
- `graph.json.tera`
**Usage**:
```rust
let mut tera = Tera::new("templates/**/*.tera")?;
let rendered = tera.render("note.md.tera", &context)?;
```
## Error Handling
**Strategy**: `thiserror` for structured errors
```rust
#[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),
}
```
**Propagation**: `?` operator throughout
## Testing Strategy
**Unit Tests**: Per module (models, parser, storage)
**Integration Tests**: Full workflow (add → save → load → query)
**Test Coverage**:
- kogral-core: 48 tests
- kogral-mcp: 5 tests
- Total: 56 tests
**Test Data**: Fixtures in `tests/fixtures/`
## Performance Considerations
**Node Lookup**: O(1) via HashMap
**Semantic Search**: O(n) with early termination (threshold filter)
**Storage**:
- Filesystem: Lazy loading (load on demand)
- Memory: Full graph in RAM
- SurrealDB: Query optimization (indexes)
**Embeddings**:
- Cache embeddings in node metadata
- Batch processing (configurable batch size)
- Async generation (non-blocking)
## Security
**No unsafe code**: `#![forbid(unsafe_code)]`
**Input validation**:
- Nickel contracts validate config
- serde validates JSON
- Custom validation for user input
**File operations**:
- Path sanitization (no `../` traversal)
- Permissions checking
- Atomic writes (temp file + rename)
## Scalability
**Small Projects** (< 1000 nodes):
- Filesystem storage
- In-memory search
- Local embeddings (fastembed)
**Medium Projects** (1000-10,000 nodes):
- Filesystem + SurrealDB sync
- Semantic search with caching
- Cloud embeddings (OpenAI/Claude)
**Large Organizations** (> 10,000 nodes):
- SurrealDB primary
- Distributed embeddings
- Multi-graph federation
## Next Steps
- **Graph Model Details**: [Graph Model](graph-model.md)
- **Storage Deep Dive**: [Storage Architecture](storage-architecture.md)
- **ADRs**: [Architectural Decisions](adrs/001-nickel-vs-toml.md)
- **Implementation**: [Development Guide](../contributing/development.md)

View File

@ -0,0 +1 @@
# Storage Architecture

38
docs/book.toml Normal file
View File

@ -0,0 +1,38 @@
[book]
title = "KOGRAL Documentation"
authors = ["KOGRAL Team"]
description = "Complete documentation for KOGRAL - Git-native knowledge graphs for developer teams"
language = "en"
multilingual = false
src = "."
[build]
build-dir = "book"
create-missing = true
[output.html]
default-theme = "rust"
preferred-dark-theme = "navy"
git-repository-url = "https://github.com/your-org/knowledge-base"
edit-url-template = "https://github.com/your-org/knowledge-base/edit/main/docs/{path}"
[output.html.print]
enable = true
[output.html.fold]
enable = true
level = 1
[output.html.search]
enable = true
limit-results = 30
teaser-word-count = 30
use-boolean-and = true
boost-title = 2
boost-hierarchy = 1
boost-paragraph = 1
expand = true
heading-split-level = 3
[preprocessor.links]
[preprocessor.index]

1
docs/book/.nojekyll Normal file
View File

@ -0,0 +1 @@
This file makes sure that Github Pages doesn't process mdBook's output.

214
docs/book/404.html Normal file
View File

@ -0,0 +1,214 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Page not found - KOGRAL Documentation</title>
<base href="/">
<!-- 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="document-not-found-404"><a class="header" href="#document-not-found-404">Document not found (404)</a></h1>
<p>This URL is invalid, sorry. Please use the navigation bar or search to continue.</p>
</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 -->
</div>
</body>
</html>

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 434 KiB

Binary file not shown.

Binary file not shown.

Binary file not shown.

227
docs/book/ai/claude.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Claude Integration - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./ai/claude.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="claude-integration"><a class="header" href="#claude-integration">Claude Integration</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ai/openai.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/ollama.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../ai/openai.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/ollama.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Embeddings Overview - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./ai/embeddings-overview.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="embeddings-overview"><a class="header" href="#embeddings-overview">Embeddings Overview</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../storage/sync.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/providers.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../storage/sync.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/providers.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

227
docs/book/ai/fastembed.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>FastEmbed Local - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./ai/fastembed.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="fastembed-local"><a class="header" href="#fastembed-local">FastEmbed Local</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ai/providers.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/openai.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../ai/providers.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/openai.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

227
docs/book/ai/ollama.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Ollama Integration - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./ai/ollama.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="ollama-integration"><a class="header" href="#ollama-integration">Ollama Integration</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ai/claude.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/semantic-search.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../ai/claude.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/semantic-search.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

227
docs/book/ai/openai.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>OpenAI Integration - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./ai/openai.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="openai-integration"><a class="header" href="#openai-integration">OpenAI Integration</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ai/fastembed.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/claude.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../ai/fastembed.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/claude.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

227
docs/book/ai/providers.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Provider Configuration - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./ai/providers.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="provider-configuration"><a class="header" href="#provider-configuration">Provider Configuration</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ai/embeddings-overview.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/fastembed.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../ai/embeddings-overview.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../ai/fastembed.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Semantic Search - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./ai/semantic-search.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="semantic-search"><a class="header" href="#semantic-search">Semantic Search</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../ai/ollama.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../templates/overview.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../ai/ollama.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../templates/overview.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>MCP Protocol - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./api/mcp-protocol.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="mcp-protocol"><a class="header" href="#mcp-protocol">MCP Protocol</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../apps/git.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../api/mcp-tools.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../apps/git.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../api/mcp-tools.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Resources - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./api/mcp-resources.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="resources"><a class="header" href="#resources">Resources</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../api/mcp-tools.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../api/rust-api.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../api/mcp-tools.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../api/rust-api.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,942 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Tools Reference - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./api/mcp-tools.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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]--&gt; 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&lt;T&gt; 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="../kb/core-concepts.html#logseq-content-blocks">Logseq Blocks Support</a></li>
<li><a href="../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"><a class="header" href="#error-handling">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"><a class="header" href="#best-practices">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"><a class="header" href="#see-also">See Also</a></h2>
<ul>
<li><a href="https://modelcontextprotocol.io/docs">MCP Specification</a></li>
<li><a href="../user-guide/quickstart.html">Quick Start Guide</a></li>
<li><a href="../user-guide/configuration.html">Configuration Reference</a></li>
<li><a href="../../crates/kb-mcp/src/">kb-mcp Source Code</a></li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../api/mcp-protocol.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../api/mcp-resources.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../api/mcp-protocol.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../api/mcp-resources.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

227
docs/book/api/rust-api.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Rust API - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./api/rust-api.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="rust-api"><a class="header" href="#rust-api">Rust API</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../api/mcp-resources.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../contributing/development.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../api/mcp-resources.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../contributing/development.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Claude Code Integration - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./apps/claude-code.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="claude-code-integration"><a class="header" href="#claude-code-integration">Claude Code Integration</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../apps/mcp-quickguide.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../apps/logseq.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../apps/mcp-quickguide.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../apps/logseq.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

227
docs/book/apps/git.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Git Workflows - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./apps/git.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="git-workflows"><a class="header" href="#git-workflows">Git Workflows</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../apps/obsidian.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../api/mcp-protocol.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../apps/obsidian.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../api/mcp-protocol.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

227
docs/book/apps/logseq.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Logseq Integration - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./apps/logseq.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="logseq-integration"><a class="header" href="#logseq-integration">Logseq Integration</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../apps/claude-code.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../apps/obsidian.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../apps/claude-code.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../apps/obsidian.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,561 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>MCP Quick Guide - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./apps/mcp-quickguide.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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"><a class="header" href="#common-workflows">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]--&gt; 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"><a class="header" href="#tips-and-tricks">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"><a class="header" href="#troubleshooting">Troubleshooting</a></h2>
<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"># 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"><a class="header" href="#kb-directory-not-found">"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"><a class="header" href="#next-steps">Next Steps</a></h2>
<ul>
<li><strong>Read</strong>: <a href="../api/mcp-tools.html">MCP Tools API Reference</a> for all available tools</li>
<li><strong>Explore</strong>: <a href="../guides/use-cases.html">Use Cases</a> for more examples</li>
<li><strong>Configure</strong>: <a href="../config/overview.html">Configuration Reference</a> to customize behavior</li>
<li><strong>Integrate</strong>: <a href="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>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../cli/nushell-scripts.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../apps/claude-code.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../cli/nushell-scripts.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../apps/claude-code.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Obsidian Compatibility - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./apps/obsidian.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="obsidian-compatibility"><a class="header" href="#obsidian-compatibility">Obsidian Compatibility</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../apps/logseq.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../apps/git.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../apps/logseq.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../apps/git.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,508 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>ADR-001: Nickel vs TOML for Configuration - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/adrs/001-nickel-vs-toml.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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" &amp; { /* 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&lt;P: AsRef&lt;Path&gt;&gt;(path: P) -&gt; Result&lt;KbConfig&gt; {
// Export Nickel to JSON
let json = export_nickel_to_json(path)?;
// Deserialize to Rust struct
let config: KbConfig = serde_json::from_str(&amp;json)?;
Ok(config)
}
fn export_nickel_to_json&lt;P: AsRef&lt;Path&gt;&gt;(path: P) -&gt; Result&lt;String&gt; {
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" &amp; { 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 &gt; /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="../../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="002-fastembed-ai-providers.html">ADR-002: FastEmbed via AI Providers</a></p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../architecture/logseq-blocks-design.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/002-fastembed-ai-providers.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../architecture/logseq-blocks-design.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/002-fastembed-ai-providers.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,566 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>ADR-002: FastEmbed via AI Providers - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/adrs/002-fastembed-ai-providers.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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"><a class="header" href="#context">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&lt;Vec&lt;f32&gt;&gt; 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&lt;Vec&lt;f32&gt;&gt; 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"><a class="header" href="#decision">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(&amp;self, texts: Vec&lt;String&gt;) -&gt; Result&lt;Vec&lt;Vec&lt;f32&gt;&gt;&gt;;
fn dimensions(&amp;self) -&gt; usize;
fn model_name(&amp;self) -&gt; &amp;str;
}
// Local implementation
pub struct FastEmbedProvider {
model: TextEmbedding,
}
impl FastEmbedProvider {
pub fn new(model_name: &amp;str) -&gt; Result&lt;Self&gt; {
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(&amp;self, texts: Vec&lt;String&gt;) -&gt; Result&lt;Vec&lt;Vec&lt;f32&gt;&gt;&gt; {
Ok(self.model.embed(texts, None)?)
}
fn dimensions(&amp;self) -&gt; usize { 384 }
fn model_name(&amp;self) -&gt; &amp;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(&amp;self, texts: Vec&lt;String&gt;) -&gt; Result&lt;Vec&lt;Vec&lt;f32&gt;&gt;&gt; {
let embeddings = self.client
.embeddings(&amp;self.model)
.embed_documents(texts)
.await?;
Ok(embeddings)
}
fn dimensions(&amp;self) -&gt; usize { self.dimensions }
fn model_name(&amp;self) -&gt; &amp;str { &amp;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: &amp;EmbeddingConfig) -&gt; Result&lt;Box&lt;dyn EmbeddingProvider&gt;&gt; {
match config.provider {
EmbeddingProviderType::FastEmbed =&gt; {
Ok(Box::new(FastEmbedProvider::new(&amp;config.model)?))
}
EmbeddingProviderType::OpenAI =&gt; {
let api_key = std::env::var(&amp;config.api_key_env)?;
Ok(Box::new(RigEmbeddingProvider::new_openai(api_key, &amp;config.model)?))
}
EmbeddingProviderType::Claude =&gt; {
let api_key = std::env::var(&amp;config.api_key_env)?;
Ok(Box::new(RigEmbeddingProvider::new_claude(api_key, &amp;config.model)?))
}
EmbeddingProviderType::Ollama =&gt; {
Ok(Box::new(RigEmbeddingProvider::new_ollama(&amp;config.model)?))
}
}
}
<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>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"><a class="header" href="#negative">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"><a class="header" href="#neutral">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"><a class="header" href="#implementation-timeline">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"><a class="header" href="#monitoring">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"><a class="header" href="#references">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"><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>Previous ADR</strong>: <a href="001-nickel-vs-toml.html">ADR-001: Nickel vs TOML</a>
<strong>Next ADR</strong>: <a href="003-hybrid-storage.html">ADR-003: Hybrid Storage Strategy</a></p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../architecture/adrs/001-nickel-vs-toml.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/003-hybrid-storage.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../architecture/adrs/001-nickel-vs-toml.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/003-hybrid-storage.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,629 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>ADR-003: Hybrid Storage Strategy - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/adrs/003-hybrid-storage.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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"><a class="header" href="#context">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 &lt; 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"><a class="header" href="#decision">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"><a class="header" href="#consequences">Consequences</a></h2>
<h3 id="positive"><a class="header" href="#positive">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"><a class="header" href="#negative">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"><a class="header" href="#neutral">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(&amp;self, graph: &amp;Graph) -&gt; Result&lt;()&gt;;
async fn load_graph(&amp;self, name: &amp;str) -&gt; Result&lt;Graph&gt;;
async fn list_graphs(&amp;self) -&gt; Result&lt;Vec&lt;String&gt;&gt;;
}
// 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"><a class="header" href="#use-cases">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"><a class="header" href="#alternatives-considered">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"><a class="header" href="#migration-path">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"><a class="header" href="#monitoring">Monitoring</a></h2>
<p><strong>Success Criteria</strong>:</p>
<ul>
<li>Developers don't notice hybrid complexity</li>
<li>Sync completes &lt; 1 second for typical changes</li>
<li>Shared guidelines accessible in &lt; 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 &lt; 0.1%)</li>
<li>User satisfaction</li>
</ul>
<hr />
<h2 id="references"><a class="header" href="#references">References</a></h2>
<ul>
<li><a href="https://surrealdb.com/docs">SurrealDB Documentation</a></li>
<li><a href="../../crates/kb-core/src/storage/mod.rs">Storage Trait Implementation</a></li>
<li><a href="../../crates/kb-core/src/storage/filesystem.rs">FilesystemStorage</a></li>
<li><a href="../../crates/kb-core/src/storage/surrealdb.rs">SurrealDbStorage</a></li>
<li><a href="../../scripts/kb-sync.nu">Sync Mechanism</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>Previous ADR</strong>: <a href="002-fastembed-ai-providers.html">ADR-002: FastEmbed via AI Providers</a>
<strong>Next ADR</strong>: <a href="004-logseq-compatibility.html">ADR-004: Logseq Compatibility</a></p>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../architecture/adrs/002-fastembed-ai-providers.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/004-logseq-blocks-support.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../architecture/adrs/002-fastembed-ai-providers.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/004-logseq-blocks-support.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,391 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>ADR-004: Logseq Blocks Support - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/adrs/004-logseq-blocks-support.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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"><a class="header" href="#context">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"><a class="header" href="#decision">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&lt;Block&gt;, // Nested blocks
// ... timestamps ...
}
pub struct BlockProperties {
pub tags: Vec&lt;String&gt;, // #card, #important
pub status: Option&lt;TaskStatus&gt;, // TODO, DONE, etc.
pub custom: HashMap&lt;String, String&gt;, // property:: value
pub block_refs: Vec&lt;String&gt;, // ((uuid))
pub page_refs: Vec&lt;String&gt;, // [[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&lt;Vec&lt;Block&gt;&gt;, // 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&lt;Block&gt;</code> (lazy, on-demand)</li>
<li><strong>Serialize</strong>: <code>Vec&lt;Block&gt;</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"><a class="header" href="#consequences">Consequences</a></h2>
<h3 id="positive"><a class="header" href="#positive">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"><a class="header" href="#negative">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"><a class="header" href="#neutral">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"><a class="header" href="#alternatives-considered">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&lt;String, Value&gt;</code>.</p>
<p><strong>Rejected</strong>: Not type-safe, harder to query, no schema validation.</p>
<h2 id="references"><a class="header" href="#references">References</a></h2>
<ul>
<li><a href="https://docs.logseq.com/#/page/blocks">Logseq Block Format</a></li>
<li><a href="../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>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../architecture/adrs/003-hybrid-storage.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/005-mcp-protocol.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../architecture/adrs/003-hybrid-storage.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../architecture/adrs/005-mcp-protocol.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>ADR-005: MCP Protocol for AI Integration - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/adrs/005-mcp-protocol.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../../architecture/adrs/004-logseq-blocks-support.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../setup/prerequisites.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../../architecture/adrs/004-logseq-blocks-support.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../../setup/prerequisites.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,660 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Config-Driven Design - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/config-driven.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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="../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 =&gt; /* ... */,
# Compose three layers
compose_config = fun defaults mode_config user_custom =&gt;
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 &gt; .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: &amp;Path) -&gt; Result&lt;Self&gt; {
let json = std::fs::read_to_string(path)?;
let config: KbConfig = serde_json::from_str(&amp;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&lt;dyn Storage&gt; = match config.storage.primary {
StorageType::Filesystem =&gt; Box::new(FilesystemStorage::new(&amp;config)?),
StorageType::Memory =&gt; Box::new(MemoryStorage::new()),
StorageType::SurrealDb =&gt; Box::new(SurrealDbStorage::new(&amp;config).await?),
};
let embeddings: Box&lt;dyn EmbeddingProvider&gt; = match config.embeddings.provider {
EmbeddingProviderType::FastEmbed =&gt; Box::new(FastEmbedProvider::new()?),
EmbeddingProviderType::OpenAI =&gt; Box::new(RigEmbeddingProvider::openai(&amp;config)?),
EmbeddingProviderType::Claude =&gt; Box::new(RigEmbeddingProvider::claude(&amp;config)?),
EmbeddingProviderType::Ollama =&gt; Box::new(RigEmbeddingProvider::ollama(&amp;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(&amp;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 &gt; targets/kb-core.json
# → Filesystem storage, fastembed, no auto-sync
# Production
nickel export .kb-config/platform/prod.ncl &gt; targets/kb-core.json
# → SurrealDB enabled, OpenAI embeddings, auto-sync
# Testing
nickel export .kb-config/platform/test.ncl &gt; 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 &amp; {
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() -&gt; Result&lt;Self&gt; {
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(&amp;output.stdout)?;
}
if let Ok(path) = std::env::var("KB_CONFIG") {
return Self::from_file(&amp;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 &gt; .kb-config/targets/kb-dev.json
nickel export --format json .kb-config/platform/prod.ncl &gt; .kb-config/targets/kb-prod.json
nickel export --format json .kb-config/platform/test.ncl &gt; .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"><a class="header" href="#best-practices">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="../config/schema.html">Configuration Schema</a></li>
<li><strong>User Guide</strong>: <a href="../config/overview.html">Configuration Guide</a></li>
<li><strong>ADR</strong>: <a href="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>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../architecture/graph-model.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/storage-architecture.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../architecture/graph-model.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/storage-architecture.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Graph Model - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/graph-model.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="graph-model"><a class="header" href="#graph-model">Graph Model</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../architecture/overview.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/config-driven.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../architecture/overview.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/config-driven.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,667 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>System Overview - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/overview.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="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="../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&lt;String, Node&gt;, // ID → Node
pub edges: Vec&lt;Edge&gt;,
pub metadata: HashMap&lt;String, Value&gt;,
}
<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&lt;String&gt;,
pub status: NodeStatus,
pub created: DateTime&lt;Utc&gt;,
pub modified: DateTime&lt;Utc&gt;,
// ... 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&lt;Utc&gt;,
}
<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(&amp;self, graph: &amp;Graph) -&gt; Result&lt;()&gt;;
async fn load_graph(&amp;self, name: &amp;str) -&gt; Result&lt;Graph&gt;;
async fn delete_graph(&amp;self, name: &amp;str) -&gt; Result&lt;()&gt;;
async fn list_graphs(&amp;self) -&gt; Result&lt;Vec&lt;String&gt;&gt;;
}
<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(&amp;self, texts: Vec&lt;String&gt;) -&gt; Result&lt;Vec&lt;Vec&lt;f32&gt;&gt;&gt;;
fn dimensions(&amp;self) -&gt; usize;
fn model_name(&amp;self) -&gt; &amp;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(&amp;query) ||
node.content.contains(&amp;query) ||
node.tags.iter().any(|tag| tag.contains(&amp;query))
})
.collect();
<span class="boring">}</span></code></pre></pre>
<h3 id="semantic-search"><a class="header" href="#semantic-search">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&lt;_&gt; = graph.nodes.values()
.filter_map(|node| {
let node_embedding = node.embedding.as_ref()?;
let similarity = cosine_similarity(&amp;query_embedding[0], node_embedding);
(similarity &gt;= threshold).then_some((node, similarity))
})
.collect();
scored.sort_by(|a, b| b.1.partial_cmp(&amp;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(&amp;query).await?;
let shared_results = shared_graph.search(&amp;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", &amp;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> (&lt; 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> (&gt; 10,000 nodes):</p>
<ul>
<li>SurrealDB primary</li>
<li>Distributed embeddings</li>
<li>Multi-graph federation</li>
</ul>
<h2 id="next-steps"><a class="header" href="#next-steps">Next Steps</a></h2>
<ul>
<li><strong>Graph Model Details</strong>: <a href="graph-model.html">Graph Model</a></li>
<li><strong>Storage Deep Dive</strong>: <a href="storage-architecture.html">Storage Architecture</a></li>
<li><strong>ADRs</strong>: <a href="adrs/001-nickel-vs-toml.html">Architectural Decisions</a></li>
<li><strong>Implementation</strong>: <a href="../contributing/development.html">Development Guide</a></li>
</ul>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../guides/best-practices.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/graph-model.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../guides/best-practices.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/graph-model.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Storage Architecture - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./architecture/storage-architecture.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="storage-architecture"><a class="header" href="#storage-architecture">Storage Architecture</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../architecture/config-driven.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/logseq-blocks-design.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../architecture/config-driven.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../architecture/logseq-blocks-design.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,78 @@
/*
Based off of the Ayu theme
Original by Dempfi (https://github.com/dempfi/ayu)
*/
.hljs {
display: block;
overflow-x: auto;
background: #191f26;
color: #e6e1cf;
}
.hljs-comment,
.hljs-quote {
color: #5c6773;
font-style: italic;
}
.hljs-variable,
.hljs-template-variable,
.hljs-attribute,
.hljs-attr,
.hljs-regexp,
.hljs-link,
.hljs-selector-id,
.hljs-selector-class {
color: #ff7733;
}
.hljs-number,
.hljs-meta,
.hljs-builtin-name,
.hljs-literal,
.hljs-type,
.hljs-params {
color: #ffee99;
}
.hljs-string,
.hljs-bullet {
color: #b8cc52;
}
.hljs-title,
.hljs-built_in,
.hljs-section {
color: #ffb454;
}
.hljs-keyword,
.hljs-selector-tag,
.hljs-symbol {
color: #ff7733;
}
.hljs-name {
color: #36a3d9;
}
.hljs-tag {
color: #00568d;
}
.hljs-emphasis {
font-style: italic;
}
.hljs-strong {
font-weight: bold;
}
.hljs-addition {
color: #91b362;
}
.hljs-deletion {
color: #d96c75;
}

818
docs/book/book.js Normal file
View File

@ -0,0 +1,818 @@
'use strict';
/* global default_theme, default_dark_theme, default_light_theme, hljs, ClipboardJS */
// Fix back button cache problem
window.onunload = function() { };
// Global variable, shared between modules
function playground_text(playground, hidden = true) {
const code_block = playground.querySelector('code');
if (window.ace && code_block.classList.contains('editable')) {
const editor = window.ace.edit(code_block);
return editor.getValue();
} else if (hidden) {
return code_block.textContent;
} else {
return code_block.innerText;
}
}
(function codeSnippets() {
function fetch_with_timeout(url, options, timeout = 6000) {
return Promise.race([
fetch(url, options),
new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)),
]);
}
const playgrounds = Array.from(document.querySelectorAll('.playground'));
if (playgrounds.length > 0) {
fetch_with_timeout('https://play.rust-lang.org/meta/crates', {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
mode: 'cors',
})
.then(response => response.json())
.then(response => {
// get list of crates available in the rust playground
const playground_crates = response.crates.map(item => item['id']);
playgrounds.forEach(block => handle_crate_list_update(block, playground_crates));
});
}
function handle_crate_list_update(playground_block, playground_crates) {
// update the play buttons after receiving the response
update_play_button(playground_block, playground_crates);
// and install on change listener to dynamically update ACE editors
if (window.ace) {
const code_block = playground_block.querySelector('code');
if (code_block.classList.contains('editable')) {
const editor = window.ace.edit(code_block);
editor.addEventListener('change', () => {
update_play_button(playground_block, playground_crates);
});
// add Ctrl-Enter command to execute rust code
editor.commands.addCommand({
name: 'run',
bindKey: {
win: 'Ctrl-Enter',
mac: 'Ctrl-Enter',
},
exec: _editor => run_rust_code(playground_block),
});
}
}
}
// updates the visibility of play button based on `no_run` class and
// used crates vs ones available on https://play.rust-lang.org
function update_play_button(pre_block, playground_crates) {
const play_button = pre_block.querySelector('.play-button');
// skip if code is `no_run`
if (pre_block.querySelector('code').classList.contains('no_run')) {
play_button.classList.add('hidden');
return;
}
// get list of `extern crate`'s from snippet
const txt = playground_text(pre_block);
const re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g;
const snippet_crates = [];
let item;
// eslint-disable-next-line no-cond-assign
while (item = re.exec(txt)) {
snippet_crates.push(item[1]);
}
// check if all used crates are available on play.rust-lang.org
const all_available = snippet_crates.every(function(elem) {
return playground_crates.indexOf(elem) > -1;
});
if (all_available) {
play_button.classList.remove('hidden');
} else {
play_button.classList.add('hidden');
}
}
function run_rust_code(code_block) {
let result_block = code_block.querySelector('.result');
if (!result_block) {
result_block = document.createElement('code');
result_block.className = 'result hljs language-bash';
code_block.append(result_block);
}
const text = playground_text(code_block);
const classes = code_block.querySelector('code').classList;
let edition = '2015';
classes.forEach(className => {
if (className.startsWith('edition')) {
edition = className.slice(7);
}
});
const params = {
version: 'stable',
optimize: '0',
code: text,
edition: edition,
};
if (text.indexOf('#![feature') !== -1) {
params.version = 'nightly';
}
result_block.innerText = 'Running...';
fetch_with_timeout('https://play.rust-lang.org/evaluate.json', {
headers: {
'Content-Type': 'application/json',
},
method: 'POST',
mode: 'cors',
body: JSON.stringify(params),
})
.then(response => response.json())
.then(response => {
if (response.result.trim() === '') {
result_block.innerText = 'No output';
result_block.classList.add('result-no-output');
} else {
result_block.innerText = response.result;
result_block.classList.remove('result-no-output');
}
})
.catch(error => result_block.innerText = 'Playground Communication: ' + error.message);
}
// Syntax highlighting Configuration
hljs.configure({
tabReplace: ' ', // 4 spaces
languages: [], // Languages used for auto-detection
});
const code_nodes = Array
.from(document.querySelectorAll('code'))
// Don't highlight `inline code` blocks in headers.
.filter(function(node) {
return !node.parentElement.classList.contains('header');
});
if (window.ace) {
// language-rust class needs to be removed for editable
// blocks or highlightjs will capture events
code_nodes
.filter(function(node) {
return node.classList.contains('editable');
})
.forEach(function(block) {
block.classList.remove('language-rust');
});
code_nodes
.filter(function(node) {
return !node.classList.contains('editable');
})
.forEach(function(block) {
hljs.highlightBlock(block);
});
} else {
code_nodes.forEach(function(block) {
hljs.highlightBlock(block);
});
}
// Adding the hljs class gives code blocks the color css
// even if highlighting doesn't apply
code_nodes.forEach(function(block) {
block.classList.add('hljs');
});
Array.from(document.querySelectorAll('code.hljs')).forEach(function(block) {
const lines = Array.from(block.querySelectorAll('.boring'));
// If no lines were hidden, return
if (!lines.length) {
return;
}
block.classList.add('hide-boring');
const buttons = document.createElement('div');
buttons.className = 'buttons';
buttons.innerHTML = '<button class="fa fa-eye" title="Show hidden lines" \
aria-label="Show hidden lines"></button>';
// add expand button
const pre_block = block.parentNode;
pre_block.insertBefore(buttons, pre_block.firstChild);
pre_block.querySelector('.buttons').addEventListener('click', function(e) {
if (e.target.classList.contains('fa-eye')) {
e.target.classList.remove('fa-eye');
e.target.classList.add('fa-eye-slash');
e.target.title = 'Hide lines';
e.target.setAttribute('aria-label', e.target.title);
block.classList.remove('hide-boring');
} else if (e.target.classList.contains('fa-eye-slash')) {
e.target.classList.remove('fa-eye-slash');
e.target.classList.add('fa-eye');
e.target.title = 'Show hidden lines';
e.target.setAttribute('aria-label', e.target.title);
block.classList.add('hide-boring');
}
});
});
if (window.playground_copyable) {
Array.from(document.querySelectorAll('pre code')).forEach(function(block) {
const pre_block = block.parentNode;
if (!pre_block.classList.contains('playground')) {
let buttons = pre_block.querySelector('.buttons');
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
const clipButton = document.createElement('button');
clipButton.className = 'clip-button';
clipButton.title = 'Copy to clipboard';
clipButton.setAttribute('aria-label', clipButton.title);
clipButton.innerHTML = '<i class="tooltiptext"></i>';
buttons.insertBefore(clipButton, buttons.firstChild);
}
});
}
// Process playground code blocks
Array.from(document.querySelectorAll('.playground')).forEach(function(pre_block) {
// Add play button
let buttons = pre_block.querySelector('.buttons');
if (!buttons) {
buttons = document.createElement('div');
buttons.className = 'buttons';
pre_block.insertBefore(buttons, pre_block.firstChild);
}
const runCodeButton = document.createElement('button');
runCodeButton.className = 'fa fa-play play-button';
runCodeButton.hidden = true;
runCodeButton.title = 'Run this code';
runCodeButton.setAttribute('aria-label', runCodeButton.title);
buttons.insertBefore(runCodeButton, buttons.firstChild);
runCodeButton.addEventListener('click', () => {
run_rust_code(pre_block);
});
if (window.playground_copyable) {
const copyCodeClipboardButton = document.createElement('button');
copyCodeClipboardButton.className = 'clip-button';
copyCodeClipboardButton.innerHTML = '<i class="tooltiptext"></i>';
copyCodeClipboardButton.title = 'Copy to clipboard';
copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title);
buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild);
}
const code_block = pre_block.querySelector('code');
if (window.ace && code_block.classList.contains('editable')) {
const undoChangesButton = document.createElement('button');
undoChangesButton.className = 'fa fa-history reset-button';
undoChangesButton.title = 'Undo changes';
undoChangesButton.setAttribute('aria-label', undoChangesButton.title);
buttons.insertBefore(undoChangesButton, buttons.firstChild);
undoChangesButton.addEventListener('click', function() {
const editor = window.ace.edit(code_block);
editor.setValue(editor.originalCode);
editor.clearSelection();
});
}
});
})();
(function themes() {
const html = document.querySelector('html');
const themeToggleButton = document.getElementById('theme-toggle');
const themePopup = document.getElementById('theme-list');
const themeColorMetaTag = document.querySelector('meta[name="theme-color"]');
const themeIds = [];
themePopup.querySelectorAll('button.theme').forEach(function(el) {
themeIds.push(el.id);
});
const stylesheets = {
ayuHighlight: document.querySelector('#ayu-highlight-css'),
tomorrowNight: document.querySelector('#tomorrow-night-css'),
highlight: document.querySelector('#highlight-css'),
};
function showThemes() {
themePopup.style.display = 'block';
themeToggleButton.setAttribute('aria-expanded', true);
themePopup.querySelector('button#' + get_theme()).focus();
}
function updateThemeSelected() {
themePopup.querySelectorAll('.theme-selected').forEach(function(el) {
el.classList.remove('theme-selected');
});
const selected = get_saved_theme() ?? 'default_theme';
let element = themePopup.querySelector('button#' + selected);
if (element === null) {
// Fall back in case there is no "Default" item.
element = themePopup.querySelector('button#' + get_theme());
}
element.classList.add('theme-selected');
}
function hideThemes() {
themePopup.style.display = 'none';
themeToggleButton.setAttribute('aria-expanded', false);
themeToggleButton.focus();
}
function get_saved_theme() {
let theme = null;
try {
theme = localStorage.getItem('mdbook-theme');
} catch (e) {
// ignore error.
}
return theme;
}
function delete_saved_theme() {
localStorage.removeItem('mdbook-theme');
}
function get_theme() {
const theme = get_saved_theme();
if (theme === null || theme === undefined || !themeIds.includes(theme)) {
if (typeof default_dark_theme === 'undefined') {
// A customized index.hbs might not define this, so fall back to
// old behavior of determining the default on page load.
return default_theme;
}
return window.matchMedia('(prefers-color-scheme: dark)').matches
? default_dark_theme
: default_light_theme;
} else {
return theme;
}
}
let previousTheme = default_theme;
function set_theme(theme, store = true) {
let ace_theme;
if (theme === 'coal' || theme === 'navy') {
stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = false;
stylesheets.highlight.disabled = true;
ace_theme = 'ace/theme/tomorrow_night';
} else if (theme === 'ayu') {
stylesheets.ayuHighlight.disabled = false;
stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = true;
ace_theme = 'ace/theme/tomorrow_night';
} else {
stylesheets.ayuHighlight.disabled = true;
stylesheets.tomorrowNight.disabled = true;
stylesheets.highlight.disabled = false;
ace_theme = 'ace/theme/dawn';
}
setTimeout(function() {
themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor;
}, 1);
if (window.ace && window.editors) {
window.editors.forEach(function(editor) {
editor.setTheme(ace_theme);
});
}
if (store) {
try {
localStorage.setItem('mdbook-theme', theme);
} catch (e) {
// ignore error.
}
}
html.classList.remove(previousTheme);
html.classList.add(theme);
previousTheme = theme;
updateThemeSelected();
}
const query = window.matchMedia('(prefers-color-scheme: dark)');
query.onchange = function() {
set_theme(get_theme(), false);
};
// Set theme.
set_theme(get_theme(), false);
themeToggleButton.addEventListener('click', function() {
if (themePopup.style.display === 'block') {
hideThemes();
} else {
showThemes();
}
});
themePopup.addEventListener('click', function(e) {
let theme;
if (e.target.className === 'theme') {
theme = e.target.id;
} else if (e.target.parentElement.className === 'theme') {
theme = e.target.parentElement.id;
} else {
return;
}
if (theme === 'default_theme' || theme === null) {
delete_saved_theme();
set_theme(get_theme(), false);
} else {
set_theme(theme);
}
});
themePopup.addEventListener('focusout', function(e) {
// e.relatedTarget is null in Safari and Firefox on macOS (see workaround below)
if (!!e.relatedTarget &&
!themeToggleButton.contains(e.relatedTarget) &&
!themePopup.contains(e.relatedTarget)
) {
hideThemes();
}
});
// Should not be needed, but it works around an issue on macOS & iOS:
// https://github.com/rust-lang/mdBook/issues/628
document.addEventListener('click', function(e) {
if (themePopup.style.display === 'block' &&
!themeToggleButton.contains(e.target) &&
!themePopup.contains(e.target)
) {
hideThemes();
}
});
document.addEventListener('keydown', function(e) {
if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) {
return;
}
if (!themePopup.contains(e.target)) {
return;
}
let li;
switch (e.key) {
case 'Escape':
e.preventDefault();
hideThemes();
break;
case 'ArrowUp':
e.preventDefault();
li = document.activeElement.parentElement;
if (li && li.previousElementSibling) {
li.previousElementSibling.querySelector('button').focus();
}
break;
case 'ArrowDown':
e.preventDefault();
li = document.activeElement.parentElement;
if (li && li.nextElementSibling) {
li.nextElementSibling.querySelector('button').focus();
}
break;
case 'Home':
e.preventDefault();
themePopup.querySelector('li:first-child button').focus();
break;
case 'End':
e.preventDefault();
themePopup.querySelector('li:last-child button').focus();
break;
}
});
})();
(function sidebar() {
const body = document.querySelector('body');
const sidebar = document.getElementById('sidebar');
const sidebarLinks = document.querySelectorAll('#sidebar a');
const sidebarToggleButton = document.getElementById('sidebar-toggle');
const sidebarToggleAnchor = document.getElementById('sidebar-toggle-anchor');
const sidebarResizeHandle = document.getElementById('sidebar-resize-handle');
let firstContact = null;
function showSidebar() {
body.classList.remove('sidebar-hidden');
body.classList.add('sidebar-visible');
Array.from(sidebarLinks).forEach(function(link) {
link.setAttribute('tabIndex', 0);
});
sidebarToggleButton.setAttribute('aria-expanded', true);
sidebar.setAttribute('aria-hidden', false);
try {
localStorage.setItem('mdbook-sidebar', 'visible');
} catch (e) {
// Ignore error.
}
}
function hideSidebar() {
body.classList.remove('sidebar-visible');
body.classList.add('sidebar-hidden');
Array.from(sidebarLinks).forEach(function(link) {
link.setAttribute('tabIndex', -1);
});
sidebarToggleButton.setAttribute('aria-expanded', false);
sidebar.setAttribute('aria-hidden', true);
try {
localStorage.setItem('mdbook-sidebar', 'hidden');
} catch (e) {
// Ignore error.
}
}
// Toggle sidebar
sidebarToggleAnchor.addEventListener('change', function sidebarToggle() {
if (sidebarToggleAnchor.checked) {
const current_width = parseInt(
document.documentElement.style.getPropertyValue('--sidebar-target-width'), 10);
if (current_width < 150) {
document.documentElement.style.setProperty('--sidebar-target-width', '150px');
}
showSidebar();
} else {
hideSidebar();
}
});
sidebarResizeHandle.addEventListener('mousedown', initResize, false);
function initResize() {
window.addEventListener('mousemove', resize, false);
window.addEventListener('mouseup', stopResize, false);
body.classList.add('sidebar-resizing');
}
function resize(e) {
let pos = e.clientX - sidebar.offsetLeft;
if (pos < 20) {
hideSidebar();
} else {
if (body.classList.contains('sidebar-hidden')) {
showSidebar();
}
pos = Math.min(pos, window.innerWidth - 100);
document.documentElement.style.setProperty('--sidebar-target-width', pos + 'px');
}
}
//on mouseup remove windows functions mousemove & mouseup
function stopResize() {
body.classList.remove('sidebar-resizing');
window.removeEventListener('mousemove', resize, false);
window.removeEventListener('mouseup', stopResize, false);
}
document.addEventListener('touchstart', function(e) {
firstContact = {
x: e.touches[0].clientX,
time: Date.now(),
};
}, { passive: true });
document.addEventListener('touchmove', function(e) {
if (!firstContact) {
return;
}
const curX = e.touches[0].clientX;
const xDiff = curX - firstContact.x,
tDiff = Date.now() - firstContact.time;
if (tDiff < 250 && Math.abs(xDiff) >= 150) {
if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) {
showSidebar();
} else if (xDiff < 0 && curX < 300) {
hideSidebar();
}
firstContact = null;
}
}, { passive: true });
})();
(function chapterNavigation() {
document.addEventListener('keydown', function(e) {
if (e.altKey || e.ctrlKey || e.metaKey) {
return;
}
if (window.search && window.search.hasFocus()) {
return;
}
const html = document.querySelector('html');
function next() {
const nextButton = document.querySelector('.nav-chapters.next');
if (nextButton) {
window.location.href = nextButton.href;
}
}
function prev() {
const previousButton = document.querySelector('.nav-chapters.previous');
if (previousButton) {
window.location.href = previousButton.href;
}
}
function showHelp() {
const container = document.getElementById('mdbook-help-container');
const overlay = document.getElementById('mdbook-help-popup');
container.style.display = 'flex';
// Clicking outside the popup will dismiss it.
const mouseHandler = event => {
if (overlay.contains(event.target)) {
return;
}
if (event.button !== 0) {
return;
}
event.preventDefault();
event.stopPropagation();
document.removeEventListener('mousedown', mouseHandler);
hideHelp();
};
// Pressing esc will dismiss the popup.
const escapeKeyHandler = event => {
if (event.key === 'Escape') {
event.preventDefault();
event.stopPropagation();
document.removeEventListener('keydown', escapeKeyHandler, true);
hideHelp();
}
};
document.addEventListener('keydown', escapeKeyHandler, true);
document.getElementById('mdbook-help-container')
.addEventListener('mousedown', mouseHandler);
}
function hideHelp() {
document.getElementById('mdbook-help-container').style.display = 'none';
}
// Usually needs the Shift key to be pressed
switch (e.key) {
case '?':
e.preventDefault();
showHelp();
break;
}
// Rest of the keys are only active when the Shift key is not pressed
if (e.shiftKey) {
return;
}
switch (e.key) {
case 'ArrowRight':
e.preventDefault();
if (html.dir === 'rtl') {
prev();
} else {
next();
}
break;
case 'ArrowLeft':
e.preventDefault();
if (html.dir === 'rtl') {
next();
} else {
prev();
}
break;
}
});
})();
(function clipboard() {
const clipButtons = document.querySelectorAll('.clip-button');
function hideTooltip(elem) {
elem.firstChild.innerText = '';
elem.className = 'clip-button';
}
function showTooltip(elem, msg) {
elem.firstChild.innerText = msg;
elem.className = 'clip-button tooltipped';
}
const clipboardSnippets = new ClipboardJS('.clip-button', {
text: function(trigger) {
hideTooltip(trigger);
const playground = trigger.closest('pre');
return playground_text(playground, false);
},
});
Array.from(clipButtons).forEach(function(clipButton) {
clipButton.addEventListener('mouseout', function(e) {
hideTooltip(e.currentTarget);
});
});
clipboardSnippets.on('success', function(e) {
e.clearSelection();
showTooltip(e.trigger, 'Copied!');
});
clipboardSnippets.on('error', function(e) {
showTooltip(e.trigger, 'Clipboard error!');
});
})();
(function scrollToTop() {
const menuTitle = document.querySelector('.menu-title');
menuTitle.addEventListener('click', function() {
document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' });
});
})();
(function controllMenu() {
const menu = document.getElementById('menu-bar');
(function controllPosition() {
let scrollTop = document.scrollingElement.scrollTop;
let prevScrollTop = scrollTop;
const minMenuY = -menu.clientHeight - 50;
// When the script loads, the page can be at any scroll (e.g. if you reforesh it).
menu.style.top = scrollTop + 'px';
// Same as parseInt(menu.style.top.slice(0, -2), but faster
let topCache = menu.style.top.slice(0, -2);
menu.classList.remove('sticky');
let stickyCache = false; // Same as menu.classList.contains('sticky'), but faster
document.addEventListener('scroll', function() {
scrollTop = Math.max(document.scrollingElement.scrollTop, 0);
// `null` means that it doesn't need to be updated
let nextSticky = null;
let nextTop = null;
const scrollDown = scrollTop > prevScrollTop;
const menuPosAbsoluteY = topCache - scrollTop;
if (scrollDown) {
nextSticky = false;
if (menuPosAbsoluteY > 0) {
nextTop = prevScrollTop;
}
} else {
if (menuPosAbsoluteY > 0) {
nextSticky = true;
} else if (menuPosAbsoluteY < minMenuY) {
nextTop = prevScrollTop + minMenuY;
}
}
if (nextSticky === true && stickyCache === false) {
menu.classList.add('sticky');
stickyCache = true;
} else if (nextSticky === false && stickyCache === true) {
menu.classList.remove('sticky');
stickyCache = false;
}
if (nextTop !== null) {
menu.style.top = nextTop + 'px';
topCache = nextTop;
}
prevScrollTop = scrollTop;
}, { passive: true });
})();
(function controllBorder() {
function updateBorder() {
if (menu.offsetTop === 0) {
menu.classList.remove('bordered');
} else {
menu.classList.add('bordered');
}
}
updateBorder();
document.addEventListener('scroll', updateBorder, { passive: true });
})();
})();

38
docs/book/book.toml Normal file
View File

@ -0,0 +1,38 @@
[book]
title = "KOGRAL Documentation"
authors = ["KOGRAL Team"]
description = "Complete documentation for KOGRAL - Git-native knowledge graphs for developer teams"
language = "en"
multilingual = false
src = "."
[build]
build-dir = "book"
create-missing = true
[output.html]
default-theme = "rust"
preferred-dark-theme = "navy"
git-repository-url = "https://github.com/your-org/knowledge-base"
edit-url-template = "https://github.com/your-org/knowledge-base/edit/main/docs/{path}"
[output.html.print]
enable = true
[output.html.fold]
enable = true
level = 1
[output.html.search]
enable = true
limit-results = 30
teaser-word-count = 30
use-boolean-and = true
boost-title = 2
boost-hierarchy = 1
boost-paragraph = 1
expand = true
heading-split-level = 3
[preprocessor.links]
[preprocessor.index]

227
docs/book/cli/commands.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Commands Reference - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./cli/commands.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="commands-reference"><a class="header" href="#commands-reference">Commands Reference</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../cli/overview.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../cli/workflows.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../cli/overview.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../cli/workflows.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

227
docs/book/cli/overview.html Normal file
View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>CLI Overview - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./cli/overview.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="cli-overview"><a class="header" href="#cli-overview">CLI Overview</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../templates/customization.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../cli/commands.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../templates/customization.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../cli/commands.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Workflows - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./cli/workflows.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="workflows"><a class="header" href="#workflows">Workflows</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../cli/commands.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../cli/nushell-scripts.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../cli/commands.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../cli/nushell-scripts.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

7
docs/book/clipboard.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Examples - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./config/examples.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="examples"><a class="header" href="#examples">Examples</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../config/inheritance.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../storage/backends.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../config/inheritance.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../storage/backends.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

View File

@ -0,0 +1,227 @@
<!DOCTYPE HTML>
<html lang="en" class="rust sidebar-visible" dir="ltr">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>Graph Settings - KOGRAL Documentation</title>
<!-- 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>
<a href="https://github.com/your-org/knowledge-base/edit/main/docs/./config/graph-settings.md" title="Suggest an edit" aria-label="Suggest an edit">
<i id="git-edit-button" class="fa fa-edit"></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="graph-settings"><a class="header" href="#graph-settings">Graph Settings</a></h1>
</main>
<nav class="nav-wrapper" aria-label="Page navigation">
<!-- Mobile navigation buttons -->
<a rel="prev" href="../config/nickel-schemas.html" class="mobile-nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../config/inheritance.html" class="mobile-nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
<div style="clear: both"></div>
</nav>
</div>
</div>
<nav class="nav-wide-wrapper" aria-label="Page navigation">
<a rel="prev" href="../config/nickel-schemas.html" class="nav-chapters previous" title="Previous chapter" aria-label="Previous chapter" aria-keyshortcuts="Left">
<i class="fa fa-angle-left"></i>
</a>
<a rel="next prefetch" href="../config/inheritance.html" class="nav-chapters next" title="Next chapter" aria-label="Next chapter" aria-keyshortcuts="Right">
<i class="fa fa-angle-right"></i>
</a>
</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 -->
</div>
</body>
</html>

Some files were not shown because too many files have changed in this diff Show More