Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
[workspace]
members = [
"crates/rskim-core",
"crates/rskim-search",
"crates/rskim",
]
resolver = "2"
Expand Down Expand Up @@ -46,6 +47,7 @@ predicates = "3.0"
tempfile = "3.0"
insta = "1.39"
criterion = "0.5"
rustc-hash = "1.1"

[workspace.metadata.dist]
cargo-dist-version = "0.14.0"
Expand Down
32 changes: 32 additions & 0 deletions crates/rskim-search/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
[package]
name = "rskim-search"
version = "2.1.0"
edition = "2021"
authors = ["Skim Contributors"]
license = "MIT"
description = "Search and indexing layer for skim's code intelligence"
repository = "https://github.com/dean0x/skim"
readme = "README.md"
keywords = ["code-search", "ast", "tree-sitter", "llm"]
categories = ["parser-implementations", "development-tools"]

[dependencies]
rskim-core = { version = "2.1.0", path = "../rskim-core" }
tree-sitter = { workspace = true }
thiserror = { workspace = true }
serde = { workspace = true }
rustc-hash = { workspace = true }

[dev-dependencies]
serde_json = { workspace = true }

[lints.clippy]
# Enforce strict error handling in library code
unwrap_used = "deny"
expect_used = "deny"
panic = "deny"
todo = "warn" # Allow during development, warn to remind removal

[lib]
name = "rskim_search"
path = "src/lib.rs"
26 changes: 26 additions & 0 deletions crates/rskim-search/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
//! Search and indexing layer for skim's code intelligence
//!
//! # Architecture
//!
//! This crate provides the 3-layer search architecture:
//! - **Layer 1**: Lexical indexing (BM25F with field boosting)
//! - **Layer 2**: AST n-gram indexing (structural code patterns)
//! - **Layer 3**: Temporal signals (git-aware recency and change patterns)
//!
//! All types and traits are defined here. Layer implementations
//! are added in subsequent waves.
//!
//! # Design Principles
//!
//! 1. **I/O-free types** - Core types have no filesystem dependencies
//! 2. **Trait-first** - Layers implement `SearchLayer`, built via `LayerBuilder`
//! 3. **FileId indirection** - All layers reference files by `FileId`, resolved via `FileTable`

mod traits;
mod types;

pub use traits::{FieldClassifier, LayerBuilder, SearchLayer};
pub use types::{
FileId, FileTable, IndexStats, LineRange, MatchSpan, Result, SearchError, SearchField,
SearchQuery, SearchResult, TemporalFlags, MAX_FILE_TABLE_ENTRIES,
};
49 changes: 49 additions & 0 deletions crates/rskim-search/src/traits.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//! Core traits for the 3-layer search architecture.
//!
//! These traits define the contracts that layer implementations must satisfy.
//! Layers are built via [`LayerBuilder`] and become immutable [`SearchLayer`]s
//! after construction. [`FieldClassifier`] is used during indexing to map
//! AST nodes to semantic [`SearchField`] values.

use std::path::Path;

use rskim_core::Language;

use crate::{FileId, Result, SearchField, SearchQuery};

/// Immutable search index that scores files against a query.
///
/// Implementations are built via [`LayerBuilder`] and are immutable after construction.
/// Callers resolve [`FileId`] values to paths via [`FileTable`].
pub trait SearchLayer: Send + Sync {
/// Score files against the given query.
///
/// Returns a list of `(FileId, score)` pairs, ordered by descending score.
/// Higher scores indicate stronger matches. Scores are not normalized across layers.
fn search(&self, query: &SearchQuery) -> Result<Vec<(FileId, f32)>>;
}

/// Builder for constructing a [`SearchLayer`].
///
/// Accepts files one at a time, then produces an immutable layer via [`build`].
/// Consumed by `build` — single-use pattern.
pub trait LayerBuilder: Send {
/// Add a file's content to the index being built.
fn add_file(&mut self, path: &Path, content: &str, language: Language) -> Result<()>;

/// Consume this builder and produce an immutable [`SearchLayer`].
///
/// Uses `Box<Self>` for object safety with `Box<dyn LayerBuilder>`.
fn build(self: Box<Self>) -> Result<Box<dyn SearchLayer>>;
}

/// Classifies tree-sitter AST nodes into semantic search fields.
///
/// Returns `None` for nodes that are not interesting for search indexing
/// (e.g., punctuation, whitespace). `None` means "skip this node."
pub trait FieldClassifier: Send + Sync {
/// Classify a tree-sitter node into a search field.
///
/// Returns `None` if the node is not relevant for indexing.
fn classify_node(&self, node: &tree_sitter::Node<'_>, source: &str) -> Option<SearchField>;
}
31 changes: 31 additions & 0 deletions crates/rskim-search/src/types/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//! Error types for search and indexing operations.

use thiserror::Error;

/// Errors that can occur during search and indexing operations.
#[non_exhaustive]
#[derive(Debug, Error)]
pub enum SearchError {
/// An I/O error occurred while reading or writing index files.
#[error("IO error: {0}")]
Io(#[from] std::io::Error),

/// The index is corrupt, missing, or in an incompatible format.
#[error("Index error: {0}")]
IndexError(String),

/// The query is malformed or contains unsupported constructs.
#[error("Invalid query: {0}")]
InvalidQuery(String),

/// An error propagated from `rskim-core`.
#[error("Core error: {0}")]
CoreError(#[from] rskim_core::SkimError),

/// A serialization or deserialization error occurred.
#[error("Serialization error: {0}")]
SerializationError(String),
}

/// Result type alias for search operations.
pub type Result<T> = std::result::Result<T, SearchError>;
Loading