README
¶
touchlog
A knowledge graph note-taking system built with Go. touchlog provides a powerful CLI for managing structured notes with automatic link resolution, graph queries, and real-time indexing.
Features
- Vault-based organization: Organize notes in type-specific directories with structured frontmatter
- Automatic indexing: SQLite-based index with automatic link resolution and incremental updates
- Graph queries: Find backlinks, neighbors, and paths between notes using graph traversal
- Graph visualization: Export knowledge graphs to Graphviz DOT format
- Real-time updates: Daemon mode with filesystem watching for automatic index updates
- Structured notes: YAML frontmatter with validation and diagnostics
- Wiki-style links: Support for
[[note:key]],[[key]], and edge-type annotations - Deterministic exports: Stable, diffable JSON and DOT exports
Installation
Prerequisites
- Go 1.22 or later
Build from Source
git clone https://github.com/sv4u/touchlog.git
cd touchlog
go build ./cmd/touchlog
The binary will be created in the current directory as touchlog.
Install via Go
go install github.com/sv4u/touchlog/v2/cmd/touchlog@latest
Quick Start
-
Initialize a vault:
touchlog initThis creates a
.touchlogdirectory with a default configuration file. -
Create a note:
touchlog newFollow the interactive prompts to create a new note with frontmatter.
-
Build the index:
touchlog index rebuildThis scans all
.Rmdfiles in your vault and builds the index. -
Query your notes:
touchlog query search --type note touchlog query backlinks --target note:my-note touchlog query neighbors --root note:my-note --max-depth 2 touchlog query paths --source note:start --destination note:end --max-depth 5 -
Export the graph:
touchlog graph export dot --out graph.dot dot -Tpng graph.dot -o graph.png
Vault Structure
A touchlog vault is a directory containing:
vault/
├── .touchlog/
│ ├── config.yaml # Vault configuration
│ └── index.db # SQLite index (auto-generated)
├── note/ # Type-specific directories
│ ├── my-note.Rmd
│ └── another-note.Rmd
├── article/
│ └── my-article.Rmd
└── ...
Note Files
Notes are stored as .Rmd files with YAML frontmatter:
---
id: note-001
type: note
key: my-note
title: My Note
state: draft
tags: [important, todo]
created: 2024-01-01T00:00:00Z
updated: 2024-01-01T00:00:00Z
---
# My Note
This note links to [[note:another-note]] and [[article:my-article]].
You can also use unqualified links: [[another-note]] if the key is unique.
Configuration
The vault configuration file (.touchlog/config.yaml) defines:
- Types: Note types with validation rules (key patterns, default states)
- Tags: Tag configuration and preferred tags
- Edges: Edge type definitions for relationships
- Templates: Template configuration (future)
Example configuration:
version: 1
types:
note:
description: A note
default_state: draft
key_pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
key_max_len: 64
article:
description: An article
default_state: draft
key_pattern: ^[a-z0-9]+(-[a-z0-9]+)*$
key_max_len: 64
tags:
preferred: [important, todo, reference]
edges:
related-to:
description: General relationship
references:
description: Reference relationship
templates:
root: templates
Commands
Vault Management
touchlog init- Initialize a new vault in the current directorytouchlog new- Create a new note interactively
Index Management
touchlog index rebuild- Rebuild the entire index from scratchtouchlog index export --out <file>- Export the index to JSON
Querying
-
touchlog query search [options]- Search notes with filters--type <types>- Filter by types (comma-separated)--state <states>- Filter by states (comma-separated)--tag <tags>- Filter by tags (comma-separated)--match-any-tag- Match any tag (default: match all)--limit <n>- Limit results--format table|json- Output format
-
touchlog query backlinks --target <node> [options]- Find backlinks to a node--direction in|out|both- Link direction (default: in)--edge-type <types>- Filter by edge types--format table|json- Output format
-
touchlog query neighbors --root <node> --max-depth <n> [options]- Find neighbors--direction in|out|both- Link direction (default: both)--edge-type <types>- Filter by edge types--format table|json- Output format
-
touchlog query paths --source <node> --destination <node> [--destination <node> ...] --max-depth <n> [options]- Find paths--max-paths <n>- Maximum paths per destination (default: 10)--direction in|out|both- Link direction (default: both)--edge-type <types>- Filter by edge types--format table|json- Output format
Graph Operations
touchlog graph export dot --out <file> [options]- Export graph to DOT format--root <node> [--root <node> ...]- Root nodes (can specify multiple)--type <types>- Filter by types--state <states>- Filter by states--tag <tags>- Filter by tags--edge-type <types>- Filter by edge types--depth <n>- Maximum depth (default: 10)--force- Overwrite existing file
Daemon
touchlog daemon start- Start the daemon for real-time indexingtouchlog daemon stop- Stop the daemontouchlog daemon status- Check daemon status
Architecture
touchlog follows a contracts-first architecture with clear separation of concerns:
Core Components
- Models (
internal/model/): Canonical data structures (Note, Frontmatter, RawLink, etc.) - Config (
internal/config/): Configuration loading and validation - Note Parser (
internal/note/): Frontmatter parsing and wiki-link extraction - Store (
internal/store/): SQLite persistence layer with migrations - Index (
internal/index/): Full-scan indexing with atomic rebuilds - Query (
internal/query/): Search and graph query execution - Graph (
internal/graph/): Graph loading and export - Daemon (
internal/daemon/): IPC server and lifecycle management - Watch (
internal/watch/): Filesystem watching and incremental indexing
Design Principles
- Contracts First: Models and schemas defined before behavior
- Derived State Only: Index and exports are always rebuildable
- Determinism: All outputs are stable and diffable
- Explicit Errors: Never silently fail or coerce invalid input
- Diagnostics: Validation produces diagnostics, not hard failures
Indexing
The index is built in two passes:
- Pass 1: Parse all notes, build
(type, key) -> idmap - Pass 2: Resolve all links using the type/key map
The index is stored in SQLite with the following schema:
meta: Metadata (schema version, etc.)nodes: Note nodes with frontmatteredges: Links between notes (with resolvedto_id)tags: Tags associated with notesdiagnostics: Parse errors and warnings
Graph Queries
All graph queries (backlinks, neighbors, paths) execute in-memory after loading the relevant subgraph from SQLite. This approach:
- Avoids complex recursive SQL
- Simplifies BFS correctness and determinism
- Aligns with future TUI graph viewer needs
All traversals maintain visited sets for cycle detection and guarantee termination.
Examples
Creating and Linking Notes
# Create a note
touchlog new
# Follow prompts to create note with key "introduction"
# Create another note that links to it
touchlog new
# Create note with key "getting-started" and add link: [[note:introduction]]
# Rebuild index
touchlog index rebuild
# Find backlinks to introduction
touchlog query backlinks --target note:introduction
Finding Related Notes
# Find all neighbors within 2 hops
touchlog query neighbors --root note:my-note --max-depth 2
# Find paths between notes
touchlog query paths --source note:start --destination note:end --max-depth 5
# Export graph for visualization
touchlog graph export dot --out graph.dot
dot -Tsvg graph.dot -o graph.svg
Using the Daemon
# Start daemon for real-time indexing
touchlog daemon start
# Create/edit notes - index updates automatically
# Query works immediately
touchlog query search --type note
# Stop daemon
touchlog daemon stop
Development
Project Structure
touchlog/
├── cmd/
│ └── touchlog/
│ └── main.go # CLI entry point
├── internal/
│ ├── model/ # Core data models
│ ├── config/ # Configuration loading
│ ├── note/ # Note parsing
│ ├── store/ # SQLite persistence
│ ├── index/ # Indexing logic
│ ├── query/ # Query execution
│ ├── graph/ # Graph operations
│ ├── daemon/ # Daemon and IPC
│ ├── watch/ # Filesystem watching
│ └── cli/ # CLI command definitions
├── testdata/
│ ├── vaults/ # Test vault fixtures
│ └── golden/ # Golden test fixtures
├── go.mod
└── README.md
Running Tests
# Run all tests
go test ./...
# Run tests with coverage
go test ./... -cover
# Run specific package tests
go test ./internal/query -v
Using Makefile
# Run all test variants (test, test-race, test-coverage)
make test-full
# Run basic tests
make test
# Run tests with race detector
make test-race
# Generate coverage reports
make test-coverage
Docker Testing
You can run tests in isolated Linux and macOS environments using Docker. This ensures consistent test execution across different platforms.
Prerequisites
- Docker and Docker Compose installed
- For macOS testing: macOS host (tests run natively on macOS)
Running Tests in Docker
Using Makefile (Recommended):
# Build Docker test image
make docker-build-test
# Run all tests in Linux Docker container
make docker-test-linux
# Run specific test variants
make docker-test-linux-basic # Basic tests only
make docker-test-linux-race # Race detector tests
make docker-test-linux-coverage # Coverage reports
# Run tests natively on macOS (requires macOS host)
make docker-test-macos
# Clean Docker test resources
make docker-clean-test
Using Docker Compose:
# Run all tests
docker-compose -f docker-compose.test.yml run --rm test-linux
# Run specific test variants
docker-compose -f docker-compose.test.yml run --rm test-linux-basic
docker-compose -f docker-compose.test.yml run --rm test-linux-race
docker-compose -f docker-compose.test.yml run --rm test-linux-coverage
Using Docker directly:
# Build the test image
docker build -f Dockerfile.test -t touchlog-test:linux .
# Run tests
docker run --rm \
-v $(pwd):/app \
-v $(pwd)/coverage:/app/coverage \
-v $(pwd)/coverage.out:/app/coverage.out \
-e CGO_ENABLED=1 \
touchlog-test:linux make test-full
Coverage Reports
Coverage reports are automatically saved to the host machine:
- HTML Report:
coverage/coverage.html- Open in a web browser - XML Report:
coverage/coverage.xml- For CI/CD integration - Coverage Profile:
coverage.out- Raw coverage data
Testing on Multiple Platforms
The GitHub Actions workflow automatically runs tests on:
- Linux (Ubuntu latest)
- macOS (macOS latest and macOS 14)
WSL Testing: For WSL (Windows Subsystem for Linux) testing, you can run the Linux Docker container on a Windows machine with WSL2:
# On Windows with WSL2, run from WSL terminal
make docker-test-linux
The Linux Docker container will run in the WSL2 environment, providing a Linux-like testing environment on Windows.
Building
# Build binary
go build ./cmd/touchlog
# Build for specific platform
GOOS=linux GOARCH=amd64 go build ./cmd/touchlog
Contributing
Commit Message Guidelines
This project follows the Conventional Commits specification.
Commit Types:
feat: A new featurefix: A bug fixdocs: Documentation only changestest: Adding or updating testsrefactor: Code refactoring without changing functionalityperf: Performance improvementsci: Changes to CI/CD configurationchore: Other changes that don't modify src or test files
Examples:
feat(query): add backlinks command
fix(index): resolve link resolution bug
docs(readme): update architecture section
test(query): add edge case tests for cycles
License
See the LICENSE file for details.