sqlite_graphrag/lib.rs
1//! # sqlite-graphrag
2//!
3//! Local GraphRAG memory for LLMs in a single SQLite file — zero external
4//! services required.
5//!
6//! `sqlite-graphrag` is a CLI-first library that persists memories, entities and
7//! typed relationships inside a single SQLite database. It combines FTS5
8//! full-text search with `sqlite-vec` KNN over locally-generated embeddings to
9//! expose a hybrid retrieval ranker tailored for LLM agents.
10//!
11//! ## CLI usage
12//!
13//! Install and initialize once, then save and recall memories:
14//!
15//! ```bash
16//! cargo install sqlite-graphrag
17//! sqlite-graphrag init
18//! sqlite-graphrag remember \
19//! --name onboarding-note \
20//! --type user \
21//! --description "first memory" \
22//! --body "hello graphrag"
23//! sqlite-graphrag recall "graphrag" --k 5
24//! ```
25//!
26//! ## Crate layout
27//!
28//! The public modules group the CLI, the SQLite storage layer and the
29//! supporting primitives (embedder, chunking, graph, namespace detection,
30//! output, paths and pragmas). The CLI binary wires them together through the
31//! commands in [`commands`].
32//!
33//! ## Exit codes
34//!
35//! Errors returned from [`errors::AppError`] map to deterministic exit codes
36//! suitable for orchestration by shell scripts and LLM agents. Consult the
37//! README for the full contract.
38
39// v1.0.97 (GAP-ROBUSTEZ-UNWRAP): production code must not panic via
40// unwrap()/expect(). Test code keeps them for brevity (cfg(test) opt-out).
41// Proven-invariant call sites carry a local #[allow] with justification.
42#![cfg_attr(not(test), warn(clippy::unwrap_used, clippy::expect_used))]
43#![deny(missing_docs)]
44
45use std::sync::atomic::{AtomicBool, AtomicU8, Ordering};
46use std::sync::OnceLock;
47use tokio_util::sync::CancellationToken;
48
49/// Signals that a shutdown signal (SIGINT / SIGTERM / SIGHUP) has been received.
50///
51/// Set in `main` via `ctrlc::set_handler`. Long-running subcommands can
52/// poll [`shutdown_requested`] to shut down gracefully before timeout.
53/// Async code should prefer [`cancel_token`] with `tokio::select!`.
54pub static SHUTDOWN: AtomicBool = AtomicBool::new(false);
55
56/// Counter of shutdown signals received. 0=none, 1=graceful, 2+=forced exit.
57pub static SIGNAL_COUNT: AtomicU8 = AtomicU8::new(0);
58
59/// Signal number that triggered shutdown (2=SIGINT, 15=SIGTERM). 0=none.
60static SIGNAL_NUMBER: AtomicU8 = AtomicU8::new(0);
61
62static CANCEL: OnceLock<CancellationToken> = OnceLock::new();
63
64/// Returns the process-wide cancellation token for async graceful shutdown.
65///
66/// The token is cancelled by the signal handler alongside [`SHUTDOWN`].
67/// Async loops should use `token.cancelled().await` inside `tokio::select!`
68/// for instant wake-up instead of polling [`shutdown_requested`].
69pub fn cancel_token() -> &'static CancellationToken {
70 CANCEL.get_or_init(CancellationToken::new)
71}
72
73/// Returns `true` if a shutdown signal has been received since the process started.
74///
75/// The value reflects the state of [`SHUTDOWN`]. Without a `ctrlc::set_handler` call,
76/// the initial state is always `false`.
77///
78/// # Examples
79///
80/// ```
81/// use sqlite_graphrag::shutdown_requested;
82///
83/// // Under normal startup conditions the signal has not been received.
84/// assert!(!shutdown_requested());
85/// ```
86///
87/// ```
88/// use std::sync::atomic::Ordering;
89/// use sqlite_graphrag::{SHUTDOWN, shutdown_requested};
90///
91/// // Simulate receiving a signal and verify that the function reflects the state.
92/// SHUTDOWN.store(true, Ordering::Release);
93/// assert!(shutdown_requested());
94/// // Restore to avoid contaminating other tests.
95/// SHUTDOWN.store(false, Ordering::Release);
96/// ```
97pub fn shutdown_requested() -> bool {
98 // ORDERING: Acquire pairs with the Release store in the signal handler (main.rs).
99 SHUTDOWN.load(Ordering::Acquire)
100}
101
102/// Returns the signal number that triggered shutdown (0 if none received).
103///
104/// Typically 2 (SIGINT) for Ctrl+C. Used to compute Unix-conventional exit
105/// code 128+N in the main function.
106pub fn shutdown_signal() -> u8 {
107 SIGNAL_NUMBER.load(Ordering::Acquire)
108}
109
110/// Resets the global shutdown flag to `false` and zeroes the signal counters.
111///
112/// Returns `true` if the flag was previously set, `false` if it was already
113/// cleared. Intended for tests and audit invocations where the SHUTDOWN flag
114/// was contaminated by an earlier signal handler in the same process tree.
115/// Production code must NOT call this — the only legitimate callers are
116/// integration tests, audit scripts, and the `--ignore-shutdown` CLI flag.
117///
118/// Note: this only resets the `SHUTDOWN` flag. The global [`CancellationToken`]
119/// remains in its previous cancelled state because `tokio_util::sync::CancellationToken`
120/// is one-shot. Callers that need a resettable token must use a per-invocation
121/// token (see [`should_obey_shutdown`]) instead of relying on the global one.
122///
123/// # Examples
124///
125/// ```
126/// use std::sync::atomic::Ordering;
127/// use sqlite_graphrag::{SHUTDOWN, try_reset_shutdown};
128///
129/// SHUTDOWN.store(true, Ordering::Release);
130/// assert!(try_reset_shutdown());
131/// assert!(!SHUTDOWN.load(Ordering::Acquire));
132/// ```
133pub fn try_reset_shutdown() -> bool {
134 // AcqRel pairs with the Release store in the signal handler and Acquire
135 // loads in [`shutdown_requested`]. The swap is intentional: we want to
136 // observe-and-reset atomically so a concurrent signal does not slip
137 // between the load and the store.
138 SHUTDOWN.swap(false, Ordering::AcqRel) | {
139 SIGNAL_COUNT.store(0, Ordering::Release);
140 SIGNAL_NUMBER.store(0, Ordering::Release);
141 // Suppress "unused" warning on the chained block; the `|` is just a
142 // sequence point and the final expression is the swap result.
143 false
144 }
145}
146
147/// Returns `true` when audit/test mode is active and long-running subcommands
148/// should ignore the cancellation token. The flag is honoured by the embedder
149/// loop in [`crate::embedder`] and by every call site that consults
150/// [`shutdown_requested`]. Production invocations always return `true` here.
151///
152/// The flag is read from XDG `shutdown.ignore` (`config set shutdown.ignore=1`).
153/// Accepted values: `1`, `true`, `yes`, `on` (case-insensitive).
154/// Anything else (including unset) means obey the cancellation token.
155pub fn should_obey_shutdown() -> bool {
156 !is_ignore_shutdown_set()
157}
158
159fn is_ignore_shutdown_set() -> bool {
160 // PROC: read once per call; this is not on a hot path. Tests set the env
161 // var in a `serial(env)` block so concurrent invocations cannot race.
162 crate::config::get_setting("shutdown.ignore")
163 .ok()
164 .flatten()
165 .map(|v| {
166 let v = v.trim().to_ascii_lowercase();
167 v == "1" || v == "true" || v == "yes" || v == "on"
168 })
169 .unwrap_or(false)
170}
171
172/// Token-aware chunking utilities for bodies that exceed the embedding window.
173pub mod chunking;
174
175/// Entity and URL extraction: URL-regex pass (the GLiNER NER pipeline was removed in v1.0.79).
176pub mod extraction;
177
178/// v1.0.75 (G21 solution): extraction backend abstraction with
179/// LLM/Embedding/None/Composite implementations.
180pub mod extract;
181
182/// `clap` definitions for the top-level `sqlite-graphrag` binary.
183pub mod backend_choice;
184pub mod cli;
185/// Shared no-op `--db` for host/XDG surfaces (GAP-SG-139).
186pub mod cli_db_noop;
187
188/// XDG-based API key management for OpenRouter and other providers.
189pub mod config;
190
191/// Subcommand handlers wired into the `clap` tree from [`cli`].
192pub mod commands;
193
194/// Compile-time constants: embedding dimensions, limits and thresholds.
195pub mod constants;
196
197/// Local embedding generation (LLM-only, one-shot per invocation).
198pub mod embedder;
199
200/// HTTP client for the OpenRouter chat-completions API (direct HTTP, no CLI subprocess).
201pub mod chat_api;
202
203/// HTTP client for the OpenRouter embeddings API (direct HTTP, no CLI subprocess).
204pub mod embedding_api;
205
206/// Shared HTTP primitives (`ApiError`, retry backoff) reused by [`chat_api`] and [`embedding_api`].
207pub mod openrouter_http;
208
209/// Canonical entity type taxonomy: 13 variants, ValueEnum + serde + rusqlite impls.
210pub mod entity_type;
211
212/// Library-wide error type and the mapping to process exit codes (see [`errors::AppError`]).
213pub mod errors;
214
215/// Graph traversal helpers over the entities and relationships tables.
216pub mod graph;
217
218/// Type aliases for AHash-backed collections in hot paths.
219pub mod hash;
220
221/// Bilingual message layer for human-facing stderr progress (`--lang en|pt`, XDG `i18n.lang`).
222pub mod i18n;
223
224/// Counting semaphore via lock files to limit parallel invocations.
225/// Provides `acquire_cli_slot` (counting semaphore) and the G28-B
226/// per-namespace heavy-job singleton `acquire_job_singleton` for
227/// `enrich`, `ingest --mode claude-code`, `ingest --mode codex`.
228pub mod lock;
229
230/// GAP-004 (v1.0.82): Cross-process slot semaphore for LLM subprocesses.
231/// `acquire_llm_slot` limits concurrent `codex`/`claude` spawns per host
232/// to prevent OAuth rate limit saturation when N+ sessions run in parallel.
233pub mod llm_slots;
234
235/// GAP-005 (v1.0.82): Exit code diagnostics for LLM subprocess crashes.
236pub mod llm {
237 pub mod exit_code_hints;
238}
239
240/// v1.0.75 (G22 solution): spawn subsystem abstraction with
241/// `VersionAdapter` trait for codex/claude/opencode executors.
242pub mod spawn;
243
244/// Memory guard: checks RAM availability before loading the ONNX model.
245pub mod memory_guard;
246
247/// Type-safe enumeration of the five `memories.source` CHECK constraint values.
248/// Replaces the footgun `pub source: String` to prevent G29-style regressions.
249#[allow(rustdoc::broken_intra_doc_links)]
250pub mod memory_source;
251
252/// Namespace resolution with precedence between flag, environment and markers.
253pub mod namespace;
254
255/// Centralized stdout/stderr emitters for CLI output formatting.
256pub mod output;
257
258/// Agent-native R-AN-01: `--print-schema` emits embedded JSON Schema and exits.
259pub mod print_schema;
260
261/// Dual-format argument parser: accepts Unix epoch and RFC 3339.
262pub mod parsers;
263
264/// G29 Step 4: preservation checks (Jaccard trigram) for LLM-enriched bodies.
265pub mod preservation;
266
267/// Filesystem paths for the project-local database and app support directories.
268pub mod paths;
269
270/// SQLite pragma helpers applied on every connection.
271pub mod pragmas;
272
273/// v1.0.76: in-process vector similarity helpers. Replaces the
274/// `sqlite-vec` KNN API with pure-Rust cosine over the BLOB-backed
275/// `memory_embeddings` / `entity_embeddings` tables.
276pub mod similarity;
277
278/// Atomic file writes (atomwrite algorithm: tempfile → fsync → rename).
279pub mod atomic_io;
280
281/// Cross-platform signal handling: SIGINT, SIGTERM, SIGHUP.
282pub mod signals;
283
284/// Centralized retry infrastructure with exponential backoff and half-jitter.
285pub mod retry;
286
287/// G28: orphan-process reaper that runs at CLI startup.
288#[allow(rustdoc::broken_intra_doc_links)]
289pub mod reaper;
290
291/// G28-D: system load average observation (pre-spawn saturation check).
292pub mod system_load;
293
294/// Persistence layer: memories, entities, chunks and version history.
295pub mod storage;
296
297/// Runtime config: flag > XDG > default (no product env) — G-T-XDG-04.
298pub mod runtime_config;
299
300/// Centralized tracing subscriber initialization with panic hook and log bridge.
301/// Local tracing subscriber init only (no remote telemetry / OTEL export).
302pub mod tracing_init;
303
304/// Cross-platform terminal initialization: UTF-8 console, ANSI colors, NO_COLOR.
305pub mod terminal;
306
307/// Display time zone for `*_iso` fields (flag `--tz`, XDG `display.tz`, fallback UTC).
308pub mod tz;
309
310/// Stdin reader with configurable timeout to prevent indefinite blocking.
311pub mod stdin_helper;
312
313/// Real tokenizer of the embedding model for accurate token counting and chunking.
314pub mod tokenizer;
315
316/// v1.0.97: repairs malformed JSON from OpenRouter chat models (markdown code
317/// fences, trailing commas, unquoted keys) before parsing into a `serde_json::Value`.
318pub mod json_repair;
319
320mod embedded_migrations {
321 #![allow(missing_docs)] // refinery embed_migrations! expands undocumented items
322 use refinery::embed_migrations;
323 embed_migrations!("migrations");
324}
325
326pub use embedded_migrations::migrations;