
Sqlite Notes
- 19 installs
- 52 repo stars
- Updated March 4, 2026
- bfollington/terma
sqlite-notes is a Claude Code skill that uses plain SQLite as a local-first note-taking and knowledge system with provenance and epistemic-status tracking.
About
sqlite-notes is a skill that uses a single local SQLite database as a personal note-taking and knowledge-management system. A developer uses it to capture notes, resources, clippings, and reflections with provenance and epistemic-status tracking, then synthesize permanent knowledge. It provides schemas, views, and queries plus AI-generated thinking snapshots called breadcrumbs.
- Turns SQLite into a local-first personal note and thinking system
- Tracks provenance (me, llm, external, llm-assisted) and epistemic status
- Full-text search with FTS5 plus AI breadcrumbs and reflections
Sqlite Notes by the numbers
- 19 all-time installs (skills.sh)
- Ranked #2,032 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Jul 28, 2026 (Skillselion catalog sync)
sqlite-notes capabilities & compatibility
Free; local SQLite only, no API keys required.
- Capabilities
- note taking · knowledge management · full text search · sql query
- Works with
- obsidian
- Use cases
- research · database
- Pricing
- Free
What sqlite-notes says it does
Personal note-taking, thinking trails, and knowledge management using plain SQLite.
Keeps all data local in a single `.sqlite/notes.db` file
Provides full-text search with FTS5 (ranking, boolean operators, snippets)
npx skills add https://github.com/bfollington/terma --skill sqlite-notesAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 19 |
|---|---|
| repo stars | ★ 52 |
| Last updated | March 4, 2026 |
| Repository | bfollington/terma ↗ |
What it does
Capture, link, and synthesize personal notes and thinking trails in one queryable local SQLite database.
Who is it for?
A queryable, linkable personal knowledge base kept in one local .sqlite/notes.db file.
Skip if: Multi-user or cloud-synced team wikis needing a hosted backend.
When should I use this skill?
Capturing fleeting ideas, saving clippings, tracking resources, or synthesizing notes into permanent knowledge.
What you get
One queryable, linkable database with full-text search that preserves how ideas developed.
- notes.db with core tables
- FTS5 search
- AI breadcrumb and reflection snapshots
By the numbers
- 5 workflow folder stages (inbox, journal, working, permanent, archive)
- 4 epistemic statuses (fleeting, developing, supported, settled)
Files
SQLite Notes - Personal Thinking Environment
A minimalist sqlite skill for note-taking, thinking trails, and knowledge building.
This skill uses SQLite as a local-first, composable thinking environment that captures the story of how you ended up thinking the way you do, with full lineage and provenance tracking. SQL is the interface, schemas are DDL, and the database is portable.
Overview
sqlite-notes transforms SQLite into a personal knowledge system that:
- Captures notes, resources, clippings, and reflections with clear provenance
- Generates AI thinking trail snapshots (breadcrumbs) to track intellectual momentum
- Organizes notes through workflow stages (inbox, working, permanent, archive)
- Links entities naturally through a flexible links table and structural foreign keys
- Synthesizes insights from multiple sources with full lineage tracking
- Provides full-text search with FTS5 (ranking, boolean operators, snippets)
- Keeps all data local in a single
.sqlite/notes.dbfile - Works seamlessly with Claude Code for AI-assisted thinking and synthesis
Philosophy
Capture-Heavy, Not Pristine
This system prioritizes capturing the raw materials of thinking over maintaining a perfectly organized knowledge graph. Notes are allowed to be messy, incomplete, and evolving. The goal is to preserve the trail of how ideas developed, not to present a polished final product.
Story Over Structure
Rather than forcing everything into a predetermined taxonomy, this system captures the story of how you ended up thinking the way you do. Notes evolve from fleeting captures to permanent references. Breadcrumbs snapshot your thinking at a moment in time. Reflections synthesize patterns that emerge.
Clear Provenance
Every piece of content explicitly tracks its origin:
- me: You authored it
- llm: AI generated it entirely
- external: Imported from elsewhere
- llm-assisted: Collaborative creation between you and AI
This transparency ensures you always know what came from where, and can trace the lineage of any idea.
Epistemic Status
Separate from who created content is how validated it is. The epistemic field tracks confidence:
| Status | Meaning |
|---|---|
| fleeting | Uncaptured intuition, shower thought, might be nothing |
| developing | Actively thinking about, exploring, not concluded |
| supported | Has backing - evidence, reasoning, sources |
| settled | Firm belief, integrated into worldview, acting on it |
This applies equally to human and AI content. A shower thought (origin: me, epistemic: fleeting) and a Claude synthesis (origin: llm, epistemic: fleeting) both start unvalidated. Through review, either can become supported or settled.
This enables workflows like syntopic reading where Claude explores sources and generates speculative connections. Those start as fleeting, get upgraded to supported when evidence backs them, and to settled when you've reviewed and accepted them into your thinking.
AI as Thinking Partner
Breadcrumbs and reflections leverage AI to:
- Surface patterns you might miss
- Connect ideas across time
- Ask questions that push thinking forward
- Synthesize insights from scattered notes
But they preserve full context - the prompts used, the notes considered, the relationships formed - so you can verify and understand the AI's reasoning.
When to Use This Skill
Use sqlite-notes as your memory-on-disk for thinking. This skill is relevant whenever you need to:
Capture & Organize Thoughts
- Quick capture of fleeting ideas without friction
- Save interesting quotes and highlights from reading
- Track external resources (articles, papers, videos, repos)
- Organize notes through workflow stages as they mature
Track Thinking Over Time
- Generate breadcrumb snapshots of current intellectual state
- See what themes are emerging in your notes
- Identify momentum (exploring, converging, scattered, breakthrough)
- Maintain continuity between thinking sessions
Synthesize Knowledge
- Generate AI reflections from notes, breadcrumbs, and clippings
- Promote valuable reflections into permanent notes
- Build evergreen notes from accumulated insights
- Trace lineage from source material to synthesized knowledge
Link Ideas
- Connect notes through explicit relationships in the links table
- Query relationships between notes, resources, and clippings with JOINs
- Discover unexpected patterns through graph traversal queries
- Leverage SQL to find multi-hop connections
Review & Process
- Weekly review of inbox notes
- Move notes between workflow stages
- Tag and categorize during review
- Identify stale working notes
Replace Scattered Notes
Instead of maintaining notes in:
- Scattered markdown files
- Ephemeral chat messages
- Bookmarks that never get read
- Mental notes that get forgotten
Keep everything in one queryable, linkable, persistent database with full-text search.
Core Tables
notes
The atomic unit of thought. Can be anything from a fleeting idea to a permanent reference note.
Key fields: id, title, body, folder, origin, epistemic, tags (JSON array), source_url, source_title, captured_at, reviewed_at
Workflow stages (folder):
- inbox: Unsorted, recently captured
- journal: Date-bound entries
- working: Active development, exploring ideas
- permanent: Evergreen, reference-quality
- archive: Preserved but no longer active
Provenance (origin): me, llm, external, llm-assisted
Epistemic status: fleeting, developing, supported, settled
breadcrumbs
AI-generated snapshot of your thinking state at a moment in time. Analyzes recent notes to surface themes, connections, questions, and momentum. Forms a continuous trail over time through the prev_breadcrumb_id foreign key.
Key fields: id, summary, themes (JSON array), connections, questions (JSON array), momentum, window_start, window_end, notes_considered, prev_breadcrumb_id (FK to breadcrumbs)
Momentum states: exploring, converging, scattered, dormant, breakthrough
resources
External reference material (articles, books, papers, videos, repos, etc.) that you want to track and potentially extract highlights from.
Key fields: id, url, title, resource_type, status, author, domain, rating, summary, tags (JSON array), added_at, finished_at
Resource types: article, book, paper, video, podcast, repo, tool, course, thread, other
Status workflow: queued → reading → finished (or abandoned/reference)
clippings
Quote, highlight, or excerpt from a resource. Captures exact text along with location and personal annotations. Uses resource_id foreign key for structural relationship (no separate link needed).
Key fields: id, content, annotation, location, chapter, source, resource_id (FK to resources), external_id, tags (JSON array), clipped_at
Sources: readwise, kindle, manual, web, pdf, other
reflections
AI-generated synthesis of notes, breadcrumbs, and clippings. Can be weekly reviews, theme explorations, connection maps, or custom formats. Tracks full generation context and can be promoted to permanent notes via promoted_to_note_id FK.
Key fields: id, title, content, reflection_type, template_used, prompt_context, model, status, epistemic, rating, feedback, promoted_to_note_id (FK to notes), generated_at
Reflection types: weekly-review, theme-synthesis, question-exploration, connection-map, insight, custom
Status workflow: draft → reviewed → promoted (or discarded)
links
Generic relationship table for flexible many-to-many connections between any entities. This is the graph table.
Key fields: source_id, target_id, rel_type
Relationship vocabulary:
- linksTo: Conceptual connection (note → note)
- derivedFrom: Evolution/refinement (note → note)
- references: External source citation (note → resource)
- includesClipping: Embedded quote (note → clipping)
- promptedBy: AI-inspired (note → breadcrumb)
- continuesFrom: Trail continuation (breadcrumb → breadcrumb, via FK but can also be in links)
- analyzedNotes: Input for AI analysis (breadcrumb → note)
- suggestedConnections: AI recommendation (breadcrumb → note)
- mentionedIn: Reverse reference (resource → note)
- relatedTo: Conceptual similarity (resource → resource)
- usedIn: Quote usage (clipping → note)
- promptedReflection: Synthesis inspiration (clipping → reflection)
- basedOnBreadcrumbs: Synthesis input (reflection → breadcrumb)
- basedOnNotes: Synthesis input (reflection → note)
- basedOnClippings: Synthesis input (reflection → clipping)
- promotedTo: Became permanent (reflection → note, via FK but can also be in links)
- followsUp: Sequential reflection (reflection → reflection)
Setup
One-Command Setup
# Initialize database with schema and views
cd /path/to/your/notes/
./skills/sqlite-notes/scripts/setup.shThis creates .sqlite/notes.db with all tables, indexes, FTS virtual tables, triggers, and views.
Manual Setup
# Create database directory
mkdir -p .sqlite
# Apply schema
sqlite3 .sqlite/notes.db < skills/sqlite-notes/assets/schema.sql
# Apply views
sqlite3 .sqlite/notes.db < skills/sqlite-notes/assets/views.sql
# Verify
sqlite3 .sqlite/notes.db "SELECT name FROM sqlite_master WHERE type='table' ORDER BY name;"Workflow Patterns
Quick Capture Flow
Goal: Get thoughts out of your head with minimal friction.
# Capture a fleeting thought
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO notes (id, body, folder, origin, captured_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Interesting thought about distributed consensus...',
'inbox',
'me',
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
SQL
# Capture with title and tags
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO notes (id, title, body, folder, origin, tags, captured_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Meeting notes - Product sync',
'Key decisions:
- Ship v2 next month
- Focus on performance',
'journal',
'me',
json_array('meetings', 'product'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
SQL
# Capture from external source
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO notes (id, title, body, folder, origin, source_url, source_title, tags, captured_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Notes from Lamport paper',
'Key insight: [[N-a8f9B3c2]] relates to this',
'inbox',
'external',
'https://example.com/paper.pdf',
'Time, Clocks, and the Ordering of Events',
json_array('distributed-systems'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
SQLWeekly Review Flow
Goal: Process inbox, move notes to appropriate folders, add links and tags.
# 1. Check what's in inbox (use view for quick access)
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_inbox;"
# 2. Review a specific note
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM notes WHERE id = 'N-20260208-a8f9b3c2';"
# 3. Move to appropriate folder, add tags, mark as reviewed
sqlite3 .sqlite/notes.db <<'SQL'
UPDATE notes
SET folder = 'working',
tags = json_array('distributed-systems', 'consensus'),
reviewed_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE id = 'N-20260208-a8f9b3c2';
SQL
# 4. Add connections to related notes
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO links (source_id, target_id, rel_type)
VALUES ('N-20260208-a8f9b3c2', 'N-20260205-x7y8z9w0', 'linksTo');
SQL
# 5. Check for stale working notes
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_stale_working;"
# 6. Promote mature note to permanent
sqlite3 .sqlite/notes.db <<'SQL'
UPDATE notes
SET folder = 'permanent',
epistemic = 'supported',
reviewed_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE id = 'N-20260208-a8f9b3c2';
SQLBreadcrumb Generation Flow
Goal: Generate AI snapshot of current thinking state.
# 1. Query recent notes as JSON for AI analysis (last 7 days in inbox + working)
sqlite3 -json .sqlite/notes.db <<'SQL'
SELECT id, title, body, folder, tags, captured_at
FROM notes
WHERE (folder IN ('inbox', 'working'))
AND captured_at >= date('now', '-7 days')
ORDER BY captured_at DESC;
SQL
# 2. Get previous breadcrumb for continuity
sqlite3 -json .sqlite/notes.db <<'SQL'
SELECT * FROM v_latest_breadcrumb;
SQL
# 3. AI analyzes the notes and generates:
# - summary: high-level state
# - themes: emerging topics
# - connections: patterns noticed
# - questions: things to explore
# - momentum: exploring/converging/scattered/dormant/breakthrough
# 4. Create the breadcrumb
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO breadcrumbs (
id, summary, themes, connections, questions, momentum,
window_start, window_end, notes_considered, prev_breadcrumb_id, generated_at
)
VALUES (
'BC-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Exploring connections between distributed systems and product design',
json_array('distributed-systems', 'product-design', 'team-dynamics'),
'Conways law appearing in both architecture and feature discussions',
json_array('How does team structure affect API design?', 'What patterns cross domains?'),
'exploring',
strftime('%Y-%m-%dT%H:%M:%SZ', date('now', '-7 days')),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
15,
(SELECT id FROM breadcrumbs ORDER BY generated_at DESC LIMIT 1),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
SQL
# 5. Batch link breadcrumb to all analyzed notes
# This is where SQL shines — one query creates N links
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO links (source_id, target_id, rel_type)
SELECT
(SELECT id FROM breadcrumbs ORDER BY generated_at DESC LIMIT 1),
notes.id,
'analyzedNotes'
FROM notes
WHERE (folder IN ('inbox', 'working'))
AND captured_at >= date('now', '-7 days');
SQLReflection Generation and Promotion
Goal: Generate AI synthesis and promote valuable insights to permanent notes.
# 1. Generate reflection from notes and breadcrumbs
# (AI analyzes input and generates title + content)
# 2. Create the reflection
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO reflections (
id, title, content, reflection_type, model, status, epistemic, generated_at
)
VALUES (
'RF-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Weekly Review: Design Patterns',
'# Key Insights
This week showed strong convergence around distributed systems patterns...
## Main Themes
- Conway''s Law in practice
- API design as team communication
- Emergent architecture
## Questions to Explore
- How to design for team evolution?',
'weekly-review',
'claude-sonnet-4-5-20250929',
'draft',
'fleeting',
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
SQL
# 3. Batch link to source material (notes and breadcrumbs that informed it)
sqlite3 .sqlite/notes.db <<'SQL'
-- Link to recent breadcrumbs
INSERT INTO links (source_id, target_id, rel_type)
SELECT
(SELECT id FROM reflections ORDER BY generated_at DESC LIMIT 1),
bc.id,
'basedOnBreadcrumbs'
FROM breadcrumbs bc
WHERE bc.generated_at >= date('now', '-14 days')
LIMIT 3;
-- Link to notes analyzed
INSERT INTO links (source_id, target_id, rel_type)
SELECT
(SELECT id FROM reflections ORDER BY generated_at DESC LIMIT 1),
n.id,
'basedOnNotes'
FROM notes n
WHERE n.folder IN ('working', 'permanent')
AND EXISTS (
SELECT 1 FROM json_each(n.tags)
WHERE value IN ('distributed-systems', 'product-design')
);
SQL
# 4. Review and rate the reflection
sqlite3 .sqlite/notes.db <<'SQL'
UPDATE reflections
SET status = 'reviewed',
rating = 4,
feedback = 'Good synthesis, helped clarify thinking on Conway''s Law'
WHERE id = 'RF-20260208-a1b2c3d4';
SQL
# 5. Promote valuable reflection to permanent note
sqlite3 .sqlite/notes.db <<'SQL'
-- Create the permanent note
INSERT INTO notes (id, title, body, folder, origin, epistemic, tags, captured_at)
SELECT
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
title,
content,
'permanent',
'llm-assisted',
'supported',
json_array('distributed-systems', 'design-patterns'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
FROM reflections
WHERE id = 'RF-20260208-a1b2c3d4';
-- Update reflection with promoted_to_note_id and status
UPDATE reflections
SET promoted_to_note_id = (
SELECT id FROM notes WHERE title = 'Weekly Review: Design Patterns' ORDER BY captured_at DESC LIMIT 1
),
status = 'promoted'
WHERE id = 'RF-20260208-a1b2c3d4';
SQLResource and Clipping Flow
Goal: Track external resources and extract valuable highlights.
# 1. Add resource to reading queue
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO resources (id, url, title, resource_type, author, status, tags, added_at)
VALUES (
'R-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'https://example.com/article',
'How to Build Better Software',
'article',
'Jane Smith',
'queued',
json_array('software-engineering', 'process'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
SQL
# 2. Start reading
sqlite3 .sqlite/notes.db <<'SQL'
UPDATE resources
SET status = 'reading'
WHERE id = 'R-20260208-x7k9m2n5';
SQL
# 3. Capture clipping from resource
# Note: resource_id FK creates structural relationship — no separate link needed
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO clippings (id, content, annotation, location, source, resource_id, tags, clipped_at)
VALUES (
'CL-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'The best code is no code at all.',
'This resonates with minimalism in design',
'p. 42',
'manual',
'R-20260208-x7k9m2n5',
json_array('minimalism'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
SQL
# 4. Use clipping in a note
sqlite3 .sqlite/notes.db <<'SQL'
-- Create note that references the clipping
INSERT INTO notes (id, body, folder, origin, captured_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'> The best code is no code at all.
This connects to minimalism principles in architecture design.',
'working',
'me',
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
-- Link note to clipping
INSERT INTO links (source_id, target_id, rel_type)
VALUES (
(SELECT id FROM notes ORDER BY captured_at DESC LIMIT 1),
'CL-20260208-a1b2c3d4',
'includesClipping'
);
SQL
# 5. Finish resource with rating
sqlite3 .sqlite/notes.db <<'SQL'
UPDATE resources
SET status = 'finished',
rating = 4,
summary = 'Good overview of software minimalism principles',
finished_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE id = 'R-20260208-x7k9m2n5';
SQLFull-Text Search
FTS5 provides powerful full-text search capabilities that memhub doesn't have.
# Basic full-text search on notes
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT n.id, n.title, snippet(notes_fts, 1, '**', '**', '...', 20) AS snippet
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'distributed AND systems'
ORDER BY rank;
SQL
# Boolean operators
sqlite3 .sqlite/notes.db <<'SQL'
-- AND: both terms must appear
SELECT id, title FROM notes WHERE rowid IN (
SELECT rowid FROM notes_fts WHERE notes_fts MATCH 'distributed AND consensus'
);
-- OR: either term
SELECT id, title FROM notes WHERE rowid IN (
SELECT rowid FROM notes_fts WHERE notes_fts MATCH 'distributed OR consensus'
);
-- NOT: exclude term
SELECT id, title FROM notes WHERE rowid IN (
SELECT rowid FROM notes_fts WHERE notes_fts MATCH 'distributed NOT systems'
);
-- Phrase search
SELECT id, title FROM notes WHERE rowid IN (
SELECT rowid FROM notes_fts WHERE notes_fts MATCH '"distributed systems"'
);
SQL
# Search with ranking and snippets
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT
n.id,
n.title,
snippet(notes_fts, 1, '>>>', '<<<', '...', 30) AS match_context,
bm25(notes_fts) AS relevance_score
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'consensus'
ORDER BY bm25(notes_fts)
LIMIT 10;
SQL
# Search clippings
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT
c.id,
c.content,
snippet(clippings_fts, 0, '**', '**', '...', 40) AS snippet
FROM clippings_fts
JOIN clippings c ON clippings_fts.rowid = c.rowid
WHERE clippings_fts MATCH 'minimalism'
ORDER BY rank;
SQL
# Search reflections
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT
r.id,
r.title,
snippet(reflections_fts, 1, '**', '**', '...', 50) AS snippet
FROM reflections_fts
JOIN reflections r ON reflections_fts.rowid = r.rowid
WHERE reflections_fts MATCH 'design patterns'
ORDER BY rank;
SQLViews (Saved Queries)
Views are pre-defined queries that you can reference like tables. They simplify common access patterns.
# Use views for common queries
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_inbox;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_working;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_evergreen;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_stale_working;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_reading_queue;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_currently_reading;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_latest_breadcrumb;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_draft_reflections;"
# Analytics views
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_tag_cloud LIMIT 20;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_monthly_activity;"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_resource_stats;"
# Graph views
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_note_graph WHERE center_id = 'N-20260208-a1b2';"
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_note_richness LIMIT 10;"
# Views compose — you can join them or filter them
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT i.*, (SELECT COUNT(*) FROM links WHERE source_id = i.id) AS link_count
FROM v_inbox i
WHERE EXISTS (SELECT 1 FROM json_each(i.tags) WHERE value = 'distributed-systems')
ORDER BY link_count DESC;
SQLQueries That Are Better in SQL
These queries showcase SQL's advantages over a key-value store with CLI abstractions.
JOINs: Clippings from Highly-Rated Resources
# One query, leveraging FK and JOIN
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT
c.id AS clipping_id,
c.content,
c.annotation,
r.title AS resource_title,
r.rating
FROM clippings c
JOIN resources r ON c.resource_id = r.id
WHERE r.rating >= 4
ORDER BY r.rating DESC, c.clipped_at DESC;
SQLIn memhub, this would require: query all clippings → extract resource IDs → query each resource → filter by rating → combine results in jq.
Aggregations: Notes Per Folder Per Origin
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT
folder,
origin,
COUNT(*) AS note_count,
COUNT(CASE WHEN epistemic = 'settled' THEN 1 END) AS settled_count
FROM notes
GROUP BY folder, origin
ORDER BY folder, origin;
SQLMemhub can't aggregate in-database. You'd need to query all notes, then aggregate in jq or scripts.
Window Functions: Breadcrumb Momentum Trend
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT
id,
summary,
momentum,
generated_at,
LAG(momentum) OVER (ORDER BY generated_at) AS prev_momentum,
CASE
WHEN momentum = 'breakthrough' THEN '!'
WHEN momentum = LAG(momentum) OVER (ORDER BY generated_at) THEN '→'
ELSE '↗'
END AS trend
FROM breadcrumbs
ORDER BY generated_at DESC;
SQLThis shows momentum changes over time. Impossible in memhub without exporting data and processing externally.
CTEs: Full Lineage of a Promoted Reflection
sqlite3 -header -column .sqlite/notes.db <<'SQL'
WITH RECURSIVE lineage AS (
-- Start with the promoted note
SELECT id, title, 'promoted_note' AS entity_type, 0 AS depth
FROM notes
WHERE id = 'N-20260208-final'
UNION ALL
-- Traverse backwards through links
SELECT
CASE
WHEN l.source_id LIKE 'RF-%' THEN l.source_id
WHEN l.source_id LIKE 'BC-%' THEN l.source_id
WHEN l.source_id LIKE 'N-%' THEN l.source_id
END AS id,
CASE
WHEN l.source_id LIKE 'RF-%' THEN (SELECT title FROM reflections WHERE id = l.source_id)
WHEN l.source_id LIKE 'BC-%' THEN (SELECT summary FROM breadcrumbs WHERE id = l.source_id)
WHEN l.source_id LIKE 'N-%' THEN (SELECT title FROM notes WHERE id = l.source_id)
END AS title,
CASE
WHEN l.source_id LIKE 'RF-%' THEN 'reflection'
WHEN l.source_id LIKE 'BC-%' THEN 'breadcrumb'
WHEN l.source_id LIKE 'N-%' THEN 'note'
END AS entity_type,
lineage.depth + 1
FROM lineage
JOIN links l ON lineage.id = l.target_id
WHERE lineage.depth < 5
)
SELECT * FROM lineage ORDER BY depth;
SQLThis traces the entire provenance chain. Memhub has basic relationship traversal but nothing like recursive CTEs.
Cross-Entity Search: UNION ALL
# Search across all content types in one query
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT 'note' AS type, id, title, captured_at AS timestamp
FROM notes
WHERE body LIKE '%consensus%'
UNION ALL
SELECT 'clipping' AS type, id, NULL AS title, clipped_at AS timestamp
FROM clippings
WHERE content LIKE '%consensus%'
UNION ALL
SELECT 'reflection' AS type, id, title, generated_at AS timestamp
FROM reflections
WHERE content LIKE '%consensus%'
ORDER BY timestamp DESC;
SQLCommon Queries
Find Notes
# All inbox notes
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_inbox;"
# Recent captures (last 7 days)
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT id, title, folder, captured_at
FROM notes
WHERE captured_at >= date('now', '-7 days')
ORDER BY captured_at DESC;
SQL
# Notes with specific tag
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT id, title, tags
FROM notes
WHERE EXISTS (
SELECT 1 FROM json_each(tags) WHERE value = 'distributed-systems'
);
SQL
# Evergreen notes
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_evergreen;"
# LLM-generated notes
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT id, title, origin, epistemic
FROM notes
WHERE origin IN ('llm', 'llm-assisted')
ORDER BY captured_at DESC;
SQL
# Stale working notes (not reviewed in 30 days)
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_stale_working;"
# Notes by origin breakdown
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT origin, COUNT(*) AS count
FROM notes
GROUP BY origin
ORDER BY count DESC;
SQLFind Resources
# Reading queue
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_reading_queue;"
# Currently reading
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_currently_reading;"
# Finished articles
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT id, title, author, rating, finished_at
FROM resources
WHERE resource_type = 'article' AND status = 'finished'
ORDER BY finished_at DESC;
SQL
# Highly rated resources
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT id, title, resource_type, rating, finished_at
FROM resources
WHERE rating >= 4
ORDER BY rating DESC, finished_at DESC;
SQLFind Breadcrumbs and Reflections
# Latest breadcrumb
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_latest_breadcrumb;"
# Breadcrumbs showing breakthrough momentum
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT id, summary, momentum, generated_at
FROM breadcrumbs
WHERE momentum = 'breakthrough'
ORDER BY generated_at DESC;
SQL
# Draft reflections needing review
sqlite3 -header -column .sqlite/notes.db "SELECT * FROM v_draft_reflections;"
# Promoted reflections
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT id, title, reflection_type, rating, promoted_to_note_id
FROM reflections
WHERE status = 'promoted'
ORDER BY generated_at DESC;
SQLProvenance Queries
# Epistemic distribution
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT epistemic, COUNT(*) AS count
FROM notes
WHERE epistemic IS NOT NULL
GROUP BY epistemic
ORDER BY
CASE epistemic
WHEN 'fleeting' THEN 1
WHEN 'developing' THEN 2
WHEN 'supported' THEN 3
WHEN 'settled' THEN 4
END;
SQL
# Origin breakdown with epistemic cross-tab
sqlite3 -header -column .sqlite/notes.db <<'SQL'
SELECT
origin,
COUNT(*) AS total,
COUNT(CASE WHEN epistemic = 'fleeting' THEN 1 END) AS fleeting,
COUNT(CASE WHEN epistemic = 'supported' THEN 1 END) AS supported,
COUNT(CASE WHEN epistemic = 'settled' THEN 1 END) AS settled
FROM notes
GROUP BY origin
ORDER BY total DESC;
SQLAI-Assisted Workflows with Claude Code
Claude Code should always use full paths to the database and heredoc style for multi-line SQL.
Quick Note Capture
User: "Capture this thought: distributed systems are really about managing uncertainty"
Claude Code:
sqlite3 .sqlite/notes.db <<'SQL'
INSERT INTO notes (id, body, folder, origin, captured_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Distributed systems are really about managing uncertainty',
'inbox',
'me',
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
SQLGenerate Breadcrumb
User: "/breadcrumb"
Claude Code:
1. Query recent notes as JSON for analysis 2. Fetch previous breadcrumb for context 3. Analyze themes, connections, momentum 4. INSERT new breadcrumb with prev_breadcrumb_id 5. Batch INSERT...SELECT into links for analyzedNotes relationships
Synthesize Reflection
User: "Generate a weekly review reflection"
Claude Code:
1. Query notes from past week 2. Fetch recent breadcrumbs 3. Generate synthesis 4. INSERT reflection 5. Batch INSERT...SELECT into links for basedOnNotes and basedOnBreadcrumbs relationships
Link Related Notes
User: "Find notes related to distributed systems and connect them"
Claude Code:
1. Query notes with relevant tags 2. Analyze content for conceptual connections 3. INSERT INTO links with linksTo relationships 4. Report on connections made
Tips & Best Practices
1. Capture First, Organize Later: Get ideas out of your head immediately. Weekly review is when you organize.
2. Trust the Folder Workflow: inbox → working → permanent mirrors how ideas mature. Don't force notes into permanent prematurely.
3. Use Origin Consistently: Always set the correct origin (me/llm/external/llm-assisted) for provenance clarity.
4. Generate Breadcrumbs Regularly: Weekly breadcrumbs create continuity and surface patterns you might miss.
5. Review Reflections: Rate and give feedback on AI-generated reflections to improve future generations.
6. Promote Selectively: Only promote reflections that genuinely add value. Not every reflection needs to become a permanent note.
7. Archive, Don't Delete: Move old working notes to archive instead of deleting. Preserves thinking history.
8. Full Lineage Always: When creating reflections or derived notes, always link back to source material.
9. Tag During Review: Don't stress about tagging during capture. Add tags during weekly review when context is clearer.
10. Leverage FTS5: Use full-text search with boolean operators for discovery. It's faster and more powerful than LIKE.
11. Use Views, Not Memorized Queries: Views are pre-optimized and self-documenting. Reference them by name.
12. JSON Output for Agent Processing: Use sqlite3 -json when Claude Code needs to process results. It's already parsed.
13. Human-Readable for Display: Use sqlite3 -header -column for tabular output that humans read.
14. Batch Link Creation: Use INSERT...SELECT to create N links at once. SQL shines here.
15. Trust CHECK Constraints: Don't validate enums in the agent. The database enforces them. If an INSERT fails, the constraint message tells you why.
Reference Files
This skill includes:
- SKILL.md: This document (philosophy, workflows, SQL patterns)
- assets/schema.sql: Complete DDL (tables, indexes, FTS, triggers)
- assets/views.sql: 13 pre-defined views for common queries
- scripts/setup.sh: One-command idempotent database initialization
- scripts/examples.sh: Workflow demonstration with sample data
- references/queries.md: Advanced query recipes (JOINs, CTEs, window functions)
Comparison: SQLite vs Memhub
Where SQLite Wins
- Full-text search: FTS5 with ranking, boolean operators, snippets. Memhub has nothing.
- JOINs: "Clippings from resources I rated 4+" is one query. Memhub needs N separate commands + jq.
- Batch linking:
INSERT INTO links ... SELECT ...creates N links at once. Memhub needs Nmemhub linkcalls. - Aggregations: Notes per folder per month, tag clouds, resource stats — all in-database.
- Views compose: A view can reference other views, use JOINs, subqueries.
- Schema is self-documenting:
.schemashows everything. No separate YAML files. - Universal tool:
sqlite3is installed everywhere. No custom CLI to distribute.
Where SQLite Loses
- Verbosity: INSERT with 8 columns is ~10 lines vs.
memhub create Note '{...}'. - Array handling:
EXISTS (SELECT 1 FROM json_each(tags) WHERE value = 'x')vs.tags contains "x". - No visualization: No
--visualizeor--format mermaid. - Quoting hazards: SQL string escaping (
Conway''s Law) is error-prone for agents. - No YAML metadata: Field descriptions/examples live in SKILL.md, not alongside the schema.
- ID generation: Must compose inline every INSERT:
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))).
The Honest Assessment
SQLite is strictly more powerful (FTS, JOINs, aggregations), but memhub is more ergonomic for single-record CRUD and relationship traversal. The CLI abstraction reduces boilerplate. The YAML schemas are self-documenting.
If your workflows are read-heavy, query-heavy, and need search/aggregations, SQLite wins. If your workflows are write-heavy with lots of one-off record creation and simple relationship traversal, memhub's CLI is faster.
For an AI agent, both are viable. SQLite requires more care with quoting and multi-line SQL, but it rewards you with composable, powerful queries. Memhub is safer (no SQL injection risk from user input) but hits a ceiling when you need JOINs or FTS.
This skill exists to test the hypothesis: "Is memhub just a skill + a database?" The answer is nuanced. Memhub adds value through its CLI ergonomics and YAML-driven schema validation. But SQLite + a good skill can achieve the same outcomes with more power and flexibility.
---
This skill demonstrates SQL-native personal knowledge management.
For customization, modify the schema and views to fit your personal thinking and note-taking style. The goal is to capture how you think, not to force your thinking into a predefined structure.
-- ============================================================================
-- SQLite Notes Schema
-- ============================================================================
-- This is the DDL for the notes domain — the SQL equivalent of the 5 YAML
-- concept schemas in the memhub-notes skill. All tables use IF NOT EXISTS
-- for idempotent initialization.
--
-- To apply this schema:
-- sqlite3 /path/to/notes.db < schema.sql
--
-- Or interactively:
-- sqlite3 /path/to/notes.db
-- .read schema.sql
-- ============================================================================
-- Performance and integrity settings
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
-- ============================================================================
-- TABLE: notes
-- ============================================================================
-- Core note-taking entity: fleeting captures, working notes, permanent notes.
-- Uses folder-based organization and epistemic status tracking.
-- ============================================================================
CREATE TABLE IF NOT EXISTS notes (
rowid INTEGER PRIMARY KEY,
id TEXT UNIQUE NOT NULL,
title TEXT,
body TEXT NOT NULL,
folder TEXT NOT NULL DEFAULT 'inbox' CHECK (folder IN ('inbox', 'journal', 'working', 'permanent', 'archive')),
origin TEXT NOT NULL DEFAULT 'me' CHECK (origin IN ('me', 'llm', 'external', 'llm-assisted')),
epistemic TEXT DEFAULT 'fleeting' CHECK (epistemic IS NULL OR epistemic IN ('fleeting', 'developing', 'supported', 'settled')),
tags TEXT DEFAULT '[]' CHECK (json_valid(tags)),
source_url TEXT,
source_title TEXT,
captured_at TEXT NOT NULL,
reviewed_at TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_notes_folder ON notes(folder);
CREATE INDEX IF NOT EXISTS idx_notes_origin ON notes(origin);
CREATE INDEX IF NOT EXISTS idx_notes_captured_at ON notes(captured_at);
CREATE INDEX IF NOT EXISTS idx_notes_epistemic ON notes(epistemic);
-- Auto-update updated_at on UPDATE
CREATE TRIGGER IF NOT EXISTS trg_notes_updated_at
AFTER UPDATE ON notes
FOR EACH ROW
BEGIN
UPDATE notes SET updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE rowid = NEW.rowid;
END;
-- ============================================================================
-- TABLE: breadcrumbs
-- ============================================================================
-- Periodic synthesis of thinking patterns: themes, connections, momentum.
-- Each breadcrumb can reference the previous one for continuity tracking.
-- ============================================================================
CREATE TABLE IF NOT EXISTS breadcrumbs (
rowid INTEGER PRIMARY KEY,
id TEXT UNIQUE NOT NULL,
summary TEXT NOT NULL,
themes TEXT DEFAULT '[]' CHECK (json_valid(themes)),
connections TEXT,
questions TEXT DEFAULT '[]' CHECK (json_valid(questions)),
momentum TEXT CHECK (momentum IS NULL OR momentum IN ('exploring', 'converging', 'scattered', 'dormant', 'breakthrough')),
window_start TEXT NOT NULL,
window_end TEXT NOT NULL,
notes_considered INTEGER NOT NULL,
prev_breadcrumb_id TEXT REFERENCES breadcrumbs(id) ON DELETE SET NULL,
raw TEXT,
generated_at TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_breadcrumbs_generated_at ON breadcrumbs(generated_at);
-- ============================================================================
-- TABLE: resources
-- ============================================================================
-- Reading list / resource queue: articles, books, videos, papers, etc.
-- Tracks status (queued → reading → finished) and optional rating.
-- ============================================================================
CREATE TABLE IF NOT EXISTS resources (
rowid INTEGER PRIMARY KEY,
id TEXT UNIQUE NOT NULL,
url TEXT NOT NULL,
title TEXT NOT NULL,
resource_type TEXT NOT NULL CHECK (resource_type IN ('article', 'book', 'paper', 'video', 'podcast', 'repo', 'tool', 'course', 'thread', 'other')),
status TEXT NOT NULL DEFAULT 'queued' CHECK (status IN ('queued', 'reading', 'finished', 'abandoned', 'reference')),
author TEXT,
domain TEXT,
rating INTEGER CHECK (rating IS NULL OR rating BETWEEN 1 AND 5),
summary TEXT,
tags TEXT DEFAULT '[]' CHECK (json_valid(tags)),
added_at TEXT NOT NULL,
finished_at TEXT,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_resources_status ON resources(status);
CREATE INDEX IF NOT EXISTS idx_resources_type ON resources(resource_type);
-- Auto-update updated_at on UPDATE
CREATE TRIGGER IF NOT EXISTS trg_resources_updated_at
AFTER UPDATE ON resources
FOR EACH ROW
BEGIN
UPDATE resources SET updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE rowid = NEW.rowid;
END;
-- ============================================================================
-- TABLE: clippings
-- ============================================================================
-- Highlighted passages / quotes from resources.
-- FK to resources table for structural relationship.
-- ============================================================================
CREATE TABLE IF NOT EXISTS clippings (
rowid INTEGER PRIMARY KEY,
id TEXT UNIQUE NOT NULL,
content TEXT NOT NULL,
annotation TEXT,
location TEXT,
chapter TEXT,
source TEXT NOT NULL DEFAULT 'manual' CHECK (source IN ('readwise', 'kindle', 'manual', 'web', 'pdf', 'other')),
resource_id TEXT REFERENCES resources(id) ON DELETE SET NULL,
external_id TEXT,
tags TEXT DEFAULT '[]' CHECK (json_valid(tags)),
clipped_at TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_clippings_resource_id ON clippings(resource_id);
CREATE INDEX IF NOT EXISTS idx_clippings_source ON clippings(source);
-- ============================================================================
-- TABLE: reflections
-- ============================================================================
-- LLM-generated reflections: weekly reviews, theme synthesis, insights.
-- Can be promoted to a permanent note via promoted_to_note_id FK.
-- ============================================================================
CREATE TABLE IF NOT EXISTS reflections (
rowid INTEGER PRIMARY KEY,
id TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
content TEXT NOT NULL,
reflection_type TEXT NOT NULL CHECK (reflection_type IN ('weekly-review', 'theme-synthesis', 'question-exploration', 'connection-map', 'insight', 'custom')),
template_used TEXT,
prompt_context TEXT,
model TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'draft' CHECK (status IN ('draft', 'reviewed', 'promoted', 'discarded')),
epistemic TEXT DEFAULT 'fleeting' CHECK (epistemic IS NULL OR epistemic IN ('fleeting', 'developing', 'supported', 'settled')),
rating INTEGER CHECK (rating IS NULL OR rating BETWEEN 1 AND 5),
feedback TEXT,
promoted_to_note_id TEXT REFERENCES notes(id) ON DELETE SET NULL,
generated_at TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
updated_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now'))
);
CREATE INDEX IF NOT EXISTS idx_reflections_status ON reflections(status);
CREATE INDEX IF NOT EXISTS idx_reflections_type ON reflections(reflection_type);
-- Auto-update updated_at on UPDATE
CREATE TRIGGER IF NOT EXISTS trg_reflections_updated_at
AFTER UPDATE ON reflections
FOR EACH ROW
BEGIN
UPDATE reflections SET updated_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE rowid = NEW.rowid;
END;
-- ============================================================================
-- TABLE: links
-- ============================================================================
-- Generic relationship table for flexible many-to-many connections.
-- Use for graph relationships: linksTo, derivedFrom, analyzedNotes, etc.
-- Structural 1:N relationships (clipping→resource) use FK columns instead.
-- ============================================================================
CREATE TABLE IF NOT EXISTS links (
id INTEGER PRIMARY KEY AUTOINCREMENT,
source_id TEXT NOT NULL,
target_id TEXT NOT NULL,
rel_type TEXT NOT NULL,
created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%SZ', 'now')),
UNIQUE(source_id, target_id, rel_type)
);
CREATE INDEX IF NOT EXISTS idx_links_source ON links(source_id, rel_type);
CREATE INDEX IF NOT EXISTS idx_links_target ON links(target_id, rel_type);
CREATE INDEX IF NOT EXISTS idx_links_rel_type ON links(rel_type);
-- ============================================================================
-- FTS5 VIRTUAL TABLE: notes_fts
-- ============================================================================
-- Full-text search on notes: title, body, and tags.
-- Uses content='notes' to reference the base table.
-- ============================================================================
CREATE VIRTUAL TABLE IF NOT EXISTS notes_fts USING fts5(
title,
body,
tags,
content='notes',
content_rowid='rowid'
);
-- Sync trigger: INSERT
CREATE TRIGGER IF NOT EXISTS trg_notes_fts_insert
AFTER INSERT ON notes
BEGIN
INSERT INTO notes_fts(rowid, title, body, tags)
VALUES (NEW.rowid, NEW.title, NEW.body, NEW.tags);
END;
-- Sync trigger: DELETE
CREATE TRIGGER IF NOT EXISTS trg_notes_fts_delete
BEFORE DELETE ON notes
BEGIN
INSERT INTO notes_fts(notes_fts, rowid, title, body, tags)
VALUES ('delete', OLD.rowid, OLD.title, OLD.body, OLD.tags);
END;
-- Sync trigger: UPDATE (delete old, insert new)
CREATE TRIGGER IF NOT EXISTS trg_notes_fts_update_before
BEFORE UPDATE ON notes
BEGIN
INSERT INTO notes_fts(notes_fts, rowid, title, body, tags)
VALUES ('delete', OLD.rowid, OLD.title, OLD.body, OLD.tags);
END;
CREATE TRIGGER IF NOT EXISTS trg_notes_fts_update_after
AFTER UPDATE ON notes
BEGIN
INSERT INTO notes_fts(rowid, title, body, tags)
VALUES (NEW.rowid, NEW.title, NEW.body, NEW.tags);
END;
-- ============================================================================
-- FTS5 VIRTUAL TABLE: clippings_fts
-- ============================================================================
-- Full-text search on clippings: content, annotation, and tags.
-- ============================================================================
CREATE VIRTUAL TABLE IF NOT EXISTS clippings_fts USING fts5(
content,
annotation,
tags,
content='clippings',
content_rowid='rowid'
);
-- Sync trigger: INSERT
CREATE TRIGGER IF NOT EXISTS trg_clippings_fts_insert
AFTER INSERT ON clippings
BEGIN
INSERT INTO clippings_fts(rowid, content, annotation, tags)
VALUES (NEW.rowid, NEW.content, NEW.annotation, NEW.tags);
END;
-- Sync trigger: DELETE
CREATE TRIGGER IF NOT EXISTS trg_clippings_fts_delete
BEFORE DELETE ON clippings
BEGIN
INSERT INTO clippings_fts(clippings_fts, rowid, content, annotation, tags)
VALUES ('delete', OLD.rowid, OLD.content, OLD.annotation, OLD.tags);
END;
-- Sync trigger: UPDATE (delete old, insert new)
CREATE TRIGGER IF NOT EXISTS trg_clippings_fts_update_before
BEFORE UPDATE ON clippings
BEGIN
INSERT INTO clippings_fts(clippings_fts, rowid, content, annotation, tags)
VALUES ('delete', OLD.rowid, OLD.content, OLD.annotation, OLD.tags);
END;
CREATE TRIGGER IF NOT EXISTS trg_clippings_fts_update_after
AFTER UPDATE ON clippings
BEGIN
INSERT INTO clippings_fts(rowid, content, annotation, tags)
VALUES (NEW.rowid, NEW.content, NEW.annotation, NEW.tags);
END;
-- ============================================================================
-- FTS5 VIRTUAL TABLE: reflections_fts
-- ============================================================================
-- Full-text search on reflections: title and content.
-- ============================================================================
CREATE VIRTUAL TABLE IF NOT EXISTS reflections_fts USING fts5(
title,
content,
content='reflections',
content_rowid='rowid'
);
-- Sync trigger: INSERT
CREATE TRIGGER IF NOT EXISTS trg_reflections_fts_insert
AFTER INSERT ON reflections
BEGIN
INSERT INTO reflections_fts(rowid, title, content)
VALUES (NEW.rowid, NEW.title, NEW.content);
END;
-- Sync trigger: DELETE
CREATE TRIGGER IF NOT EXISTS trg_reflections_fts_delete
BEFORE DELETE ON reflections
BEGIN
INSERT INTO reflections_fts(reflections_fts, rowid, title, content)
VALUES ('delete', OLD.rowid, OLD.title, OLD.content);
END;
-- Sync trigger: UPDATE (delete old, insert new)
CREATE TRIGGER IF NOT EXISTS trg_reflections_fts_update_before
BEFORE UPDATE ON reflections
BEGIN
INSERT INTO reflections_fts(reflections_fts, rowid, title, content)
VALUES ('delete', OLD.rowid, OLD.title, OLD.content);
END;
CREATE TRIGGER IF NOT EXISTS trg_reflections_fts_update_after
AFTER UPDATE ON reflections
BEGIN
INSERT INTO reflections_fts(rowid, title, content)
VALUES (NEW.rowid, NEW.title, NEW.content);
END;
-- ============================================================================
-- END OF SCHEMA
-- ============================================================================
-- SQLite Notes Skill — Views (Saved Queries)
--
-- This file defines views for common access patterns in the sqlite-notes skill.
-- Views are essentially saved queries that can be referenced like tables.
-- All views use CREATE VIEW IF NOT EXISTS for idempotent initialization.
--
-- View categories:
-- - Workflow Views: inbox, working, evergreen, stale_working
-- - Resource Views: reading_queue, currently_reading
-- - AI Content Views: latest_breadcrumb, draft_reflections
-- - Analytics Views: tag_cloud, monthly_activity, resource_stats
-- - Graph Views: note_graph, note_richness
-- ============================================================================
-- WORKFLOW VIEWS
-- ============================================================================
-- v_inbox: Notes in inbox folder for triage and initial processing
CREATE VIEW IF NOT EXISTS v_inbox AS
SELECT
id,
title,
substr(body, 1, 120) AS preview,
origin,
tags,
captured_at
FROM notes
WHERE folder = 'inbox'
ORDER BY captured_at DESC;
-- v_working: Notes being actively developed in working folder
CREATE VIEW IF NOT EXISTS v_working AS
SELECT
id,
title,
substr(body, 1, 120) AS preview,
epistemic,
tags,
captured_at,
reviewed_at
FROM notes
WHERE folder = 'working'
ORDER BY captured_at DESC;
-- v_evergreen: Notes in permanent/evergreen folder
CREATE VIEW IF NOT EXISTS v_evergreen AS
SELECT
id,
title,
substr(body, 1, 120) AS preview,
origin,
epistemic,
tags,
captured_at
FROM notes
WHERE folder = 'permanent'
ORDER BY captured_at DESC;
-- v_stale_working: Working notes not reviewed in 30+ days (needs attention)
CREATE VIEW IF NOT EXISTS v_stale_working AS
SELECT
id,
title,
folder,
reviewed_at,
CAST(julianday('now') - julianday(reviewed_at) AS INTEGER) AS days_since_review
FROM notes
WHERE folder = 'working'
AND (reviewed_at IS NULL OR reviewed_at < date('now', '-30 days'))
ORDER BY reviewed_at ASC;
-- ============================================================================
-- RESOURCE VIEWS
-- ============================================================================
-- v_reading_queue: Resources queued for reading (backlog)
CREATE VIEW IF NOT EXISTS v_reading_queue AS
SELECT
id,
title,
resource_type,
author,
tags,
added_at
FROM resources
WHERE status = 'queued'
ORDER BY added_at ASC;
-- v_currently_reading: Resources being actively read with clip counts
CREATE VIEW IF NOT EXISTS v_currently_reading AS
SELECT
r.id,
r.title,
r.resource_type,
r.author,
(SELECT COUNT(*) FROM clippings WHERE resource_id = r.id) AS clip_count
FROM resources r
WHERE r.status = 'reading'
ORDER BY r.added_at ASC;
-- ============================================================================
-- AI CONTENT VIEWS
-- ============================================================================
-- v_latest_breadcrumb: Most recent breadcrumb for quick context
CREATE VIEW IF NOT EXISTS v_latest_breadcrumb AS
SELECT *
FROM breadcrumbs
ORDER BY generated_at DESC
LIMIT 1;
-- v_draft_reflections: AI reflections in draft state (not yet reviewed)
CREATE VIEW IF NOT EXISTS v_draft_reflections AS
SELECT
id,
title,
reflection_type,
generated_at
FROM reflections
WHERE status = 'draft'
ORDER BY generated_at DESC;
-- ============================================================================
-- ANALYTICS VIEWS
-- ============================================================================
-- v_tag_cloud: Tag usage statistics across all notes
CREATE VIEW IF NOT EXISTS v_tag_cloud AS
SELECT
value AS tag,
COUNT(*) AS count
FROM notes,
json_each(notes.tags)
WHERE tags != '[]'
GROUP BY tag
ORDER BY count DESC;
-- v_monthly_activity: Note creation counts by month and folder
CREATE VIEW IF NOT EXISTS v_monthly_activity AS
SELECT
strftime('%Y-%m', captured_at) AS month,
folder,
COUNT(*) AS note_count
FROM notes
GROUP BY month, folder
ORDER BY month DESC;
-- v_resource_stats: Resource counts and average ratings by type and status
CREATE VIEW IF NOT EXISTS v_resource_stats AS
SELECT
resource_type,
status,
COUNT(*) AS count,
AVG(rating) AS avg_rating
FROM resources
GROUP BY resource_type, status
ORDER BY resource_type, status;
-- ============================================================================
-- GRAPH VIEWS
-- ============================================================================
-- v_note_graph: Bidirectional view of note relationships (incoming + outgoing links)
-- Usage: SELECT * FROM v_note_graph WHERE center_id = 'N-20260208-a1b2';
CREATE VIEW IF NOT EXISTS v_note_graph AS
-- Outgoing links (this note links to others)
SELECT
l.source_id AS center_id,
'outgoing' AS direction,
l.rel_type,
l.target_id AS linked_id,
COALESCE(n.title, substr(n.body, 1, 60)) AS linked_title
FROM links l
LEFT JOIN notes n ON l.target_id = n.id
WHERE l.source_id LIKE 'N-%'
AND l.target_id LIKE 'N-%'
UNION ALL
-- Incoming links (other notes link to this note)
SELECT
l.target_id AS center_id,
'incoming' AS direction,
l.rel_type,
l.source_id AS linked_id,
COALESCE(n.title, substr(n.body, 1, 60)) AS linked_title
FROM links l
LEFT JOIN notes n ON l.source_id = n.id
WHERE l.source_id LIKE 'N-%'
AND l.target_id LIKE 'N-%';
-- v_note_richness: Notes ranked by their connectivity (links + tags)
CREATE VIEW IF NOT EXISTS v_note_richness AS
SELECT
n.id,
n.title,
(SELECT COUNT(*) FROM links WHERE source_id = n.id) AS outgoing_links,
(SELECT COUNT(*) FROM links WHERE target_id = n.id) AS incoming_links,
(SELECT COUNT(*) FROM json_each(n.tags)) AS tag_count,
(SELECT COUNT(*) FROM links WHERE source_id = n.id) +
(SELECT COUNT(*) FROM links WHERE target_id = n.id) AS total_links
FROM notes n
ORDER BY total_links DESC, tag_count DESC;
Advanced SQL Query Recipes
This document provides advanced SQL patterns for the sqlite-notes skill, organized by query technique. These go beyond the basic patterns shown in SKILL.md and demonstrate SQL's full power for knowledge management.
1. Full-Text Search Recipes
Basic Search Across Notes
Search notes for a single term with snippets showing match context.
SELECT
n.id,
n.title,
snippet(notes_fts, 1, '**', '**', '...', 30) AS snippet
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'consensus'
ORDER BY rank
LIMIT 10;Boolean Search (AND, OR, NOT)
Combine terms with boolean operators for precise searching.
-- Both terms must appear
SELECT n.id, n.title
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'distributed AND systems'
ORDER BY rank;
-- Either term (broader search)
SELECT n.id, n.title
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'consensus OR raft OR paxos'
ORDER BY rank;
-- Exclude term (refinement)
SELECT n.id, n.title
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'distributed NOT systems'
ORDER BY rank;Phrase Matching
Search for exact phrases using double quotes.
SELECT
n.id,
n.title,
snippet(notes_fts, 1, '>>>', '<<<', '...', 40) AS match_context
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH '"Conway''s Law"'
ORDER BY rank;Ranked Results with BM25 Scoring
Return results ranked by relevance score (lower is more relevant).
SELECT
n.id,
n.title,
bm25(notes_fts) AS relevance_score,
snippet(notes_fts, 1, '>>>', '<<<', '...', 30) AS snippet
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'distributed systems'
ORDER BY bm25(notes_fts)
LIMIT 20;Search Across All Entity Types
Search notes, clippings, and reflections in a single query using UNION ALL.
SELECT 'note' AS type, n.id, n.title, n.captured_at AS timestamp
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'consensus'
UNION ALL
SELECT 'clipping' AS type, c.id, substr(c.content, 1, 60) AS title, c.clipped_at AS timestamp
FROM clippings_fts
JOIN clippings c ON clippings_fts.rowid = c.rowid
WHERE clippings_fts MATCH 'consensus'
UNION ALL
SELECT 'reflection' AS type, r.id, r.title, r.generated_at AS timestamp
FROM reflections_fts
JOIN reflections r ON reflections_fts.rowid = r.rowid
WHERE reflections_fts MATCH 'consensus'
ORDER BY timestamp DESC;Search with Folder/Origin Filter
Combine FTS with structured filters for targeted search.
SELECT
n.id,
n.title,
n.folder,
snippet(notes_fts, 1, '**', '**', '...', 30) AS snippet
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'architecture'
AND n.folder IN ('working', 'permanent')
AND n.origin = 'me'
ORDER BY rank;2. JOIN Recipes
Clippings from Highly-Rated Resources
Find all highlights from resources you rated 4 or 5 stars.
SELECT
c.id AS clipping_id,
c.content,
c.annotation,
r.title AS resource_title,
r.author,
r.rating
FROM clippings c
JOIN resources r ON c.resource_id = r.id
WHERE r.rating >= 4
ORDER BY r.rating DESC, c.clipped_at DESC;Notes with Their Linked Resource Titles
Show notes that reference external resources with the resource metadata.
SELECT
n.id AS note_id,
n.title AS note_title,
r.title AS resource_title,
r.author,
r.url
FROM notes n
JOIN links l ON n.id = l.source_id
JOIN resources r ON l.target_id = r.id
WHERE l.rel_type = 'references'
ORDER BY n.captured_at DESC;Breadcrumbs with Analyzed Note Count and Titles
Show breadcrumbs with aggregated information about the notes they analyzed.
SELECT
bc.id AS breadcrumb_id,
bc.summary,
bc.momentum,
bc.generated_at,
COUNT(l.target_id) AS analyzed_count,
GROUP_CONCAT(n.title, ' | ') AS analyzed_titles
FROM breadcrumbs bc
LEFT JOIN links l ON bc.id = l.source_id AND l.rel_type = 'analyzedNotes'
LEFT JOIN notes n ON l.target_id = n.id
GROUP BY bc.id, bc.summary, bc.momentum, bc.generated_at
ORDER BY bc.generated_at DESC;Reflections with Source Material Lineage
Show reflections with all their source notes and breadcrumbs.
SELECT
r.id AS reflection_id,
r.title AS reflection_title,
r.status,
r.rating,
GROUP_CONCAT(DISTINCT CASE WHEN l.rel_type = 'basedOnNotes' THEN n.title END, ' | ') AS source_notes,
GROUP_CONCAT(DISTINCT CASE WHEN l.rel_type = 'basedOnBreadcrumbs' THEN bc.summary END, ' | ') AS source_breadcrumbs
FROM reflections r
LEFT JOIN links l ON r.id = l.source_id
LEFT JOIN notes n ON l.target_id = n.id AND l.rel_type = 'basedOnNotes'
LEFT JOIN breadcrumbs bc ON l.target_id = bc.id AND l.rel_type = 'basedOnBreadcrumbs'
WHERE r.status = 'promoted'
GROUP BY r.id, r.title, r.status, r.rating
ORDER BY r.generated_at DESC;Resource → Clippings → Notes Chain
Three-table join showing which notes use clippings from which resources.
SELECT
r.title AS resource_title,
r.author,
c.content AS clipping,
n.title AS note_title,
n.folder AS note_folder
FROM resources r
JOIN clippings c ON r.id = c.resource_id
JOIN links l ON c.id = l.target_id AND l.rel_type = 'includesClipping'
JOIN notes n ON l.source_id = n.id
WHERE r.rating >= 4
ORDER BY r.title, c.clipped_at;3. Aggregation Recipes
Notes Per Folder Per Month
Track note creation trends over time by folder (same as v_monthly_activity view).
SELECT
strftime('%Y-%m', captured_at) AS month,
folder,
COUNT(*) AS note_count
FROM notes
GROUP BY month, folder
ORDER BY month DESC, folder;Tag Frequency with Co-occurrence
Find which tags appear together most frequently.
-- Tag pairs that co-occur
SELECT
t1.value AS tag1,
t2.value AS tag2,
COUNT(*) AS co_occurrence_count
FROM notes n
JOIN json_each(n.tags) t1
JOIN json_each(n.tags) t2
WHERE json_valid(n.tags)
AND t1.value < t2.value -- Avoid duplicates and self-pairs
GROUP BY t1.value, t2.value
HAVING COUNT(*) >= 2
ORDER BY co_occurrence_count DESC
LIMIT 20;Epistemic Progression by Folder
Show how many notes are at each epistemic status within each folder.
SELECT
folder,
COUNT(*) AS total_notes,
COUNT(CASE WHEN epistemic = 'fleeting' THEN 1 END) AS fleeting,
COUNT(CASE WHEN epistemic = 'developing' THEN 1 END) AS developing,
COUNT(CASE WHEN epistemic = 'supported' THEN 1 END) AS supported,
COUNT(CASE WHEN epistemic = 'settled' THEN 1 END) AS settled
FROM notes
WHERE epistemic IS NOT NULL
GROUP BY folder
ORDER BY folder;Resource Completion Rate by Type
Calculate what percentage of each resource type gets finished vs abandoned.
SELECT
resource_type,
COUNT(*) AS total,
COUNT(CASE WHEN status = 'finished' THEN 1 END) AS finished,
COUNT(CASE WHEN status = 'abandoned' THEN 1 END) AS abandoned,
ROUND(100.0 * COUNT(CASE WHEN status = 'finished' THEN 1 END) / COUNT(*), 1) AS completion_rate
FROM resources
WHERE status IN ('finished', 'abandoned')
GROUP BY resource_type
ORDER BY completion_rate DESC;Average Time from Queued to Finished
Calculate how long resources spend in your reading queue before completion.
SELECT
resource_type,
COUNT(*) AS finished_count,
ROUND(AVG(julianday(finished_at) - julianday(added_at)), 1) AS avg_days_to_finish
FROM resources
WHERE status = 'finished'
AND finished_at IS NOT NULL
GROUP BY resource_type
ORDER BY avg_days_to_finish;4. Window Function Recipes
Breadcrumb Momentum Trend
Show how momentum changes over time using LAG to compare with previous breadcrumb.
SELECT
id,
summary,
momentum,
generated_at,
LAG(momentum) OVER (ORDER BY generated_at) AS prev_momentum,
CASE
WHEN momentum = 'breakthrough' THEN '! Breakthrough'
WHEN momentum = LAG(momentum) OVER (ORDER BY generated_at) THEN '→ Stable'
WHEN momentum = 'converging' AND LAG(momentum) OVER (ORDER BY generated_at) = 'exploring' THEN '↗ Focusing'
WHEN momentum = 'exploring' AND LAG(momentum) OVER (ORDER BY generated_at) = 'converging' THEN '↙ Diverging'
ELSE '↔ Changing'
END AS trend
FROM breadcrumbs
ORDER BY generated_at DESC;Running Total of Notes Captured Per Week
Calculate cumulative note capture over time.
SELECT
strftime('%Y-W%W', captured_at) AS week,
COUNT(*) AS notes_this_week,
SUM(COUNT(*)) OVER (ORDER BY strftime('%Y-W%W', captured_at)) AS total_notes
FROM notes
GROUP BY week
ORDER BY week DESC
LIMIT 20;Note Capture Velocity (Rolling 7-Day Window)
Calculate notes per day over a rolling 7-day window to see capture trends.
SELECT
date(captured_at) AS day,
COUNT(*) AS notes_today,
AVG(COUNT(*)) OVER (
ORDER BY date(captured_at)
ROWS BETWEEN 6 PRECEDING AND CURRENT ROW
) AS avg_notes_7day
FROM notes
GROUP BY day
ORDER BY day DESC
LIMIT 30;Rank Notes by Link Count Within Each Folder
Show the most connected notes in each folder using window functions.
SELECT
folder,
id,
title,
link_count,
RANK() OVER (PARTITION BY folder ORDER BY link_count DESC) AS rank_in_folder
FROM (
SELECT
n.folder,
n.id,
n.title,
(SELECT COUNT(*) FROM links WHERE source_id = n.id OR target_id = n.id) AS link_count
FROM notes n
)
WHERE rank_in_folder <= 5
ORDER BY folder, rank_in_folder;5. CTE Recipes
Full Lineage of a Promoted Reflection
Trace backwards from a promoted note through reflection, breadcrumbs, and source notes.
WITH RECURSIVE lineage AS (
-- Start with the promoted note
SELECT
id,
title AS text,
'note' AS entity_type,
captured_at AS timestamp,
0 AS depth
FROM notes
WHERE id = 'N-20260208-xyz123' -- Replace with actual note ID
UNION ALL
-- Follow links backwards
SELECT
l.source_id AS id,
COALESCE(
(SELECT title FROM notes WHERE id = l.source_id),
(SELECT title FROM reflections WHERE id = l.source_id),
(SELECT summary FROM breadcrumbs WHERE id = l.source_id)
) AS text,
CASE
WHEN l.source_id LIKE 'N-%' THEN 'note'
WHEN l.source_id LIKE 'RF-%' THEN 'reflection'
WHEN l.source_id LIKE 'BC-%' THEN 'breadcrumb'
END AS entity_type,
COALESCE(
(SELECT captured_at FROM notes WHERE id = l.source_id),
(SELECT generated_at FROM reflections WHERE id = l.source_id),
(SELECT generated_at FROM breadcrumbs WHERE id = l.source_id)
) AS timestamp,
lineage.depth + 1 AS depth
FROM lineage
JOIN links l ON lineage.id = l.target_id
WHERE lineage.depth < 10
)
SELECT * FROM lineage
ORDER BY depth, timestamp DESC;Breadcrumb Chain Traversal
Follow the breadcrumb trail backwards through prev_breadcrumb_id references.
WITH RECURSIVE breadcrumb_chain AS (
-- Start with most recent breadcrumb
SELECT
id,
summary,
momentum,
generated_at,
prev_breadcrumb_id,
1 AS position
FROM breadcrumbs
ORDER BY generated_at DESC
LIMIT 1
UNION ALL
-- Follow prev_breadcrumb_id links
SELECT
bc.id,
bc.summary,
bc.momentum,
bc.generated_at,
bc.prev_breadcrumb_id,
chain.position + 1
FROM breadcrumb_chain chain
JOIN breadcrumbs bc ON chain.prev_breadcrumb_id = bc.id
WHERE chain.position < 10
)
SELECT * FROM breadcrumb_chain
ORDER BY position;Orphan Detection
Find notes with no incoming or outgoing links.
WITH linked_notes AS (
SELECT DISTINCT source_id AS id FROM links
WHERE source_id LIKE 'N-%'
UNION
SELECT DISTINCT target_id AS id FROM links
WHERE target_id LIKE 'N-%'
)
SELECT
n.id,
n.title,
n.folder,
n.captured_at
FROM notes n
WHERE n.id NOT IN (SELECT id FROM linked_notes)
AND n.folder IN ('working', 'permanent')
ORDER BY n.captured_at DESC;Connected Components (Note Clusters)
Find clusters of interconnected notes using recursive traversal.
WITH RECURSIVE components AS (
-- Seed: pick an unvisited note
SELECT
id,
id AS component_id,
1 AS depth
FROM notes
WHERE id = 'N-20260208-abc123' -- Replace with starting note
UNION
-- Traverse links bidirectionally
SELECT
CASE
WHEN l.source_id LIKE 'N-%' AND l.source_id NOT IN (SELECT id FROM components) THEN l.source_id
WHEN l.target_id LIKE 'N-%' AND l.target_id NOT IN (SELECT id FROM components) THEN l.target_id
END AS id,
c.component_id,
c.depth + 1
FROM components c
JOIN links l ON (c.id = l.source_id OR c.id = l.target_id)
WHERE (l.source_id LIKE 'N-%' OR l.target_id LIKE 'N-%')
AND c.depth < 20
)
SELECT
n.id,
n.title,
c.component_id,
c.depth
FROM components c
JOIN notes n ON c.id = n.id
ORDER BY c.depth, n.title;6. Dashboard Queries
Overall Stats Summary
Quick snapshot of database health and activity.
SELECT
'Total Notes' AS metric,
CAST(COUNT(*) AS TEXT) AS value
FROM notes
UNION ALL
SELECT 'Inbox Count', CAST(COUNT(*) AS TEXT)
FROM notes WHERE folder = 'inbox'
UNION ALL
SELECT 'Permanent Notes', CAST(COUNT(*) AS TEXT)
FROM notes WHERE folder = 'permanent'
UNION ALL
SELECT 'Total Resources', CAST(COUNT(*) AS TEXT)
FROM resources
UNION ALL
SELECT 'Reading Queue', CAST(COUNT(*) AS TEXT)
FROM resources WHERE status = 'queued'
UNION ALL
SELECT 'Total Clippings', CAST(COUNT(*) AS TEXT)
FROM clippings
UNION ALL
SELECT 'Total Breadcrumbs', CAST(COUNT(*) AS TEXT)
FROM breadcrumbs
UNION ALL
SELECT 'Total Reflections', CAST(COUNT(*) AS TEXT)
FROM reflections
UNION ALL
SELECT 'Promoted Reflections', CAST(COUNT(*) AS TEXT)
FROM reflections WHERE status = 'promoted';Inbox Health Check
Monitor inbox size, oldest unreviewed item, and average age.
SELECT
COUNT(*) AS inbox_count,
MIN(captured_at) AS oldest_capture,
CAST(julianday('now') - julianday(MIN(captured_at)) AS INTEGER) AS oldest_age_days,
CAST(AVG(julianday('now') - julianday(captured_at)) AS INTEGER) AS avg_age_days
FROM notes
WHERE folder = 'inbox';Reading Pipeline Status
Track resources at each stage of the reading workflow.
SELECT
status,
COUNT(*) AS count,
ROUND(AVG(CASE WHEN rating IS NOT NULL THEN rating END), 1) AS avg_rating
FROM resources
GROUP BY status
ORDER BY
CASE status
WHEN 'queued' THEN 1
WHEN 'reading' THEN 2
WHEN 'finished' THEN 3
WHEN 'reference' THEN 4
WHEN 'abandoned' THEN 5
END;Knowledge Growth Metrics
Track permanent note creation and reflection promotion over time.
SELECT
strftime('%Y-%m', captured_at) AS month,
COUNT(*) AS permanent_notes_created,
(SELECT COUNT(*)
FROM reflections
WHERE status = 'promoted'
AND strftime('%Y-%m', generated_at) = strftime('%Y-%m', notes.captured_at)
) AS reflections_promoted
FROM notes
WHERE folder = 'permanent'
GROUP BY month
ORDER BY month DESC
LIMIT 12;Recent Activity Summary (Last 7 Days)
Show what happened in the past week across all entity types.
SELECT
'Notes Captured' AS activity,
COUNT(*) AS count
FROM notes
WHERE captured_at >= date('now', '-7 days')
UNION ALL
SELECT 'Resources Added', COUNT(*)
FROM resources
WHERE added_at >= date('now', '-7 days')
UNION ALL
SELECT 'Clippings Created', COUNT(*)
FROM clippings
WHERE clipped_at >= date('now', '-7 days')
UNION ALL
SELECT 'Breadcrumbs Generated', COUNT(*)
FROM breadcrumbs
WHERE generated_at >= date('now', '-7 days')
UNION ALL
SELECT 'Reflections Created', COUNT(*)
FROM reflections
WHERE generated_at >= date('now', '-7 days')
UNION ALL
SELECT 'Links Created', COUNT(*)
FROM links
WHERE created_at >= date('now', '-7 days');Epistemic Progress Tracker
Monitor how notes are progressing through epistemic stages over time.
SELECT
strftime('%Y-%m', reviewed_at) AS review_month,
COUNT(*) AS notes_reviewed,
COUNT(CASE WHEN epistemic = 'supported' THEN 1 END) AS reached_supported,
COUNT(CASE WHEN epistemic = 'settled' THEN 1 END) AS reached_settled
FROM notes
WHERE reviewed_at IS NOT NULL
AND reviewed_at >= date('now', '-6 months')
GROUP BY review_month
ORDER BY review_month DESC;---
Usage Tips
1. Test on Sample Data First: Run these queries on a test database before production to understand output format.
2. Parameterize When Needed: Replace literal IDs (like 'N-20260208-abc123') with subqueries or pass them as variables.
3. Use EXPLAIN QUERY PLAN: Prefix queries with EXPLAIN QUERY PLAN to verify index usage for performance.
4. Build from Simple to Complex: Start with simple JOINs, then add CTEs and window functions as needed.
5. Save Useful Queries as Views: If you run a query frequently, save it as a view in views.sql.
6. Combine Techniques: These recipes can be combined - use CTEs with window functions, JOINs with FTS, etc.
7. Format for Readability: Use consistent indentation and line breaks to make complex queries maintainable.
8. Comment Complex Logic: Add SQL comments (-- comment) to explain non-obvious patterns.
#!/bin/bash
# sqlite-notes example workflows
# Demonstrates the full lifecycle of notes, resources, clippings, breadcrumbs, and reflections
#
# Usage:
# ./examples.sh # Creates /tmp/demo-sqlite-notes.db
# ./examples.sh /path/to/demo.db # Creates at specified path
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
DB_PATH="${1:-/tmp/demo-sqlite-notes.db}"
echo "=========================================="
echo "sqlite-notes workflow demonstration"
echo "Database: $DB_PATH"
echo "=========================================="
echo ""
# Initialize database with schema and views
echo "--- Setting up database ---"
"$SCRIPT_DIR/setup.sh" "$DB_PATH"
echo ""
# Helper: Execute SQL with PRAGMA trusted_schema
sql() {
echo "PRAGMA trusted_schema=ON; $1" | sqlite3 "$DB_PATH"
}
# Helper: Display query with header
query() {
echo "PRAGMA trusted_schema=ON; $2" | sqlite3 -header -column "$DB_PATH"
}
echo "=========================================="
echo "WORKFLOW 1: Quick Capture"
echo "=========================================="
echo ""
echo "Capturing 5 notes across different folders..."
echo ""
# 1. Fleeting inbox thought
echo "1. Fleeting inbox thought"
sql "
INSERT INTO notes (id, body, folder, origin, epistemic, captured_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Distributed systems are fundamentally about managing partial failures and uncertainty',
'inbox',
'me',
'fleeting',
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-5 hours')
);
"
# 2. Journal entry with tags
echo "2. Journal entry with tags"
sql "
INSERT INTO notes (id, title, body, folder, origin, tags, captured_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Team meeting - architecture decisions',
'Key decisions from today''s architecture review:
- Moving to event-driven architecture for order processing
- Conway''s Law is showing up again - team structure mirrors system boundaries
- Need to explore CQRS for read/write separation',
'journal',
'me',
json_array('meetings', 'architecture', 'team'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-4 hours')
);
"
# 3. External source note with URL
echo "3. External source note with URL"
sql "
INSERT INTO notes (id, title, body, folder, origin, source_url, source_title, tags, captured_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Lamport - Time and Ordering',
'Logical clocks provide partial ordering without synchronized physical time.
Key insight: happens-before relation is transitive and defines causal dependencies.
Connection to our distributed tracing work - we need causal ordering, not wall-clock time.',
'inbox',
'external',
'https://lamport.azurewebsites.net/pubs/time-clocks.pdf',
'Time, Clocks, and the Ordering of Events in a Distributed System',
json_array('distributed-systems', 'papers', 'consensus'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-3 hours')
);
"
# 4. Working note about an idea being developed
echo "4. Working note about an idea being developed"
sql "
INSERT INTO notes (id, title, body, folder, origin, epistemic, tags, captured_at, reviewed_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'API Design as Communication Protocol',
'APIs are not just technical contracts - they are communication protocols between teams.
When teams are loosely coupled, APIs become the primary interface. The API design reveals:
- What information teams need to share
- What decisions are local vs. global
- Where coordination is required
This connects to Conway''s Law and microservices boundaries.
Questions to explore:
- How do we design APIs that minimize coordination?
- What patterns support team autonomy?',
'working',
'me',
'developing',
json_array('api-design', 'team-dynamics', 'architecture'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-2 hours'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-1 hour')
);
"
# 5. Permanent/settled note
echo "5. Permanent/settled note"
sql "
INSERT INTO notes (id, title, body, folder, origin, epistemic, tags, captured_at, reviewed_at)
VALUES (
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Conway''s Law',
'Conway''s Law: Organizations which design systems are constrained to produce designs which are copies of the communication structures of these organizations.
Implications:
- System architecture mirrors organizational structure
- Changing architecture often requires organizational change
- Team boundaries should align with system boundaries
- Microservices reflect team autonomy
Evidence:
- Observed in multiple projects across different companies
- Empirical research supports this (see MacCormack 2012)
- Reverse Conway Maneuver uses this deliberately
This is a settled principle that should inform all architecture decisions.',
'permanent',
'me',
'settled',
json_array('architecture', 'organization', 'principles'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-30 days'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-25 days')
);
"
echo ""
echo "Notes captured. Checking inbox:"
echo ""
query "Inbox view" "SELECT * FROM v_inbox LIMIT 3;"
echo ""
echo "=========================================="
echo "WORKFLOW 2: Resources & Clippings"
echo "=========================================="
echo ""
# Add two resources
echo "Adding 2 resources to reading queue..."
sql "
INSERT INTO resources (id, url, title, resource_type, author, status, tags, added_at)
VALUES
('R-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'https://martinfowler.com/articles/microservices.html',
'Microservices Guide',
'article',
'Martin Fowler',
'queued',
json_array('microservices', 'architecture'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-2 days')),
('R-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'https://www.usenix.org/system/files/conference/osdi14/osdi14-paper-ongaro.pdf',
'In Search of an Understandable Consensus Algorithm',
'paper',
'Diego Ongaro and John Ousterhout',
'queued',
json_array('consensus', 'raft', 'distributed-systems'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-1 day'));
"
echo ""
# Update first resource to 'reading'
echo "Starting to read first resource..."
RESOURCE_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM resources WHERE title LIKE '%Microservices%' LIMIT 1;")
echo "Resource ID: $RESOURCE_ID"
sql "UPDATE resources SET status = 'reading' WHERE id = '$RESOURCE_ID';"
echo ""
# Add 2 clippings from that resource
echo "Capturing 2 clippings from resource..."
sql "
INSERT INTO clippings (id, content, annotation, location, source, resource_id, tags, clipped_at)
VALUES
('CL-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'The microservice architectural style is an approach to developing a single application as a suite of small services, each running in its own process and communicating with lightweight mechanisms.',
'Key definition - emphasizes independence and lightweight communication',
'Introduction',
'manual',
'$RESOURCE_ID',
json_array('microservices', 'definition'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-30 minutes')),
('CL-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'You can have a team responsible for many microservices. But that team should be able to change each service independently, without coordinating with other teams.',
'This connects directly to Conway''s Law and team autonomy',
'Section: Organized around Business Capabilities',
'manual',
'$RESOURCE_ID',
json_array('microservices', 'team-structure'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now', '-15 minutes'));
"
echo ""
# Finish the resource with rating
echo "Finishing resource with rating..."
sql "
UPDATE resources
SET status = 'finished',
rating = 4,
summary = 'Comprehensive overview of microservices patterns and tradeoffs. Strong emphasis on organizational aspects.',
finished_at = strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
WHERE id = '$RESOURCE_ID';
"
echo ""
echo "Currently reading resources:"
query "Currently reading" "SELECT * FROM v_currently_reading;"
echo ""
echo "=========================================="
echo "WORKFLOW 3: Link Notes"
echo "=========================================="
echo ""
echo "Creating links between related notes..."
# Get some note IDs for linking
CONWAY_NOTE=$(sqlite3 "$DB_PATH" "SELECT id FROM notes WHERE title = 'Conway''s Law' LIMIT 1;")
API_NOTE=$(sqlite3 "$DB_PATH" "SELECT id FROM notes WHERE title LIKE '%API Design%' LIMIT 1;")
LAMPORT_NOTE=$(sqlite3 "$DB_PATH" "SELECT id FROM notes WHERE source_title LIKE '%Time%Clocks%' LIMIT 1;")
DISTRIBUTED_NOTE=$(sqlite3 "$DB_PATH" "SELECT id FROM notes WHERE body LIKE '%partial failures%' LIMIT 1;")
sql "
INSERT INTO links (source_id, target_id, rel_type)
VALUES
('$API_NOTE', '$CONWAY_NOTE', 'linksTo'),
('$LAMPORT_NOTE', '$DISTRIBUTED_NOTE', 'derivedFrom');
"
echo "Links created:"
echo " - API Design note links to Conway's Law"
echo " - Lamport note derived from distributed systems thought"
echo ""
echo "=========================================="
echo "WORKFLOW 4: Query Views"
echo "=========================================="
echo ""
echo "Inbox view:"
query "Inbox" "SELECT * FROM v_inbox LIMIT 3;"
echo ""
echo "Tag cloud (top 10):"
query "Tag cloud" "SELECT * FROM v_tag_cloud LIMIT 10;"
echo ""
echo "Reading queue:"
query "Reading queue" "SELECT * FROM v_reading_queue;"
echo ""
echo "Note graph for Conway's Law note:"
query "Note graph" "SELECT * FROM v_note_graph WHERE center_id = '$CONWAY_NOTE';"
echo ""
echo "=========================================="
echo "WORKFLOW 5: Generate Breadcrumb"
echo "=========================================="
echo ""
echo "Generating breadcrumb analyzing recent notes..."
# Count recent notes
NOTE_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM notes WHERE folder IN ('inbox', 'working') AND captured_at >= date('now', '-7 days');")
sql "
INSERT INTO breadcrumbs (
id, summary, themes, connections, questions, momentum,
window_start, window_end, notes_considered, prev_breadcrumb_id, generated_at
)
VALUES (
'BC-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Exploring connections between distributed systems theory and organizational design. Strong convergence around Conway''s Law as a unifying principle.',
json_array('distributed-systems', 'architecture', 'team-dynamics', 'conways-law'),
'Three themes emerging: 1) Distributed systems concepts apply to organization design, 2) API design as team communication protocol, 3) Causal ordering vs wall-clock time in both technical and organizational contexts.',
json_array('How do we design systems that support organizational evolution?', 'What other distributed systems patterns apply to teams?', 'Can we measure organizational coupling like system coupling?'),
'converging',
strftime('%Y-%m-%dT%H:%M:%SZ', date('now', '-7 days')),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now'),
$NOTE_COUNT,
NULL,
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
"
BREADCRUMB_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM breadcrumbs ORDER BY generated_at DESC LIMIT 1;")
echo "Created breadcrumb: $BREADCRUMB_ID"
echo ""
# Batch link breadcrumb to analyzed notes
echo "Batch linking breadcrumb to analyzed notes..."
sql "
INSERT INTO links (source_id, target_id, rel_type)
SELECT
'$BREADCRUMB_ID',
notes.id,
'analyzedNotes'
FROM notes
WHERE (folder IN ('inbox', 'working'))
AND captured_at >= date('now', '-7 days');
"
LINK_COUNT=$(sqlite3 "$DB_PATH" "SELECT COUNT(*) FROM links WHERE source_id = '$BREADCRUMB_ID' AND rel_type = 'analyzedNotes';")
echo "Created $LINK_COUNT analyzedNotes links"
echo ""
echo "Latest breadcrumb:"
query "Latest breadcrumb" "SELECT id, summary, momentum, themes FROM v_latest_breadcrumb;"
echo ""
echo "=========================================="
echo "WORKFLOW 6: Generate Reflection"
echo "=========================================="
echo ""
echo "Generating reflection from notes and breadcrumbs..."
sql "
INSERT INTO reflections (
id, title, content, reflection_type, model, status, epistemic, generated_at
)
VALUES (
'RF-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
'Conway''s Law as Organizing Principle',
'# Conway''s Law as Organizing Principle
This week''s notes reveal Conway''s Law emerging as a central organizing principle for both technical and organizational design.
## Key Insights
**1. APIs as Communication Protocols**
API design is not just a technical concern - it reflects and shapes how teams communicate. When we design an API, we are designing the communication protocol between teams. The API surface area reveals coordination requirements.
**2. Distributed Systems Patterns Apply to Organizations**
Concepts from distributed systems - partial failures, eventual consistency, causal ordering - apply equally to organizational design. Teams face similar challenges: asynchronous communication, independent decision-making, managing shared state.
**3. Architecture and Organization Co-evolve**
You cannot change architecture without considering organizational structure, and vice versa. The Reverse Conway Maneuver recognizes this: design the team structure to produce the architecture you want.
## Connections to Source Material
- Lamport''s work on logical clocks and causal ordering applies to team decision-making
- Microservices patterns reflect team autonomy requirements
- Event-driven architecture supports loose coupling between teams
## Questions to Explore
- Can we apply consensus algorithms to organizational decision-making?
- What is the organizational equivalent of eventual consistency?
- How do we measure and optimize for organizational coupling?
## Recommendation
This synthesis should be promoted to a permanent note. It connects multiple threads of thinking and provides a framework for future architecture and organization design decisions.',
'theme-synthesis',
'claude-sonnet-4-5-20250929',
'draft',
'developing',
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
);
"
REFLECTION_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM reflections ORDER BY generated_at DESC LIMIT 1;")
echo "Created reflection: $REFLECTION_ID"
echo ""
# Batch link to source notes
echo "Linking reflection to source notes..."
sql "
INSERT INTO links (source_id, target_id, rel_type)
VALUES
('$REFLECTION_ID', '$CONWAY_NOTE', 'basedOnNotes'),
('$REFLECTION_ID', '$API_NOTE', 'basedOnNotes'),
('$REFLECTION_ID', '$LAMPORT_NOTE', 'basedOnNotes');
"
# Link to breadcrumb
sql "
INSERT INTO links (source_id, target_id, rel_type)
VALUES ('$REFLECTION_ID', '$BREADCRUMB_ID', 'basedOnBreadcrumbs');
"
echo "Linked reflection to 3 notes and 1 breadcrumb"
echo ""
# Promote reflection to permanent note
echo "Reviewing and promoting reflection..."
sql "
UPDATE reflections
SET status = 'reviewed',
rating = 5,
epistemic = 'supported',
feedback = 'Excellent synthesis - connects multiple threads coherently and opens new questions'
WHERE id = '$REFLECTION_ID';
"
# Create permanent note from reflection
sql "
INSERT INTO notes (id, title, body, folder, origin, epistemic, tags, captured_at)
SELECT
'N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))),
title,
content,
'permanent',
'llm-assisted',
'supported',
json_array('architecture', 'organization', 'conways-law', 'synthesis'),
strftime('%Y-%m-%dT%H:%M:%SZ', 'now')
FROM reflections
WHERE id = '$REFLECTION_ID';
"
PROMOTED_NOTE_ID=$(sqlite3 "$DB_PATH" "SELECT id FROM notes WHERE title LIKE '%Conway''s Law as Organizing%' LIMIT 1;")
# Update reflection with promoted_to_note_id
sql "
UPDATE reflections
SET promoted_to_note_id = '$PROMOTED_NOTE_ID',
status = 'promoted'
WHERE id = '$REFLECTION_ID';
"
echo "Reflection promoted to permanent note: $PROMOTED_NOTE_ID"
echo ""
echo "=========================================="
echo "WORKFLOW 7: Full-Text Search"
echo "=========================================="
echo ""
echo "Search for 'distributed AND systems':"
query "FTS search" "
SELECT n.id, n.title, snippet(notes_fts, 1, '>>>', '<<<', '...', 30) AS match
FROM notes_fts
JOIN notes n ON notes_fts.rowid = n.rowid
WHERE notes_fts MATCH 'distributed AND systems'
ORDER BY rank
LIMIT 3;
"
echo ""
echo "Search clippings for 'team':"
query "Clipping search" "
SELECT c.id, snippet(clippings_fts, 0, '**', '**', '...', 40) AS match
FROM clippings_fts
JOIN clippings c ON clippings_fts.rowid = c.rowid
WHERE clippings_fts MATCH 'team'
ORDER BY rank;
"
echo ""
echo "Phrase search for 'Conway''s Law' in reflections:"
query "Reflection search" "
SELECT r.id, r.title, snippet(reflections_fts, 1, '>>>', '<<<', '...', 50) AS match
FROM reflections_fts
JOIN reflections r ON reflections_fts.rowid = r.rowid
WHERE reflections_fts MATCH '\"Conway''s Law\"'
ORDER BY rank;
"
echo ""
echo "=========================================="
echo "WORKFLOW 8: Aggregations"
echo "=========================================="
echo ""
echo "Monthly activity:"
query "Monthly activity" "SELECT * FROM v_monthly_activity;"
echo ""
echo "Resource stats:"
query "Resource stats" "SELECT * FROM v_resource_stats;"
echo ""
echo "Notes by origin:"
query "Origin breakdown" "
SELECT origin, COUNT(*) AS count
FROM notes
GROUP BY origin
ORDER BY count DESC;
"
echo ""
echo "Notes by folder:"
query "Folder breakdown" "
SELECT folder, COUNT(*) AS count
FROM notes
GROUP BY folder
ORDER BY count DESC;
"
echo ""
echo "Epistemic status distribution:"
query "Epistemic distribution" "
SELECT epistemic, COUNT(*) AS count
FROM notes
WHERE epistemic IS NOT NULL
GROUP BY epistemic
ORDER BY
CASE epistemic
WHEN 'fleeting' THEN 1
WHEN 'developing' THEN 2
WHEN 'supported' THEN 3
WHEN 'settled' THEN 4
END;
"
echo ""
echo "=========================================="
echo "WORKFLOW 9: Final Stats"
echo "=========================================="
echo ""
echo "Database summary:"
query "Table counts" "
SELECT
(SELECT COUNT(*) FROM notes) AS notes,
(SELECT COUNT(*) FROM breadcrumbs) AS breadcrumbs,
(SELECT COUNT(*) FROM resources) AS resources,
(SELECT COUNT(*) FROM clippings) AS clippings,
(SELECT COUNT(*) FROM reflections) AS reflections,
(SELECT COUNT(*) FROM links) AS links;
"
echo ""
echo "=========================================="
echo "Demo Complete!"
echo "=========================================="
echo ""
echo "Database preserved at: $DB_PATH"
echo ""
echo "Try exploring with:"
echo " sqlite3 $DB_PATH"
echo ""
echo "Example queries:"
echo " SELECT * FROM v_inbox;"
echo " SELECT * FROM v_evergreen;"
echo " SELECT * FROM v_tag_cloud;"
echo " SELECT * FROM v_note_richness;"
echo ""
#!/bin/bash
# sqlite-notes setup script
# Initializes a notes database with full schema, views, FTS, and triggers
#
# Usage:
# ./setup.sh # Creates ./.sqlite/notes.db
# ./setup.sh /path/to/notes.db # Creates at specified path
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(dirname "$SCRIPT_DIR")"
ASSETS_DIR="$SKILL_DIR/assets"
DB_PATH="${1:-./.sqlite/notes.db}"
DB_DIR="$(dirname "$DB_PATH")"
echo "=== sqlite-notes setup ==="
echo "Database: $DB_PATH"
echo ""
# Check prerequisites
if ! command -v sqlite3 &> /dev/null; then
echo "Error: sqlite3 not found."
exit 1
fi
if [ ! -f "$ASSETS_DIR/schema.sql" ]; then
echo "Error: schema.sql not found at $ASSETS_DIR/schema.sql"
exit 1
fi
if [ ! -f "$ASSETS_DIR/views.sql" ]; then
echo "Error: views.sql not found at $ASSETS_DIR/views.sql"
exit 1
fi
# Create directory
mkdir -p "$DB_DIR"
# Load schema (idempotent)
echo "Loading schema..."
{
echo "PRAGMA trusted_schema=ON;"
cat "$ASSETS_DIR/schema.sql"
} | sqlite3 "$DB_PATH"
# Load views (idempotent)
echo "Loading views..."
{
echo "PRAGMA trusted_schema=ON;"
cat "$ASSETS_DIR/views.sql"
} | sqlite3 "$DB_PATH"
# Verify
echo ""
echo "Tables:"
sqlite3 "$DB_PATH" ".tables"
echo ""
echo "Views:"
sqlite3 "$DB_PATH" "SELECT name FROM sqlite_master WHERE type='view' ORDER BY name;"
echo ""
echo "=== Setup complete ==="
echo ""
echo "Quick start:"
echo " sqlite3 $DB_PATH \"PRAGMA trusted_schema=ON; INSERT INTO notes (id, body, folder, origin, captured_at) VALUES ('N-' || strftime('%Y%m%d', 'now') || '-' || lower(hex(randomblob(4))), 'My first note!', 'inbox', 'me', strftime('%Y-%m-%dT%H:%M:%SZ', 'now'));\""
echo ""
echo "Note: PRAGMA trusted_schema=ON is required for FTS5 trigger operations."
Related skills
FAQ
Where is the data stored?
All data is kept local in a single `.sqlite/notes.db` file.
What does provenance track?
Every piece of content tracks origin: me, llm, external, or llm-assisted.