Skip to main content

ix/
lib.rs

1//! ix — sub-millisecond code search via sparse trigram indexing.
2//!
3//! `ix` pre-computes a byte-level trigram index to narrow search candidates
4//! to a fraction of the total file set, then verifies matches with a
5//! memory-constant streaming architecture. This eliminates the linear-scan
6//! bottleneck of traditional tools on large codebases.
7//!
8//! # Installation
9//!
10//! ```bash
11//! cargo install moeix
12//! ```
13//!
14//! # Quick Start
15//!
16//! ```bash
17//! ix --build /path/to/repo
18//! ix "fn validate"
19//! ix --regex "fn\\s+\\w+_handler" --context 3
20//! ```
21//!
22//! # Library Usage
23//!
24//! ```rust,ignore
25//! use ix::{Reader, Executor};
26//!
27//! let reader = Reader::open(".ix/shard.ix")?;
28//! let mut executor = Executor::new(&reader);
29//! let matches = executor.execute(query);
30//! ```
31//!
32//! # Module Build Order
33//!
34//! `format` → `varint` → `trigram` → `bloom` → `posting` →
35//! `string_pool` → `builder` → `reader` → `planner` → `executor` → `scanner`
36//!
37//! Cache layer: `posting_cache` → `neg_cache` → `regex_pool` → `cache_policy`
38//!
39//! # Feature Flags
40//!
41//! - **`notify`** (default) — File watcher + daemon (`ixd`) + Unix domain socket IPC
42//! - **`decompress`** — gz/zst/bz2/xz decompression
43//! - **`archive`** — zip/tar archive support
44//! - **`full`** — All optional features
45
46// Lint configuration — Tier 1 security hardened, pedantic with pragmatic allow
47#![warn(clippy::pedantic)]
48#![allow(clippy::cast_possible_truncation)] // 64-bit target; try_from guards runtime
49#![allow(clippy::cast_possible_wrap)] // controlled internal values
50#![allow(clippy::cast_sign_loss)] // controlled internal values
51#![allow(clippy::module_name_repetitions)] // module::Type is idiomatic
52#![allow(clippy::too_many_lines)] // scanner/executor are well-structured
53#![warn(missing_docs)] // enterprise: every public item documented
54#![cfg_attr(test, allow(missing_docs))] // tests: no doc needed for test fns
55#![warn(clippy::semicolon_if_nothing_returned)]
56#![warn(clippy::non_ascii_literal)]
57#![warn(clippy::unimplemented)]
58#![warn(clippy::use_self)]
59#![warn(clippy::string_slice)]
60#![warn(clippy::clone_on_ref_ptr)]
61
62extern crate llmosafe;
63
64pub mod archive;
65pub mod bloom;
66pub mod builder;
67/// Adaptive cache policy driven by `ResourceGuard` memory pressure.
68pub mod cache_policy;
69pub mod config;
70#[cfg(feature = "notify")]
71pub mod daemon_sock;
72pub mod decompress;
73/// Error types for the ix crate.
74pub mod error;
75pub mod executor;
76pub mod format;
77#[cfg(feature = "notify")]
78pub mod idle;
79/// Negative result cache — skips re-verification of known non-matching files.
80pub mod neg_cache;
81pub mod planner;
82pub mod posting;
83/// LRU cache for decoded posting lists, keyed by trigram.
84pub mod posting_cache;
85pub mod reader;
86/// Compiled regex pool — caches `Regex` objects to avoid recompilation.
87pub mod regex_pool;
88pub mod scanner;
89pub mod string_pool;
90pub mod trigram;
91pub mod varint;
92#[cfg(feature = "notify")]
93pub mod watcher;
94
95#[cfg(feature = "notify")]
96pub use crate::builder::Builder;
97#[cfg(feature = "notify")]
98pub use crate::daemon_sock::{
99    ClientMessage, DaemonClient, DaemonServer, FileChange, FileOp, ServerMessage,
100};
101#[cfg(feature = "notify")]
102pub use crate::format::Beacon;
103#[cfg(feature = "notify")]
104pub use crate::idle::IdleTracker;
105#[cfg(feature = "notify")]
106pub use crate::watcher::Watcher;
107
108/// Run the daemon watching the given directory for changes and rebuilding the index.
109///
110/// # Errors
111///
112/// Returns an error if the root cannot be canonicalized, the index cannot be built,
113/// or the file watcher fails.
114///
115/// # Panics
116///
117/// Panics if the Ctrl-C handler cannot be set (which is extremely rare).
118#[cfg(feature = "notify")]
119pub fn run_daemon(path: &std::path::Path) -> crate::error::Result<()> {
120    use std::fs;
121    use std::sync::Arc;
122    use std::sync::atomic::{AtomicBool, Ordering};
123    use std::time::Duration;
124
125    let root = path.canonicalize().map_err(crate::error::Error::Io)?;
126
127    println!("ixd: watching {}...", root.display());
128
129    let mut builder = Builder::new(&root)?;
130
131    // Lazy startup: if index exists, just update. Otherwise build.
132    let ix_dir = root.join(".ix");
133    let index_file = ix_dir.join("shard.ix");
134    if index_file.exists() {
135        println!("ixd: existing index found, performing startup update...");
136        // In a real implementation we would load the 'files' table from the shard
137        // to know which files to check for changes. For now we just build to satisfy
138        // the current Builder API, but we've already optimized Builder's memory.
139        builder.build()?;
140    } else {
141        builder.build()?;
142    }
143
144    println!(
145        "ixd: initial index ready ({} files, {} trigrams)",
146        builder.files_len(),
147        builder.trigrams_len()
148    );
149
150    let mut watcher = Watcher::new(&root);
151    let rx = watcher.start()?;
152
153    let ix_dir = root.join(".ix");
154    if !ix_dir.exists() {
155        fs::create_dir_all(&ix_dir)?;
156    }
157    let mut beacon = Beacon::new(&root);
158    beacon.write_to(&ix_dir)?;
159
160    let mut idle = IdleTracker::new();
161
162    let running = Arc::new(AtomicBool::new(true));
163    let r = std::sync::Arc::clone(&running);
164    ctrlc::set_handler(move || {
165        r.store(false, Ordering::SeqCst);
166    })
167    .expect("Error setting Ctrl-C handler");
168
169    while running.load(Ordering::SeqCst) {
170        match rx.recv_timeout(Duration::from_secs(5)) {
171            Ok(changed_files) => {
172                println!(
173                    "ixd: {} files changed, updating index...",
174                    changed_files.len()
175                );
176
177                beacon.status = "indexing".to_string();
178                beacon.last_event_at = std::time::SystemTime::now()
179                    .duration_since(std::time::UNIX_EPOCH)
180                    .unwrap_or_default()
181                    .as_secs();
182                let _ = beacon.write_to(&ix_dir);
183
184                idle.record_change();
185                builder.update(&changed_files)?;
186
187                beacon.status = "idle".to_string();
188                let _ = beacon.write_to(&ix_dir);
189            }
190            Err(crossbeam_channel::RecvTimeoutError::Timeout) => {}
191            Err(crossbeam_channel::RecvTimeoutError::Disconnected) => break,
192        }
193    }
194
195    println!("ixd: shutting down");
196    let _ = fs::remove_file(ix_dir.join("beacon.json"));
197    watcher.stop();
198    Ok(())
199}