artur-shlyapnikov/simgrep

Local semantic search, hybrid ranking, and code exploration from the command line.

2

stars

139

commits

Python

primary language

Sep 9, 2026

updated

cli
embeddings
local-first
python
semantic-search

README

simgrep

simgrep is a command-line tool for semantic search in local files. It finds text snippets based on meaning, not just keywords — useful when you know what you're looking for but not the exact words used.

Unlike grep or ripgrep, simgrep embeds your query and your files into a shared vector space and retrieves the most semantically similar chunks. A search for "database connection errors" will surface code that says pool exhausted or timeout connecting to postgres — things a regex would miss.

Requirements: Python 3.12+

Installation

# recommended: use uv to create a virtual environment
uv venv
source .venv/bin/activate

# Install the package
uv pip install .

For development (editable install + dev dependencies):

make install

If you run simgrep from source without installing, you can invoke it from any directory via:

uv run --project /path/to/simgrep python -m simgrep.main --help

How it works

simgrep has two modes:

  • Ephemeral search — one-off searches. Indexes files in memory, searches, discards the index. No setup required.
  • Persistent projects — for searching the same codebase repeatedly. Builds a persistent index on disk; subsequent searches and incremental updates are fast.

Retrieval is dense-only: sentence-transformer embeddings, USearch vectors, DuckDB metadata, and lexical term tables for hybrid ranking.

Local Project Init

Initialize from repository root (creates .simgrep/project.toml):

simgrep init

Usage

No setup needed. simgrep builds a temporary in-memory index on the fly, searches it, and discards it.

Default interactive behavior:

  • Hybrid ranking is enabled by default (lexical_top=50, lexical_weight=0.25).
  • Relative paths are shown by default.
  • Per-result ranking explanation is available via --why flag (hidden by default).
  • Result diversity defaults to --diversity window (use none|file|package).

Search a directory:

simgrep search "database connection errors" ./src

Filter by file type:

simgrep search "async function examples" ./my_project --pattern "*.py"

# Multiple patterns
simgrep search "api documentation" ./docs --pattern "*.md" --pattern "*.rst"

Change output format — list only file paths instead of showing matching text:

simgrep search "user authentication flow" ./docs --format paths

Limit results — top 3 most relevant:

simgrep search "database connection pool" ./configs --top 3

Force pure semantic ranking (disable hybrid defaults):

simgrep search "database connection pool" ./configs --no-hybrid

Force one-off ephemeral mode even if an indexed project exists for the path:

simgrep search "database connection pool" ./configs --ephemeral

Persistent projects

Use persistent projects when you search the same directory repeatedly — a codebase, a notes folder, etc.

1. Initialize a project from the directory you want to search:

cd /path/to/my-codebase
simgrep init

This creates .simgrep/project.toml and marks the current directory as an indexed path.

2. Add more paths (optional):

simgrep project add-path ./backend
simgrep project add-path ./docs

3. Build the index:

simgrep index

The first run downloads the embedding model and indexes all files — this takes a moment. Subsequent runs are incremental.

4. Search:

simgrep search "user session management"

simgrep detects the active project from your current directory.

When a search path is provided, simgrep prefers persistent search if that path is covered by the active indexed project. Use --ephemeral to force one-off mode or --persistent to fail fast when no persistent index is usable.

5. Update after file changes:

simgrep index

Only new and modified files are processed.

Indexing with multiple workers

Speed up indexing with concurrent file processing:

simgrep index --workers 4

Default worker count is conservative; increase it for large repositories.

Check project status

simgrep status

Interactive REPL

Run a persistent interactive session that reuses loaded model/index:

simgrep repl

Supported file types

simgrep reads plain-text formats directly: .txt, .md, .rst, .py, .js, .ts, .tsx, .jsx, .java, .go, .rs, .c, .cpp, .h, .cs, .rb, .php, .swift, .kt, .scala, .sh, .bash, .zsh, .toml, .yaml, .yml, .json, .xml, .html, .css, .sql, Dockerfile.

Other text-like formats (e.g. .org, .tex) are attempted via the unstructured fallback. Binary formats (.pdf, .zip, images, databases) are skipped.

Model Cache

Default model: ibm-granite/granite-embedding-30m-english.

Pre-download models before going offline:

make download-models

Troubleshooting

"Persistent index not found" — run simgrep index from within the project directory before searching.

Slow first run — the embedding model is downloaded from Hugging Face Hub on first use (~hundreds of MB). Use make download-models to pre-cache it.

No results returned — your --pattern may not match any files. Try simgrep search "..." ./path --pattern "*.py" with an explicit pattern, or omit --pattern to search all files.

Contributors

artur-shlyapnikov/simgrep

Local semantic search, hybrid ranking, and code exploration from the command line.

2

stars

139

commits

Python

primary language

Sep 9, 2026

updated

cli
embeddings
local-first
python
semantic-search

README

simgrep

simgrep is a command-line tool for semantic search in local files. It finds text snippets based on meaning, not just keywords — useful when you know what you're looking for but not the exact words used.

Unlike grep or ripgrep, simgrep embeds your query and your files into a shared vector space and retrieves the most semantically similar chunks. A search for "database connection errors" will surface code that says pool exhausted or timeout connecting to postgres — things a regex would miss.

Requirements: Python 3.12+

Installation

# recommended: use uv to create a virtual environment
uv venv
source .venv/bin/activate

# Install the package
uv pip install .

For development (editable install + dev dependencies):

make install

If you run simgrep from source without installing, you can invoke it from any directory via:

uv run --project /path/to/simgrep python -m simgrep.main --help

How it works

simgrep has two modes:

  • Ephemeral search — one-off searches. Indexes files in memory, searches, discards the index. No setup required.
  • Persistent projects — for searching the same codebase repeatedly. Builds a persistent index on disk; subsequent searches and incremental updates are fast.

Retrieval is dense-only: sentence-transformer embeddings, USearch vectors, DuckDB metadata, and lexical term tables for hybrid ranking.

Local Project Init

Initialize from repository root (creates .simgrep/project.toml):

simgrep init

Usage

No setup needed. simgrep builds a temporary in-memory index on the fly, searches it, and discards it.

Default interactive behavior:

  • Hybrid ranking is enabled by default (lexical_top=50, lexical_weight=0.25).
  • Relative paths are shown by default.
  • Per-result ranking explanation is available via --why flag (hidden by default).
  • Result diversity defaults to --diversity window (use none|file|package).

Search a directory:

simgrep search "database connection errors" ./src

Filter by file type:

simgrep search "async function examples" ./my_project --pattern "*.py"

# Multiple patterns
simgrep search "api documentation" ./docs --pattern "*.md" --pattern "*.rst"

Change output format — list only file paths instead of showing matching text:

simgrep search "user authentication flow" ./docs --format paths

Limit results — top 3 most relevant:

simgrep search "database connection pool" ./configs --top 3

Force pure semantic ranking (disable hybrid defaults):

simgrep search "database connection pool" ./configs --no-hybrid

Force one-off ephemeral mode even if an indexed project exists for the path:

simgrep search "database connection pool" ./configs --ephemeral

Persistent projects

Use persistent projects when you search the same directory repeatedly — a codebase, a notes folder, etc.

1. Initialize a project from the directory you want to search:

cd /path/to/my-codebase
simgrep init

This creates .simgrep/project.toml and marks the current directory as an indexed path.

2. Add more paths (optional):

simgrep project add-path ./backend
simgrep project add-path ./docs

3. Build the index:

simgrep index

The first run downloads the embedding model and indexes all files — this takes a moment. Subsequent runs are incremental.

4. Search:

simgrep search "user session management"

simgrep detects the active project from your current directory.

When a search path is provided, simgrep prefers persistent search if that path is covered by the active indexed project. Use --ephemeral to force one-off mode or --persistent to fail fast when no persistent index is usable.

5. Update after file changes:

simgrep index

Only new and modified files are processed.

Indexing with multiple workers

Speed up indexing with concurrent file processing:

simgrep index --workers 4

Default worker count is conservative; increase it for large repositories.

Check project status

simgrep status

Interactive REPL

Run a persistent interactive session that reuses loaded model/index:

simgrep repl

Supported file types

simgrep reads plain-text formats directly: .txt, .md, .rst, .py, .js, .ts, .tsx, .jsx, .java, .go, .rs, .c, .cpp, .h, .cs, .rb, .php, .swift, .kt, .scala, .sh, .bash, .zsh, .toml, .yaml, .yml, .json, .xml, .html, .css, .sql, Dockerfile.

Other text-like formats (e.g. .org, .tex) are attempted via the unstructured fallback. Binary formats (.pdf, .zip, images, databases) are skipped.

Model Cache

Default model: ibm-granite/granite-embedding-30m-english.

Pre-download models before going offline:

make download-models

Troubleshooting

"Persistent index not found" — run simgrep index from within the project directory before searching.

Slow first run — the embedding model is downloaded from Hugging Face Hub on first use (~hundreds of MB). Use make download-models to pre-cache it.

No results returned — your --pattern may not match any files. Try simgrep search "..." ./path --pattern "*.py" with an explicit pattern, or omit --pattern to search all files.

Contributors

Languages

Python

98.7%

Makefile

1.3%