vtcode_indexer.md
February 25, 2026 ยท View on GitHub
vtcode-indexer provides a lightweight, pluggable workspace indexer suitable for
command-line tooling and autonomous agents that need fast filesystem scans
without external services.
Core concepts
SimpleIndexerwalks a workspace, caches metadata, and offers helpers for search, file lookup, and content retrieval.SimpleIndexerConfiglets callers toggle hidden directory handling, specify custom index directories, and refine include/exclude lists without hardcoding VT Code's.vtcodelayout.IndexStorageis a trait abstraction for persistingFileIndexentries. The crate ships with a Markdown implementation and accepts custom backends throughSimpleIndexer::with_storage.TraversalFiltercentralizes directory descent and file-level inclusion decisions, giving downstream users a single place to implement glob rules or binary detection before indexing.
Customizing persistence
The default MarkdownIndexStorage writes summaries as Markdown files under the
configured index directory. Downstream projects can provide their own
implementation to target alternative formats or external services:
use std::path::Path;
use std::sync::Arc;
use anyhow::Result;
use vtcode_indexer::{FileIndex, IndexStorage, SimpleIndexer};
#[derive(Clone, Default)]
struct MemoryStorage;
impl IndexStorage for MemoryStorage {
fn init(&self, _index_dir: &Path) -> Result<()> {
Ok(())
}
fn persist(&self, _index_dir: &Path, entry: &FileIndex) -> Result<()> {
println!("indexed {} ({} bytes)", entry.path, entry.size);
Ok(())
}
}
let mut indexer = SimpleIndexer::new(workspace_root.clone())
.with_storage(Arc::new(MemoryStorage::default()));
indexer.init()?;
indexer.index_directory(workspace_root.as_path())?;
Any IndexStorage implementation is free to establish connections, emit
telemetry, or fan out writes during persist as long as it surfaces errors
through anyhow::Result.
Tailoring traversal
TraversalFilter implementors can short-circuit directory descent or file-level
indexing. The default ConfigTraversalFilter follows SimpleIndexerConfig
settings. Custom filters can extend those decisions with domain-specific logic:
use std::path::Path;
use std::sync::Arc;
use vtcode_indexer::{ConfigTraversalFilter, SimpleIndexer, SimpleIndexerConfig, TraversalFilter};
#[derive(Default)]
struct SkipGeneratedFilter {
inner: ConfigTraversalFilter,
}
impl TraversalFilter for SkipGeneratedFilter {
fn should_descend(&self, path: &Path, config: &SimpleIndexerConfig) -> bool {
if path.ends_with("generated") {
return false;
}
self.inner.should_descend(path, config)
}
fn should_index_file(&self, path: &Path, config: &SimpleIndexerConfig) -> bool {
if path
.extension()
.and_then(|ext| ext.to_str())
.is_some_and(|ext| ext.eq_ignore_ascii_case("lock"))
{
return false;
}
self.inner.should_index_file(path, config)
}
}
let config = SimpleIndexerConfig::new(workspace_root.clone());
let mut indexer = SimpleIndexer::with_config(config)
.with_filter(Arc::new(SkipGeneratedFilter::default()));
indexer.init()?;
indexer.index_directory(workspace_root.as_path())?;
Filters can coordinate with configuration to honor allowlists while still blocking noisy directories or file types.
End-to-end example
The examples/custom_storage.rs program demonstrates indexing a temporary
workspace using in-memory storage and a filter that skips Rust sources. Run it
with cargo run -p vtcode-indexer --example custom_storage to see the indexed
paths printed to stdout.
Next steps
- Publish release notes and crate-level documentation updates before the first crates.io release.
- Gather feedback from early adopters to validate trait ergonomics and identify additional feature flags worth exposing.