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