Skip to content
Merged
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
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ Repository Intelligence searches local code and returns verbatim source lines fo
> [!NOTE]
> **Status:** Experimental research prototype (v0.1.0) for local code inspection.

![An offline search printing the top three matching source lines](docs/demo/offline-search.gif)

- Searches code using text matching, offline hash embeddings, or local neural models.
- Responds to questions by quoting exact source lines instead of generating text; quotes match the source, but relevance is not guaranteed.
- Stores portable repository index snapshots without external database dependencies.
Expand All @@ -36,6 +38,7 @@ cargo run --locked -- --index evaluation/corpus /tmp/ri.ri
# 2. Retrieve evidence offline without calling a model
cargo run --locked -- --semantic evaluation/corpus "index header"
cargo run --locked -- --hybrid evaluation/corpus "index header"
cargo run --locked -- --hybrid evaluation/corpus "index header" --top 3

# 3. Query a saved snapshot
cargo run --locked -- --load-index /tmp/ri.ri "index header"
Expand Down
Binary file added docs/demo/offline-search.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions docs/demo/offline-search.tape
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
Output docs/demo/offline-search.gif
Set Shell "bash"
Set FontSize 14
Set Width 1400
Set Height 190
Set Padding 24
Set Theme "Catppuccin Mocha"
Set TypingSpeed 40ms
Hide
Type `export PS1='$ ' PATH="$PWD/target/release:$PATH"` Enter
Type "clear" Enter
Show
Type `repository-intelligence --hybrid evaluation/corpus "index header" --top 3`
Sleep 300ms
Enter
Sleep 4s
85 changes: 81 additions & 4 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,15 @@ fn main() {
}
let mut args = raw_args.into_iter();
let mode = args.next().unwrap_or_else(|| ".".into());
let parsed = match extract_top(args.collect()) {
Ok(parsed) => parsed,
Err(error) => {
eprintln!("error: {error}");
std::process::exit(2);
}
};
let top = parsed.0;
let mut args = parsed.1.into_iter();
match mode.as_str() {
"--help" | "-h" => {
println!("Usage: repository-intelligence [OPTIONS] <repo> [query...]");
Expand All @@ -46,6 +55,7 @@ fn main() {
println!(" --analytics [repo] Print index statistics");
println!(" --serve [address] [repo] Start local HTTP service (default: 127.0.0.1:8080)");
println!(" --embedding <provider> Embedding provider (hash or nomic)");
println!(" --top <N> Limit printed search results (positive integer)");
println!(" --help, -h Print this help message");
}
"--answer" => answer_command(&mut args, false),
Expand Down Expand Up @@ -81,7 +91,11 @@ fn main() {
.unwrap_or_else(|| ".repository-intelligence/index.ri".into());
let query = args.collect::<Vec<_>>().join(" ");
let index = Index::load_from(Path::new(&index_path)).expect("load index");
for item in index.search_evidence(&query, 10, RetrievalMode::Hybrid) {
for item in index
.search_evidence(&query, 10, RetrievalMode::Hybrid)
.into_iter()
.take(top.unwrap_or(usize::MAX))
{
println!(
"{}\t{:.6}\t{}",
item.citation(),
Expand All @@ -99,7 +113,11 @@ fn main() {
let repository = args.next().unwrap_or_else(|| ".".into());
let query = args.collect::<Vec<_>>().join(" ");
let index = Index::build(Path::new(&repository)).expect("index repository");
for item in index.search_evidence(&query, 10, retrieval) {
for item in index
.search_evidence(&query, 10, retrieval)
.into_iter()
.take(top.unwrap_or(usize::MAX))
{
println!(
"{}\t{:.6}\t{}",
item.citation(),
Expand All @@ -112,7 +130,11 @@ fn main() {
let repository = args.next().unwrap_or_else(|| ".".into());
let query = args.collect::<Vec<_>>().join(" ");
let index = Index::build(Path::new(&repository)).expect("index repository");
for commit in index.search_commits(&query, 10) {
for commit in index
.search_commits(&query, 10)
.into_iter()
.take(top.unwrap_or(usize::MAX))
{
println!("{}\t{}\t{}", commit.sha, commit.date, commit.subject);
}
}
Expand All @@ -123,13 +145,43 @@ fn main() {
std::process::exit(2);
}
let index = Index::build(Path::new(&mode)).expect("index repository");
for hit in index.search(&query, 10) {
for hit in index
.search(&query, 10)
.into_iter()
.take(top.unwrap_or(usize::MAX))
{
println!("{}:{}\t{}", hit.path.display(), hit.line, hit.text.trim());
}
}
}
}

fn extract_top(args: Vec<String>) -> Result<(Option<usize>, Vec<String>), String> {
let mut top = None;
let mut remaining = Vec::with_capacity(args.len());
let mut iter = args.into_iter();
while let Some(arg) = iter.next() {
if arg == "--top" {
if top.is_some() {
return Err("--top may be specified only once".into());
}
let value = iter
.next()
.ok_or_else(|| "--top requires a positive integer".to_owned())?;
let parsed = value
.parse::<usize>()
.map_err(|_| "--top requires a positive integer".to_owned())?;
if parsed == 0 {
return Err("--top must be greater than 0".into());
}
top = Some(parsed);
} else {
remaining.push(arg);
}
}
Ok((top, remaining))
}

fn answer_command(args: &mut impl Iterator<Item = String>, json_mode: bool) {
let repository = args.next().unwrap_or_else(|| ".".into());
let question = args.collect::<Vec<_>>().join(" ");
Expand Down Expand Up @@ -494,3 +546,28 @@ fn json_escape(value: &str) -> String {
}
escaped
}

#[cfg(test)]
mod top_tests {
use super::extract_top;

#[test]
fn parses_top_and_preserves_other_arguments() {
let (top, args) = extract_top(vec![
"repo".into(),
"query".into(),
"--top".into(),
"3".into(),
])
.unwrap();
assert_eq!(top, Some(3));
assert_eq!(args, vec!["repo", "query"]);
}

#[test]
fn rejects_invalid_top_values() {
for value in ["0", "abc"] {
assert!(extract_top(vec!["--top".into(), value.into()]).is_err());
}
}
}
Loading