Bobbin
Local-first code context engine. Semantic search, keyword search, and git coupling analysis — all running on your machine. No API keys. No cloud. Sub-100ms queries.
Bobbin indexes the structure, history, and meaning of your codebase, then delivers precisely the right context when you (or your AI agent) need it.
What Bobbin Does
- Hybrid search — semantic + keyword results fused via Reciprocal Rank Fusion. Ask in natural language or grep by pattern.
- Git temporal coupling — discovers files that change together in your commit history, revealing hidden dependencies no import graph can see.
- Task-aware context assembly —
bobbin context "fix the login bug"builds a budget-controlled bundle of the most relevant code, ready for an AI agent. - MCP server —
bobbin serveexposes 12 tools to Claude Code, Cursor, and any MCP-compatible agent. - Claude Code hooks — automatically injects relevant code context into every prompt, and primes new sessions with project overview and index stats.
- GPU-accelerated indexing — CUDA support for 10-25x faster embedding on NVIDIA GPUs. Index 57K chunks in under 5 minutes.
Quick Start
cargo install bobbin
cd your-project
bobbin init && bobbin index
bobbin search "error handling"
See Installation and Quick Start for full setup instructions.
Navigate This Book
| Section | What You’ll Find |
|---|---|
| Getting Started | Installation, first index, core concepts, agent setup |
| Guides | Searching, context assembly, git coupling, hooks, multi-repo |
| CLI Reference | Every command with flags, examples, and output formats |
| MCP Integration | AI agent tools, client configuration, HTTP mode |
| Configuration | Full .bobbin/config.toml reference |
| Architecture | System design, storage, embedding pipeline |
| Evaluation | Methodology, results across ruff/flask/polars, metrics |
Installation
Bobbin is a Rust application distributed via Cargo. It runs entirely locally — no API keys, no cloud services, no data leaves your machine.
Requirements
- Rust toolchain (1.75+): Install via rustup
- Git: Required for temporal coupling analysis
- C compiler: Required by tree-sitter build (usually pre-installed on Linux/macOS)
Install from Source
cargo install bobbin
This builds an optimized release binary with LTO enabled and installs it to ~/.cargo/bin/bobbin.
Build from Repository
git clone https://github.com/scbrown/bobbin.git
cd bobbin
cargo build --release
The binary is at target/release/bobbin.
First-Run Behavior
On first use, bobbin automatically downloads the embedding model (all-MiniLM-L6-v2, ~23 MB) to a local cache directory. This is a one-time download — subsequent runs use the cached model.
The model cache location follows platform conventions:
- Linux:
~/.cache/bobbin/models/ - macOS:
~/Library/Caches/bobbin/models/
Verify Installation
bobbin --version
Shell Completions
Generate completions for your shell:
bobbin completions bash > ~/.local/share/bash-completion/completions/bobbin
bobbin completions zsh > ~/.zfunc/_bobbin
bobbin completions fish > ~/.config/fish/completions/bobbin.fish
Next Steps
- Quick Start — initialize and search your first repository
- Agent Setup — connect bobbin to Claude Code, Cursor, or other AI tools
Quick Start
Get bobbin running on your codebase in under two minutes.
1. Initialize
Navigate to your project and initialize bobbin:
cd your-project
bobbin init
This creates a .bobbin/ directory containing configuration (config.toml), a SQLite database for coupling data, and a LanceDB vector store.
2. Index
Build the search index:
bobbin index
Bobbin walks your repository (respecting .gitignore), parses source files with tree-sitter into semantic chunks (functions, classes, structs, etc.), generates 384-dimensional embeddings using a local ONNX model, and stores everything in LanceDB.
Indexing a typical project (10k–50k lines) takes 10–30 seconds.
3. Search
Find code by meaning:
bobbin search "error handling"
This runs a hybrid search combining semantic similarity (vector search) with keyword matching (full-text search), fused via Reciprocal Rank Fusion. Results show the file, function name, line range, and a content preview.
4. Explore More
Try these commands to see what bobbin can do:
# Keyword/regex search
bobbin grep "TODO"
# Task-aware context assembly
bobbin context "fix the login bug"
# Find files that change together
bobbin related src/main.rs
# Find symbol definitions and usages
bobbin refs parse_config
# Identify high-churn, high-complexity files
bobbin hotspots
# Check index statistics
bobbin status
5. Interactive Tour
For a guided, interactive walkthrough of every feature, run:
bobbin tour
The tour runs each command against your actual repository, explaining what it does and how to use it. You can also tour a specific feature:
bobbin tour search
bobbin tour hooks
Next Steps
- Core Concepts — understand chunks, embeddings, hybrid search, and coupling
- Agent Setup — connect bobbin to an AI coding assistant via MCP
- Configuration — tune index patterns, search weights, and embedding settings
Core Concepts
Bobbin’s design is built around a few key ideas. Understanding them will help you get the most out of the tool.
Chunks
A chunk is a semantic unit of code extracted from a source file. Rather than indexing entire files or arbitrary line ranges, bobbin uses tree-sitter to parse source code into meaningful structural units:
| Chunk Type | Languages | Example |
|---|---|---|
function | Rust, TypeScript, Python, Go, Java, C++ | fn parse_config(...) |
method | TypeScript, Java, C++ | class.handleRequest() |
class | TypeScript, Python, Java, C++ | class AuthService |
struct | Rust, Go, C++ | struct Config |
enum | Rust, Java, C++ | enum Status |
interface | TypeScript, Java | interface Handler |
trait | Rust | trait Serialize |
impl | Rust | impl Config |
module | Rust | mod auth |
section | Markdown | ## Architecture |
table | Markdown | Markdown tables |
code_block | Markdown | Fenced code blocks |
Files that don’t match a supported language fall back to line-based chunking (50 lines per chunk with 10-line overlap).
See Chunk Types Reference for the complete list.
Embeddings
Each chunk is converted into a 384-dimensional vector using the all-MiniLM-L6-v2 model, run locally via ONNX Runtime. These vectors capture semantic meaning — similar code produces similar vectors, even when the wording differs.
Bobbin supports contextual embedding enrichment: before computing a chunk’s vector, it can prepend surrounding lines for additional context. This is configurable per language in [embedding.context].
Search Modes
Bobbin offers three search modes:
| Mode | How It Works | Best For |
|---|---|---|
| Hybrid (default) | Combines semantic + keyword via RRF | General-purpose queries |
| Semantic | Vector similarity (ANN) only | Conceptual queries (“authentication logic”) |
| Keyword | Full-text search (FTS) only | Exact identifiers (“handleRequest”) |
Reciprocal Rank Fusion (RRF) merges the ranked results from both semantic and keyword search. The semantic_weight config (default: 0.7) controls the balance.
See Search Modes Reference for details.
Temporal Coupling
Temporal coupling measures how often two files change together in git history. If auth.rs and middleware.rs frequently appear in the same commits, they have high coupling — modifying one likely means you should look at the other.
Bobbin analyzes git history (configurable depth, default: 1000 commits) and stores coupling scores in SQLite. This data powers:
bobbin related <file>— list files coupled to a given filebobbin context <query>— automatically expand search results with coupled files
Context Assembly
The context command combines search and coupling into a single context bundle:
- Search: Find chunks matching your query
- Expand: Add temporally coupled files for each match
- Deduplicate: Remove redundant chunks across files
- Budget: Trim to fit a line budget (default: 500 lines)
The result is a focused set of code that’s relevant to a task — ideal for feeding to an AI agent or understanding a change’s scope.
Hotspots
A hotspot is a file that is both frequently changed (high churn) and complex (high AST complexity). Hotspot score is the geometric mean of normalized churn and complexity. These files represent the riskiest parts of a codebase — they change often and are hard to change safely.
Storage
Bobbin uses two storage backends:
| Store | Technology | Contents |
|---|---|---|
| Primary | LanceDB | Chunks, vector embeddings, full-text search index |
| Metadata | SQLite | Temporal coupling data, file metadata |
All data lives in .bobbin/ within your repository. Nothing is sent externally.
Next Steps
- Agent Setup — connect bobbin to AI coding tools
- Searching Guide — advanced search techniques
- Architecture Overview — deeper dive into internals
Agent Setup
Bobbin integrates with AI coding assistants through the Model Context Protocol (MCP). This gives your AI agent semantic search, code coupling analysis, and context assembly capabilities over your codebase.
Claude Code
Option 1: MCP Server (Recommended)
Add bobbin as an MCP server in your Claude Code configuration:
Project-level (.claude/settings.json):
{
"mcpServers": {
"bobbin": {
"command": "bobbin",
"args": ["serve"],
"env": {}
}
}
}
Global (~/.claude/settings.json): Same format, applies to all projects.
Once configured, Claude Code can use bobbin’s tools (search, grep, context, related, find_refs, list_symbols, read_chunk, hotspots, prime) directly in conversation.
Option 2: Hook Integration
For automatic context injection on every prompt (no manual tool calls needed):
bobbin hook install
This registers hooks in Claude Code’s settings.json that:
- On every prompt (
UserPromptSubmit): Search your codebase for code relevant to the prompt and inject it as context. - After compaction (
SessionStart): Restore codebase awareness when context is compressed.
You can also install the git hook for automatic re-indexing:
bobbin hook install-git-hook
See hook CLI reference for configuration options (--threshold, --budget, --global).
Both Together
MCP server and hooks complement each other:
- Hooks provide passive, automatic context on every prompt
- MCP tools let the agent actively search, explore, and analyze code
# Set up both
bobbin hook install
# Add MCP server to .claude/settings.json (see above)
Cursor
Add bobbin as an MCP server in Cursor’s settings:
.cursor/mcp.json:
{
"mcpServers": {
"bobbin": {
"command": "bobbin",
"args": ["serve"]
}
}
}
Other MCP Clients
Any MCP-compatible client can connect to bobbin. The server communicates via stdio by default:
bobbin serve # MCP server on stdio
bobbin serve --server # HTTP REST API instead
For remote or shared deployments, see HTTP Mode.
Verifying the Connection
Once configured, your AI agent should have access to bobbin’s tools. Test by asking it to:
- “Search for error handling code” (uses
searchtool) - “What files are related to
src/main.rs?” (usesrelatedtool) - “Find the definition of
parse_config” (usesfind_refstool)
Prerequisites
Before connecting an agent, make sure your repository is initialized and indexed:
bobbin init
bobbin index
Next Steps
- MCP Overview — how the MCP integration works
- MCP Tools Reference — all available tools and their parameters
- Client Configuration — detailed configuration for each client
Searching
Bobbin gives you two ways to search your codebase: semantic search that understands what your code does, and keyword grep that matches exact text. By default, both run together in hybrid mode, giving you the best of both worlds.
When to use which
Semantic search shines when you know what you’re looking for but not what it’s called. Ask a question in plain English and bobbin finds relevant code even if your query shares no words with the source.
Keyword grep is better when you know the exact identifier, error message, or string literal. It searches the full-text index built during bobbin index.
Hybrid mode (the default) runs both and merges the results using Reciprocal Rank Fusion (RRF). This is the right choice most of the time.
Your first search
After running bobbin init and bobbin index, try a natural-language query:
bobbin search "how does authentication work"
Bobbin embeds your query into the same vector space as your code, finds the closest chunks, and also runs a keyword search. The merged results appear ranked by relevance.
Choosing a search mode
Use --mode to pick a specific engine:
# Hybrid (default) — best general-purpose choice
bobbin search "database connection pooling"
# Semantic only — good for conceptual queries
bobbin search "retry logic with exponential backoff" --mode semantic
# Keyword only — good for exact identifiers
bobbin search "MAX_RETRY_COUNT" --mode keyword
Semantic-only mode is useful when your query is a description rather than code. Keyword-only mode avoids false positives when you’re hunting for a specific symbol name.
Filtering results
By chunk type
Bobbin parses code into structural chunks: functions, classes, structs, enums, traits, interfaces, impl blocks, modules, and documentation sections. Narrow your search with --type:
# Only functions
bobbin search "parse config file" --type function
# Only structs
bobbin search "database connection" --type struct
# Only documentation frontmatter
bobbin search "retry behavior" --type doc
For markdown files, additional chunk types let you target specific document structures:
# Heading-delimited sections
bobbin search "deployment steps" --type section
# Tables (API references, config options, comparison charts)
bobbin search "HTTP status codes" --type table
# Fenced code blocks (examples, snippets)
bobbin search "docker compose" --type code_block
See Indexing Documentation for a full guide to searching docs.
By repository
In a multi-repo setup, filter to a single repository:
bobbin search "auth middleware" --repo backend
Adjusting result count
The default limit is 10. Increase it when you need a broader view:
bobbin search "error handling" --limit 30
Keyword grep
bobbin grep searches the full-text index for exact terms and patterns:
# Simple keyword search
bobbin grep "TODO"
# Case-insensitive
bobbin grep "handlerequest" --ignore-case
# Regex post-filter (FTS results are filtered by the regex)
bobbin grep "fn.*test" --regex
# With surrounding context lines
bobbin grep "deprecated" --type function --context 2
Grep is fast because it queries the FTS index built at index time rather than scanning files on disk.
Practical workflows
Exploring unfamiliar code
When you join a new project and want to understand the authentication flow:
bobbin search "user authentication flow"
bobbin search "login endpoint" --type function
bobbin search "session management"
Each query returns the most relevant code chunks with file paths and line ranges, giving you an instant map of where things live.
Tracking down a bug
You see an error message in production logs. Start with a keyword search to find where it originates:
bobbin grep "connection refused: retry limit exceeded"
Then broaden with semantic search to find related retry and connection logic:
bobbin search "connection retry and timeout handling"
Finding usage patterns
Need to see how a specific API is used across the codebase?
bobbin grep "DatabasePool::new" --context 3
The --context flag shows lines around each match, so you can see initialization patterns without opening every file.
JSON output for scripting
Both search and grep support --json for integration with other tools:
# Pipe search results into jq
bobbin search "auth" --json | jq '.results[].file_path'
# Feed grep results to another script
bobbin grep "FIXME" --json | jq '.results[] | "\(.file_path):\(.start_line)"'
Tuning search quality
Semantic weight
The [search] section in .bobbin/config.toml controls hybrid weighting:
[search]
semantic_weight = 0.7 # 0.0 = keyword only, 1.0 = semantic only
Raise this if your queries tend to be natural-language descriptions. Lower it if you mostly search for identifiers.
Contextual embeddings
For better semantic retrieval on documentation-heavy projects, enable contextual embedding enrichment:
[embedding.context]
context_lines = 5
enabled_languages = ["markdown", "python"]
This includes surrounding lines when computing each chunk’s vector, improving retrieval quality.
Advanced search features
Recency weighting
Recently modified files get a scoring boost. This helps surface actively-maintained code over stale artifacts.
[search]
recency_half_life_days = 30.0 # After 30 days, boost drops to 50%
recency_weight = 0.3 # Max 30% score penalty for old files (0.0 = disabled)
The formula: score * (1.0 - weight + weight * decay). At recency_weight = 0.3, a very old file loses at most 30% of its score. At 1.0, old files can lose 100%.
Set recency_weight = 0.0 to disable entirely (treat all files equally regardless of age).
Doc demotion
Documentation and config files naturally score high on many queries because they describe everything. Doc demotion reduces their ranking so source code surfaces first.
[search]
doc_demotion = 0.3 # Multiply doc/config RRF scores by 0.3 (70% penalty)
Values: 1.0 = no demotion, 0.0 = completely suppress docs. Only affects files classified as documentation or config — source and test files are untouched.
Bridge mode
Bridging discovers source files through documentation and commit context. When a search matches a doc file that references auth.rs, bridging can find and include auth.rs even if it didn’t match directly.
Four modes:
| Mode | Behavior |
|---|---|
off | No bridging (baseline) |
inject | Add discovered files as new results (default) |
boost | Boost scores of files already in results |
boost_inject | Both: boost existing + inject undiscovered |
Bridge mode is configured in calibration.json (via bobbin calibrate --bridge-sweep) or in context assembly config. The bridge_boost_factor controls how much existing scores increase: final_score *= (1.0 + factor).
Keyword-triggered repo scoping
In multi-repo setups, certain queries should automatically scope to specific repos. Configure this in [hooks]:
[[hooks.keyword_repos]]
keywords = ["ansible", "playbook", "deploy"]
repos = ["goldblum", "homelab-mcp"]
[[hooks.keyword_repos]]
keywords = ["bobbin", "search", "index"]
repos = ["bobbin"]
When any keyword matches (case-insensitive substring), search is scoped to those repos instead of searching everywhere. This reduces noise when the query clearly targets a specific domain.
Repo affinity boost
Files from the agent’s current repo get a configurable score multiplier:
[hooks]
repo_affinity_boost = 2.0 # 2x score for local repo files (1.0 = disabled)
This biases results toward the repo the agent is working in, which is usually what you want.
RRF constant (rrf_k)
Controls how Reciprocal Rank Fusion merges semantic and keyword results:
[search]
rrf_k = 60.0 # Standard value
Lower values make top-ranked results dominate more. Higher values flatten the score distribution, giving lower-ranked results more influence. Most users should leave this at 60.0.
Calibrating all parameters
Rather than tuning manually, use bobbin calibrate to auto-tune:
bobbin calibrate --apply # Quick sweep of core params
bobbin calibrate --full --apply # Extended: also tunes recency + coupling
bobbin calibrate --bridge-sweep --apply # Sweep bridge mode using calibrated core
See calibrate CLI reference for details.
Next steps
- Context Assembly — use search results as seeds for a broader context bundle
- Tags & Effects — boost or demote results by tag patterns
- Deps & Refs — follow import chains and symbol references
- Access Control — role-based result filtering
searchCLI reference — full flag referencegrepCLI reference — full flag reference
Indexing Documentation
Bobbin isn’t just for source code. Its markdown parser understands headings, tables, code blocks, and YAML frontmatter, making it a powerful tool for indexing documentation repos, wikis, and knowledge bases built with tools like mdBook, Sphinx, MkDocs, or Docusaurus.
Why index your docs?
Documentation scattered across repos, wikis, and knowledge bases is hard to search. A keyword search for “authentication” returns hundreds of hits without context. Bobbin’s semantic search lets you ask questions like “how does the OAuth flow work” and get back the specific documentation sections that answer your question.
Getting started
Step 1: Initialize bobbin
cd ~/docs-repo
bobbin init
Step 2: Configure for documentation
Markdown files (**/*.md) are included by default. For a pure docs repo, you can tighten the include patterns and skip irrelevant directories:
[index]
include = [
"**/*.md",
]
exclude = [
"**/node_modules/**",
"**/build/**",
"**/dist/**",
"**/.git/**",
"**/site/**", # MkDocs build output
"**/book/**", # mdBook build output
]
Step 3: Index
bobbin index
Bobbin parses each markdown file into structural chunks:
- Sections — content under each heading, with the full heading hierarchy preserved
- Tables — standalone table chunks for easy lookup
- Code blocks — fenced code blocks extracted as individual chunks
- Frontmatter — YAML frontmatter metadata captured as
docchunks
Step 4: Search
bobbin search "how does deployment work"
How markdown parsing works
Bobbin uses pulldown-cmark to parse markdown into structural chunks. Understanding the chunk types helps you write more effective queries.
Sections
Every heading creates a section chunk containing the heading and all content up to the next heading of the same or higher level. Section names include the full heading hierarchy:
# API Reference → "API Reference"
## Authentication → "API Reference > Authentication"
### OAuth Flow → "API Reference > Authentication > OAuth Flow"
## Rate Limiting → "API Reference > Rate Limiting"
This means searching for “Authentication > OAuth” matches exactly the right section, even in a large document with many headings.
Frontmatter
YAML frontmatter at the top of a markdown file is extracted as a doc chunk:
---
title: Deployment Guide
tags: [ops, deployment, kubernetes]
status: published
---
# Deployment Guide
...
The frontmatter chunk captures the full YAML block, so you can search for metadata like tags and titles.
Tables
Markdown tables are extracted as standalone table chunks named after their parent section:
## HTTP Methods
| Method | Description | Idempotent |
|--------|---------------|------------|
| GET | Retrieve | Yes |
| POST | Create | No |
| PUT | Replace | Yes |
| DELETE | Remove | Yes |
This table becomes a chunk named “HTTP Methods (table)”, searchable independently from the surrounding text.
Code blocks
Fenced code blocks are extracted as code_block chunks, named by their language tag:
## Installation
```bash
pip install mypackage
```
This produces a chunk named “code: bash” linked to the “Installation” section.
Searching documentation
By section content
Natural-language queries work well for finding documentation sections:
bobbin search "how to configure the database connection"
bobbin search "troubleshooting SSL certificate errors"
By chunk type
Filter to specific markdown chunk types with --type:
# Find documentation sections about authentication
bobbin search "authentication" --type section
# Find tables (API references, config options, comparison charts)
bobbin search "configuration options" --type table
# Find code examples
bobbin search "kubernetes deployment" --type code_block
# Find frontmatter (metadata, tags, document properties)
bobbin search "status: draft" --type doc
Grep for exact content
Use bobbin grep when you know the exact text:
# Find all documents tagged with "deprecated"
bobbin grep "deprecated" --type doc
# Find sections mentioning a specific API endpoint
bobbin grep "/api/v2/users" --type section
# Find all bash code examples
bobbin grep "#!/bin/bash" --type code_block
Documentation repo patterns
mdBook
mdBook projects have a src/ directory with markdown files and a SUMMARY.md:
[index]
include = ["**/*.md"]
exclude = ["**/book/**"] # Skip build output
bobbin index --source ./src
MkDocs
MkDocs uses docs/ as the source directory:
[index]
include = ["**/*.md"]
exclude = ["**/site/**"] # Skip build output
bobbin index --source ./docs
Sphinx (with MyST)
Sphinx projects using MyST markdown have .md files alongside .rst:
[index]
include = ["**/*.md"]
exclude = ["**/_build/**"]
Wiki repositories
Git-based wikis (GitHub Wiki, GitLab Wiki) are plain directories of markdown files:
git clone https://github.com/org/repo.wiki.git
cd repo.wiki
bobbin init && bobbin index
Tuning for documentation
Contextual embeddings
For documentation-heavy projects, contextual embedding enrichment improves search quality by including surrounding context when computing each chunk’s vector:
[embedding.context]
context_lines = 5
enabled_languages = ["markdown"]
This is enabled for markdown by default. Increase context_lines if your documents have short sections that need more surrounding context to be meaningful.
Semantic weight
Documentation queries tend to be natural language rather than exact identifiers. Consider raising the semantic weight:
[search]
semantic_weight = 0.8
Practical workflows
Searching a knowledge base
You maintain an internal knowledge base with hundreds of documents. A new team member asks “how do we handle incident response?”
bobbin search "incident response process"
bobbin search "on-call rotation and escalation" --type section
bobbin search "severity levels" --type table
Finding outdated documentation
Grep for markers that indicate staleness:
bobbin grep "TODO" --type section
bobbin grep "FIXME" --type section
bobbin grep "deprecated" --type doc
bobbin grep "status: draft" --type doc
Cross-referencing code and docs
Index both your code and documentation into the same bobbin store:
bobbin index --repo api --source ~/projects/api
bobbin index --repo docs --source ~/projects/docs
Now search across both:
# Find the code for something mentioned in docs
bobbin search "rate limiting implementation" --repo api
# Find the docs for something you see in code
bobbin search "RateLimiter configuration" --repo docs
Next steps
- Searching — general search techniques
- Multi-Repo — indexing docs alongside code repos
- Chunk Types — full reference for markdown chunk types
- Configuration Reference — tuning settings for doc-heavy projects
Context Assembly
When you need to understand a task’s full scope, searching for individual code snippets isn’t enough. The bobbin context command builds a context bundle — a curated set of code chunks plus their temporally coupled neighbors — sized to fit a budget you control.
How it works
Context assembly follows three steps:
- Search — runs hybrid search for your query and collects the top-ranked code chunks.
- Coupling expansion — for each result, looks up files that frequently change together in git history (temporal coupling) and pulls in related chunks.
- Budget fitting — trims the assembled context to stay within a line budget, prioritizing the highest-relevance items.
The result is a focused package of code that’s relevant to your task and includes the surrounding files you’d likely need to touch.
Basic usage
bobbin context "fix the login validation bug"
This returns up to 500 lines (the default budget) of context: the most relevant code chunks for “login validation” plus files that are temporally coupled to them.
Controlling the output
Budget
The --budget flag sets the maximum number of content lines:
# Small, focused context
bobbin context "auth token refresh" --budget 200
# Larger context for a broad refactoring task
bobbin context "refactor database layer" --budget 2000
A tighter budget forces bobbin to be more selective, keeping only the highest-scored results. A larger budget includes more coupled files and lower-ranked matches.
Content mode
Choose how much code to include per chunk:
# Full source code (good for feeding to an AI agent)
bobbin context "add caching to API" --content full
# 3-line preview per chunk (default in terminal)
bobbin context "add caching to API" --content preview
# Paths and metadata only (useful for planning)
bobbin context "add caching to API" --content none
full is the most useful mode when you’re assembling context for an AI coding assistant — it gets the actual code, not just pointers to it.
preview is the default when output goes to a terminal. It gives you enough to decide if a result is relevant without flooding your screen.
none returns file paths, line ranges, chunk types, and relevance scores. Useful when you just need to know which files matter.
Coupling depth
Control how aggressively bobbin expands via temporal coupling:
# No coupling expansion — search results only
bobbin context "auth" --depth 0
# One level of coupling (default)
bobbin context "auth" --depth 1
# Two levels — coupled files of coupled files
bobbin context "auth" --depth 2
Depth 0 gives you raw search results. Depth 1 adds files that change alongside those results. Depth 2 goes one step further, which is useful for understanding ripple effects across a codebase but can get noisy.
Fine-tuning coupling
# Only include strongly coupled files
bobbin context "auth" --coupling-threshold 0.3
# Include more coupled files per seed
bobbin context "auth" --max-coupled 5
The --coupling-threshold filters out weak relationships. The default of 0.1 is permissive — raise it if your context is pulling in too many loosely related files.
Practical workflows
Feeding context to an AI agent
The primary use case for bobbin context is giving an AI coding assistant the right code for a task:
# Assemble context and pipe it to clipboard
bobbin context "implement rate limiting for API endpoints" --content full --budget 1000 | pbcopy
Paste the output into your AI conversation. The context bundle includes file paths, line ranges, and full source code — everything the agent needs to understand and modify the relevant code.
Scoping a refactoring task
Before starting a refactoring, use context assembly to understand what you’ll need to touch:
bobbin context "error handling patterns" --content none --budget 1000
The --content none output gives you a manifest of files, chunk types, and coupling relationships. Use it as a checklist.
Understanding blast radius
When you’re about to change a widely-used module, check what’s coupled to it:
bobbin context "DatabasePool configuration" --depth 2 --content preview
Depth 2 shows you not just what directly uses the database pool, but what changes alongside those users. This reveals the true blast radius of your change.
JSON output for automation
bobbin context "auth" --json | jq '.files[].path'
The JSON output includes relevance scores, coupling scores, budget usage, and full chunk metadata — useful for building custom tooling on top of bobbin.
How coupling expansion works
During indexing, bobbin analyzes git history to find files that frequently appear in the same commits. This is called temporal coupling. The [git] section in your config controls the analysis:
[git]
coupling_enabled = true
coupling_depth = 1000 # Commits to analyze
coupling_threshold = 3 # Minimum co-changes to establish a link
When bobbin context runs with --depth 1 (the default), it:
- Takes each search result file.
- Looks up its top coupled files (limited by
--max-coupled, default 3). - Filters by
--coupling-threshold(default 0.1). - Includes relevant chunks from those coupled files in the context bundle.
This means if auth.rs frequently changes alongside session.rs and middleware.rs, a query about authentication will automatically surface all three.
Next steps
- Git Coupling — deep dive into temporal coupling analysis
- Searching — understand the search engine that seeds context assembly
contextCLI reference — full flag reference
Git Coupling
Code that changes together belongs together — or at least, you need to know about it together. Bobbin’s temporal coupling analysis mines your git history to discover which files are linked by shared change patterns, even when they have no import relationship.
What is temporal coupling?
Temporal coupling measures how often two files appear in the same git commit. If auth.rs and session.rs are modified in 30 of the same 50 commits, they have a high coupling score. This signal is independent of the language, the import graph, or the directory structure.
This matters because:
- Hidden dependencies — files may be logically coupled without any import between them (a schema file and the code that queries it, a test file and its fixtures).
- Change propagation — when you modify one file, coupled files are likely candidates for coordinated changes.
- Code review scope — temporal coupling reveals what a reviewer should look at beyond the diff.
How bobbin computes coupling
During bobbin index, if [git].coupling_enabled is true (the default), bobbin walks the last N commits (controlled by coupling_depth, default 1000) and records which files change together. The coupling score between two files is:
score = co_changes / min(changes_a, changes_b)
Where co_changes is the number of commits touching both files, and changes_a/changes_b are their individual commit counts. A pair needs at least coupling_threshold (default 3) co-changes to be stored.
Finding related files
The bobbin related command shows temporal coupling for a specific file:
bobbin related src/auth.rs
Output lists files ranked by coupling score, highest first:
src/session.rs 0.82 (shared 41 of 50 commits)
src/middleware/auth.rs 0.64 (shared 32 of 50 commits)
tests/auth_test.rs 0.58 (shared 29 of 50 commits)
src/config.rs 0.24 (shared 12 of 50 commits)
Filtering by strength
Show only strongly coupled files:
bobbin related src/auth.rs --threshold 0.5
This filters out weak coupling signals, showing only files with a score above 0.5.
Adjusting result count
bobbin related src/auth.rs --limit 20
Practical workflows
Before modifying a file
When you’re about to make changes to a file, check what else might need updating:
bobbin related src/database/pool.rs
If pool.rs is strongly coupled to migrations.rs and connection_config.rs, those files likely need attention when you change the pool implementation.
Understanding a module’s true boundaries
Directory structure doesn’t always reflect logical boundaries. Temporal coupling reveals the real modules:
# Check what's coupled to a core type
bobbin related src/types/user.rs --limit 15
You might discover that user.rs is tightly coupled to files in three different directories — that’s the actual module boundary for “user” functionality.
Finding missing test coverage
If a source file has high coupling with its test file, that test is actively maintained alongside the implementation. If there’s no test file in the coupling results, the tests may be stale or missing:
bobbin related src/parser.rs | grep test
No test files in the output? That’s a signal worth investigating.
Code review preparation
Before reviewing a PR that touches api/handlers.rs, check what the author might have missed:
bobbin related src/api/handlers.rs --threshold 0.3
If the coupling analysis shows api/validators.rs should usually change alongside handlers but the PR doesn’t touch it, that’s worth flagging.
Coupling in context assembly
Temporal coupling is also used by bobbin context to expand search results. When you run:
bobbin context "fix authentication bug" --depth 1
Bobbin finds code matching your query, then pulls in temporally coupled files. This is why context assembly often surfaces files you didn’t directly search for but will need to understand.
See Context Assembly for details on how coupling expansion works within the context pipeline.
Configuration
Control coupling analysis in .bobbin/config.toml:
[git]
# Enable/disable temporal coupling analysis
coupling_enabled = true
# How many commits to analyze (more = more accurate, slower indexing)
coupling_depth = 5000
# Minimum co-changes to establish a coupling link
coupling_threshold = 3
coupling_depth — controls how far back to scan for co-change patterns. The default of 5000 covers most project histories. Set to 0 to scan the full history.
coupling_threshold — raising this eliminates noise from files that coincidentally appeared in a few commits together. The default of 3 is a reasonable minimum.
Limitations
- Squashed merges lose information. If your workflow squashes feature branches into single commits, coupling analysis sees fewer data points.
- Merge commits are excluded (
--no-merges) since they don’t represent real co-changes. - Mega-commits (>50 files) are skipped automatically to prevent false coupling from reformats, renames, and dependency updates.
- New files have no history. Until a file has been modified in several commits, it won’t show meaningful coupling data.
Next steps
- Hotspots — combine coupling insights with complexity and churn analysis
- Context Assembly — coupling-aware context bundles
relatedCLI reference — full flag reference- Configuration Reference —
[git]settings
Hotspots
Not all code is equally risky. A file that’s both complex and frequently changed is far more likely to harbor bugs than one that’s simple or stable. Bobbin’s hotspot analysis identifies these high-risk files by combining git churn with AST-based complexity scoring.
The hotspot model
A hotspot is a file that scores high on two axes:
- Churn — how often the file has been modified in git history. High churn means the code is actively evolving, which increases the chance of introducing defects.
- Complexity — how structurally complex the file is, measured by analyzing its AST. Deep nesting, many branches, and large functions all contribute to higher complexity.
The hotspot score is the geometric mean of these two signals:
score = sqrt(churn_normalized * complexity)
A file must score high on both to be a hotspot. A simple file that changes constantly, or a complex file that never changes, won’t rank high. The intersection is what matters.
Finding hotspots
bobbin hotspots
This shows the top 20 hotspots from the last year, ranked by score:
Score Churn Complexity File
0.823 47 0.72 src/cli/hook.rs
0.756 38 0.68 src/index/parser.rs
0.691 52 0.41 src/search/hybrid.rs
0.634 29 0.65 src/config.rs
...
Adjusting the time window
Narrow or widen the churn analysis period:
# Last 3 months — focus on recent activity
bobbin hotspots --since "3 months ago"
# Last 6 months
bobbin hotspots --since "6 months ago"
# All time
bobbin hotspots --since "10 years ago"
A shorter window emphasizes current hotspots. A longer window surfaces chronic problem files.
Filtering by score
Show only files above a minimum score:
bobbin hotspots --threshold 0.5
This is useful when you want a short, actionable list of the worst offenders.
Scoping to a directory
Analyze a specific subsystem:
bobbin hotspots --path src/search
Result count
bobbin hotspots --limit 10 # Top 10 only
bobbin hotspots --limit 50 # Broader view
How complexity is measured
Bobbin uses Tree-sitter to parse each file’s AST and compute a weighted complexity score in the range [0, 1]. The scoring considers:
- Nesting depth — deeply nested code (loops inside conditionals inside match arms) scores higher.
- Branch count — if/else chains, match arms, and ternary expressions add complexity.
- Function size — longer functions are harder to reason about.
- Structural density — how much logic is packed into a given span of code.
Non-code files (Markdown, JSON, YAML, TOML) and unsupported languages are excluded automatically.
Supported languages: Rust, TypeScript/JavaScript, Python, Go, Java, C, C++.
Practical workflows
Prioritizing refactoring
You have limited time for tech debt. Hotspots tell you where to focus:
bobbin hotspots --threshold 0.6 -n 10
The top 10 files above 0.6 are your highest-impact refactoring targets. Simplifying these files will reduce the most bug-prone, hardest-to-maintain code in your project.
Sprint planning
At the start of a sprint, check which files in the areas you’ll be working on are hotspots:
bobbin hotspots --path src/api --since "3 months ago"
If a hotspot is in your path, consider allocating time to simplify it before adding more features on top.
Tracking improvements over time
Run hotspot analysis before and after a refactoring effort:
# Before: snapshot current hotspots
bobbin hotspots --json > hotspots-before.json
# ... do the refactoring work ...
# After: compare
bobbin hotspots --json > hotspots-after.json
Use the JSON output to compare scores and verify that your refactoring actually reduced the hotspot score for the targeted files.
CI integration
Add hotspot analysis to your CI pipeline to catch regressions:
bobbin hotspots --json --threshold 0.8
If any file exceeds 0.8, fail the check or emit a warning. This prevents new code from becoming a hotspot without anyone noticing.
Combining with file history
For a hotspot that surprises you, dig into its change history:
bobbin history src/cli/hook.rs
The history output shows commit dates, authors, and messages. You’ll see why the file has high churn — is it active feature development, repeated bug fixes, or configuration changes?
Verbose output
For a deeper understanding of the scoring:
bobbin hotspots --verbose
Verbose mode includes a legend explaining the scoring methodology and shows both raw and normalized values.
Next steps
- Git Coupling — discover which files change together
- Deps & Refs — understand import chains for hotspot files
hotspotsCLI reference — full flag referencehistoryCLI reference — dig into file change history
Deps & Refs
Understanding how code connects — which files import what, where symbols are defined and used — is essential for navigating a codebase and planning changes. Bobbin provides two complementary tools: deps for import-level dependencies and refs for symbol-level references.
Import dependencies with deps
The deps command shows the import graph for a file. During indexing, bobbin extracts import, use, require, and equivalent statements from each file and resolves them to actual file paths on disk.
Forward dependencies
See what a file imports:
bobbin deps src/main.rs
Output shows each import and where it resolves to:
src/main.rs imports:
use crate::config::Config → src/config.rs
use crate::cli::run → src/cli/mod.rs
use crate::storage::LanceStore → src/storage/lance.rs
This tells you what main.rs depends on — the files that must exist and be correct for main.rs to work.
Reverse dependencies
Find out what depends on a file:
bobbin deps --reverse src/config.rs
Output shows every file that imports from config.rs:
Files that import src/config.rs:
src/main.rs use crate::config::Config
src/cli/init.rs use crate::config::Config
src/cli/index.rs use crate::config::Config
src/cli/search.rs use crate::config::Config
src/cli/hook.rs use crate::config::{Config, HooksConfig}
This is the blast radius of changing config.rs. Every file in this list might need attention if you modify Config’s API.
Both directions
See the full picture at once:
bobbin deps --both src/search/hybrid.rs
Symbol references with refs
While deps works at the file level, refs works at the symbol level. It finds where specific functions, types, and traits are defined and used.
Finding a symbol’s definition and usages
bobbin refs find Config
Output shows the definition location and every usage:
Definition:
struct Config — src/config.rs:10-25
Usages (12):
src/main.rs:5 use crate::config::Config;
src/cli/init.rs:3 use crate::config::Config;
src/cli/index.rs:8 let config = Config::load(path)?;
...
Filtering by symbol type
When a name is ambiguous, filter by type:
# Only struct definitions named "Config"
bobbin refs find --type struct Config
# Only functions named "parse"
bobbin refs find --type function parse
Listing symbols in a file
Get an overview of everything defined in a file:
bobbin refs symbols src/config.rs
Output:
src/config.rs (4 symbols):
struct Config lines 10-25
struct HooksConfig lines 30-48
impl Default for Config lines 50-70
fn load lines 72-95
Use --verbose to include signatures:
bobbin refs symbols --verbose src/config.rs
Practical workflows
Understanding a module before changing it
Before modifying a module, map its surface area:
# What does this module expose?
bobbin refs symbols src/search/hybrid.rs
# Who depends on it?
bobbin deps --reverse src/search/hybrid.rs
# What does it depend on?
bobbin deps src/search/hybrid.rs
This three-command sequence gives you a complete picture: what the module contains, what it needs, and what would break if you change its API.
Planning a safe rename
You want to rename a function. First, find every usage:
bobbin refs find process_batch
The output lists every file and line that references process_batch. This is your rename checklist.
Impact analysis
When changing a type’s definition, trace the impact:
# Where is SearchResult defined?
bobbin refs find --type struct SearchResult
# What files import the module containing it?
bobbin deps --reverse src/types.rs
Combine forward deps (what SearchResult depends on) with reverse deps (what depends on SearchResult) to understand the full impact chain.
Navigating unfamiliar code
You’re looking at a function that calls store.upsert_chunks(). Where is that defined?
bobbin refs find upsert_chunks
Bobbin shows you the definition file and line number. No IDE required.
Finding dead code candidates
List all symbols in a file, then check each for usages:
bobbin refs symbols src/utils.rs
bobbin refs find helper_function_a
bobbin refs find helper_function_b
If a symbol has a definition but zero usages, it may be dead code worth removing.
JSON output for scripting
Both commands support --json:
# Get all dependents as a JSON array
bobbin deps --reverse --json src/config.rs | jq '.dependents[].source_file'
# Get all usages of a symbol
bobbin refs find --json Config | jq '.usages[].file_path'
Deps vs refs vs related
These three tools answer different questions:
| Tool | Question | Level |
|---|---|---|
deps | What does this file import / what imports it? | File (import graph) |
refs | Where is this symbol defined / used? | Symbol (name resolution) |
related | What files change alongside this one? | File (git history) |
Use deps when you care about the build-time dependency graph. Use refs when you care about a specific identifier. Use related when you care about change-time coupling that may not follow the import graph.
Next steps
- Git Coupling — temporal relationships beyond the import graph
- Hotspots — find the most critical files to understand
depsCLI reference — full flag referencerefsCLI reference — full flag reference
Multi-Repo
Many projects span multiple repositories — a backend, a frontend, shared libraries, infrastructure configs. Bobbin’s multi-repo support lets you index all of them into a single store and search across everything at once, or filter to a specific repo when you need to.
How it works
Every chunk in bobbin’s index is tagged with a repository name. By default this is "default", but you can set it explicitly with the --repo flag during indexing. At search time, you can filter by repo name or search across all of them.
The index itself lives in a single .bobbin/ directory. Multiple repos feed into the same LanceDB vector store and SQLite metadata store.
Setting up multi-repo indexing
Step 1: Initialize bobbin
Pick a location for your shared index. This can be any directory — it doesn’t need to be inside any of the repositories:
mkdir ~/code-index && cd ~/code-index
bobbin init
Or use an existing repo’s .bobbin/ as the shared store:
cd ~/projects/backend
bobbin init
Step 2: Index each repository
Use --repo to name each repository and --source to point at its source directory:
# Index the backend
bobbin index --repo backend --source ~/projects/backend
# Index the frontend
bobbin index --repo frontend --source ~/projects/frontend
# Index shared libraries
bobbin index --repo shared-lib --source ~/projects/shared-lib
Each repository’s chunks are tagged with the name you provide. Files from different repos coexist in the same index.
Step 3: Search
Search across everything:
bobbin search "user authentication"
Results show which repo each chunk came from. Or filter to a specific repo:
bobbin search "user authentication" --repo backend
Practical workflows
Cross-repo search
You’re debugging an issue that spans the API and the frontend. Search both at once:
bobbin search "session token validation"
Results from both repos appear in a single ranked list, so you can see the backend’s token generation alongside the frontend’s token handling.
Scoped grep
Find a specific identifier within one repo:
bobbin grep "UserProfile" --repo frontend
This avoids noise from the backend’s different UserProfile type.
Cross-repo context assembly
Assemble context that spans repositories:
bobbin context "refactor the authentication flow" --content full
Context assembly pulls from all indexed repos by default. The coupling expansion step works within each repo’s git history (since repos have separate git histories), but the initial search spans everything.
To focus on one repo:
bobbin context "refactor auth" --repo backend --content full
Keeping the index current
Re-index individual repos as they change:
# Only re-index the backend (incremental)
bobbin index --repo backend --source ~/projects/backend --incremental
# Force re-index the frontend
bobbin index --repo frontend --source ~/projects/frontend --force
Incremental mode (--incremental) skips files whose content hash hasn’t changed, making updates fast.
Watch mode for multiple repos
Run separate watchers for each repo:
# Terminal 1: watch backend
bobbin watch --repo backend --source ~/projects/backend
# Terminal 2: watch frontend
bobbin watch --repo frontend --source ~/projects/frontend
Each watcher monitors its source directory and updates the shared index with the correct repo tag. See Watch & Automation for details on setting these up as background services.
Naming conventions
Choose repo names that are short and meaningful. They appear in search results and are used in --repo filters:
# Good — short, descriptive
bobbin index --repo api --source ~/projects/api-server
bobbin index --repo web --source ~/projects/web-client
bobbin index --repo infra --source ~/projects/infrastructure
# Avoid — too long or ambiguous
bobbin index --repo my-company-api-server-v2 --source ...
Documentation alongside code
A common multi-repo pattern is indexing documentation repos alongside their code counterparts. This lets you cross-reference docs and implementation:
# Index the code
bobbin index --repo api --source ~/projects/api-server
# Index the documentation
bobbin index --repo docs --source ~/projects/docs-site/src
# Index a wiki
bobbin index --repo wiki --source ~/projects/repo.wiki
Now you can search across both code and documentation:
# Find the docs explaining a feature
bobbin search "rate limiting configuration" --repo docs
# Find the code implementing what the docs describe
bobbin search "rate limiter middleware" --repo api
# Search everything at once
bobbin search "rate limiting"
You can also filter by chunk type across repos. For example, find all tables in documentation:
bobbin search "API endpoints" --repo docs --type table
Or find code examples in the docs:
bobbin search "authentication example" --repo docs --type code_block
For detailed guidance on indexing documentation, see Indexing Documentation.
Monorepo alternative
If your code is in a monorepo, you don’t need multi-repo indexing. A single bobbin index covers everything. But you might still use --repo to logically partition a monorepo:
cd ~/monorepo
bobbin init
# Index different top-level directories as separate "repos"
bobbin index --repo services --source ./services
bobbin index --repo packages --source ./packages
bobbin index --repo tools --source ./tools
This lets you filter searches to --repo services without the overhead of managing separate worktrees.
Limitations
- Git coupling is per-repo. Temporal coupling analysis uses each repository’s git history. Cross-repo coupling (files from different repos that change at the same time) is not tracked.
- Config is shared. All repos indexed into the same
.bobbin/share the sameconfig.tomlsettings (include/exclude patterns, embedding model, etc.). If repos need different include patterns, you’ll need to manage that at the indexing level. - No automatic discovery. You must explicitly index each repo. There’s no “scan this directory for repos” feature.
Next steps
- Watch & Automation — keep multi-repo indexes fresh
- Searching — search techniques that work across repos
- Context Assembly — cross-repo context bundles
indexCLI reference — full indexing options
Tags & Effects
Tags let you control how bobbin scores and filters search results. By assigning tags to chunks via pattern rules, then configuring effects (boost, demote, exclude, pin), you tune search quality without changing the indexing pipeline.
Overview
The tag system has three layers:
- Rules — Glob patterns that assign tags to files during indexing
- Effects — Score adjustments applied when tagged chunks appear in results
- Scoped Effects — Role-specific overrides (e.g., boost lifecycle docs only for witness)
Configuration lives in .bobbin/tags.toml at the root of your bobbin data directory.
Tags Configuration
Rules
Rules match file paths and assign tags:
[[rules]]
pattern = "**/CHANGELOG.md"
tags = ["type:changelog"]
[[rules]]
pattern = "**/ansible/roles/*/tasks/main.yml"
tags = ["domain:iac", "criticality:high"]
repo = "goldblum" # Optional: only apply when indexing this repo
pattern— Standard glob pattern matched against relative file pathstags— List of tag strings to assign (convention:namespace:value)repo— Optional repo scope (only applies during indexing of that repo)
Glob pattern note: Patterns are matched against paths relative to the repo root (e.g.,
snapshots/ian/2026-03-12.md, not the absolute path). The**/prefix matches both root-level and nested paths —**/CHANGELOG.mdmatches bothCHANGELOG.mdanddocs/CHANGELOG.md.
Effects
Effects modify scores when tagged chunks appear in search results:
[effects."type:changelog"]
boost = -0.6 # Demote: score *= (1 + boost) = 0.4
[effects."auto:init"]
exclude = true # Remove entirely from results
[effects."criticality:high"]
boost = 0.2 # Boost: score *= 1.2
[effects."feedback:hot"]
boost = 0.3
pin = true # Always include, bypass relevance threshold
budget_reserve = 20 # Reserve 20 lines of budget for pinned chunks
Score formula: final_score = raw_score * product(1 + boost) for all matching tags,
clamped to [0.01, 10.0].
| Field | Type | Description |
|---|---|---|
boost | float | Score multiplier. Positive = boost, negative = demote. |
exclude | bool | Remove chunks with this tag from results entirely. |
pin | bool | Bypass relevance threshold; always include if budget allows. |
budget_reserve | int | Lines of budget reserved for pinned chunks. |
Scoped Effects
Override global effects for specific roles:
# Globally demote lifecycle docs
[effects."domain:lifecycle"]
boost = -0.3
# But boost them for witness role
[[effects_scoped]]
tag = "domain:lifecycle"
role = "*/witness"
boost = 0.2
# Exclude internal docs for external users
[[effects_scoped]]
tag = "type:internal"
role = "external/*"
exclude = true
The role field supports glob patterns. When a request includes a role
(via --role flag or BOBBIN_ROLE env var), scoped effects override
global effects for matching roles.
Tag Assignment Sources
Tags are assigned from four sources during indexing. All sources merge — multiple matching rules union their tags into a comma-separated sorted string per chunk.
1. Convention Tags (auto-assigned)
| Tag | Applied to |
|---|---|
auto:init | Go init() functions |
auto:test | Test functions (Go, Rust, Python, JS) |
auto:docs | Documentation files (markdown, rst, etc.) |
auto:config | Config files (YAML, TOML, JSON, .env, etc.) |
auto:generated | Generated code (.min.js, .gen.go, .generated.ts, etc.) |
2. Pattern Rules (tags.toml)
Glob patterns matched against repo-relative paths. See Rules above.
3. Frontmatter Tags
Markdown files with YAML frontmatter between --- fences:
---
tags: [canonical, architecture]
---
Supported field names: tags, bobbin-tags, labels. Supports inline arrays, single values, and block lists. Tags without a : prefix are auto-prefixed with user: (e.g., canonical → user:canonical).
4. Code Comment Directives
Inline tag assignment in source code:
#![allow(unused)]
fn main() {
// bobbin:tag security critical
fn handle_auth() { ... }
bobbin:tag deprecated
def old_api():
}
Supports //, #, and /* */ comment styles. Tags are applied to the chunk containing the comment. Same user: auto-prefix as frontmatter.
Role Specificity
When multiple scoped effects match a role, the most specific pattern wins. Specificity is counted by non-wildcard path segments:
| Pattern | Specificity | Example match |
|---|---|---|
aegis/crew/stryder | 3 (most specific) | Exact agent |
*/crew/stryder | 2 | Any rig’s stryder |
*/crew/* | 1 | Any crew member |
* | 0 (least specific) | Everyone |
If a scoped effect matches, it fully replaces the global effect for that tag — it does not stack.
Tag Naming Conventions
Use namespace:value format for clarity:
| Namespace | Purpose | Examples |
|---|---|---|
auto: | Auto-assigned by bobbin | auto:test, auto:init |
type: | Document/chunk type | type:changelog, type:design, type:eval |
role: | Agent instruction files | role:claude-md, role:agents-md |
domain: | Domain/topic area | domain:iac, domain:lifecycle, domain:comms |
criticality: | Importance level | criticality:high |
feedback: | Feedback-driven scoring | feedback:hot, feedback:cold |
Scoring Mechanics
When tag effects are applied during context assembly, the formula is:
effective_factor = product(1 + boost for each matching tag effect)
final_score = raw_score * clamp(effective_factor, 0.01, 10.0)
Examples:
boost = 0.5→ factor = 1.5 → 50% score increaseboost = -0.8→ factor = 0.2 → 80% score decrease- Multiple tags:
canonical(boost +0.5) +architecture(boost +0.2) → factor = 1.5 × 1.2 = 1.8
When a chunk has any tag with a configured effect, the tag effect replaces the default doc_demotion. This means a doc file tagged criticality:high with boost = 0.3 gets a 30% boost instead of the usual doc demotion penalty.
Exclude vs demote vs pin
| Mechanism | When applied | Effect | Use case |
|---|---|---|---|
Exclude (exclude = true) | Pre-search (SQL WHERE clause) | Chunk never fetched from DB | Noise that always wastes budget |
Demote (negative boost) | Post-search scoring | Score reduced but chunk still included | Sometimes useful, usually low-priority |
Pin (pin = true) | Assembly stage (reserved budget) | Bypasses relevance threshold, injected first | Critical content that must always appear |
Excludes are the most efficient — chunks are filtered at the database level before scoring. Use them for content that is never useful (auto-generated files, boilerplate init functions).
Pinned chunks have reserved budget (budget_reserve lines, default 50) and are always injected regardless of query relevance. Use sparingly.
/search vs /context
The /search endpoint returns raw scores — only exclude filters are applied (pre-search WHERE clauses). Boost/demote/pin effects are NOT applied.
The /context endpoint and hook injection apply the full scoring pipeline: excludes + boosts + demotes + pins.
To verify tag effects are working, test with bobbin context or the /context API, not /search.
Scoped Effect Resolution
When resolving effects for a role, specificity wins:
- Check
[[effects_scoped]]entries matching both tag AND role glob - Most specific role pattern wins (count non-wildcard segments)
- Fall back to global
[effects.tag] - No match = no effect (default behavior)
[effects.test]
boost = -0.3 # Global: demote test files
[[effects_scoped]]
tag = "test"
role = "aegis/crew/sentinel"
boost = 0.3 # Sentinel: BOOST test files (review coverage)
Scoped effects can also un-exclude a globally excluded tag:
[effects.internal]
exclude = true # Global: always exclude
[[effects_scoped]]
tag = "internal"
role = "aegis/*"
exclude = false # Aegis agents CAN see internal content
Example: Reducing Noise
A common pattern is to identify noisy document types and demote them:
# Problem: Changelog entries surface in unrelated queries
[effects."type:changelog"]
boost = -0.6 # Demote to 40% of original score
[[rules]]
pattern = "**/CHANGELOG.md"
tags = ["type:changelog"]
# Problem: Eval task YAML files match cargo/build queries
[effects."type:eval"]
boost = -0.5
[[rules]]
pattern = "**/eval/tasks/**"
tags = ["type:eval"]
repo = "bobbin"
# But un-demote for the developer who works on evals
[[effects_scoped]]
tag = "type:eval"
role = "aegis/crew/stryder"
boost = 0.0
Applying Changes
After modifying tags.toml:
-
Reindex affected repos to apply new tag rules to chunks:
bobbin index /path/to/data --repo <name> --source /path/to/repo --force -
Restart the server if running in HTTP mode (tags config is loaded at startup):
sudo systemctl restart bobbin
Tag effects are applied during context assembly (/context endpoint), so
restarting the server picks up both new effect weights and new tag assignments
from the reindex.
Note: The
/searchendpoint returns raw relevance scores without tag effects. To verify that tag boosts/demotions are working, test with the/contextendpoint or thebobbin contextCLI command.
Debugging Tags
To see which tags are assigned to search results, use the JSON output:
bobbin search "your query" --json | jq '.results[].tags'
Check tag coverage after indexing:
✓ Indexed 942 files (12270 chunks)
Tags: 41096 tagged, 21327 untagged chunks
High untagged counts are normal — not every chunk needs tags. Focus rules on document types that cause noise (changelogs, templates, test files) or that need boosting (critical configs, design docs).
To filter search results by tag:
bobbin search "your query" --tag type:guide # Include only guides
bobbin search "your query" --exclude-tag type:eval # Exclude eval artifacts
Feedback System
Bobbin tracks what context it injects and lets agents rate it. Over time, feedback data drives automatic quality improvements — files that are consistently useful get boosted, files that are consistently noise get demoted.
How It Works
1. Injection Tracking
Every time bobbin injects context via hooks, it records:
- injection_id — Unique identifier for this injection
- query — The prompt that triggered the injection
- files/chunks — What was injected (paths, chunk IDs, content)
- agent — Which agent received the injection
- session_id — The Claude Code session
2. Feedback Collection
Agents rate injections as useful, noise, or harmful:
| Rating | Meaning |
|---|---|
useful | The injected code was relevant and helpful |
noise | The injection was irrelevant to the task |
harmful | The injection was actively misleading |
Feedback is collected automatically via the feedback_prompt_interval config:
[hooks]
feedback_prompt_interval = 5 # Prompt every 5 injections (0 = disabled)
Every N injections, bobbin prompts the agent to rate up to 3 unrated injections from the current session.
3. Feedback Tags
Accumulated feedback generates tags on files:
| Tag | Meaning |
|---|---|
feedback:hot | File is frequently rated as useful |
feedback:cold | File is frequently rated as noise |
These tags are loaded during indexing and merged into chunk tags. Combined with tag effects:
[effects."feedback:hot"]
boost = 0.3 # Boost frequently-useful files
pin = true # Always include if budget allows
[effects."feedback:cold"]
boost = -0.3 # Demote frequently-noisy files
HTTP API
| Endpoint | Method | Description |
|---|---|---|
/feedback | POST | Submit a rating: {injection_id, agent, rating, reason} |
/feedback | GET | List feedback with filters: ?rating=noise&agent=stryder&limit=50 |
/feedback/stats | GET | Aggregated stats: total injections, ratings by type, coverage |
/injections/{id} | GET | View injection detail: query, files, formatted output, feedback |
/feedback/lineage | POST | Record an action that resolves feedback |
/feedback/lineage | GET | List resolution actions |
Lineage Tracking
When feedback leads to a fix (new tag rule, access control change, code fix), record it via lineage:
{
"action_type": "tag_effect",
"description": "Added type:changelog demotion to reduce changelog noise",
"commit_hash": "abc123",
"bead": "aegis-xyz",
"agent": "stryder"
}
Valid action types: access_rule, tag_effect, config_change, code_fix, exclusion_rule.
Lineage records link feedback to concrete improvements, creating an audit trail of search quality evolution.
Web UI
The Feedback tab in the web UI shows:
- Injection and feedback counts with coverage percentage
- Rating breakdown (useful/noise/harmful)
- Filterable feedback list by rating and agent
- Click any feedback record to see the full injection detail: what was injected, the query, files included, and all ratings
Best Practices
- Keep feedback_prompt_interval low (3-5) during initial deployment to build up data quickly
- Review noise patterns in the Feedback tab — repeated noise on the same files indicates missing tag rules or exclusions
- Use lineage to track what you changed in response to feedback — this closes the loop
- Check feedback:hot/cold tags after reindexing to verify the system is learning from ratings
Archive Integration
Bobbin can index structured markdown records alongside code — agent memories, communication logs, HLA records, or any collection of markdown files with YAML frontmatter. Archive records are searchable via the same search and context APIs as code.
Configuration
Archive sources are configured in .bobbin/config.toml:
[archive]
enabled = true
webhook_secret = "" # Optional: Forgejo webhook auth token
[[archive.sources]]
name = "pensieve"
path = "/var/lib/bobbin/archives/pensieve"
schema = "agent-memory"
name_field = "agent"
[[archive.sources]]
name = "hla"
path = "/var/lib/bobbin/archives/hla"
schema = "human-intent"
name_field = "channel"
Source fields
| Field | Type | Description |
|---|---|---|
name | string | Source label — used as language tag in chunks and as a search filter |
path | string | Filesystem path to the directory of markdown records |
schema | string | YAML frontmatter value to match (e.g., "agent-memory") — files without this in frontmatter are skipped |
name_field | string | Optional frontmatter field used to prefix chunk names (e.g., "channel" → "telegram/{record_id}") |
Record Format
Archive records are markdown files with YAML frontmatter:
---
schema: agent-memory
id: mem-2026-0322-abc
timestamp: 2026-03-22T12:00:00Z
agent: stryder
tags: [bobbin, search-quality]
---
## Context
Discovered that tag effects only apply via /context endpoint, not /search.
The CLI returns raw LanceDB scores without boosts.
The frontmatter must contain the schema value matching your source config. Other fields (id, timestamp, etc.) are extracted as metadata.
Field handling
id— Record identifier (used in chunk ID generation)timestamp— Parsed for date-based file path grouping (YYYY/MM/DD/)source:block — Nested keys are flattened (e.g.,source:\n channel: telegrambecomes fieldchannel)- Chunk IDs — Generated via
SHA256(source:id:timestamp)for deduplication
Searching Archives
Archive records appear in regular search results. Filter by source name:
bobbin search "agent memory about search quality" --repo pensieve
HTTP API
| Endpoint | Description |
|---|---|
GET /archive/search?q=<query>&source=<name>&limit=10 | Search archive records |
GET /archive/entry/{id} | Fetch a single record by ID |
GET /archive/recent?days=30&source=<name> | Recent records with optional date range |
Web UI
Toggle “Include archive” in the Search tab to merge archive results into code search.
Webhook Integration
For automatic re-indexing when archive sources are updated via git push:
[archive]
webhook_secret = "your-secret-token"
Configure a Forgejo/Gitea push webhook pointing to POST /webhook/push. When a push event matches a configured repo, bobbin triggers an incremental re-index of that source.
Use Cases
- Agent memories (pensieve): Index agent context snapshots for cross-agent search
- Communication logs (HLA): Index human-agent interaction records
- Knowledge bases: Index structured documentation collections
- Incident records: Index postmortem and investigation reports
Indexing
Archive sources are indexed alongside code during bobbin index. The --force flag re-indexes all records:
bobbin index /var/lib/bobbin --force
Records are chunked like markdown files — headings create chunk boundaries, with frontmatter metadata preserved as chunk attributes.
Watch & Automation
Bobbin’s index is only useful if it’s current. This guide covers three strategies for keeping your index up to date: filesystem watching for real-time updates, git hooks for commit-time indexing, and patterns for CI integration.
Strategy 1: Watch mode
The bobbin watch command monitors your filesystem for changes and re-indexes automatically:
bobbin watch
This starts a long-running process that:
- Detects file creates, modifications, and deletions using OS-native notifications (inotify on Linux, FSEvents on macOS).
Debouncesrapid changes (default 500ms) to batch them efficiently.- Re-parses, re-embeds, and upserts chunks for changed files.
- Removes chunks for deleted files.
- Skips files whose content hash hasn’t changed.
Press Ctrl+C to stop cleanly.
Tuning debounce
The debounce interval controls how long bobbin waits after a change before processing it. Lower values give faster feedback; higher values reduce CPU usage during bursts of changes:
# Fast feedback (200ms) — good for active development
bobbin watch --debounce-ms 200
# Slower batching (2000ms) — good for background daemon use
bobbin watch --debounce-ms 2000
The default of 500ms works well for most workflows.
Multi-repo watching
Run separate watchers for each repository in a multi-repo setup:
# Watch the backend repo
bobbin watch --repo backend --source ~/projects/backend
# Watch the frontend repo (in another terminal)
bobbin watch --repo frontend --source ~/projects/frontend
See Multi-Repo for the full multi-repo indexing setup.
Strategy 2: Git post-commit hook
If you don’t need real-time updates, a post-commit hook re-indexes after each commit:
bobbin hook install-git-hook
This creates (or appends to) .git/hooks/post-commit with a call to bobbin index. Only files changed in the commit are re-indexed.
Advantages over watch mode:
- No background process to manage.
- Only runs when you commit, not on every file save.
- Works well in CI environments where filesystem watchers aren’t practical.
Disadvantages:
- Index is stale between commits. Uncommitted changes aren’t reflected.
- Adds a small delay to each commit.
Remove the hook when you no longer want it:
bobbin hook uninstall-git-hook
Strategy 3: Explicit re-indexing
For maximum control, run bobbin index manually when you need it:
# Full re-index
bobbin index
# Incremental — only changed files
bobbin index --incremental
# Force re-index everything
bobbin index --force
Incremental mode checks file content hashes and skips unchanged files. It’s fast for routine updates.
Running watch as a background service
Using systemd (Linux)
Bobbin can generate a systemd user service unit:
bobbin watch --generate-systemd > ~/.config/systemd/user/bobbin-watch.service
systemctl --user daemon-reload
systemctl --user enable --now bobbin-watch
The generated unit:
- Uses
Type=simplewith automatic restart on failure. - Sets
RestartSec=5andRUST_LOG=info. - Includes the resolved working directory and any
--repo/--sourceflags.
Check status:
systemctl --user status bobbin-watch
journalctl --user -u bobbin-watch -f
Stop or disable:
systemctl --user stop bobbin-watch
systemctl --user disable bobbin-watch
Using a PID file
For non-systemd setups, use the --pid-file flag for daemon management:
bobbin watch --pid-file /tmp/bobbin-watch.pid &
Check if the watcher is running:
kill -0 $(cat /tmp/bobbin-watch.pid) 2>/dev/null && echo "running" || echo "stopped"
Stop it:
kill $(cat /tmp/bobbin-watch.pid)
Using tmux or screen
A simple approach for development machines:
# Start in a detached tmux session
tmux new-session -d -s bobbin-watch 'bobbin watch'
# Attach to check on it
tmux attach -t bobbin-watch
CI integration patterns
Index as a CI step
Add bobbin indexing to your CI pipeline so the index is always fresh on the main branch:
# GitHub Actions example
- name: Build bobbin index
run: |
bobbin init --if-needed
bobbin index --incremental
Hotspot checks in CI
Use hotspot analysis as a quality gate:
# Fail if any file exceeds a hotspot score of 0.8
HOTSPOTS=$(bobbin hotspots --json --threshold 0.8 | jq '.count')
if [ "$HOTSPOTS" -gt 0 ]; then
echo "Warning: $HOTSPOTS files exceed hotspot threshold"
bobbin hotspots --threshold 0.8
exit 1
fi
Pre-commit context check
Verify that changes to coupled files are coordinated:
# In a pre-commit or CI script
for file in $(git diff --name-only HEAD~1); do
bobbin related "$file" --threshold 0.5
done
This surfaces strongly coupled files that the commit might have missed.
Combining strategies
The strategies aren’t mutually exclusive. A common setup:
- Watch mode on your development machine for real-time updates during coding.
- Post-commit hook as a safety net in case the watcher wasn’t running.
- CI indexing to maintain a canonical index on the main branch.
The incremental indexing in each strategy means redundant runs are cheap — bobbin skips files that haven’t changed.
Troubleshooting
Watcher stops after a while
If the watcher exits silently, check the logs. Common causes:
- The
.bobbin/directory was deleted or moved. - Disk is full (LanceDB needs space for vector storage).
- inotify watch limit reached on Linux. Increase it:
echo 65536 | sudo tee /proc/sys/fs/inotify/max_user_watches
Index seems stale
Verify the watcher is actually running and processing events:
bobbin status
The status output shows when the index was last updated. If it’s older than expected, the watcher may have stopped or the debounce interval may be too high.
High CPU during large changes
A bulk operation (branch switch, large merge) triggers many file events. The debounce interval batches these, but indexing the batch can still be CPU-intensive. This is temporary and normal. If it’s disruptive, increase --debounce-ms.
Next steps
- Hooks — automatic context injection into Claude Code sessions
- Multi-Repo — watching multiple repositories
watchCLI reference — full flag referenceindexCLI reference — manual indexing options
Hooks
Bobbin’s hook system integrates with Claude Code to automatically inject relevant code context into every conversation. When you type a prompt, bobbin searches your index for code related to what you’re asking about and injects it before Claude sees the message. The result: Claude understands your codebase without you having to copy-paste code.
How hooks work
Bobbin installs two Claude Code hooks:
- UserPromptSubmit — fires on every prompt you send. Bobbin runs a semantic search against your query, and if the results are relevant enough, injects them as context that Claude can see.
- SessionStart (compact) — fires after Claude Code compacts its context window. Bobbin re-injects key context so Claude doesn’t lose codebase awareness after compaction.
Both hooks are non-blocking and add minimal latency to your interaction.
Setting up hooks
Prerequisites
You need a bobbin index first:
bobbin init
bobbin index
Install hooks
bobbin hook install
This adds hook entries to your project’s .claude/settings.json. From now on, every prompt in this project directory triggers automatic context injection.
For global installation (all projects):
bobbin hook install --global
Global hooks write to ~/.claude/settings.json and fire in every Claude Code session, regardless of project.
Verify installation
bobbin hook status
Output shows whether hooks are installed and the current configuration:
Claude Code hooks: installed
Git hook: not installed
Configuration:
threshold: 0.5
budget: 150 lines
content_mode: preview
min_prompt_length: 10
gate_threshold: 0.75
dedup: enabled
Tuning injection behavior
The defaults work for most projects, but you can tune how aggressively bobbin injects context.
Threshold
The threshold controls the minimum relevance score for a search result to be included in the injected context:
bobbin hook install --threshold 0.3 # More results, lower relevance bar
bobbin hook install --threshold 0.7 # Fewer results, higher quality
Lower values mean more code gets injected (broader context). Higher values mean only highly relevant code is injected (tighter focus).
You can also set this in .bobbin/config.toml:
[hooks]
threshold = 0.5
Budget
The budget limits the total lines of injected context:
bobbin hook install --budget 200 # More context
bobbin hook install --budget 50 # Minimal context
A larger budget gives Claude more code to work with. A smaller budget keeps injections concise and avoids overwhelming the context window.
[hooks]
budget = 150
Gate threshold
The gate threshold is a separate check that decides whether injection happens at all. Before injecting, bobbin checks the top semantic search result’s raw similarity score. If it falls below the gate threshold, the entire injection is skipped — your prompt isn’t relevant to the indexed code.
[hooks]
gate_threshold = 0.75 # Default: skip injection if top result < 0.75 similarity
Lower values make injection more eager (fires on loosely related prompts). Higher values make it more conservative (only fires when the prompt clearly relates to indexed code).
Minimum prompt length
Very short prompts (“yes”, “ok”, “continue”) don’t benefit from code injection. The min_prompt_length setting skips injection for prompts below a character threshold:
[hooks]
min_prompt_length = 10 # Default
Content mode
Controls how much code is included per result:
[hooks]
content_mode = "preview" # 3-line excerpts (default)
# content_mode = "full" # Full source code
# content_mode = "none" # Paths and metadata only
preview is the default and works well for most cases — it gives Claude enough to understand each result without consuming too much of the context window.
full is useful when you want Claude to have complete function implementations in context, not just previews.
Deduplication
By default, bobbin tracks what it injected in the previous prompt and skips re-injection if the results haven’t changed. This avoids flooding Claude’s context with the same code on follow-up messages about the same topic.
[hooks]
dedup_enabled = true # Default
Set to false if you want every prompt to get fresh injection regardless.
Progressive reducing
Over a long conversation, bobbin avoids re-injecting code you’ve already seen. The reducing system keeps a per-session ledger of every chunk injected and filters duplicates on subsequent turns.
[hooks]
reducing_enabled = true # Default
How it works:
- Each injection records which chunks were sent, stored in
.bobbin/session/<session_id>/ledger.jsonl - On the next prompt, bobbin filters out chunks already in the ledger
- Only new or changed chunks are injected
- The ledger resets on context compaction (SessionStart hook)
This means early prompts get full context, and follow-up prompts get only new relevant code — keeping the context window efficient.
When reducing fires, you’ll see output like:
bobbin: injecting 3 new chunks (7 previously injected, turn 4)
Set reducing_enabled = false to inject all matching chunks every time (useful for debugging).
Keyword-triggered repo scoping
In multi-repo setups, certain queries clearly target specific repos. Configure keyword rules to auto-scope search:
[[hooks.keyword_repos]]
keywords = ["ansible", "playbook", "deploy"]
repos = ["goldblum", "homelab-mcp"]
[[hooks.keyword_repos]]
keywords = ["bobbin", "search", "embedding"]
repos = ["bobbin"]
[[hooks.keyword_repos]]
keywords = ["beads", "bd ", "issue"]
repos = ["beads"]
When any keyword matches (case-insensitive substring), search is scoped to only those repos. If no keywords match, all repos are searched.
Multiple rules can match simultaneously — their repo lists are merged and deduplicated.
Repo affinity boost
Files from the agent’s current working directory repo get a score multiplier:
[hooks]
repo_affinity_boost = 2.0 # Default: 2x score for local repo
This is a soft bias — cross-repo results still appear, but local files rank higher. Set to 1.0 to disable.
Feedback prompts
Bobbin periodically prompts agents to rate injected context quality:
[hooks]
feedback_prompt_interval = 5 # Every 5 injections (0 = disabled)
Every N injections, bobbin shows unrated injection IDs and prompts:
bobbin: 3 unrated injections this session. Rate with:
bobbin feedback submit --injection <id> --rating <useful|noise|harmful>
Ratings feed into the Feedback System — over time, frequently useful code gets boosted and noisy code gets demoted.
Format modes
Control the output format of injected context:
[hooks]
format_mode = "standard" # Default
| Mode | Description |
|---|---|
standard | File paths, line ranges, and code content |
minimal | Compact output, fewer decorators |
verbose | Extended metadata (scores, tags, chunk types) |
xml | XML-tagged output for structured parsing |
The show_docs setting controls whether documentation files appear in injection output:
[hooks]
show_docs = true # Default. Set false to inject code-only
When false, doc files are still used for bridge discovery but not shown to the agent.
Full configuration reference
All settings in .bobbin/config.toml under [hooks]:
| Setting | Default | Description |
|---|---|---|
threshold | 0.5 | Minimum relevance score to include a result |
budget | 300 | Maximum lines of injected context |
content_mode | "full" | Display mode: full, preview, or none |
min_prompt_length | 20 | Skip injection for prompts shorter than this (chars) |
gate_threshold | 0.45 | Minimum top-result similarity to inject at all |
dedup_enabled | true | Skip injection when results match previous turn |
reducing_enabled | true | Progressive reducing (delta injection across session) |
show_docs | true | Include documentation files in output |
format_mode | "standard" | Output format: standard, minimal, verbose, xml |
skip_prefixes | [] | Prompt prefixes that skip injection entirely |
keyword_repos | [] | Keyword-triggered repo scoping rules |
repo_affinity_boost | 2.0 | Score multiplier for current repo files |
feedback_prompt_interval | 5 | Prompt for feedback every N injections (0 = disabled) |
Git hooks
Separately from Claude Code hooks, bobbin can install a git post-commit hook to keep the index fresh:
bobbin hook install-git-hook
This re-indexes files changed in each commit. See Watch & Automation for details on keeping your index current.
Hot topics
After using hooks for a while, bobbin accumulates data on which code areas are most frequently injected. The hot-topics subcommand generates a summary:
bobbin hook hot-topics
This writes hot-topics.md with analytics about your most-referenced code. Use it to identify which parts of your codebase come up most often in AI conversations — these are the areas worth keeping well-documented and well-structured.
Use --force to regenerate even if the injection count hasn’t reached the automatic threshold:
bobbin hook hot-topics --force
Reactions (PostToolUse hooks)
Reactions are rule-based context injections that fire after specific tool calls. Unlike the prompt-level hook, reactions target tool usage patterns — when an agent edits a config file, runs a deploy command, or touches a specific codebase area, bobbin can inject relevant guidance and related files.
Reactions live in .bobbin/reactions.toml:
[[reactions]]
name = "service-restart-runbook"
tool = "Bash"
[reactions.match]
command = "systemctl (restart|stop|start)"
guidance = "Check the runbook for {args.command} before restarting services in production."
search_query = "service restart runbook {file_stem}"
max_context_lines = 30
[[reactions]]
name = "ansible-role-edit"
tool = "Edit"
[reactions.match]
file_path = ".*/ansible/roles/.*"
guidance = "This is an Ansible role. Test with: ansible-playbook --check -l test"
use_coupling = true
coupling_threshold = 0.15
[[reactions]]
name = "credential-guard"
tool = "Edit"
[reactions.match]
file_path = ".*\\.(env|credentials|secret).*"
guidance = "WARNING: This file may contain credentials. Never commit secrets to git."
Reaction fields
| Field | Type | Description |
|---|---|---|
name | string | Rule name (used for dedup and metrics) |
tool | string | Tool name glob (e.g., "Edit", "Bash", "mcp__homelab__*") |
match | table | Parameter match conditions — keys are param names, values are regex |
guidance | string | Text shown to the agent. Supports {args.X} and {file_stem} templating |
search_query | string | Search query template for injecting related files |
search_group | string | Index group to search |
search_tags | list | Tag filters for scoped search |
max_context_lines | int | Max lines to inject (default: 50) |
use_coupling | bool | Use git coupling instead of search |
coupling_threshold | float | Min coupling score (default: 0.1) |
roles | list | Role patterns this reaction applies to (glob, e.g., "aegis/crew/*") |
Role-scoped reactions
Restrict reactions to specific agent roles:
[[reactions]]
name = "deploy-check"
tool = "Bash"
[reactions.match]
command = "deploy|kubectl"
roles = ["*/crew/*"] # Only crew members, not polecats
guidance = "Deploy detected. Verify staging tests passed first."
Noise filtering
In multi-agent environments, many prompts are operational (patrol nudges, reactor alerts, handoff messages) rather than development queries. Bobbin automatically detects these and skips injection to avoid wasting tokens on irrelevant context.
Automated message detection recognizes patterns like:
- Patrol nudges (
"Auto-patrol: ...","PATROL LOOP ...") - Reactor alerts (
"[reactor] ...") - Handoff/startup protocols (
"HANDOFF COMPLETE","STARTUP PROTOCOL") - Agent role announcements (
"aegis Crew ian, checking in.") - System reminder blocks (
"<system-reminder>...")
This is hardcoded in the hook logic and not configurable — it targets patterns that are definitively operational.
For user-configurable noise filtering, use skip_prefixes:
[hooks]
skip_prefixes = [
"git ", "bd ", "gt ",
"cargo build", "go test",
"y", "n", "ok", "done",
]
See Hooks Configuration for full skip_prefixes documentation.
Directory navigation injection
When an agent encounters a file-not-found error or tries to read a directory, bobbin can inject the directory tree to help navigation:
- EISDIR (directory read): Shows the directory tree (up to 20 lines)
- File not found: Shows the parent directory tree to help locate the right file
This fires via the try_directory_navigation() function in the hook and requires no configuration.
Removing hooks
Remove Claude Code hooks:
bobbin hook uninstall
# Or for global hooks:
bobbin hook uninstall --global
Remove the git post-commit hook:
bobbin hook uninstall-git-hook
Practical workflows
Daily development setup
Install hooks once per project, then forget about them:
cd ~/projects/my-app
bobbin init && bobbin index
bobbin hook install
bobbin hook install-git-hook # Keep index fresh on commits
Now every Claude Code conversation in this directory automatically gets relevant code context.
Combining hooks with watch mode
For real-time index updates (not just on commits):
bobbin hook install # Context injection
bobbin watch & # Real-time index updates
This ensures the injected context reflects your latest uncommitted changes, not just what was last committed.
Debugging injection
If Claude doesn’t seem to be getting the right context, check what bobbin would inject for a given prompt:
echo '{"query": "how does auth work"}' | bobbin hook inject-context --no-dedup
Or check the hook status to verify configuration:
bobbin hook status --json
Next steps
- Watch & Automation — keep your index current
- Searching — understand the search engine behind injection
hookCLI reference — full subcommand reference- Configuration Reference — all
config.tomlsettings
Access Control (RBAC)
Bobbin supports role-based access control to restrict which repos and file paths are visible in search results. This is especially useful in multi-agent environments like Gas Town, where different agents should only see repos relevant to their domain.
How It Works
Filtering happens post-search, pre-response: the search engine runs against the full index, then results from denied repos/paths are stripped. This keeps relevance ranking intact while enforcing access boundaries.
All commands that return repo-scoped results are filtered: search, grep, context, related, refs, similar, hotspots, impact, review, and hook inject. The same filtering applies to HTTP API endpoints.
Role Resolution
Your role is resolved in priority order:
| Priority | Source | Example |
|---|---|---|
| 1 | --role CLI flag | bobbin search --role human "auth" |
| 2 | BOBBIN_ROLE env var | export BOBBIN_ROLE=human |
| 3 | GT_ROLE env var | Set by Gas Town automatically |
| 4 | BD_ACTOR env var | Set by Gas Town automatically |
| 5 | Default | "default" |
Gas Town agents get filtering for free — their BD_ACTOR is already set.
Configuration
Add an [access] section to .bobbin/config.toml:
[access]
# When true, repos not in any deny list are visible to all roles.
# When false, repos must be explicitly allowed. Default: true.
default_allow = true
# Human sees everything
[[access.roles]]
name = "human"
allow = ["*"]
# Default role: exclude personal repos
[[access.roles]]
name = "default"
deny = ["cv", "resume", "personal-planning"]
# Aegis crew: infra-focused repos
[[access.roles]]
name = "aegis/crew/*"
allow = ["aegis", "bobbin", "gastown", "homelab-mcp", "goldblum", "hla-records"]
deny = ["cv", "resume", "personal-planning"]
# Specific agent override
[[access.roles]]
name = "aegis/crew/ian"
allow = ["aegis", "bobbin", "personal-planning"]
deny = []
# Path-level restrictions within allowed repos
[[access.roles]]
name = "bobbin/*"
allow = ["bobbin", "homelab-mcp"]
deny_paths = ["harnesses/*/CLAUDE.md", "crew/*/CLAUDE.md"]
Role Matching
Roles match hierarchically — most specific wins:
- Exact match:
aegis/crew/ianmatches role namedaegis/crew/ian - Wildcard match:
aegis/crew/ianmatchesaegis/crew/*(prefix/*) - Less specific wildcard:
aegis/polecats/alphamatchesaegis/* - Default fallback: if no pattern matches, uses role named
default - No config: if no
[access]section exists, everything is visible
Deny Precedence
- Deny always beats allow:
deny = ["secret-repo"]wins even withallow = ["*"] - deny_paths: glob patterns that block specific file paths within allowed repos
- default_allow: controls visibility of repos not mentioned in any rule
Examples
Lock down everything, grant explicitly
[access]
default_allow = false
[[access.roles]]
name = "human"
allow = ["*"]
[[access.roles]]
name = "default"
allow = ["bobbin"]
Agents with no role match see only bobbin. Humans see everything.
Block sensitive paths
[[access.roles]]
name = "default"
deny = ["cv"]
deny_paths = ["harnesses/*/CLAUDE.md", ".env*", "secrets/**"]
HTTP API usage
GET /search?q=auth+flow&role=aegis/crew/ian
GET /context?q=fix+login&role=human
No Config = No Filtering
If there’s no [access] section in config.toml, RBAC is completely disabled and all repos are visible to all callers. This is the default for backward compatibility.
Context Bundles
Context bundles are curated knowledge anchors — named groups of files, symbols, docs, and keywords that capture a concept or subsystem. They let agents (and humans) bootstrap context for a topic instantly instead of searching from scratch each time.
Why Bundles?
Without bundles, every new session starts cold: the agent searches, reads files, builds a mental model, then works. With bundles, domain knowledge is captured once and reused:
# Cold start (slow, token-heavy)
bobbin search "context assembly pipeline"
# ... read 5 files ... search again ... read 3 more ...
# With bundles (instant)
bobbin bundle show context/pipeline --deep
# Full source code for all key files + symbols, ready to work
Bundles are particularly valuable for:
- Handoffs: reference a bundle so the next session can load context immediately
- Polecat dispatch: include
bobbin bundle show <name> --deepin sling args - Feature work: create a bundle as you explore, then attach it to the bead with
b:<slug> - Onboarding:
bobbin bundle listshows the knowledge map of the entire codebase
Bundle Structure
A bundle contains:
| Field | Purpose | Example |
|---|---|---|
name | Hierarchical identifier | context/pipeline |
description | One-line summary | “5-phase assembly: seed → coupling → bridge → filter → budget” |
files | Central source files | src/search/context.rs |
refs | Specific symbols (file::Symbol) | src/tags.rs::BundleConfig |
docs | Related documentation | docs/designs/context-bundles.md |
keywords | Trigger terms for search | bundle, context bundle, b:slug |
includes | Other bundles to compose | tags |
Depth Levels
Bundles support three levels of detail:
- L0 —
bobbin bundle list: tree view of all bundles (names + descriptions) - L1 —
bobbin bundle show <name>: outline with file paths, symbol names, doc paths - L2 —
bobbin bundle show <name> --deep: full source code for all refs and files
Use L0 to orient, L1 to plan, L2 to work.
Creating Bundles
From Research
The recommended workflow is to create bundles as you explore code:
# 1. Search and discover
bobbin search "reranking pipeline"
bobbin refs find RerankerConfig
bobbin related src/search/reranker.rs
# 2. Create the bundle with what you found
bobbin bundle create "search/reranking" --global \
-d "Score normalization and result reranking" \
-k "rerank,score,normalize,hybrid search" \
-f "src/search/reranker.rs" \
-r "src/search/reranker.rs::RerankerConfig,src/search/reranker.rs::rerank_results" \
--docs "docs/guides/searching.md"
# 3. Add more as you discover relevant files
bobbin bundle add "search/reranking" --global \
-f "src/search/scorer.rs" \
-r "src/search/scorer.rs::normalize_scores"
Using the /bundle Skill
If your environment has the /bundle skill, it automates the research-to-bundle pipeline:
/bundle "context assembly pipeline"
/bundle "reactor alert processing" --save
The skill searches broadly, reads key files, finds symbol relationships, and synthesizes a bundle definition.
Guidelines
- Prefer
file::Symbolrefs over whole files — symbols are more precise - Keep bundles focused: 5-15 refs is ideal, not 50
- Use hierarchical names:
search/reranking,context/pipeline,hooks/error - Generate keywords from queries that produced the best results
- Check
bobbin bundle listfirst to avoid duplicates
Bundle-Aware Workflow
Linking Bundles to Beads
Use the b:<slug> label convention to connect beads to bundles:
# Create a bead with bundle reference
bd new -t task "Improve reranking scores" -l b:search/reranking
# When starting work on a bundled bead
bobbin bundle show search/reranking --deep # Load full context
Handoff Pattern
When handing off work, reference the bundle so the next session bootstraps instantly:
gt handoff -s "Reranking improvements" -m "Working on bo-xyz. Bundle: search/reranking"
The next session runs bobbin bundle show search/reranking --deep and has full context.
Evolving Bundles
Bundles should grow as you learn:
# Discovered a new relevant file during work
bobbin bundle add "search/reranking" --global -f "src/search/weights.rs"
# Found an important symbol
bobbin bundle add "search/reranking" --global -r "src/search/weights.rs::WeightConfig"
# Remove something that turned out to be irrelevant
bobbin bundle remove "search/reranking" --global -f "src/old_scorer.rs"
Storage
Bundles are stored in tags.toml configuration files:
- Global:
~/.config/bobbin/tags.toml(shared across repos, use--global) - Per-repo:
.bobbin/tags.toml(repo-specific)
The --global flag is recommended for bundles that span concepts across repos.
Composing Bundles
Use includes to build bundle hierarchies:
bobbin bundle create "context" --global \
-d "Assembles relevant code for agent prompts" \
-f "src/search/context.rs"
bobbin bundle create "context/pipeline" --global \
-d "5-phase assembly: seed → coupling → bridge → filter → budget" \
-f "src/search/context.rs,src/search/scorer.rs" \
-i "tags" # Include the tags bundle
When you bobbin bundle show context --deep, included bundles are expanded too.
CLI Overview
All commands support these global flags:
| Flag | Description |
|---|---|
--json | Output in JSON format |
--quiet | Suppress non-essential output |
--verbose | Show detailed progress |
--server <URL> | Use remote bobbin server (thin-client mode) |
Commands
| Command | Description |
|---|---|
bobbin init | Initialize bobbin in current repository |
bobbin index | Build/rebuild the search index |
bobbin search | Hybrid search (combines semantic + keyword) |
bobbin context | Assemble task-relevant context from search + git coupling |
bobbin grep | Keyword/regex search with highlighting |
bobbin deps | Import dependency analysis |
bobbin refs | Symbol reference resolution |
bobbin related | Find files related to a given file |
bobbin history | Show commit history and churn statistics |
bobbin hotspots | Identify high-churn/complexity code |
bobbin impact | Predict which files are affected by a change |
bobbin review | Assemble review context from a git diff |
bobbin similar | Find semantically similar code or detect duplicates |
bobbin status | Show index statistics |
bobbin serve | Start MCP server for AI agent integration |
bobbin tour | Interactive tour of bobbin features |
bobbin prime | Generate LLM-friendly overview with live stats |
bobbin benchmark | Run embedding benchmarks |
bobbin watch | Watch mode for automatic re-indexing |
bobbin completions | Generate shell completions |
bobbin hook | Claude Code hook integration |
Supported Languages
Bobbin uses Tree-sitter for structure-aware parsing, and pulldown-cmark for Markdown:
| Language | Extensions | Extracted Units |
|---|---|---|
| Rust | .rs | functions, impl blocks, structs, enums, traits, modules |
| TypeScript | .ts, .tsx | functions, methods, classes, interfaces |
| Python | .py | functions, classes |
| Go | .go | functions, methods, type declarations |
| Java | .java | methods, constructors, classes, interfaces, enums |
| C++ | .cpp, .cc, .hpp | functions, classes, structs, enums |
| Markdown | .md | sections, tables, code blocks, YAML frontmatter |
Other file types fall back to line-based chunking (50 lines per chunk with 10-line overlap).
init
Initialize Bobbin in a repository. Creates a .bobbin/ directory with configuration, SQLite database, and LanceDB vector store.
Usage
bobbin init [PATH]
Examples
bobbin init # Initialize in current directory
bobbin init /path/to/repo
bobbin init --force # Overwrite existing configuration
Options
| Flag | Description |
|---|---|
--force | Overwrite existing configuration |
index
Build or update the search index. Walks repository files, parses them with Tree-sitter (or pulldown-cmark for Markdown), generates embeddings, and stores everything in LanceDB.
Usage
bobbin index [PATH] [OPTIONS]
Examples
bobbin index # Full index of current directory
bobbin index --incremental # Only update changed files
bobbin index --force # Force reindex all files
bobbin index --repo myproject # Tag chunks with a repository name
bobbin index --source /other/repo --repo other # Index a different directory
Options
| Flag | Short | Description |
|---|---|---|
--incremental | Only update changed files | |
--force | Force reindex all files | |
--repo <NAME> | Repository name for multi-repo indexing (default: “default”) | |
--source <PATH> | Source directory to index files from (defaults to path) |
search
Search across your codebase. By default uses hybrid search combining semantic (vector similarity) and keyword (FTS) results using Reciprocal Rank Fusion (RRF).
Usage
bobbin search <QUERY> [OPTIONS]
Examples
bobbin search "error handling" # Hybrid search (default)
bobbin search "database connection" --limit 20 # More results
bobbin search "auth" --type function # Filter by chunk type
bobbin search "auth" --mode semantic # Semantic-only search
bobbin search "handleAuth" --mode keyword # Keyword-only search
bobbin search "auth" --repo myproject # Search within a specific repo
Options
| Flag | Short | Description |
|---|---|---|
--type <TYPE> | -t | Filter by chunk type (function, method, class, struct, enum, interface, module, impl, trait, doc, section, table, code_block) |
--limit <N> | -n | Maximum results (default: 10) |
--mode <MODE> | -m | Search mode: hybrid (default), semantic, or keyword |
--repo <NAME> | -r | Filter to a specific repository |
Search Modes
| Mode | Description |
|---|---|
hybrid | Combines semantic + keyword using RRF (default) |
semantic | Vector similarity search only |
keyword | Full-text keyword search only |
context
Assemble task-relevant context from search results and git history. Searches for code matching your query, then expands results with temporally coupled files (files that change together in git history). Outputs a context bundle optimized for feeding to AI agents or for understanding a task’s scope.
Usage
bobbin context <QUERY> [OPTIONS]
Examples
bobbin context "fix the login bug" # Default: 500 line budget
bobbin context "refactor auth" --budget 1000 # Larger context budget
bobbin context "add tests" --content full # Include full code content
bobbin context "auth" --content none # Paths/metadata only
bobbin context "auth" --depth 0 # No coupling expansion
bobbin context "auth" --repo myproject --json # JSON output for a specific repo
Options
| Flag | Short | Description |
|---|---|---|
--budget <LINES> | -b | Maximum lines of content to include (default: 500) |
--content <MODE> | -c | Content mode: full, preview (default for terminal), none |
--depth <N> | -d | Coupling expansion depth, 0 = no coupling (default: 1) |
--max-coupled <N> | Max coupled files per seed file (default: 3) | |
--limit <N> | -n | Max initial search results (default: 20) |
--coupling-threshold <F> | Min coupling score threshold (default: 0.1) | |
--repo <NAME> | -r | Filter to a specific repository |
Context Bundle
The context bundle includes:
- Direct matches: Code chunks matching your query, ranked by relevance
- Coupled files: Files with shared commit history to the direct matches
- Budget tracking: How many lines were used out of the budget
- File metadata: Paths, chunk types, line ranges, relevance scores
deps
Show import dependencies for a file, including what it imports and what depends on it.
Synopsis
bobbin deps [OPTIONS] <FILE>
Description
The deps command queries the bobbin metadata store to display import relationships for a given file. By default it shows forward dependencies (what the file imports). With --reverse it shows reverse dependencies (files that import this file). Use --both to see both directions at once.
Dependencies are extracted during indexing from import, use, require, and similar statements depending on the language. Resolved paths show the actual file on disk; unresolved imports are marked accordingly.
Arguments
| Argument | Description |
|---|---|
<FILE> | File to show dependencies for (required) |
Options
| Option | Short | Description |
|---|---|---|
--reverse | -r | Show reverse dependencies (files that import this file) |
--both | -b | Show both directions (imports and dependents) |
Examples
Show what a file imports:
bobbin deps src/main.rs
Show what files depend on a module:
bobbin deps --reverse src/config.rs
Show both imports and dependents:
bobbin deps --both src/lib.rs
JSON output for scripting:
bobbin deps --json src/main.rs
JSON Output
When --json is passed, the output has this structure:
{
"file": "src/main.rs",
"imports": [
{
"specifier": "use crate::config::Config",
"resolved_path": "src/config.rs"
}
],
"dependents": null
}
With --reverse or --both, the dependents array is populated:
{
"file": "src/config.rs",
"imports": null,
"dependents": [
{
"specifier": "use crate::config::Config",
"source_file": "src/main.rs"
}
]
}
Fields that are null are omitted from the JSON.
Prerequisites
Requires a bobbin index. Run bobbin init and bobbin index first.
See Also
grep
Keyword and regex search using LanceDB full-text search.
Usage
bobbin grep <PATTERN> [OPTIONS]
Examples
bobbin grep "TODO"
bobbin grep "handleRequest" --ignore-case
bobbin grep "fn.*test" --regex # Regex post-filter
bobbin grep "TODO" --type function --context 2 # With context lines
bobbin grep "auth" --repo myproject
Options
| Flag | Short | Description |
|---|---|---|
--ignore-case | -i | Case insensitive search |
--regex | -E | Use extended regex matching (post-filters FTS results) |
--type <TYPE> | -t | Filter by chunk type |
--limit <N> | -n | Maximum results (default: 10) |
--context <N> | -C | Number of context lines around matches (default: 0) |
--repo <NAME> | -r | Filter to a specific repository |
refs
Find symbol references and list symbols defined in a file.
Synopsis
bobbin refs find [OPTIONS] <SYMBOL>
bobbin refs symbols [OPTIONS] <FILE>
Description
The refs command has two subcommands:
find— Locate where a symbol is defined and list all usages across the indexed codebase.symbols— List every symbol (functions, structs, traits, etc.) defined in a specific file.
Both subcommands query the vector store index built by bobbin index.
Global Options
These options apply to both subcommands:
| Option | Short | Default | Description |
|---|---|---|---|
--path <DIR> | . | Directory to search in | |
--repo <NAME> | -r | Filter results to a specific repository |
Subcommands
refs find
Find the definition and usages of a symbol by name.
bobbin refs find [OPTIONS] <SYMBOL>
| Argument/Option | Short | Default | Description |
|---|---|---|---|
<SYMBOL> | Symbol name to find references for (required) | ||
--type <TYPE> | -t | Filter by symbol type (function, struct, trait, etc.) | |
--limit <N> | -n | 20 | Maximum number of usage results |
Example:
# Find all references to "Config"
bobbin refs find Config
# Find only struct definitions named "Config"
bobbin refs find --type struct Config
# Limit results to 5
bobbin refs find -n 5 parse_file
refs symbols
List all symbols defined in a file.
bobbin refs symbols <FILE>
| Argument | Description |
|---|---|
<FILE> | File path to list symbols for (required) |
Example:
bobbin refs symbols src/main.rs
# With verbose output to see signatures
bobbin refs symbols --verbose src/config.rs
JSON Output
refs find --json
{
"symbol": "Config",
"type": "struct",
"definition": {
"name": "Config",
"chunk_type": "struct",
"file_path": "src/config.rs",
"start_line": 10,
"end_line": 25,
"signature": "pub struct Config { ... }"
},
"usage_count": 3,
"usages": [
{
"file_path": "src/main.rs",
"line": 5,
"context": "use crate::config::Config;"
}
]
}
refs symbols --json
{
"file": "src/config.rs",
"count": 4,
"symbols": [
{
"name": "Config",
"chunk_type": "struct",
"start_line": 10,
"end_line": 25,
"signature": "pub struct Config { ... }"
}
]
}
Prerequisites
Requires a bobbin index. Run bobbin init and bobbin index first.
See Also
related
Find files that are temporally coupled to a given file – files that frequently change together in git history.
Usage
bobbin related <FILE> [OPTIONS]
Examples
bobbin related src/auth.rs
bobbin related src/auth.rs --limit 20
bobbin related src/auth.rs --threshold 0.5 # Only strong coupling
Options
| Flag | Short | Description |
|---|---|---|
--limit <N> | -n | Maximum results (default: 10) |
--threshold <F> | Minimum coupling score (default: 0.0) |
history
Show commit history and churn statistics for a file.
Usage
bobbin history <FILE> [OPTIONS]
Examples
bobbin history src/main.rs
bobbin history src/main.rs --limit 50
bobbin history src/main.rs --json # JSON output with stats
Options
| Flag | Short | Description |
|---|---|---|
--limit <N> | -n | Maximum entries to show (default: 20) |
Output
Output includes:
- Commit date, author, and message for each entry
- Referenced issue IDs (if present in commit messages)
- Statistics: total commits, churn rate (commits/month), author breakdown
hotspots
Identify code hotspots — files with both high git churn and high structural complexity.
Synopsis
bobbin hotspots [OPTIONS]
Description
The hotspots command combines git commit history with AST-based complexity analysis to surface files that are both frequently changed and structurally complex. These files are the most likely sources of bugs and the best candidates for refactoring.
The hotspot score is the geometric mean of normalized churn and complexity:
score = sqrt(churn_normalized * complexity)
Where:
- churn is the number of commits touching a file in the time window, normalized against the most-changed file.
- complexity is a weighted AST complexity score in the range [0, 1].
Non-code files (markdown, JSON, YAML, TOML) and unknown file types are automatically excluded.
Options
| Option | Short | Default | Description |
|---|---|---|---|
--path <DIR> | . | Directory to analyze | |
--since <EXPR> | 1 year ago | Time window for churn analysis (git date expression) | |
--limit <N> | -n | 20 | Maximum number of hotspots to show |
--threshold <SCORE> | 0.0 | Minimum hotspot score (0.0–1.0) |
Examples
Show top 20 hotspots from the last year:
bobbin hotspots
Narrow to the last 3 months, top 10:
bobbin hotspots --since "3 months ago" -n 10
Only show files with a score above 0.5:
bobbin hotspots --threshold 0.5
Verbose output includes a legend explaining the scoring:
bobbin hotspots --verbose
JSON output for CI or dashboards:
bobbin hotspots --json
JSON Output
{
"count": 3,
"since": "1 year ago",
"hotspots": [
{
"file": "src/cli/hook.rs",
"score": 0.823,
"churn": 47,
"complexity": 0.72,
"language": "rust"
}
]
}
Supported Languages
Complexity analysis supports: Rust, TypeScript/JavaScript, Python, Go, Java, C, C++.
Prerequisites
Requires a bobbin index and a git repository. Run bobbin init and bobbin index first.
See Also
- Hotspots Guide — strategies for using hotspot data
- status — check index statistics
impact
Predict which files are affected by a change to a target file or function.
Synopsis
bobbin impact [OPTIONS] <TARGET>
Description
The impact command combines git co-change coupling, semantic similarity, and dependency signals to predict which files would be affected if you change a target file or function. It produces a ranked list of impacted files with signal attribution.
Use --depth for transitive expansion: at depth 2+, the command also checks the impact of impacted files, widening the blast radius estimate.
Options
| Option | Short | Default | Description |
|---|---|---|---|
--path <DIR> | . | Directory to analyze | |
--depth <N> | -d | 1 | Transitive impact depth (1–3) |
--mode <MODE> | -m | combined | Signal mode: combined, coupling, semantic, deps |
--limit <N> | -n | 15 | Maximum number of results |
--threshold <SCORE> | -t | 0.1 | Minimum impact score (0.0–1.0) |
--repo <NAME> | -r | Filter to a specific repository |
Examples
Show files impacted by changing a file:
bobbin impact src/auth/middleware.rs
Show transitive impact (depth 2):
bobbin impact src/auth/middleware.rs --depth 2
Use only coupling signal:
bobbin impact src/auth/middleware.rs --mode coupling
JSON output:
bobbin impact src/auth/middleware.rs --json
Prerequisites
Requires a bobbin index and a git repository. Run bobbin init and bobbin index first.
See Also
review
Assemble review context from a git diff.
Synopsis
bobbin review [OPTIONS] [RANGE] [PATH]
Description
The review command finds the indexed code chunks that overlap with changed lines from a git diff, then expands via temporal coupling. It returns a budget-aware context bundle annotated with which files were changed.
Use this to quickly understand what you need to review after changes are made.
Arguments
| Argument | Description |
|---|---|
RANGE | Commit range (e.g., HEAD~3..HEAD) |
PATH | Directory to search in (default: .) |
Options
| Option | Short | Default | Description |
|---|---|---|---|
--branch <BRANCH> | -b | Compare branch against main | |
--staged | Only staged changes | ||
--budget <LINES> | 500 | Maximum lines of context to include | |
--depth <N> | -d | 1 | Coupling expansion depth (0 = no coupling) |
--content <MODE> | -c | Content mode: full, preview, none | |
--repo <NAME> | -r | Filter coupled files to specific repository |
Examples
Review unstaged changes:
bobbin review
Review the last 3 commits:
bobbin review HEAD~3..HEAD
Review staged changes only:
bobbin review --staged
Review a feature branch against main:
bobbin review --branch feature/auth
JSON output:
bobbin review --json
Prerequisites
Requires a bobbin index and a git repository. Run bobbin init and bobbin index first.
See Also
similar
Find semantically similar code chunks or scan for near-duplicate clusters.
Synopsis
bobbin similar [OPTIONS] [TARGET]
bobbin similar --scan [OPTIONS]
Description
The similar command uses vector similarity to find code that is semantically close to a target, or to scan the entire codebase for duplicate/near-duplicate clusters.
Single-target mode: Provide a chunk reference (file.rs:function_name) or free text to find similar code.
Scan mode: Set --scan to detect duplicate/near-duplicate code clusters across the codebase.
Options
| Option | Short | Default | Description |
|---|---|---|---|
--scan | Scan entire codebase for near-duplicate clusters | ||
--threshold <SCORE> | -t | 0.85 | Minimum cosine similarity threshold |
--limit <N> | -n | 10 | Maximum number of results or clusters |
--repo <NAME> | -r | Filter to a specific repository | |
--cross-repo | In scan mode, compare chunks across different repos |
Examples
Find code similar to a specific function:
bobbin similar "src/search/hybrid.rs:search"
Find code similar to a free-text description:
bobbin similar "error handling with retries"
Scan the codebase for near-duplicates:
bobbin similar --scan
Lower the threshold for broader matches:
bobbin similar --scan --threshold 0.7
JSON output:
bobbin similar --scan --json
Prerequisites
Requires a bobbin index. Run bobbin init and bobbin index first.
See Also
status
Show index statistics.
Usage
bobbin status [OPTIONS]
Examples
bobbin status
bobbin status --detailed # Per-language breakdown
bobbin status --repo myproject # Stats for a specific repo
Options
| Flag | Short | Description |
|---|---|---|
--detailed | Show per-language breakdown | |
--repo <NAME> | -r | Stats for a specific repository only |
calibrate
Auto-tune search parameters by probing your git history. Samples recent commits, builds queries from their diffs, and grid-sweeps search params to find the combination that best retrieves the changed files.
Usage
bobbin calibrate [OPTIONS] [PATH]
Examples
bobbin calibrate # Quick calibration (20 samples)
bobbin calibrate --apply # Apply best config to .bobbin/calibration.json
bobbin calibrate --full # Extended sweep (recency + coupling params)
bobbin calibrate --full --resume # Resume interrupted full sweep
bobbin calibrate --bridge-sweep # Sweep bridge params using calibrated core
bobbin calibrate -n 50 --since "1 year" # More samples, wider time range
bobbin calibrate --repo myproject # Calibrate a specific repo in multi-repo setup
Options
| Flag | Short | Description |
|---|---|---|
--samples <N> | -n | Number of commits to sample (default: 20) |
--since <RANGE> | Time range to sample from, git format (default: “6 months ago”) | |
--search-limit <N> | Max results per probe. Omit to sweep [10, 20, 30, 40] | |
--budget <N> | Budget lines per probe. Omit to sweep [150, 300, 500] | |
--apply | Write best config to .bobbin/calibration.json | |
--full | Extended sweep: also tunes recency and coupling parameters | |
--resume | Resume an interrupted --full sweep from cache | |
--bridge-sweep | Sweep bridge_mode + bridge_boost_factor only | |
--repo <NAME> | Repo to calibrate (for multi-repo setups) | |
--source <DIR> | Override source path for git sampling | |
--verbose | Show detailed per-commit results |
How It Works
- Sample N recent commits from git history (stratified across the time range)
- Build queries from commit messages — each message becomes a search probe
- Grid-sweep parameter combinations across all configured dimensions
- Score each combination by precision, recall, and F1 against ground truth (modified files)
- Rank by F1 and report the best configuration
With --apply, writes the best params to .bobbin/calibration.json, which takes precedence over config.toml values at search time.
Sample selection
Commits are filtered before sampling:
- Included: Commits with 2–30 changed files within the
--sincewindow - Excluded: Merge commits, reverts, and noise commits (prefixes:
chore:,ci:,docs:,style:,build:,release:,bump,auto-merge,update dependency) - Sampling: Evenly-spaced picks across filtered candidates (stratified, not random)
If >50% of sampled commits have very short messages (<20 chars) or generic text (“fix”, “wip”, “temp”), calibration warns that accuracy may be reduced.
Scoring
Each probe scores the context bundle returned for a commit message query against the files actually modified in that commit:
precision = |injected ∩ truth| / |injected|
recall = |injected ∩ truth| / |truth|
f1 = 2 × precision × recall / (precision + recall)
Configs are ranked by average F1 across all sampled commits.
Sweep Modes
Core sweep (default)
Sweeps 5 core parameter dimensions:
| Parameter | Values |
|---|---|
semantic_weight | 0.0, 0.3, 0.5, 0.7, 0.9 |
doc_demotion | 0.1, 0.3, 0.5 |
search_limit | 10, 20, 30, 40 (or CLI override) |
budget_lines | 150, 300, 500 (or CLI override) |
rrf_k | 60.0 (fixed) |
Total: 180 configs × N commits = ~3,600 probes at default 20 samples. Takes a few minutes.
Full sweep (--full)
Extends the core sweep with recency, coupling depth, and bridge parameters:
| Additional parameter | Values |
|---|---|
recency_half_life_days | 7, 14, 30, 90 |
recency_weight | 0.0, 0.15, 0.30, 0.50 |
coupling_depth | 500, 2000, 5000, 20000 |
bridge_mode | Off, Inject, Boost, BoostInject |
bridge_boost_factor | 0.15, 0.3, 0.5 |
Total: ~960 configs × 4 coupling depths × N commits. Significantly longer (~15-30 min). Re-indexes coupling data per depth, so each depth is a separate probe run.
Bridge sweep (--bridge-sweep)
Requires an existing calibration.json from a prior core or full sweep. Uses the calibrated core params and only sweeps bridge mode + boost factor (7 configs). Very fast.
calibration.json
The output file contains:
{
"calibrated_at": "2026-03-22T12:00:00Z",
"snapshot": {
"chunk_count": 5103,
"file_count": 312,
"primary_language": "rust",
"repo_age_days": 180,
"recent_commit_rate": 2.5
},
"best_config": {
"semantic_weight": 0.3,
"doc_demotion": 0.1,
"rrf_k": 60.0,
"budget_lines": 300,
"search_limit": 40,
"bridge_mode": "inject"
},
"top_results": [ ... ],
"sample_count": 20,
"probe_count": 3600
}
Precedence: calibration.json > config.toml > compiled defaults. All search and context operations read calibration.json if present.
Auto-recalibration
The CalibrationGuard triggers automatic recalibration during indexing when:
- First run: No prior calibration exists
- Chunk count changed >20%: Significant codebase growth or shrinkage
- Primary language changed: Project shifted languages
- >30 days since last calibration: Stale calibration
Cache and --resume
Full sweeps can be interrupted. The cache is saved after each coupling depth completes to .bobbin/calibration_cache.json. Use --resume to pick up where you left off — previously completed depths are restored from cache, and only remaining depths are re-run. Cache is cleared on successful completion.
Output
Shows a ranked table of parameter combinations with recall scores. The top result is the recommended configuration. Use --json for machine-readable output.
serve
Start an MCP (Model Context Protocol) server, exposing Bobbin’s search and analysis capabilities to AI agents like Claude and Cursor.
Usage
bobbin serve [PATH] [OPTIONS]
Examples
bobbin serve # Serve current directory
bobbin serve /path/to/repo # Serve a specific repository
MCP Tools Exposed
| Tool | Description |
|---|---|
search | Semantic/hybrid/keyword code search |
grep | Pattern matching with regex support |
context | Task-aware context assembly |
related | Find temporally coupled files |
read_chunk | Read a specific code chunk with context lines |
See MCP Integration for configuration examples.
benchmark
Benchmark embedding models to compare load times, single-query latency, and batch throughput.
Synopsis
bobbin benchmark -q <QUERY> [OPTIONS] [PATH]
Description
The benchmark command runs timed trials against one or more ONNX embedding models. For each model it measures:
- Load time — how long it takes to initialize the model.
- Single embed — per-query embedding latency (mean, min, max, p50, p95).
- Batch embed — latency for embedding all queries in a single batch.
If no --model is specified, all three built-in models are tested:
all-MiniLM-L6-v2bge-small-en-v1.5gte-small
Models are automatically downloaded if not already cached.
Arguments
| Argument | Default | Description |
|---|---|---|
[PATH] | . | Directory containing .bobbin/ config |
Options
| Option | Short | Default | Description |
|---|---|---|---|
--query <TEXT> | -q | Queries to benchmark (required, can be repeated) | |
--model <NAME> | -m | all built-in | Models to compare (can be repeated) |
--iterations <N> | 5 | Number of iterations per query | |
--batch-size <N> | 32 | Batch size for embedding |
Examples
Benchmark with a single query:
bobbin benchmark -q "authentication middleware"
Compare two models with multiple queries:
bobbin benchmark \
-q "error handling" \
-q "database connection pool" \
-m all-MiniLM-L6-v2 \
-m bge-small-en-v1.5 \
--iterations 10
JSON output for programmatic comparison:
bobbin benchmark -q "test query" --json
JSON Output
{
"models": [
{
"model": "all-MiniLM-L6-v2",
"dimension": 384,
"load_time_ms": 45.2,
"embed_single": {
"mean_ms": 3.12,
"min_ms": 2.80,
"max_ms": 4.01,
"p50_ms": 3.05,
"p95_ms": 3.90
},
"embed_batch": {
"mean_ms": 8.45,
"min_ms": 7.90,
"max_ms": 9.10,
"p50_ms": 8.40,
"p95_ms": 9.05
}
}
],
"queries": ["test query"],
"iterations": 5
}
See Also
- Embedding Settings — configure which model bobbin uses
- index — build the search index using the configured model
watch
Watch for file changes and re-index continuously in the background.
Synopsis
bobbin watch [OPTIONS] [PATH]
Description
The watch command starts a long-running process that monitors the filesystem for changes and incrementally re-indexes modified files. It uses OS-native file notification (inotify on Linux, FSEvents on macOS) with configurable debouncing to batch rapid changes.
The watcher:
- Creates/modifies — re-parses, re-embeds, and upserts chunks for changed files.
- Deletes — removes chunks for deleted files from the vector store.
- Skips —
.git/and.bobbin/directories are always excluded. Additional include/exclude patterns come frombobbin.toml. - Deduplicates — files whose content hash hasn’t changed are skipped.
The process responds to Ctrl+C and SIGTERM for clean shutdown.
Arguments
| Argument | Default | Description |
|---|---|---|
[PATH] | . | Directory containing .bobbin/ config |
Options
| Option | Default | Description |
|---|---|---|
--repo <NAME> | Repository name for multi-repo indexing | |
--source <DIR> | same as PATH | Source directory to watch (if different from config dir) |
--debounce-ms <MS> | 500 | Debounce interval in milliseconds |
--pid-file <FILE> | Write PID to this file for daemon management | |
--generate-systemd | Print a systemd service unit to stdout and exit |
Examples
Watch the current directory:
bobbin watch
Watch with a shorter debounce for fast feedback:
bobbin watch --debounce-ms 200
Multi-repo setup — watch a separate source directory:
bobbin watch --repo frontend --source ../frontend-app
Write a PID file for daemon management:
bobbin watch --pid-file /tmp/bobbin-watch.pid &
Generate a systemd user service:
bobbin watch --generate-systemd > ~/.config/systemd/user/bobbin-watch.service
systemctl --user enable --now bobbin-watch
Systemd Integration
The --generate-systemd flag prints a ready-to-use systemd service unit. The generated unit:
- Uses
Type=simplewith automatic restart on failure. - Sets
RestartSec=5andRUST_LOG=info. - Includes the resolved working directory and any
--repo/--sourceflags.
Prerequisites
Requires a bobbin index. Run bobbin init and bobbin index first to create the initial index, then use watch to keep it up to date.
See Also
- Watch & Automation Guide — patterns for continuous indexing
- index — one-shot full index build
- hook install-git-hook — alternative: re-index on git commit
completions
Generate shell completion scripts for bobbin.
Synopsis
bobbin completions <SHELL>
Description
The completions command outputs a shell completion script to stdout. Pipe the output to the appropriate file for your shell to enable tab-completion for all bobbin commands, subcommands, and options.
Supported shells: bash, zsh, fish, elvish, powershell.
Arguments
| Argument | Description |
|---|---|
<SHELL> | Shell to generate completions for (required) |
Setup
Bash
bobbin completions bash > ~/.local/share/bash-completion/completions/bobbin
Or for the current session only:
source <(bobbin completions bash)
Zsh
bobbin completions zsh > ~/.zfunc/_bobbin
Make sure ~/.zfunc is in your $fpath (add fpath=(~/.zfunc $fpath) to ~/.zshrc before compinit).
Fish
bobbin completions fish > ~/.config/fish/completions/bobbin.fish
Elvish
bobbin completions elvish > ~/.config/elvish/lib/bobbin.elv
PowerShell
bobbin completions powershell > $HOME\.config\powershell\bobbin.ps1
# Add to your $PROFILE: . $HOME\.config\powershell\bobbin.ps1
See Also
- Installation — installing bobbin
- CLI Overview — all available commands
hook
Manage Claude Code hooks for automatic context injection.
Synopsis
bobbin hook <SUBCOMMAND> [OPTIONS]
Description
The hook command manages the integration between bobbin and Claude Code. When installed, bobbin hooks fire on every user prompt to inject semantically relevant code context, giving Claude automatic awareness of your codebase.
The hook system has two layers:
- Claude Code hooks — entries in
settings.jsonthat callbobbin hook inject-contextandbobbin hook session-contextautomatically. - Git hooks — an optional post-commit hook that re-indexes changed files after each commit.
Subcommands
install
Install bobbin hooks into Claude Code’s settings.json.
bobbin hook install [OPTIONS]
| Option | Description |
|---|---|
--global | Install to ~/.claude/settings.json instead of project-local <git-root>/.claude/settings.json |
--threshold <SCORE> | Minimum relevance score to include in injected context |
--budget <LINES> | Maximum lines of injected context |
This registers two hook entries:
- UserPromptSubmit — calls
bobbin hook inject-contexton every prompt, adding relevant code snippets. - SessionStart (compact matcher) — calls
bobbin hook session-contextafter context compaction to restore codebase awareness.
uninstall
Remove bobbin hooks from Claude Code settings.
bobbin hook uninstall [OPTIONS]
| Option | Description |
|---|---|
--global | Remove from global settings instead of project-local |
status
Show installed hooks and current configuration values.
bobbin hook status [PATH]
| Argument | Default | Description |
|---|---|---|
[PATH] | . | Directory to check |
Displays whether Claude Code hooks and the git hook are installed, along with the active configuration (threshold, budget, content mode, gate threshold, dedup settings).
inject-context
Handle UserPromptSubmit events. This is called internally by Claude Code — you typically do not run it manually.
bobbin hook inject-context [OPTIONS]
| Option | Description |
|---|---|
--threshold <SCORE> | Minimum relevance score (overrides config) |
--budget <LINES> | Maximum lines of context (overrides config) |
--content-mode <MODE> | Display mode: full, preview, or none (overrides config) |
--min-prompt-length <N> | Minimum prompt length to trigger injection (overrides config) |
--gate-threshold <SCORE> | Minimum raw semantic similarity to inject at all (overrides config) |
--no-dedup | Force injection even if results match the previous session |
--format-mode <MODE> | Injection format: standard (default), minimal, verbose, or xml |
Each injection includes a unique injection_id (e.g. [injection_id: inj-01KJ...]) in the output. Agents can reference this ID when submitting feedback via bobbin feedback submit or the bobbin_feedback_submit MCP tool.
session-context
Handle SessionStart compact events. Called internally by Claude Code after context compaction.
bobbin hook session-context [OPTIONS]
| Option | Description |
|---|---|
--budget <LINES> | Maximum lines of context (overrides config) |
install-git-hook
Install a post-commit git hook that runs bobbin index after each commit.
bobbin hook install-git-hook
Creates or appends to .git/hooks/post-commit. The hook re-indexes only files changed in the commit.
uninstall-git-hook
Remove the bobbin post-commit git hook.
bobbin hook uninstall-git-hook
hot-topics
Generate hot-topics.md from injection frequency data. Analyzes which code areas are most frequently injected as context and writes a summary.
bobbin hook hot-topics [OPTIONS] [PATH]
| Argument/Option | Default | Description |
|---|---|---|
[PATH] | . | Directory to operate on |
--force | Regenerate even if the injection count hasn’t reached the threshold |
Examples
Set up hooks for a project:
bobbin hook install
Set up globally with custom thresholds:
bobbin hook install --global --threshold 0.3 --budget 200
Check current hook status:
bobbin hook status
Also install the git hook for automatic post-commit indexing:
bobbin hook install-git-hook
Remove all hooks:
bobbin hook uninstall
bobbin hook uninstall-git-hook
JSON output for status:
bobbin hook status --json
JSON Output (status)
{
"hooks_installed": true,
"git_hook_installed": true,
"config": {
"threshold": 0.2,
"budget": 150,
"content_mode": "preview",
"min_prompt_length": 20,
"gate_threshold": 0.15,
"dedup_enabled": true
}
}
Prerequisites
Requires a bobbin index. Run bobbin init and bobbin index first.
See Also
- Hooks Guide — detailed setup and tuning guide
- Hooks Configuration — all configurable hook parameters
- watch — alternative continuous indexing via filesystem watcher
tour
Interactive guided walkthrough of bobbin features.
Synopsis
bobbin tour [OPTIONS] [FEATURE]
Description
The tour command runs an interactive walkthrough that demonstrates bobbin’s features using your actual codebase. Each step shows a real command and its output, pausing for you to read before continuing.
Provide a feature name to run only that section of the tour.
Arguments
| Argument | Description |
|---|---|
FEATURE | Run tour for a specific feature only (e.g., search, hooks) |
Options
| Option | Default | Description |
|---|---|---|
--path <DIR> | . | Directory to tour |
--non-interactive | Skip interactive pauses (run all steps continuously) | |
--list | List available tour steps without running them |
Examples
Run the full interactive tour:
bobbin tour
Tour a specific feature:
bobbin tour search
List available tour steps:
bobbin tour --list
Non-interactive mode (useful for CI or demos):
bobbin tour --non-interactive
Prerequisites
Requires a bobbin index. Run bobbin init and bobbin index first.
See Also
- Quick Start — getting started guide
prime
Show LLM-friendly project overview with live stats.
Synopsis
bobbin prime [OPTIONS] [PATH]
Description
The prime command generates a comprehensive project overview designed to be consumed by LLMs and AI agents. It includes what bobbin does, architecture details, available commands, MCP tools, and live index statistics.
Use --section to request a specific part, or --brief for a compact summary.
Arguments
| Argument | Description |
|---|---|
PATH | Directory to check (default: .) |
Options
| Option | Default | Description |
|---|---|---|
--brief | Show brief (compact) overview only | |
--section <NAME> | Show a specific section (e.g., architecture, commands, mcp tools) |
Examples
Show full project overview:
bobbin prime
Brief summary:
bobbin prime --brief
Specific section:
bobbin prime --section "mcp tools"
JSON output:
bobbin prime --json
Prerequisites
Works best with a bobbin index for live stats, but can run without one.
See Also
- status — index statistics
bundle
Explore and manage context bundles. Bundles are named, hierarchical groups of files, symbols, docs, and keywords that capture a concept or subsystem.
Usage
bobbin bundle <COMMAND> [OPTIONS]
Subcommands
list
Show all bundles in a tree view (L0 map).
bobbin bundle list
bobbin bundle list --json
Output shows bundle hierarchy with names, descriptions, and member counts:
Context Bundles (9 total):
context — "Assembles relevant code for agent prompts" (1 files)
├── pipeline — "5-phase assembly: seed → coupling → bridge → filter → budget" (2 files)
└── tags — "Tag-based scoring, pinning, and access control" (3 files)
hook — "CLI injection into agent prompts" (1 files)
search — "Hybrid semantic + keyword search" (1 files)
└── lance — "LanceDB vector store backend" (1 files)
show
Display a bundle’s contents. L1 (outline) by default, L2 (full source) with --deep.
bobbin bundle show <NAME> # L1: paths and symbol names
bobbin bundle show <NAME> --deep # L2: full source code included
bobbin bundle show <NAME> --json # JSON output
L1 output lists file paths, symbol references, docs, and keywords.
L2 output (--deep) reads and includes the full content of every ref and file — use this to bootstrap working context for a task.
create
Create a new bundle.
bobbin bundle create <NAME> [OPTIONS]
bobbin bundle create <NAME> --global # Store in ~/.config/bobbin/tags.toml
Options:
| Flag | Short | Description |
|---|---|---|
--description | -d | One-line description |
--keywords | -k | Comma-separated trigger keywords |
--files | -f | Comma-separated file paths |
--refs | -r | Comma-separated file::Symbol references |
--docs | Comma-separated documentation file paths | |
--includes | -i | Comma-separated names of bundles to compose |
--global | Store in global config instead of per-repo |
Examples:
bobbin bundle create "search/reranking" --global \
-d "Score normalization and result reranking" \
-k "rerank,score,normalize" \
-f "src/search/reranker.rs" \
-r "src/search/reranker.rs::RerankerConfig"
bobbin bundle create "context/pipeline" --global \
-d "5-phase assembly pipeline" \
-f "src/search/context.rs,src/search/scorer.rs" \
-i "tags"
add
Add members to an existing bundle.
bobbin bundle add <NAME> [OPTIONS]
bobbin bundle add <NAME> --global -f "src/new_file.rs"
bobbin bundle add <NAME> --global -r "src/file.rs::NewSymbol"
bobbin bundle add <NAME> --global -k "new keyword"
bobbin bundle add <NAME> --global --docs "docs/new-guide.md"
Supports the same -f, -r, -k, --docs flags as create.
remove
Remove members from a bundle, or delete the entire bundle.
bobbin bundle remove <NAME> --global -f "src/old_file.rs"
bobbin bundle remove <NAME> --global -r "src/file.rs::OldSymbol"
bobbin bundle remove <NAME> --global --all # Delete entire bundle
Global Options
| Flag | Description |
|---|---|
--json | Output in JSON format |
--quiet | Suppress non-essential output |
--verbose | Show detailed progress |
--server <URL> | Use remote bobbin server |
--role <ROLE> | Role for access filtering |
Storage
Bundles are defined as [[bundles]] entries in tags.toml:
- Per-repo:
.bobbin/tags.toml - Global:
~/.config/bobbin/tags.toml(use--global)
Global bundles are recommended for concepts that span multiple repos.
See Also
- Context Bundles Guide — workflow patterns, best practices
- Tags & Effects — bundles share the tags.toml config
- context — context assembly uses bundle data
MCP Overview
Bobbin implements a Model Context Protocol (MCP) server that exposes code search and analysis capabilities to AI coding assistants. When connected, an AI agent can search your codebase semantically, find coupled files, resolve symbol references, and assemble task-focused context — all without leaving the conversation.
How It Works
AI Agent (Claude Code, Cursor, etc.)
│
├── MCP Protocol (JSON-RPC over stdio)
│
▼
Bobbin MCP Server
│
├── Tools: search, grep, context, related, find_refs, ...
├── Resources: bobbin://index/stats
├── Prompts: explore_codebase
│
▼
Local Storage (LanceDB + SQLite)
The MCP server runs as a subprocess of the AI client. It communicates via stdin/stdout using the MCP JSON-RPC protocol. All processing happens locally — no data is sent to external services.
Starting the Server
bobbin serve # MCP server on stdio (default)
bobbin serve --server # HTTP REST API
bobbin serve --server --mcp # Both HTTP and MCP simultaneously
In normal use, you don’t start the server manually. Your AI client launches it automatically based on its MCP configuration.
Capabilities
The bobbin MCP server advertises three capability types:
Tools
Nine tools for code search and analysis:
| Tool | Description |
|---|---|
search | Semantic/hybrid/keyword code search |
grep | Keyword and regex pattern matching |
context | Task-aware context assembly with coupling expansion |
related | Find temporally coupled files |
find_refs | Find symbol definitions and usages |
list_symbols | List all symbols defined in a file |
read_chunk | Read a specific code section by file and line range |
hotspots | Find high-churn, high-complexity files |
prime | Get an LLM-friendly project overview with live stats |
See Tools Reference for complete parameter documentation.
Resources
| URI | Description |
|---|---|
bobbin://index/stats | Index statistics (file count, chunk count, languages) |
Prompts
| Name | Description |
|---|---|
explore_codebase | Guided exploration prompt with suggested queries for a focus area |
The explore_codebase prompt accepts an optional focus parameter: architecture, entry_points, dependencies, tests, or any custom query.
Protocol Version
Bobbin implements MCP protocol version 2024-11-05 using the rmcp Rust library.
Next Steps
- Tools Reference — detailed documentation for each tool
- Client Configuration — setup for Claude Code, Cursor, and other clients
- HTTP Mode — running bobbin as a remote HTTP server
Tools Reference
All tools are available when bobbin runs as an MCP server (bobbin serve). Each tool accepts JSON parameters and returns JSON results.
search
Search for code using natural language. Finds functions, classes, and other code elements that match the semantic meaning of your query.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | yes | — | Natural language search query |
type | string | no | all | Filter by chunk type: function, method, class, struct, enum, interface, module, impl, trait |
limit | integer | no | 10 | Maximum number of results |
mode | string | no | hybrid | Search mode: hybrid, semantic, or keyword |
repo | string | no | all | Filter to a specific repository |
Response fields: query, mode, count, results[] (each with file_path, name, chunk_type, start_line, end_line, score, match_type, language, content_preview)
grep
Search for code using exact keywords or regex patterns.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
pattern | string | yes | — | Pattern to search for |
ignore_case | boolean | no | false | Case-insensitive search |
regex | boolean | no | false | Enable regex matching (post-filters FTS results) |
type | string | no | all | Filter by chunk type |
limit | integer | no | 10 | Maximum number of results |
repo | string | no | all | Filter to a specific repository |
Response fields: pattern, count, results[] (each with file_path, name, chunk_type, start_line, end_line, score, language, content_preview, matching_lines[])
context
Assemble a comprehensive context bundle for a task. Combines semantic search results with temporally coupled files from git history.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | yes | — | Natural language task description |
budget | integer | no | 500 | Maximum lines of content |
depth | integer | no | 1 | Coupling expansion depth (0 = no coupling) |
max_coupled | integer | no | 3 | Max coupled files per seed file |
limit | integer | no | 20 | Max initial search results |
coupling_threshold | float | no | 0.1 | Minimum coupling score |
repo | string | no | all | Filter to a specific repository |
Response fields: query, budget (max_lines, used_lines), files[] (each with path, language, relevance, score, coupled_to[], chunks[]), summary (total_files, total_chunks, direct_hits, coupled_additions)
related
Find files related to a given file based on git commit history (temporal coupling).
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file | string | yes | — | File path relative to repo root |
limit | integer | no | 10 | Maximum number of results |
threshold | float | no | 0.0 | Minimum coupling score (0.0–1.0) |
Response fields: file, related[] (each with path, score, co_changes)
find_refs
Find the definition and all usages of a symbol by name.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
symbol | string | yes | — | Exact symbol name (e.g., parse_config) |
type | string | no | all | Filter by symbol type |
limit | integer | no | 20 | Maximum number of usage results |
repo | string | no | all | Filter to a specific repository |
Response fields: symbol, definition (name, chunk_type, file_path, start_line, end_line, signature), usage_count, usages[] (each with file_path, line, context)
list_symbols
List all symbols (functions, structs, traits, etc.) defined in a file.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file | string | yes | — | File path relative to repo root |
repo | string | no | all | Filter to a specific repository |
Response fields: file, count, symbols[] (each with name, chunk_type, start_line, end_line, signature)
read_chunk
Read a specific section of code from a file by line range.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file | string | yes | — | File path relative to repo root |
start_line | integer | yes | — | Starting line number |
end_line | integer | yes | — | Ending line number |
context | integer | no | 0 | Context lines to include before and after |
Response fields: file, start_line, end_line, actual_start_line, actual_end_line, content, language
hotspots
Identify code hotspots — files with both high churn and high complexity.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
since | string | no | 1 year ago | Time window (e.g., 6 months ago, 3 months ago) |
limit | integer | no | 20 | Maximum number of hotspots |
threshold | float | no | 0.0 | Minimum hotspot score (0.0–1.0) |
Response fields: count, since, hotspots[] (each with file, score, churn, complexity, language)
prime
Get an LLM-friendly overview of the bobbin project with live index statistics.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
section | string | no | all | Specific section: what bobbin does, architecture, supported languages, key commands, mcp tools, quick start, configuration |
brief | boolean | no | false | Compact overview (title and first section only) |
Response fields: primer (markdown text), section, initialized, stats (total_files, total_chunks, total_embeddings, languages[], last_indexed)
impact
Predict which files are affected by a change to a target file or function. Combines git co-change coupling and semantic similarity signals.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
target | string | yes | — | File path or file:symbol reference |
depth | integer | no | 1 | Transitive expansion depth (0–3) |
mode | string | no | combined | Signal mode: combined, coupling, semantic, deps |
threshold | float | no | 0.1 | Minimum impact score |
limit | integer | no | 20 | Maximum number of results |
review
Assemble review context from a git diff. Finds indexed chunks overlapping changed lines and expands via temporal coupling.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
diff | string | no | unstaged | Diff spec: unstaged, staged, branch:<name>, commit:<range> |
budget | integer | no | 500 | Maximum lines of context |
depth | integer | no | 1 | Coupling expansion depth |
similar
Find code chunks semantically similar to a target, or scan for duplicate clusters.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
target | string | no | — | Chunk reference (file.rs:function_name) or free text |
scan | boolean | no | false | Scan entire codebase for near-duplicate clusters |
threshold | float | no | 0.85 | Minimum similarity score |
limit | integer | no | 10 | Maximum results |
cross_repo | boolean | no | false | Include cross-repo matches |
search_beads
Search for beads (issues/tasks) using natural language. Requires beads to be indexed via bobbin index --include-beads.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | yes | — | Natural language query |
priority | integer | no | all | Filter by priority (1–4) |
status | string | no | all | Filter by status |
assignee | string | no | all | Filter by assignee |
limit | integer | no | 10 | Maximum results |
enrich | boolean | no | true | Enrich with live Dolt metadata |
dependencies
Show import dependencies for a file. Returns forward and/or reverse dependencies.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file | string | yes | — | File path relative to repo root |
reverse | boolean | no | false | Show reverse dependencies (what imports this file) |
both | boolean | no | false | Show both forward and reverse |
repo | string | no | all | Filter to a specific repository |
file_history
Show git commit history for a specific file, with author breakdown and churn rate.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
file | string | yes | — | File path relative to repo root |
limit | integer | no | 20 | Maximum commits to return |
status
Show current index status and statistics.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
languages | boolean | no | false | Include per-language breakdown |
commit_search
Search git commit history using natural language.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | yes | — | Natural language query |
author | string | no | all | Filter by author |
file | string | no | all | Filter by file path |
limit | integer | no | 10 | Maximum results |
feedback_submit
Submit feedback on a bobbin context injection. Rate injections as useful, noise, or harmful.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
injection_id | string | yes | — | Injection ID from [injection_id: inj-xxx] |
rating | string | yes | — | useful, noise, or harmful |
agent | string | no | auto | Agent identity (auto-detected from env) |
reason | string | no | — | Explanation (max 1000 chars) |
feedback_list
List recent feedback records with optional filters.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
rating | string | no | all | Filter by rating |
agent | string | no | all | Filter by agent |
limit | integer | no | 20 | Maximum results (max 50) |
feedback_stats
Get aggregated feedback statistics — total injections, coverage rate, rating breakdown, and lineage counts.
Parameters: None.
feedback_lineage_store
Record a lineage action that ties feedback to a concrete fix. Links feedback records to commits, beads, or config changes.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
feedback_ids | integer[] | yes | — | Feedback record IDs to link |
action_type | string | yes | — | code_fix, config_change, tag_effect, access_rule, or exclusion_rule |
bead | string | no | — | Associated bead ID |
commit_hash | string | no | — | Git commit hash |
description | string | yes | — | What was done |
agent | string | no | auto | Agent identity |
feedback_lineage_list
List lineage records showing how feedback was acted on.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
feedback_id | integer | no | all | Filter by feedback ID |
bead | string | no | all | Filter by bead ID |
commit_hash | string | no | all | Filter by commit hash |
limit | integer | no | 20 | Maximum results (max 50) |
archive_search
Search archive records (HLA chat logs, Pensieve agent memory) using natural language.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query | string | yes | — | Natural language query |
source | string | no | all | Filter: hla or pensieve |
filter | string | no | all | Filter by name/channel |
after | string | no | — | Only records after date (YYYY-MM-DD) |
before | string | no | — | Only records before date (YYYY-MM-DD) |
limit | integer | no | 10 | Maximum results |
mode | string | no | hybrid | hybrid, semantic, or keyword |
archive_recent
List recent archive records by date.
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
after | string | yes | — | Only records after date (YYYY-MM-DD) |
source | string | no | all | Filter: hla or pensieve |
limit | integer | no | 20 | Maximum results |
Client Configuration
Step-by-step MCP configuration for each supported AI coding client.
Claude Code
MCP Server
Add to .claude/settings.json (project-level) or ~/.claude/settings.json (global):
{
"mcpServers": {
"bobbin": {
"command": "bobbin",
"args": ["serve"],
"env": {}
}
}
}
Claude Code will launch the bobbin MCP server automatically when you start a session. The agent can then call tools like search, context, and find_refs directly.
Hook Integration
For automatic context injection without manual tool calls:
# Project-local hooks
bobbin hook install
# Global hooks (all projects)
bobbin hook install --global
# With custom settings
bobbin hook install --threshold 0.3 --budget 200
The hook system and MCP server work independently. You can use either or both.
Verifying
Ask Claude Code: “What MCP tools do you have available?” It should list the bobbin tools (search, grep, context, etc.).
Cursor
Add to .cursor/mcp.json in your project root:
{
"mcpServers": {
"bobbin": {
"command": "bobbin",
"args": ["serve"]
}
}
}
Restart Cursor after adding the configuration. The MCP server will start automatically.
Windsurf
Add to your Windsurf MCP configuration:
{
"mcpServers": {
"bobbin": {
"command": "bobbin",
"args": ["serve"]
}
}
}
Generic MCP Client
Any MCP-compatible client can connect to bobbin. The server uses stdio transport by default:
# The client launches this command and communicates via stdin/stdout
bobbin serve
The server advertises:
- Protocol version:
2024-11-05 - Server name:
bobbin - Capabilities: tools, resources, prompts
Remote Server
For shared or centralized deployments, use HTTP mode instead of stdio:
# Start HTTP server on port 3030
bobbin serve --server --port 3030
Then configure your client to use the --server flag for thin-client mode:
# CLI queries hit the remote server
bobbin search "auth" --server http://localhost:3030
See HTTP Mode for details.
Multi-Repository Setup
Bobbin indexes one repository at a time. For multi-repo setups, run a separate bobbin instance per repository. Each MCP server is scoped to its repository root.
Troubleshooting
“Bobbin not initialized”: Run bobbin init && bobbin index in your project first.
“No indexed content”: Run bobbin index to build the search index.
Tools not appearing: Check that bobbin is on your PATH. Try running bobbin serve manually to verify it starts without errors.
Slow first query: The first query after startup loads the ONNX model (~1–2 seconds). Subsequent queries are fast.
HTTP Mode
Bobbin can run as an HTTP REST API server for centralized deployments, shared team use, or webhook-driven indexing.
Starting the HTTP Server
bobbin serve --server # HTTP on port 3030 (default)
bobbin serve --server --port 8080 # Custom port
bobbin serve --server --mcp # HTTP + MCP stdio simultaneously
The server binds to 0.0.0.0 on the specified port and includes CORS headers for browser-based clients.
REST API Endpoints
| Method | Path | Description |
|---|---|---|
GET | /search | Semantic/hybrid/keyword search |
GET | /grep | Keyword/regex pattern search |
GET | /context | Task-aware context assembly |
GET | /chunk/{id} | Read a specific chunk by ID |
GET | /read | Read file lines by path and range |
GET | /related | Find temporally coupled files |
GET | /refs | Find symbol definitions and usages |
GET | /symbols | List symbols in a file |
GET | /hotspots | Identify high-churn complex files |
GET | /impact | Predict change impact |
GET | /review | Diff-aware review context |
GET | /similar | Find similar code or duplicate clusters |
GET | /prime | Project overview with live stats |
GET | /beads | Search indexed beads/issues |
GET | /status | Index statistics |
GET | /metrics | Prometheus metrics |
POST | /webhook/push | Trigger re-indexing (for CI/CD) |
GET /search
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | required | Search query |
mode | string | hybrid | Search mode: hybrid, semantic, keyword |
type | string | all | Filter by chunk type |
limit | integer | 10 | Maximum results |
repo | string | all | Filter by repository |
curl "http://localhost:3030/search?q=error+handling&limit=5"
GET /grep
| Parameter | Type | Default | Description |
|---|---|---|---|
pattern | string | required | Search pattern |
ignore_case | bool | false | Case-insensitive search |
regex | bool | false | Enable regex matching |
type | string | all | Filter by chunk type |
limit | integer | 10 | Maximum results |
repo | string | all | Filter by repository |
curl "http://localhost:3030/grep?pattern=handleAuth&limit=5"
GET /context
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | required | Task description |
budget | integer | 500 | Max lines of content |
depth | integer | 1 | Coupling expansion depth |
max_coupled | integer | 3 | Max coupled files per seed |
limit | integer | 20 | Max initial search results |
coupling_threshold | float | 0.1 | Min coupling score |
repo | string | all | Filter by repository |
curl "http://localhost:3030/context?q=refactor+auth+flow&budget=300"
GET /read
| Parameter | Type | Default | Description |
|---|---|---|---|
file | string | required | File path (relative to repo root) |
start_line | integer | required | Start line number |
end_line | integer | required | End line number |
context | integer | 0 | Extra context lines before/after |
curl "http://localhost:3030/read?file=src/main.rs&start_line=1&end_line=20"
GET /related
| Parameter | Type | Default | Description |
|---|---|---|---|
file | string | required | File path to find related files for |
limit | integer | 10 | Maximum results |
threshold | float | 0.0 | Min coupling score |
curl "http://localhost:3030/related?file=src/auth.rs&limit=5"
GET /refs
| Parameter | Type | Default | Description |
|---|---|---|---|
symbol | string | required | Symbol name to find |
type | string | all | Filter by symbol type |
limit | integer | 20 | Max usage results |
repo | string | all | Filter by repository |
curl "http://localhost:3030/refs?symbol=parse_config"
GET /symbols
| Parameter | Type | Default | Description |
|---|---|---|---|
file | string | required | File path |
repo | string | all | Filter by repository |
curl "http://localhost:3030/symbols?file=src/config.rs"
GET /hotspots
| Parameter | Type | Default | Description |
|---|---|---|---|
since | string | 1 year ago | Time window for churn analysis |
limit | integer | 20 | Maximum results |
threshold | float | 0.0 | Min hotspot score |
curl "http://localhost:3030/hotspots?since=6+months+ago&limit=10"
GET /impact
| Parameter | Type | Default | Description |
|---|---|---|---|
target | string | required | File or file:function target |
depth | integer | 1 | Transitive depth (1-3) |
mode | string | combined | Signal: combined, coupling, semantic, deps |
limit | integer | 15 | Maximum results |
threshold | float | 0.1 | Min impact score |
repo | string | all | Filter by repository |
curl "http://localhost:3030/impact?target=src/auth.rs&depth=2"
GET /review
| Parameter | Type | Default | Description |
|---|---|---|---|
diff | string | unstaged | Diff spec: unstaged, staged, branch:<name>, or commit range |
budget | integer | 500 | Max lines of content |
depth | integer | 1 | Coupling expansion depth |
repo | string | all | Filter by repository |
curl "http://localhost:3030/review?diff=staged&budget=300"
GET /similar
| Parameter | Type | Default | Description |
|---|---|---|---|
target | string | - | Chunk ref or text (required unless scan=true) |
scan | bool | false | Scan for duplicate clusters |
threshold | float | 0.85/0.90 | Min similarity threshold |
limit | integer | 10 | Max results or clusters |
repo | string | all | Filter by repository |
cross_repo | bool | false | Cross-repo comparison in scan mode |
curl "http://localhost:3030/similar?target=src/auth.rs:login&limit=5"
curl "http://localhost:3030/similar?scan=true&threshold=0.9"
GET /prime
| Parameter | Type | Default | Description |
|---|---|---|---|
section | string | - | Specific section to show |
brief | bool | false | Compact overview only |
curl "http://localhost:3030/prime?brief=true"
GET /beads
| Parameter | Type | Default | Description |
|---|---|---|---|
q | string | required | Search query |
priority | integer | - | Filter by priority (1-4) |
status | string | - | Filter by status |
assignee | string | - | Filter by assignee |
rig | string | - | Filter by rig name |
limit | integer | 10 | Maximum results |
enrich | bool | true | Enrich with live Dolt data |
curl "http://localhost:3030/beads?q=auth+bug&status=open"
GET /status
Returns index statistics in JSON format.
curl http://localhost:3030/status
GET /metrics
Returns Prometheus-compatible metrics.
curl http://localhost:3030/metrics
POST /webhook/push
Triggers re-indexing. Useful for CI/CD pipelines or git webhook integrations.
curl -X POST http://localhost:3030/webhook/push
Thin-Client Mode
When a remote bobbin server is running, CLI commands can delegate to it instead of accessing local storage:
bobbin search "auth" --server http://localhost:3030
bobbin status --server http://localhost:3030
The --server flag is a global option available on all commands. When set, the CLI acts as a thin client that forwards requests to the HTTP server.
Use Cases
Team Server
Run bobbin on a shared machine with a large codebase indexed once:
# On the server
bobbin init
bobbin index
bobbin serve --server --port 3030
Team members connect via --server flag or configure their AI client to point at the server.
CI/CD Integration
Add a webhook step to your CI pipeline to keep the index fresh:
# After deploy or merge
curl -X POST http://bobbin-server:3030/webhook/push
Combined Mode
Run both HTTP and MCP simultaneously for maximum flexibility:
bobbin serve --server --mcp
This starts the HTTP server on the configured port and the MCP stdio server concurrently. The MCP server handles AI client connections while the HTTP server handles REST API requests and webhooks.
Configuration Reference
Configuration Hierarchy
Bobbin uses a layered configuration system. Each layer overrides the one above:
| Priority | Location | Purpose |
|---|---|---|
| 1 (lowest) | Compiled defaults | Sensible out-of-the-box values |
| 2 | ~/.config/bobbin/config.toml | Machine-wide defaults (global config) |
| 3 | .bobbin/config.toml | Project-specific settings (per-repo config) |
| 4 | .bobbin/calibration.json | Auto-tuned search parameters |
| 5 (highest) | CLI flags | Per-invocation overrides |
How merging works
When both global and per-repo configs exist, they are deep-merged:
- Tables merge recursively — a per-repo
[search]section only needs to set the fields it wants to override. Unset fields inherit from global config. - Arrays replace wholesale — a per-repo
index.includereplaces the entire global include list. - Scalars replace — a per-repo
search.semantic_weight = 0.5overrides the global value.
Example
Global config (~/.config/bobbin/config.toml):
[server]
url = "http://search.svc"
[hooks]
gate_threshold = 0.50
budget = 300
skip_prefixes = ["git ", "bd ", "gt "]
Per-repo config (.bobbin/config.toml):
[search]
semantic_weight = 0.8
[hooks]
budget = 500 # Override just this field; gate_threshold inherits 0.50
Result: server.url = http://search.svc, hooks.gate_threshold = 0.50, hooks.budget = 500, search.semantic_weight = 0.8.
Calibration overlay
bobbin calibrate --apply writes optimal search parameters to .bobbin/calibration.json. These override config.toml values for: semantic_weight, doc_demotion, rrf_k, and optionally recency_half_life_days, recency_weight, coupling_depth, budget_lines, search_limit, bridge_mode, bridge_boost_factor.
The search, context, and hook commands all load calibration.json and prefer its values over config.toml. CLI flags still override everything.
Tags and reactions
Two additional config files have their own global/local merge behavior:
tags.toml— tag pattern rules. Loaded from.bobbin/tags.tomlor~/.config/bobbin/tags.toml(server loads at startup; requires restart to pick up changes).reactions.toml— hook reaction rules. Merged: global~/.config/bobbin/reactions.toml+ local.bobbin/reactions.toml. Local rules override global rules by name.
Full Default Configuration
[index]
# Glob patterns for files to include
include = [
"**/*.rs",
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx",
"**/*.py",
"**/*.go",
"**/*.md",
]
# Glob patterns for files to exclude (in addition to .gitignore)
exclude = [
"**/node_modules/**",
"**/target/**",
"**/dist/**",
"**/.git/**",
"**/build/**",
"**/__pycache__/**",
]
# Whether to respect .gitignore files
use_gitignore = true
[embedding]
# Embedding model (downloaded automatically on first run)
model = "all-MiniLM-L6-v2"
# Batch size for embedding generation
batch_size = 32
[embedding.context]
# Number of context lines to include before and after a chunk
# when generating its embedding. More context improves retrieval
# quality at the cost of slightly longer indexing time.
context_lines = 5
# Languages where contextual embedding is enabled.
# Contextual embedding enriches each chunk with surrounding
# lines before computing its vector, improving search relevance.
enabled_languages = ["markdown"]
[search]
# Default number of search results
default_limit = 10
# Weight for semantic vs keyword search in hybrid mode.
# 0.0 = keyword only, 1.0 = semantic only, default 0.7.
semantic_weight = 0.7
[git]
# Enable temporal coupling analysis (tracks which files
# frequently change together in git history)
coupling_enabled = true
# Number of commits to analyze for coupling relationships
coupling_depth = 5000
# Minimum number of co-changes required to establish a coupling link
coupling_threshold = 3
# Enable semantic commit indexing (embed commit messages for search)
commits_enabled = true
# How many commits back to index for semantic search (0 = all)
commits_depth = 0
Documentation-Heavy Projects
For projects that are primarily markdown documentation (doc sites, wikis, knowledge bases), consider these tuned settings:
[index]
include = [
"**/*.md",
]
exclude = [
"**/node_modules/**",
"**/build/**",
"**/dist/**",
"**/.git/**",
"**/site/**", # MkDocs build output
"**/book/**", # mdBook build output
"**/_build/**", # Sphinx build output
]
[embedding.context]
context_lines = 5
enabled_languages = ["markdown"]
[search]
semantic_weight = 0.8 # Favor semantic search for natural-language queries
Key differences from the defaults:
- Include restricted to
**/*.mdto skip non-documentation files - Exclude adds common doc-tool build output directories
- Semantic weight raised to 0.8 since documentation queries tend to be natural language
For projects that mix code and documentation, keep the default include patterns and add the documentation-specific exclude patterns.
See Indexing Documentation for a full walkthrough.
Sections
| Section | Description | Details |
|---|---|---|
[index] | File selection patterns and gitignore behavior | Index Settings |
[embedding] | Embedding model and batch processing | Embedding Settings |
[embedding.context] | Contextual embedding enrichment | Embedding Settings |
[search] | Search defaults and hybrid weighting | Search Settings |
[git] | Temporal coupling analysis from git history | See below |
[hooks] | Claude Code hook integration and injection tuning | Hooks Configuration |
[git] Settings
| Key | Type | Default | Description |
|---|---|---|---|
coupling_enabled | bool | true | Enable temporal coupling analysis |
coupling_depth | int | 5000 | How many commits back to analyze for coupling |
coupling_threshold | int | 3 | Minimum co-changes to establish a coupling relationship |
commits_enabled | bool | true | Enable semantic commit indexing |
commits_depth | int | 0 | How many commits back to index for semantic search (0 = all) |
Index Settings
The [index] section controls which files are indexed.
Configuration
[index]
include = [
"**/*.rs",
"**/*.ts",
"**/*.tsx",
"**/*.js",
"**/*.jsx",
"**/*.py",
"**/*.go",
"**/*.md",
]
exclude = [
"**/node_modules/**",
"**/target/**",
"**/dist/**",
"**/.git/**",
"**/build/**",
"**/__pycache__/**",
]
use_gitignore = true
Options
| Key | Type | Default | Description |
|---|---|---|---|
include | string[] | See above | Glob patterns for files to include |
exclude | string[] | See above | Additional exclusion patterns (on top of .gitignore) |
use_gitignore | bool | true | Whether to respect .gitignore files |
Notes
- Include patterns determine which file extensions are parsed and indexed. Add patterns to index additional file types.
- Exclude patterns are applied in addition to
.gitignore. Use them to skip generated code, vendor directories, or other non-useful content. - When
use_gitignoreistrue, files matched by.gitignoreare automatically excluded even if they match an include pattern.
Search Settings
The [search] section controls search behavior defaults.
Configuration
[search]
default_limit = 10
semantic_weight = 0.7
Options
| Key | Type | Default | Description |
|---|---|---|---|
default_limit | int | 10 | Default number of results returned |
semantic_weight | float | 0.7 | Balance between semantic (1.0) and keyword (0.0) in hybrid mode |
Semantic Weight
The semantic_weight parameter controls how hybrid search blends results:
- 1.0 = pure semantic search (vector similarity only)
- 0.0 = pure keyword search (full-text search only)
- 0.7 (default) = heavily favors semantic matches, with keyword results filling in exact-match gaps
The hybrid search uses Reciprocal Rank Fusion (RRF) to combine results from both search modes. See Architecture: Storage & Data Flow for details on the RRF algorithm.
Embedding Settings
The [embedding] and [embedding.context] sections control embedding model and batch processing.
Configuration
[embedding]
model = "all-MiniLM-L6-v2"
batch_size = 32
[embedding.context]
context_lines = 5
enabled_languages = ["markdown"]
[embedding] Options
| Key | Type | Default | Description |
|---|---|---|---|
model | string | "all-MiniLM-L6-v2" | ONNX embedding model name. Downloaded to ~/.cache/bobbin/models/ on first use. |
batch_size | int | 32 | Number of chunks to embed per batch |
[embedding.context] Options
Controls contextual embedding, where chunks are embedded with surrounding source lines for better retrieval.
| Key | Type | Default | Description |
|---|---|---|---|
context_lines | int | 5 | Lines of context before and after each chunk |
enabled_languages | string[] | ["markdown"] | Languages where contextual embedding is active |
Notes
- The embedding model is downloaded automatically on first run and cached in
~/.cache/bobbin/models/. - Contextual embedding enriches each chunk with surrounding lines before computing its vector, improving search relevance at the cost of slightly longer indexing time.
- Increasing
batch_sizemay improve indexing throughput but uses more memory.
Hooks Configuration
All hook settings live under [hooks] in .bobbin/config.toml (local) or
~/.config/bobbin/config.toml (global).
Settings Reference
| Key | Type | Default | Description |
|---|---|---|---|
threshold | float | 0.5 | Minimum relevance score to include a result in injected context |
budget | int | 300 | Maximum lines of injected context per prompt |
content_mode | string | "full" | Display mode: "full", "preview", or "none" |
min_prompt_length | int | 20 | Skip injection for prompts shorter than this (chars) |
gate_threshold | float | 0.65 | Minimum top-result semantic similarity to inject at all |
dedup_enabled | bool | true | Skip re-injection when results match previous turn |
show_docs | bool | true | Include documentation files in output (false = code only, docs still used for bridging) |
format_mode | string | "standard" | Output format: "standard", "minimal", "verbose", or "xml" |
reducing_enabled | bool | true | Track injected chunks across turns; only inject new/changed chunks |
repo_affinity_boost | float | 2.0 | Score multiplier for files from the agent’s current repo. Set 1.0 to disable |
feedback_prompt_interval | int | 5 | Prompt agents to rate injections every N injections. 0 = disabled |
skip_prefixes | list | [] | Prompt prefixes that skip injection entirely (case-insensitive) |
keyword_repos | list | [] | Keyword-triggered repo scoping rules (see below) |
Gate Threshold vs Threshold
These are two different filters:
-
gate_threshold— Decides whether injection happens at all. Checks the top semantic search result’s raw similarity. Below this → skip the entire injection. This prevents bobbin from injecting irrelevant context when your prompt has nothing to do with the indexed code. -
threshold— Once injection is triggered, this controls which individual results are included. Results scoring below this threshold are dropped from the injection.
Typical tuning: raise gate_threshold (e.g., 0.75) if you get too many injections on unrelated prompts. Lower threshold (e.g., 0.3) if you want more results per injection.
Skip Prefixes
Skip injection entirely for prompts that start with operational commands:
[hooks]
skip_prefixes = [
"git ", "git push", "git pull", "git status",
"bd ready", "bd list", "bd show", "bd close",
"gt hook", "gt mail", "gt handoff", "gt prime",
"cargo check", "cargo build", "cargo test",
"go test", "go build", "go vet",
"y", "n", "yes", "no", "ok", "done",
]
Matching is case-insensitive and prefix-based (the prompt is trimmed before matching). This prevents wasting tokens on context injection for commands where the agent just needs to run a tool, not understand code.
Keyword Repos
Route queries to specific repositories based on keywords. Without this, every query searches all indexed repos, which can return noisy cross-repo results.
[[hooks.keyword_repos]]
keywords = ["bobbin", "search index", "embedding", "context injection"]
repos = ["bobbin"]
[[hooks.keyword_repos]]
keywords = ["beads", "bd ", "issue tracking"]
repos = ["beads"]
[[hooks.keyword_repos]]
keywords = ["traefik", "reverse proxy", "TLS certificate"]
repos = ["aegis", "goldblum"]
[[hooks.keyword_repos]]
keywords = ["shanty", "tmux", "terminal"]
repos = ["shanty"]
When a prompt matches any keyword (case-insensitive substring), the search is scoped to the matched repos instead of searching all repos. Multiple rules can match — repos are deduplicated.
If no keywords match, the search falls back to all repos (default behavior).
Format Modes
| Mode | Description |
|---|---|
standard | File paths, chunk names, line ranges, and content. Default. |
minimal | File paths and chunk names only. Lowest token cost. |
verbose | Full metadata including tags, scores, and match types. |
xml | XML-structured output for agents that parse structured context. |
Reducing (Delta Injection)
When reducing_enabled = true (default), bobbin tracks which chunks were
injected in previous turns. On subsequent prompts, only new or changed
chunks are injected — chunks already in the agent’s context are skipped.
This reduces token waste on multi-turn conversations about the same topic.
Disable it (false) if you need every turn to get the full context
(e.g., testing injection quality).
Repo Affinity
repo_affinity_boost gives a score multiplier to files from the agent’s
current repository (detected from the working directory). Default 2.0
means results from the current repo score 2x higher than cross-repo results.
Set to 1.0 to treat all repos equally. Useful when you frequently need
context from other projects.
Example: Multi-Agent Environment
For environments with many agents working across multiple repos:
[hooks]
threshold = 0.5
budget = 300
gate_threshold = 0.65
min_prompt_length = 20
dedup_enabled = true
reducing_enabled = true
repo_affinity_boost = 2.0
feedback_prompt_interval = 5
# Skip operational commands
skip_prefixes = [
"git ", "bd ", "gt ",
"cargo ", "go ", "npm ",
"y", "n", "ok", "done",
]
# Route queries to relevant repos
[[hooks.keyword_repos]]
keywords = ["bobbin", "search", "embedding"]
repos = ["bobbin"]
[[hooks.keyword_repos]]
keywords = ["beads", "issue", "bead"]
repos = ["beads"]
[[hooks.keyword_repos]]
keywords = ["gas town", "gastown", "molecule", "polecat"]
repos = ["gastown"]
Architecture Overview
Bobbin is a local-first code context engine built in Rust. It provides semantic and keyword search over codebases using:
- Tree-sitter for structural code parsing (Rust, TypeScript, Python, Go, Java, C++)
- pulldown-cmark for semantic markdown parsing (sections, tables, code blocks, frontmatter)
- ONNX Runtime for local embedding generation (all-MiniLM-L6-v2)
- LanceDB for primary storage: chunks, vector embeddings, and full-text search
- SQLite for temporal coupling data and global metadata
- rmcp for MCP server integration with AI agents
Module Structure
src/
├── main.rs # Entry point, CLI initialization
├── config.rs # Configuration management (.bobbin/config.toml)
├── types.rs # Shared types (Chunk, SearchResult, etc.)
│
├── cli/ # Command-line interface
│ ├── mod.rs # Command dispatcher
│ ├── init.rs # Initialize bobbin in a repository
│ ├── index.rs # Build/update the search index
│ ├── search.rs # Semantic search command
│ ├── grep.rs # Keyword/regex search command
│ ├── related.rs # Find related files command
│ ├── history.rs # File commit history and churn statistics
│ ├── status.rs # Index status and statistics
│ └── serve.rs # Start MCP server
│
├── index/ # Indexing engine
│ ├── mod.rs # Module exports
│ ├── parser.rs # Tree-sitter + pulldown-cmark code parsing
│ ├── embedder.rs # ONNX embedding generation
│ └── git.rs # Git history analysis (temporal coupling)
│
├── mcp/ # MCP (Model Context Protocol) server
│ ├── mod.rs # Module exports
│ ├── server.rs # MCP server implementation
│ └── tools.rs # Tool request/response types (search, grep, related, read_chunk)
│
├── search/ # Query engine
│ ├── mod.rs # Module exports
│ ├── semantic.rs # Vector similarity search (LanceDB ANN)
│ ├── keyword.rs # Full-text search (LanceDB FTS)
│ └── hybrid.rs # Combined search with RRF
│
└── storage/ # Persistence layer
├── mod.rs # Module exports
├── lance.rs # LanceDB: chunks, vectors, FTS (primary storage)
└── sqlite.rs # SQLite: temporal coupling + global metadata
Data Flow
Indexing Pipeline
Repository Files
│
▼
┌─────────────┐
│ File Walker │ (respects .gitignore)
└─────────────┘
│
▼
┌────────────────┐
│ Tree-sitter / │ → Extract semantic chunks (functions, classes, sections, etc.)
│ pulldown-cmark │
└────────────────┘
│
▼
┌─────────────┐
│ Embedder │ → Generate 384-dim vectors via ONNX
│ (ONNX) │ (with optional contextual enrichment)
└─────────────┘
│
▼
┌─────────────┐
│ LanceDB │ → Store chunks, vectors, metadata, and FTS index
│ (primary) │
└─────────────┘
Query Pipeline
User Query
│
▼
┌─────────────┐
│ Embedder │ → Query embedding
└─────────────┘
│
├────────────────────┐
▼ ▼
┌─────────────┐ ┌─────────────┐
│ LanceDB │ │ LanceDB FTS │
│ (ANN) │ │ (keyword) │
└─────────────┘ └─────────────┘
│ │
└────────┬───────────┘
▼
┌─────────────┐
│ Hybrid RRF │ → Reciprocal Rank Fusion
└─────────────┘
│
▼
Results
See also: Storage & Data Flow | Embedding Pipeline | Language Support
Storage & Data Flow
Bobbin uses a dual-storage architecture: LanceDB as the primary store for chunks, embeddings, and full-text search, with SQLite handling temporal coupling data and global metadata.
LanceDB (Primary Storage)
All chunk data, embeddings, and full-text search live in LanceDB:
chunks table:
- id: string # SHA256-based unique chunk ID
- vector: float[384] # MiniLM embedding
- repo: string # Repository name (for multi-repo support)
- file_path: string # Relative file path
- file_hash: string # Content hash for incremental indexing
- language: string # Programming language
- chunk_type: string # function, method, class, section, etc.
- chunk_name: string? # Function/class/section name (nullable)
- start_line: uint32 # Starting line number
- end_line: uint32 # Ending line number
- content: string # Original chunk content
- full_context: string? # Context-enriched text used for embedding (nullable)
- indexed_at: string # Timestamp
LanceDB also maintains an FTS index on the content field for keyword search.
SQLite (Auxiliary)
SQLite only stores temporal coupling data and global metadata:
-- Temporal coupling (git co-change relationships)
CREATE TABLE coupling (
file_a TEXT NOT NULL,
file_b TEXT NOT NULL,
score REAL,
co_changes INTEGER,
last_co_change INTEGER,
PRIMARY KEY (file_a, file_b)
);
-- Global metadata
CREATE TABLE meta (
key TEXT PRIMARY KEY,
value TEXT
);
Key Types
Chunk
A semantic unit extracted from source code:
#![allow(unused)]
fn main() {
struct Chunk {
id: String, // SHA256-based unique ID
file_path: String, // Source file path
chunk_type: ChunkType,// function, class, struct, etc.
name: Option<String>, // Function/class name
start_line: u32, // Starting line number
end_line: u32, // Ending line number
content: String, // Actual code content
language: String, // Programming language
}
}
SearchResult
#![allow(unused)]
fn main() {
struct SearchResult {
chunk: Chunk, // The matched chunk
score: f32, // Relevance score
match_type: Option<MatchType>, // How it was matched
}
}
Hybrid Search (RRF)
The hybrid search combines semantic (vector) and keyword (FTS) results using Reciprocal Rank Fusion:
RRF_score = semantic_weight / (k + semantic_rank) + keyword_weight / (k + keyword_rank)
Where:
k = 60(standard RRF constant)semantic_weightfrom config (default 0.7)keyword_weight = 1 - semantic_weight
Results that appear in both searches get boosted scores and are marked as [hybrid] matches.
Embedding Pipeline
Bobbin generates 384-dimensional vector embeddings locally using ONNX Runtime with the all-MiniLM-L6-v2 model. No data leaves your machine.
Pipeline Overview
Source File
│
▼
┌────────────────┐
│ Tree-sitter / │ Parse into semantic chunks
│ pulldown-cmark │ (functions, classes, sections, etc.)
└────────────────┘
│
▼
┌────────────────┐
│ Context │ Optionally enrich with surrounding lines
│ Enrichment │ (configurable per language)
└────────────────┘
│
▼
┌────────────────┐
│ ONNX Runtime │ Generate 384-dim vectors
│ (MiniLM-L6-v2) │ Batched processing (default: 32)
└────────────────┘
│
▼
┌────────────────┐
│ LanceDB │ Store vectors + metadata
└────────────────┘
Contextual Embedding
By default, chunks are embedded with their literal content. For configured languages (currently Markdown), Bobbin enriches each chunk with surrounding context lines before computing the embedding. This improves retrieval quality by giving the embedding model more semantic signal.
Configuration:
[embedding.context]
context_lines = 5 # Lines before/after each chunk
enabled_languages = ["markdown"] # Languages with context enrichment
Model Details
| Property | Value |
|---|---|
| Model | all-MiniLM-L6-v2 |
| Dimensions | 384 |
| Runtime | ONNX Runtime (CPU) |
| Model location | ~/.cache/bobbin/models/ |
| Download | Automatic on first run |
Batch Processing
Embeddings are generated in configurable batches (default: 32 chunks per batch) to balance throughput and memory usage. The batch size can be tuned in configuration:
[embedding]
batch_size = 32
Language Support
Bobbin uses Tree-sitter for structure-aware parsing of source code, and pulldown-cmark for Markdown documents.
Supported Languages
| Language | Extensions | Parser | Extracted Units |
|---|---|---|---|
| Rust | .rs | Tree-sitter | functions, impl blocks, structs, enums, traits, modules |
| TypeScript | .ts, .tsx | Tree-sitter | functions, methods, classes, interfaces |
| Python | .py | Tree-sitter | functions, classes |
| Go | .go | Tree-sitter | functions, methods, type declarations |
| Java | .java | Tree-sitter | methods, constructors, classes, interfaces, enums |
| C++ | .cpp, .cc, .hpp | Tree-sitter | functions, classes, structs, enums |
| Markdown | .md | pulldown-cmark | sections, tables, code blocks, YAML frontmatter |
Chunk Types
All extracted units map to a ChunkType enum:
#![allow(unused)]
fn main() {
enum ChunkType {
Function, // Standalone functions
Method, // Class methods
Class, // Class definitions
Struct, // Struct definitions (Rust, C++, Go)
Enum, // Enum definitions
Interface, // Interface definitions (TS, Java)
Module, // Module definitions
Impl, // Impl blocks (Rust)
Trait, // Trait definitions (Rust)
Doc, // Documentation chunks (Markdown frontmatter, preamble)
Section, // Heading-delimited sections (Markdown)
Table, // Table elements (Markdown)
CodeBlock, // Fenced code blocks (Markdown)
Other, // Fallback for line-based chunks
}
}
Fallback Chunking
File types without a dedicated parser fall back to line-based chunking: 50 lines per chunk with 10-line overlap. This ensures all files in the include patterns are searchable, even without structural parsing.
File Selection
Which files get indexed is controlled by configuration:
[index]
include = ["**/*.rs", "**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.py", "**/*.go", "**/*.md"]
exclude = ["**/node_modules/**", "**/target/**", "**/dist/**", "**/.git/**"]
use_gitignore = true
Evaluation Methodology
Bobbin’s evaluation framework measures how semantic code context affects AI agent performance on real bug fixes across open-source projects.
Approach: Commit-Revert
Each evaluation task is based on a real bug fix from a well-tested open-source project:
- Select a commit that fixes a bug and has a passing test suite
- Check out the parent of that commit (the broken state)
- Give the agent the bug description and test command
- Measure whether the agent can reproduce the fix
This approach has several advantages:
- Ground truth exists — the actual commit shows exactly what needed to change
- Tests are authoritative — the project’s own test suite validates correctness
- Difficulty is natural — real bugs have realistic complexity and cross-file dependencies
Two Approaches
Each task is run twice under controlled conditions:
| Approach | Description | Settings |
|---|---|---|
| no-bobbin | Agent works with only its built-in knowledge and the prompt | Empty hooks (isolated from user config) |
| with-bobbin | Agent receives semantic code context via bobbin’s hook system | bobbin hook inject-context on each prompt |
The with-bobbin approach injects relevant code snippets automatically when the agent processes its prompt, giving it awareness of related files, function signatures, and code patterns.
Isolation
Each run uses an independent, freshly cloned workspace in a temporary directory. The no-bobbin approach uses an explicit empty settings file (settings-no-bobbin.json) to prevent contamination from user-level Claude Code hooks. This ensures the control group never receives bobbin context.
Scoring Dimensions
Test Pass Rate
Does the agent’s fix make the test suite pass? This is the primary success metric — a fix that doesn’t pass tests is a failed attempt, regardless of how close the code looks.
File-Level Precision
Definition: Of the files the agent modified, what fraction were also modified in the ground truth commit?
Precision = |agent_files ∩ ground_truth_files| / |agent_files|
What it measures: Surgical accuracy. High precision (close to 1.0) means the agent only touched files that actually needed changing. Low precision means the agent made unnecessary modifications — touching files that weren’t part of the real fix.
Example: Ground truth modifies files A, B, C. Agent modifies A, B, D, E.
Precision = |{A,B}| / |{A,B,D,E}| = 2/4 = 0.50- The agent found 2 correct files but also touched 2 unnecessary ones.
File-Level Recall
Definition: Of the files modified in the ground truth commit, what fraction did the agent also modify?
Recall = |agent_files ∩ ground_truth_files| / |ground_truth_files|
What it measures: Completeness. High recall (close to 1.0) means the agent found all files that needed changing. Low recall means the agent missed some required files.
Example: Ground truth modifies files A, B, C. Agent modifies A, B, D, E.
Recall = |{A,B}| / |{A,B,C}| = 2/3 = 0.67- The agent found 2 of the 3 required files but missed file C.
F1 Score
Definition: The harmonic mean of precision and recall.
F1 = 2 × (Precision × Recall) / (Precision + Recall)
Why harmonic mean? Unlike an arithmetic mean, the harmonic mean penalizes extreme imbalances. An agent that touches every file in the repo would have recall = 1.0 but precision ≈ 0.0, and F1 would correctly be near 0 rather than 0.5.
Interpretation guide:
| F1 Range | Meaning |
|---|---|
| 1.0 | Perfect — agent modified exactly the same files as the ground truth |
| 0.7-0.9 | Strong — agent found most files with minimal extras |
| 0.4-0.6 | Partial — agent found some files but missed others or added extras |
| 0.0-0.3 | Weak — agent’s changes have little overlap with the ground truth |
Why F1 matters for context engines: Bobbin’s value proposition is that semantic context helps agents find the right files to modify. Without context, agents often explore broadly (low precision) or miss related files (low recall). F1 captures both failure modes in a single number.
Duration
Wall-clock time for the agent to complete its work. Includes thinking, tool calls, and compilation. Faster is better, all else equal, but correctness always trumps speed.
GPU-Accelerated Indexing
The with-bobbin approach requires indexing the target codebase before the agent starts. Bobbin automatically detects NVIDIA CUDA GPUs and uses them for embedding inference:
| Project | Files | Chunks | CPU Index Time | GPU Index Time |
|---|---|---|---|---|
| flask | 210 | ~700 | ~2s | ~2s |
| polars | 3,089 | ~50K | Pending | Pending |
| ruff | 5,874 | ~57K | >30 min (timeout) | ~83s |
GPU acceleration makes large-codebase evaluation practical. Without it, indexing ruff’s 57K chunks was the primary bottleneck — consistently timing out at the 30-minute mark. With GPU (RTX 4070 Super), embedding throughput jumps from ~100 chunks/s to ~2,400 chunks/s.
The GPU is only used during the indexing phase. Search queries are sub-100ms regardless.
Native Metrics
Each with-bobbin run captures detailed observability data via bobbin’s metrics infrastructure:
BOBBIN_METRICS_SOURCE— The eval runner sets this env var before each agent invocation, tagging all metric events with a unique run identifier (e.g.,ruff-001_with-bobbin_1)..bobbin/metrics.jsonl— Append-only log of metric events emitted by bobbin commands and hooks during the run.
Events captured include:
| Event | Source | What It Captures |
|---|---|---|
command | CLI dispatch | Every bobbin invocation: command name, duration, success/failure |
hook_injection | inject-context hook | Files returned, chunks returned, top semantic score, budget lines used |
hook_gate_skip | inject-context hook | Query text, top score, gate threshold (when injection is skipped due to weak match) |
hook_dedup_skip | inject-context hook | When injection is skipped because context hasn’t changed since last prompt |
After each run, the eval runner reads the metrics log and computes:
- Injection count — How many times bobbin injected context during the run
- Gate skip count — How many prompts were below the relevance threshold
- Injection-to-ground-truth overlap — Precision and recall of the files bobbin injected vs. the files that actually needed changing
This data appears in the bobbin_metrics field of each result JSON, enabling analysis of why bobbin helped (or didn’t) on a given task.
LLM Judge
Optionally, an LLM judge performs pairwise comparison of agent diffs across three dimensions:
- Consistency: Does the solution follow codebase conventions?
- Completeness: Are edge cases handled?
- Minimality: Is the diff surgical, or does it include unnecessary changes?
The judge uses a flip-and-draw protocol (running comparison in both orders) to detect and mitigate position bias.
Task Selection Criteria
Tasks are curated to be:
- Self-contained — fixable without external documentation or API access
- Well-tested — the project’s test suite reliably catches the bug
- Cross-file — the fix typically touches 2-5 files (not trivial single-line changes)
- Diverse — spanning multiple languages, frameworks, and bug categories
Current task suites:
| Suite | Project | Language | Files | Code Lines | Tasks | Difficulty |
|---|---|---|---|---|---|---|
| flask | pallets/flask | Python | 210 | 26K | 5 | easy-medium |
| polars | pola-rs/polars | Rust+Python | 3,089 | 606K | 5 | easy-medium |
| ruff | astral-sh/ruff | Rust+Python | 5,874 | 696K | 5 | easy-medium |
See Project Catalog for full LOC breakdowns and index statistics.
Adding a New Project
To add a new evaluation project:
-
Find bug-fix commits — Look for commits in well-tested repos where the test suite catches the bug. The fix should touch 2-5 files.
-
Create task YAML files — Add
eval/tasks/<project>-NNN.yamlwith:id: project-001 repo: org/repo commit: <full-sha> description: | Description of the bug and what the agent should fix. Implement the fix. Run the test suite with the test command to verify. setup_command: "<build/install steps>" test_command: "<specific test command>" language: rust difficulty: easy tags: [bug-fix, ...] -
Run
tokeion the cloned repo at the pinned commit and add a section to Project Catalog. -
Create a results page — Add
eval/<project>.mdto the book with task descriptions and a placeholder for results. -
Update SUMMARY.md — Add the new results page to the Evaluation section.
-
Run evals —
just eval-task <project>-001runs a single task. Results are written toeval/results/runs/.
Results Summary
Overall Comparison
| Metric | no-bobbin | with-bobbin | with-bobbin+blame_bridging=false | with-bobbin+coupling_depth=0 | with-bobbin+doc_demotion=0.0 | with-bobbin+gate_threshold=1.0 | with-bobbin+recency_weight=0.0 | with-bobbin+semantic_weight=0.0 |
|---|---|---|---|---|---|---|---|---|
| Runs | 30 | 36 | 3 | 3 | 3 | 3 | 3 | 4 |
| Test Pass Rate | 66.7% | 58.3% | 100.0% | 100.0% | 100.0% | 100.0% | 100.0% | 100.0% |
| Avg Precision | 85.1% | 90.1% | 33.3% | 55.6% | 55.6% | 77.8% | 77.8% | 23.6% |
| Avg Recall | 60.2% | 64.5% | 33.3% | 33.3% | 55.6% | 55.6% | 55.6% | 33.3% |
| Avg F1 | 68.3% | 71.9% | 33.3% | 38.9% | 55.6% | 61.1% | 61.1% | 25.2% |
| Avg Duration | 4.2m | 3.6m | 4.6m | 4.7m | 5.0m | 5.4m | 4.9m | 4.7m |
| Avg Cost | $1.24 | $1.44 | $1.25 | $1.45 | $1.48 | $1.39 | $1.43 | $1.45 |
| Avg Input Tokens | 1,250,122 | 1,788,633 | 1,517,002 | 1,780,506 | 1,882,003 | 1,711,955 | 1,813,406 | 1,812,429 |
| Avg Output Tokens | 7,411 | 8,918 | 7,551 | 8,723 | 8,933 | 8,572 | 8,413 | 9,374 |
Metric Overview
F1 Score by Task
Score Distribution
Duration
Recent Trend
Per-Task Results
| Task | Language | Difficulty | Approach | Tests | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|---|---|---|
| cargo-001 | rust | easy | no-bobbin | 100.0% | 100.0% | 100.0% | 100.0% | 5.2m | $1.04 |
| cargo-001 | rust | easy | with-bobbin | 100.0% | 100.0% | 100.0% | 100.0% | 4.6m | $1.03 |
| flask-001 | — | — | no-bobbin | 0.0% | 100.0% | 33.3% | 50.0% | 1.3m | $0.00 |
| flask-001 | — | — | with-bobbin | 0.0% | 100.0% | 33.3% | 50.0% | 1.3m | $0.00 |
| flask-002 | — | — | no-bobbin | 0.0% | 100.0% | 66.7% | 80.0% | 3.1m | $0.00 |
| flask-002 | — | — | with-bobbin | 0.0% | 100.0% | 55.6% | 70.0% | 3.5m | $0.00 |
| flask-003 | — | — | no-bobbin | 0.0% | 100.0% | 60.0% | 75.0% | 2.2m | $0.00 |
| flask-003 | — | — | with-bobbin | 0.0% | 100.0% | 60.0% | 75.0% | 2.4m | $0.00 |
| flask-004 | — | — | no-bobbin | 0.0% | 100.0% | 70.0% | 81.9% | 3.3m | $0.00 |
| flask-004 | — | — | with-bobbin | 0.0% | 100.0% | 60.0% | 75.0% | 3.2m | $0.00 |
| flask-005 | — | — | no-bobbin | 0.0% | 100.0% | 50.0% | 66.7% | 2.6m | $0.00 |
| flask-005 | — | — | with-bobbin | 0.0% | 100.0% | 58.3% | 73.0% | 1.9m | $0.00 |
| polars-004 | rust | medium | no-bobbin | 100.0% | 100.0% | 66.7% | 80.0% | 4.4m | $0.81 |
| polars-005 | rust | medium | no-bobbin | 100.0% | 100.0% | 66.7% | 79.4% | 6.4m | $1.74 |
| ruff-001 | rust | medium | no-bobbin | 100.0% | 31.7% | 33.3% | 32.4% | 4.3m | $1.23 |
| ruff-001 | rust | medium | with-bobbin | 100.0% | 70.2% | 61.9% | 63.6% | 4.4m | $1.52 |
| ruff-001 | rust | medium | with-bobbin+blame_bridging=false | 100.0% | 33.3% | 33.3% | 33.3% | 4.6m | $1.25 |
| ruff-001 | rust | medium | with-bobbin+coupling_depth=0 | 100.0% | 55.6% | 33.3% | 38.9% | 4.7m | $1.45 |
| ruff-001 | rust | medium | with-bobbin+doc_demotion=0.0 | 100.0% | 55.6% | 55.6% | 55.6% | 5.0m | $1.48 |
| ruff-001 | rust | medium | with-bobbin+gate_threshold=1.0 | 100.0% | 77.8% | 55.6% | 61.1% | 5.4m | $1.39 |
| ruff-001 | rust | medium | with-bobbin+recency_weight=0.0 | 100.0% | 77.8% | 55.6% | 61.1% | 4.9m | $1.43 |
| ruff-001 | rust | medium | with-bobbin+semantic_weight=0.0 | 100.0% | 23.6% | 33.3% | 25.2% | 4.7m | $1.45 |
| ruff-002 | rust | easy | no-bobbin | 100.0% | 100.0% | 40.0% | 57.1% | 4.8m | $0.00 |
| ruff-002 | rust | easy | with-bobbin | 100.0% | 100.0% | 40.0% | 57.1% | 4.3m | $1.38 |
| ruff-003 | rust | medium | no-bobbin | 100.0% | 100.0% | 83.3% | 90.0% | 9.2m | $0.00 |
| ruff-003 | rust | medium | with-bobbin | 100.0% | 100.0% | 77.8% | 86.7% | 6.3m | $1.92 |
| ruff-004 | rust | easy | no-bobbin | 100.0% | 46.7% | 66.7% | 54.2% | 3.9m | $0.00 |
| ruff-004 | rust | easy | with-bobbin | 100.0% | 63.3% | 83.3% | 70.8% | 4.5m | $1.67 |
| ruff-005 | rust | easy | no-bobbin | 100.0% | 100.0% | 100.0% | 100.0% | 3.6m | $0.00 |
| ruff-005 | rust | easy | with-bobbin | 100.0% | 100.0% | 100.0% | 100.0% | 2.8m | $0.63 |
Historical Trends
F1 Trend
Test Pass Rate Trend
Duration Trend
Run Comparison
| Run | Completed | Pass Rate | Avg F1 | Avg Duration |
|---|---|---|---|---|
| 20260210 | 2 | 100.0% | 66.7% | 3.6m |
| 20260210 | 2 | 100.0% | 57.1% | 4.4m |
| 20260210 | 2 | 100.0% | 80.0% | 8.2m |
| 20260210 | 1 | 100.0% | 33.3% | 4.1m |
| 20260210 | 1 | 100.0% | 33.3% | 3.8m |
| 20260210 | 2 | 100.0% | 100.0% | 3.9m |
| 20260210 | 2 | 0.0% | 50.0% | 1.6m |
| 20260210 | 2 | 0.0% | 65.0% | 4.3m |
| 20260210 | 2 | 0.0% | 75.0% | 3.2m |
| 20260210 | 2 | 0.0% | 75.0% | 3.7m |
| 20260210 | 2 | 0.0% | 66.7% | 2.3m |
| 20260210 | 2 | 100.0% | 33.3% | 4.4m |
| 20260210 | 2 | 100.0% | 57.1% | 4.7m |
| 20260211 | 2 | 100.0% | 90.0% | 7.3m |
| 20260211 | 2 | 100.0% | 75.0% | 3.5m |
| 20260211 | 2 | 100.0% | 100.0% | 3.3m |
| 20260211 | 2 | 0.0% | 50.0% | 1.3m |
| 20260211 | 2 | 0.0% | 80.0% | 2.8m |
| 20260211 | 2 | 0.0% | 75.0% | 1.7m |
| 20260211 | 2 | 0.0% | 76.2% | 2.3m |
| 20260211 | 2 | 0.0% | 81.9% | 3.1m |
| 20260211 | 1 | 0.0% | 50.0% | 51s |
| 20260211 | 1 | 0.0% | 80.0% | 2.6m |
| 20260211 | 1 | 0.0% | 75.0% | 1.9m |
| 20260211 | 1 | 0.0% | 75.0% | 2.6m |
| 20260211 | 1 | 0.0% | 66.7% | 1.8m |
| 20260216 | 3 | 100.0% | 80.0% | 4.4m |
| 20260216 | 3 | 100.0% | 79.4% | 6.4m |
| 20260226 | 1 | 100.0% | 100.0% | 3.1m |
| 20260227 | 1 | 100.0% | 57.1% | 4.4m |
| 20260227 | 1 | 100.0% | 100.0% | 6.2m |
| 20260227 | 1 | 100.0% | 100.0% | 3.4m |
| 20260227 | 1 | 100.0% | 100.0% | 1.4m |
| 20260228 | 1 | 100.0% | 33.3% | 3.4m |
| 20260228 | 1 | 100.0% | 75.0% | 7.4m |
| 20260302 | 1 | 100.0% | 100.0% | 5.2m |
| 20260302 | 2 | 100.0% | 19.4% | 4.8m |
| 20260302 | 2 | 100.0% | 30.9% | 5.0m |
| 20260302 | 21 | 100.0% | 48.8% | 4.9m |
| 20260302 | 1 | 100.0% | 100.0% | 4.6m |
Task Heatmap
Project Catalog
Projects used in bobbin evaluations, with codebase statistics.
astral-sh/ruff
Lines of Code
| Language | Files | Code | Comments | Blanks | Total |
|---|---|---|---|---|---|
| Rust | 1,750 | 440,633 | 25,755 | 54,388 | 520,776 |
| Python | 3,690 | 212,248 | 12,844 | 42,183 | 267,275 |
| JSON | 134 | 15,801 | 0 | 10 | 15,811 |
| TSX | 30 | 4,810 | 163 | 553 | 5,526 |
| TOML | 145 | 3,654 | 220 | 484 | 4,358 |
| YAML | 25 | 3,194 | 292 | 339 | 3,825 |
| SVG | 28 | 1,308 | 71 | 66 | 1,445 |
| TypeScript | 14 | 363 | 61 | 56 | 480 |
| CSS | 3 | 322 | 6 | 49 | 377 |
| Jupyter Notebooks | 35 | 289 | 154 | 68 | 511 |
| Total | 5,874 | 696,104 | 1,212,699 |
Bobbin Index Stats
- Index duration: 82.64s
pallets/flask
Lines of Code
| Language | Files | Code | Comments | Blanks | Total |
|---|---|---|---|---|---|
| Python | 83 | 13,856 | 836 | 3,515 | 18,207 |
| ReStructuredText | 84 | 11,089 | 0 | 3,939 | 15,028 |
| SVG | 2 | 451 | 2 | 2 | 455 |
| HTML | 20 | 256 | 0 | 18 | 274 |
| Forge Config | 3 | 126 | 7 | 20 | 153 |
| CSS | 2 | 109 | 1 | 25 | 135 |
| Autoconf | 9 | 48 | 2 | 0 | 50 |
| INI | 1 | 43 | 3 | 4 | 50 |
| Batch | 1 | 26 | 1 | 8 | 35 |
| SQL | 2 | 22 | 2 | 4 | 28 |
| Total | 210 | 26,132 | 34,921 |
Bobbin Index Stats
- Index duration: 1.66s
pola-rs/polars
Lines of Code
| Language | Files | Code | Comments | Blanks | Total |
|---|---|---|---|---|---|
| Rust | 2,008 | 361,685 | 13,199 | 46,759 | 421,643 |
| Python | 725 | 228,936 | 5,839 | 32,248 | 267,023 |
| ReStructuredText | 102 | 6,593 | 0 | 1,469 | 8,062 |
| TOML | 50 | 4,217 | 141 | 352 | 4,710 |
| YAML | 2 | 743 | 10 | 11 | 764 |
| Makefile | 7 | 460 | 18 | 97 | 575 |
| Nix | 1 | 409 | 52 | 41 | 502 |
| JSON | 8 | 408 | 0 | 0 | 408 |
| SVG | 3 | 271 | 0 | 0 | 271 |
| HTML | 4 | 253 | 0 | 1 | 254 |
| Total | 2,913 | 606,877 | 739,330 |
rust-lang/cargo
Lines of Code
| Language | Files | Code | Comments | Blanks | Total |
|---|---|---|---|---|---|
| Rust | 1,277 | 263,828 | 10,721 | 22,803 | 297,352 |
| SVG | 362 | 14,485 | 0 | 1,061 | 15,546 |
| TOML | 647 | 5,834 | 338 | 1,209 | 7,381 |
| JSON | 17 | 3,430 | 0 | 0 | 3,430 |
| JavaScript | 1 | 509 | 66 | 68 | 643 |
| Shell | 6 | 319 | 40 | 44 | 403 |
| Python | 2 | 88 | 16 | 18 | 122 |
| Dockerfile | 2 | 44 | 2 | 9 | 55 |
| XML | 1 | 21 | 7 | 0 | 28 |
| CSS | 1 | 6 | 3 | 1 | 10 |
| Total | 2,317 | 291,133 | 371,758 |
Bobbin Index Stats
- Index duration: 28.29s
Cargo (Rust)
cargo-001 easy
Commit: a96e747227
Task prompt
Fix
cargo checkto not panic when usingbuild-dirconfig with a workspace containing a proc macro that depends on a dylib crate. The code incompilation\_files.rscalls.expect("artifact-dir was not locked")but artifact-dir is never locked for check builds. Replace the.expect()calls with?to propagate the None gracefully instead of panicking.
| Approach | Tests Pass | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|
| no-bobbin | 100.0% | 100.0% | 100.0% | 100.0% | 5.2m | $1.04 |
| with-bobbin | 100.0% | 100.0% | 100.0% | 100.0% | 4.6m | $1.03 |
Ground truth files: src/cargo/core/compiler/build_runner/compilation_files.rs, tests/testsuite/check.rs
Files touched (no-bobbin): src/cargo/core/compiler/build_runner/compilation_files.rs, tests/testsuite/check.rs
Files touched (with-bobbin): src/cargo/core/compiler/build_runner/compilation_files.rs, tests/testsuite/check.rs
Polars (Rust)
polars-004 medium
Commit: 052e68fc47
Task prompt
Fix the sortedness tracking for
concat\_strwith multiple inputs. The optimizer incorrectly marks the output ofconcat\_stron multiple columns as sorted, which can cause wrong results in downstream operations that rely on sorted invariants. The fix should not propagate sortedness metadata when concat_str operates on multiple input columns, since concatenating multiple sorted columns does not produce a sorted result.
Implement the fix. Run the test suite with the test command to verify.
| Approach | Tests Pass | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|
| no-bobbin | 100.0% | 100.0% | 66.7% | 80.0% | 4.4m | $0.81 |
Ground truth files: crates/polars-plan/src/plans/optimizer/sortedness.rs, py-polars/tests/unit/lazyframe/test_optimizations.py, py-polars/tests/unit/streaming/test_streaming_join.py
Files touched (no-bobbin): crates/polars-plan/src/plans/optimizer/sortedness.rs, py-polars/tests/unit/lazyframe/test_optimizations.py
polars-005 medium
Commit: 8f60a2d641
Task prompt
Fix inconsistent behavior when dividing literals by zero. Currently,
pl.lit(1) // pl.lit(0)produces different results depending on the dtype: panics for unsigned types, returns 0 for same-type signed ints, and returns null for mixed types. The correct behavior is to always return null when dividing by zero, matching standard SQL/dataframe semantics. Fix the literal simplification logic in the optimizer to handle division-by-zero cases consistently.
Implement the fix. Run the test suite with the test command to verify.
| Approach | Tests Pass | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|
| no-bobbin | 100.0% | 100.0% | 66.7% | 79.4% | 6.4m | $1.74 |
Ground truth files: crates/polars-plan/src/dsl/expr/mod.rs, crates/polars-plan/src/plans/optimizer/simplify_expr/mod.rs, crates/polars-utils/src/floor_divmod.rs, py-polars/tests/unit/expr/test_literal.py
Files touched (no-bobbin): crates/polars-plan/src/plans/optimizer/simplify_expr/mod.rs, crates/polars-utils/src/floor_divmod.rs, py-polars/tests/unit/expr/test_literal.py
Ruff (Rust)
ruff-001 medium
Commit: f14fd5d885
Task prompt
Fix the Python formatter to preserve parentheses around exception handler tuples when any element is a starred expression (Python 3.14+). Without parentheses, a starred element in an except clause is parsed as
except*(exception group syntax) rather than a tuple containing a starred element, changing semantics. Add a check for starred expressions in the tuple before removing parentheses.
| Approach | Tests Pass | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|
| no-bobbin | 100.0% | 31.7% | 33.3% | 32.4% | 4.3m | $1.23 |
| with-bobbin | 100.0% | 70.2% | 61.9% | 63.6% | 4.4m | $1.52 |
| with-bobbin+blame_bridging=false | 100.0% | 33.3% | 33.3% | 33.3% | 4.6m | $1.25 |
| with-bobbin+coupling_depth=0 | 100.0% | 55.6% | 33.3% | 38.9% | 4.7m | $1.45 |
| with-bobbin+doc_demotion=0.0 | 100.0% | 55.6% | 55.6% | 55.6% | 5.0m | $1.48 |
| with-bobbin+gate_threshold=1.0 | 100.0% | 77.8% | 55.6% | 61.1% | 5.4m | $1.39 |
| with-bobbin+recency_weight=0.0 | 100.0% | 77.8% | 55.6% | 61.1% | 4.9m | $1.43 |
| with-bobbin+semantic_weight=0.0 | 100.0% | 23.6% | 33.3% | 25.2% | 4.7m | $1.45 |
Ground truth files: crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/try.py, crates/ruff_python_formatter/src/other/except_handler_except_handler.rs, crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap
Files touched (no-bobbin): crates/ruff_python_formatter/resources/test/fixtures/black/cases/remove_except_types_parens.py, crates/ruff_python_formatter/src/other/except_handler_except_handler.rs, crates/ruff_python_formatter/tests/snapshots/black_compatibility@cases__remove_except_types_parens.py.snap
Files touched (with-bobbin): crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/try.py, crates/ruff_python_formatter/src/other/except_handler_except_handler.rs, crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap
Files touched (with-bobbin+blame_bridging=false): crates/ruff_python_formatter/resources/test/fixtures/ruff/except_handler_starred.py, crates/ruff_python_formatter/src/other/except_handler_except_handler.rs, crates/ruff_python_formatter/tests/snapshots/format@except_handler_starred.py.snap
Files touched (with-bobbin+coupling_depth=0): crates/ruff_python_formatter/src/other/except_handler_except_handler.rs
Files touched (with-bobbin+doc_demotion=0.0): crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/try.py, crates/ruff_python_formatter/src/other/except_handler_except_handler.rs, crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap
Files touched (with-bobbin+gate_threshold=1.0): crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/try.py, crates/ruff_python_formatter/src/other/except_handler_except_handler.rs, crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap
Files touched (with-bobbin+recency_weight=0.0): crates/ruff_python_formatter/resources/test/fixtures/ruff/statement/try.py, crates/ruff_python_formatter/src/other/except_handler_except_handler.rs, crates/ruff_python_formatter/tests/snapshots/format@statement__try.py.snap
Files touched (with-bobbin+semantic_weight=0.0): crates/ruff_python_formatter/resources/test/fixtures/black/cases/remove_except_types_parens.py, crates/ruff_python_formatter/resources/test/fixtures/black/cases/remove_except_types_parens.py.expect, crates/ruff_python_formatter/src/other/except_handler_except_handler.rs
ruff-002 easy
Commit: ddeadcbd18
Task prompt
Add
multiprocessing.Valueto the list of functions excluded from the flake8-boolean-trap rule FBT003. TheValueconstructor commonly takes a boolean as its second argument (e.g.,Value("b", False)), and this is a legitimate use case that should not trigger a boolean positional argument warning. Implement a newis\_semantically\_allowed\_func\_callhelper that uses qualified name resolution to match the call, and integrate it into the existingallow\_boolean\_traplogic.
| Approach | Tests Pass | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|
| no-bobbin | 100.0% | 100.0% | 40.0% | 57.1% | 4.8m | $0.00 |
| with-bobbin | 100.0% | 100.0% | 40.0% | 57.1% | 4.3m | $1.38 |
Ground truth files: crates/ruff_linter/resources/test/fixtures/flake8_boolean_trap/FBT.py, crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs, crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__FBT001_FBT.py.snap, crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__FBT003_FBT.py.snap, crates/ruff_linter/src/rules/flake8_boolean_trap/snapshots/ruff_linter__rules__flake8_boolean_trap__tests__extend_allowed_callable.snap
Files touched (no-bobbin): crates/ruff_linter/resources/test/fixtures/flake8_boolean_trap/FBT.py, crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs
Files touched (with-bobbin): crates/ruff_linter/resources/test/fixtures/flake8_boolean_trap/FBT.py, crates/ruff_linter/src/rules/flake8_boolean_trap/helpers.rs
ruff-003 medium
Commit: 80dbc62a76
Task prompt
Fix the pylint PLC2701 (import-private-name) rule in two ways: (1) Stop flagging dunder submodules like
\_\_main\_\_as private imports by using theis\_dunderhelper instead of a simplestarts\_with("\_\_")check, which incorrectly matched dunder names. (2) Improve diagnostic ranges to point at the exact private name segment rather than the entire import binding, using a tokenizer to locate the private segment in module paths and alias name ranges for imported members.
| Approach | Tests Pass | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|
| no-bobbin | 100.0% | 100.0% | 83.3% | 90.0% | 9.2m | $0.00 |
| with-bobbin | 100.0% | 100.0% | 77.8% | 86.7% | 6.3m | $1.92 |
Ground truth files: crates/ruff_linter/resources/test/fixtures/pylint/import_private_name/submodule/__main__.py, crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs, crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2701_import_private_name__submodule____main__.py.snap
Files touched (no-bobbin): crates/ruff_linter/resources/test/fixtures/pylint/import_private_name/submodule/__main__.py, crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs, crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2701_import_private_name__submodule____main__.py.snap
Files touched (with-bobbin): crates/ruff_linter/resources/test/fixtures/pylint/import_private_name/submodule/__main__.py, crates/ruff_linter/src/rules/pylint/rules/import_private_name.rs, crates/ruff_linter/src/rules/pylint/snapshots/ruff_linter__rules__pylint__tests__PLC2701_import_private_name__submodule____main__.py.snap
ruff-004 easy
Commit: ceb876b823
Task prompt
Fix inconsistent handling of forward references (stringized annotations) in the flake8-pyi PYI034 rule for
\_\_new\_\_,\_\_enter\_\_, and\_\_aenter\_\_methods. Previously, return type annotations using forward references likedef \_\_new\_\_(cls) -> "MyClass"were not detected, while non-stringized versions were. Add a newis\_name\_or\_stringized\_namehelper that usesmatch\_maybe\_stringized\_annotationto resolve both plain and stringized annotations, and use it for the return type checks.
| Approach | Tests Pass | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|
| no-bobbin | 100.0% | 46.7% | 66.7% | 54.2% | 3.9m | $0.00 |
| with-bobbin | 100.0% | 63.3% | 83.3% | 70.8% | 4.5m | $1.67 |
Ground truth files: crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py, crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs, crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.py.snap
Files touched (no-bobbin): crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py, crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.pyi, crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs, crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.py.snap, crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.pyi.snap
Files touched (with-bobbin): crates/ruff_linter/resources/test/fixtures/flake8_pyi/PYI034.py, crates/ruff_linter/src/rules/flake8_pyi/rules/non_self_return_type.rs, crates/ruff_linter/src/rules/flake8_pyi/snapshots/ruff_linter__rules__flake8_pyi__tests__PYI034_PYI034.py.snap
ruff-005 easy
Commit: aa9c9bf01f
Task prompt
Suppress diagnostic output when running
ruff format --check --silent. The--silentflag was not being respected in format check mode, causing “Would reformat” messages to still appear. Fix by wrapping the output-writing match block in alog\_level > LogLevel::Silentguard so that no stdout output is produced in silent mode. Add integration tests for both--silentand--quietmodes to verify correct output behavior.
| Approach | Tests Pass | Precision | Recall | F1 | Duration | Cost |
|---|---|---|---|---|---|---|
| no-bobbin | 100.0% | 100.0% | 100.0% | 100.0% | 3.6m | $0.00 |
| with-bobbin | 100.0% | 100.0% | 100.0% | 100.0% | 2.8m | $0.63 |
Ground truth files: crates/ruff/src/commands/format.rs, crates/ruff/tests/cli/format.rs
Files touched (no-bobbin): crates/ruff/src/commands/format.rs, crates/ruff/tests/cli/format.rs
Files touched (with-bobbin): crates/ruff/src/commands/format.rs, crates/ruff/tests/cli/format.rs
Chunk Types
Bobbin parses source files into semantic chunks — structural units like functions, classes, and documentation sections. Each chunk is stored with its type, name, line range, content, and embedding vector.
Code Chunk Types
These chunk types are extracted by tree-sitter from supported programming languages.
| Type | Description | Languages |
|---|---|---|
function | Standalone function definitions | Rust (fn), TypeScript, Python (def), Go (func), Java, C++ |
method | Functions defined inside a class or type | TypeScript, Java, C++ |
class | Class definitions (including body) | TypeScript, Python, Java, C++ |
struct | Struct/record type definitions | Rust, Go, C++ |
enum | Enumeration type definitions | Rust, Java, C++ |
interface | Interface definitions | TypeScript, Java |
trait | Trait definitions | Rust |
impl | Implementation blocks | Rust (impl Type) |
module | Module declarations | Rust (mod) |
Markdown Chunk Types
These chunk types are extracted by pulldown-cmark from Markdown files.
| Type | Description | Example |
|---|---|---|
section | Content under a heading (including the heading) | ## Architecture and its body text |
table | Markdown tables | | Column | Column | |
code_block | Fenced code blocks | ```rust ... ``` |
doc | YAML frontmatter blocks | ---\ntitle: "..." |
section
A section chunk captures a heading and all content up to the next heading of the same or higher level. Section names include the full heading hierarchy, so nested headings produce names like "API Reference > Authentication > OAuth Flow".
Given this markdown:
# API Reference
Overview text.
## Authentication
Auth details here.
### OAuth Flow
OAuth steps.
Bobbin produces three section chunks:
"API Reference"— contains “Overview text.”"API Reference > Authentication"— contains “Auth details here.”"API Reference > Authentication > OAuth Flow"— contains “OAuth steps.”
Content before the first heading (excluding frontmatter) becomes a doc chunk named “Preamble”.
Search example:
bobbin search "OAuth authorization" --type section
table
Table chunks capture the full markdown table. They are named after their parent section heading — for example, a table under ## Configuration becomes "Configuration (table)".
Given this markdown:
## Configuration
| Key | Default | Description |
|----------|---------|----------------------|
| timeout | 30 | Request timeout (s) |
| retries | 3 | Max retry attempts |
Bobbin produces one table chunk named "Configuration (table)".
Search example:
bobbin search "timeout settings" --type table
bobbin grep "retries" --type table
code_block
Code block chunks capture fenced code blocks. They are named by their language tag — ```bash produces a chunk named "code: bash".
Given this markdown:
## Installation
```bash
pip install mypackage
```
```python
import mypackage
mypackage.init()
```
Bobbin produces two code_block chunks: "code: bash" and "code: python".
Search example:
bobbin search "install dependencies" --type code_block
bobbin grep "pip install" --type code_block
doc (frontmatter)
Doc chunks capture YAML frontmatter at the top of a markdown file. The chunk is named "Frontmatter".
Given this markdown:
---
title: Deployment Guide
tags: [ops, deployment]
status: published
---
# Deployment Guide
Bobbin produces one doc chunk named "Frontmatter" containing the YAML block.
Search example:
bobbin grep "status: draft" --type doc
bobbin search "deployment guide metadata" --type doc
Special Chunk Types
| Type | Description |
|---|---|
commit | Git commit messages (used internally for history analysis) |
other | Fallback for line-based chunks from unsupported file types |
Line-Based Fallback
Files that don’t match a supported language are split into line-based chunks: 50 lines per chunk with a 10-line overlap between consecutive chunks. These chunks have type other.
Filtering by Type
Both the CLI and MCP tools support filtering by chunk type:
# CLI
bobbin search "auth" --type function
bobbin grep "TODO" --type struct
# MCP tool
search(query: "auth", type: "function")
grep(pattern: "TODO", type: "struct")
Accepted type values (case-insensitive, with aliases):
| Value | Aliases |
|---|---|
function | func, fn |
method | — |
class | — |
struct | — |
enum | — |
interface | — |
module | mod |
impl | — |
trait | — |
doc | documentation |
section | — |
table | — |
code_block | codeblock |
commit | — |
other | — |
Language-to-Chunk Mapping
| Language | Extensions | Chunk Types Extracted |
|---|---|---|
| Rust | .rs | function, method, struct, enum, trait, impl, module |
| TypeScript | .ts, .tsx | function, method, class, interface |
| Python | .py | function, class |
| Go | .go | function, method, struct |
| Java | .java | method, class, interface, enum |
| C++ | .cpp, .cc, .hpp | function, method, class, struct, enum |
| Markdown | .md | section, table, code_block, doc |
| Other | * | other (line-based) |
Search Modes
Bobbin supports three search modes, selectable via the --mode flag (CLI) or mode parameter (MCP).
Hybrid (Default)
bobbin search "error handling" # hybrid is the default
bobbin search "error handling" --mode hybrid
Hybrid search runs both semantic and keyword searches in parallel, then merges results using Reciprocal Rank Fusion (RRF).
How RRF Works
- Run semantic search → get ranked list A
- Run keyword search → get ranked list B
- For each result, compute:
score = w / (k + rank_A) + (1 - w) / (k + rank_B)w=semantic_weight(default: 0.7)k= smoothing constant (60)
- Sort by combined score
Results that appear in both lists get boosted. Results unique to one list still appear but with lower scores.
When to Use
Hybrid is the best default for most queries. It handles both conceptual queries (“functions that validate user input”) and specific terms (“parseConfig”) well.
Semantic
bobbin search "authentication middleware" --mode semantic
Semantic search converts your query into a 384-dimensional vector using the same embedding model as the index, then finds the most similar code chunks via approximate nearest neighbor (ANN) search in LanceDB.
Strengths
- Finds conceptually similar code even when wording differs
- “error handling” matches
catch,Result<T>,try/except - Good for exploratory queries when you don’t know exact names
Limitations
- May miss exact identifier matches that keyword search would find
- Requires the embedding model to be loaded (slight startup cost on first query)
Keyword
bobbin search "handleRequest" --mode keyword
bobbin grep "handleRequest" # grep always uses keyword mode
Keyword search uses LanceDB’s full-text search (FTS) index. It matches tokens in chunk content and names.
Strengths
- Fast, exact matching
- Finds specific identifiers, variable names, and error messages
- No embedding model needed
Limitations
- No semantic understanding — “error handling” won’t match “catch”
- Token-based, not substring-based (FTS tokenization rules apply)
Comparison
| Feature | Hybrid | Semantic | Keyword |
|---|---|---|---|
| Conceptual matching | Yes | Yes | No |
| Exact identifier matching | Yes | Weak | Yes |
| Speed | Moderate | Moderate | Fast |
| Requires embeddings | Yes | Yes | No |
| Default mode | Yes | No | No |
Configuration
The hybrid search balance is controlled by semantic_weight in .bobbin/config.toml:
[search]
semantic_weight = 0.7 # 0.0 = keyword only, 1.0 = semantic only
default_limit = 10
Higher values favor semantic results; lower values favor keyword matches. The default (0.7) works well for most codebases.
grep vs search –mode keyword
Both use the same underlying FTS index. The differences:
| Feature | bobbin grep | bobbin search --mode keyword |
|---|---|---|
| Regex support | Yes (--regex) | No |
| Case-insensitive | Yes (-i) | No |
| Matching lines shown | Yes | No |
| Output format | Grep-style with line highlighting | Search-style with scores |
Exit Codes
Bobbin uses standard Unix exit codes. All commands follow the same convention.
Exit Code Table
| Code | Meaning | Common Causes |
|---|---|---|
0 | Success | Command completed normally |
1 | General error | Invalid arguments, missing configuration, runtime errors |
2 | Usage error | Invalid command syntax (from clap argument parser) |
Common Error Scenarios
Not Initialized (exit 1)
Error: Bobbin not initialized in /path/to/project. Run `bobbin init` first.
Occurs when running any command that requires an index (search, grep, context, status, serve, etc.) before running bobbin init.
No Indexed Content (exit 0, empty results)
Commands like search and grep return exit code 0 with zero results if the index exists but is empty. Run bobbin index to populate it.
Invalid Arguments (exit 2)
error: unexpected argument '--foo' found
The clap argument parser returns exit code 2 for unrecognized flags, missing required arguments, or invalid argument values.
File Not Found (exit 1)
Error: File not found in index: src/nonexistent.rs
Occurs when related or read_chunk references a file that isn’t in the index.
Invalid Search Mode (exit 1)
Error: Invalid search mode: 'fuzzy'. Use 'hybrid', 'semantic', or 'keyword'
Using Exit Codes in Scripts
# Check if bobbin is initialized
if bobbin status --quiet 2>/dev/null; then
echo "Index ready"
else
bobbin init && bobbin index
fi
# Search with error handling
if ! bobbin search "auth" --json > results.json; then
echo "Search failed" >&2
exit 1
fi
JSON Error Output
When using --json mode, errors are still printed to stderr as plain text. Only successful results are written to stdout as JSON.
Glossary
A
- ANN (Approximate Nearest Neighbor)
- Vector similarity search algorithm used by LanceDB to find embeddings closest to a query vector. Trades exact precision for speed on large datasets.
C
- Chunk
- A semantic unit of code extracted from a source file — a function, class, struct, markdown section, etc. Chunks are the fundamental unit of indexing and search in bobbin. See Chunk Types.
- Chunk Type
- The structural category of a chunk:
function,method,class,struct,enum,interface,trait,impl,module,section,table,code_block,doc,commit, orother. - Context Assembly
- The process of building a focused bundle of code relevant to a task. Combines search results with temporally coupled files, deduplicates, and trims to a line budget. See
bobbin context. - Context Budget
- Maximum number of lines of code content included in a context bundle. Default: 500 lines.
- Contextual Embedding
- Enriching a chunk with surrounding lines before computing its embedding vector. Improves search relevance by giving the embedding model more context about what the chunk does.
- Coupling
- See Temporal Coupling.
E
- Embedding
- A fixed-length numerical vector (384 dimensions) that represents the semantic meaning of a chunk. Generated locally using the all-MiniLM-L6-v2 ONNX model.
F
- FTS (Full-Text Search)
- Token-based keyword search provided by LanceDB’s built-in full-text search index. Powers keyword mode and
bobbin grep.
H
- Hotspot
- A file with both high churn (frequently changed) and high complexity (complex AST structure). Hotspot score is the geometric mean of normalized churn and complexity. See
bobbin hotspots. - Hybrid Search
- The default search mode that combines semantic and keyword search results via Reciprocal Rank Fusion (RRF). See Search Modes.
L
- LanceDB
- Embedded columnar vector database used as bobbin’s primary storage. Stores chunks, embedding vectors, and the full-text search index.
- Line-Based Chunking
- Fallback parsing strategy for unsupported file types. Splits files into chunks of 50 lines with 10-line overlap.
M
- MCP (Model Context Protocol)
- An open protocol for connecting AI assistants to external tools and data sources. Bobbin implements an MCP server that exposes its search and analysis capabilities. See MCP Overview.
O
- ONNX Runtime
- Cross-platform inference engine used by bobbin to run the embedding model locally. No GPU required.
P
- Primer
- An LLM-friendly overview document of the bobbin project, shown via
bobbin prime. Includes architecture, commands, and live index statistics.
R
- Reciprocal Rank Fusion (RRF)
- Algorithm for merging multiple ranked lists. Used by hybrid search to combine semantic and keyword results. Each result’s score is based on its rank position in each list, weighted by
semantic_weight.
S
- Semantic Search
- Search by meaning using vector similarity. Converts the query into an embedding and finds the most similar chunks via ANN search. See Search Modes.
- Semantic Weight
- Configuration value (0.0–1.0) that controls the balance between semantic and keyword results in hybrid search. Default: 0.7 (favors semantic).
T
- Temporal Coupling
- A measure of how often two files change together in git history. Files with high coupling scores are likely related — changing one often means the other needs changes too. See
bobbin related. - Thin Client
- CLI mode where bobbin forwards requests to a remote HTTP server instead of accessing local storage. Enabled via the
--server <URL>global flag. - Tree-sitter
- Incremental parsing library used by bobbin to extract structural code elements (functions, classes, etc.) from source files. Supports Rust, TypeScript, Python, Go, Java, and C++.
V
- Vector Store
- The LanceDB database that holds chunk embeddings and supports both ANN search and full-text search. Located in
.bobbin/lance/.
Bobbin Vision & Mission
Mission Statement
search · coupling · context — local · private · fast
Bobbin is a local-first context engine that gives developers and AI agents deep, structured access to codebases without sending data to the cloud.
Vision
In the era of AI-assisted development, context is everything. Current tools treat code as flat text, losing the rich structural and temporal information that makes codebases understandable. Bobbin changes this by treating code as a living, evolving artifact with history, structure, and relationships.
Bobbin enables “Temporal RAG” - retrieval that understands not just what code says, but how it evolved and what changes together.
Core Principles
1. Local-First, Always
- All indexing and search happens on your machine
- No data leaves your environment
- Works offline, works air-gapped
- Your code stays yours
2. Structure-Aware
- Code is parsed, not chunked arbitrarily
- Functions, classes, and modules are first-class citizens
- Respects language semantics via Tree-sitter
3. Temporally-Aware
- Git history is a retrieval signal, not just version control
- Files that change together are semantically linked
- Understand “what usually changes when X changes”
4. Agent-Ready, Human-Friendly
- CLI interface works for both humans and AI agents
- No special agent protocols required (MCP optional)
- Simple, composable commands
What Bobbin Is
- A code indexer that understands syntax structure
- A documentation indexer for markdown/text files
- A git forensics engine that tracks file relationships over time
- A semantic search engine using local embeddings
- A keyword search engine for precise lookups
- A context aggregator that pulls related information together
What Bobbin Is Not
- Not a task manager or agent harness
- Not an IDE extension (headless by design)
- Not a cloud service
- Not an AI agent itself - it serves agents
Key Differentiators
| Feature | Traditional RAG | Bobbin |
|---|---|---|
| Chunking | Fixed token windows | AST-based structural units |
| History | HEAD only | Full git timeline |
| Relationships | Vector similarity only | Temporal coupling + similarity |
| Privacy | Often cloud-based | Strictly local |
| Runtime | Client-server | Embedded/serverless |
Target Users
- AI Coding Agents - Claude Code, Cursor, Aider, custom agents
- Developers - Direct CLI usage for code exploration
- Tool Builders - Foundation for context-aware dev tools
- Agent Harnesses - Middleware integration for orchestration tools
Success Metrics
- Sub-second query latency on repos up to 1M LOC
- Zero network calls during operation
- Retrieval precision that matches or exceeds cloud alternatives
- Seamless adoption in existing workflows
Technology Choices
| Component | Choice | Rationale |
|---|---|---|
| Language | Rust | Performance, memory safety, ecosystem (Tree-sitter, LanceDB) |
| Vector Store | LanceDB | Embedded, serverless, git-friendly storage |
| Parser | Tree-sitter | Incremental, multi-language, battle-tested |
| Embeddings | Local ONNX (all-MiniLM-L6-v2) | Fast CPU inference, no API dependency |
Bobbin: search · coupling · context — local · private · fast.
Roadmap
Phase 1: Foundation (MVP) – Complete
- Tree-sitter code indexing (Rust, TypeScript, Python)
- LanceDB vector storage
- SQLite metadata + FTS
- CLI:
initcommand - CLI:
indexcommand (full and incremental) - Configuration management
- ONNX embedding generation
- CLI:
searchcommand - CLI:
grepcommand - CLI:
statuscommand
Phase 2: Intelligence – Complete
- Hybrid search (RRF combining semantic + keyword)
- Git temporal coupling analysis
- Related files suggestions
- Additional language support (Go, Java, C++)
Phase 3: Polish – In Progress
- MCP server integration (
bobbin serve) - Multi-repo support (
--repoflag) - LanceDB-primary storage consolidation
- Contextual embeddings
- Semantic markdown chunking (pulldown-cmark)
- File history and churn analysis (
bobbin history) - Context assembly command (
bobbin context) - Watch mode / file watcher daemon (
bobbin watch) - Shell completions (
bobbin completions) - Code hotspot identification (
bobbin hotspots) - Symbol reference resolution (
bobbin refs) - Import/dependency analysis (
bobbin deps) - AST complexity metrics
- Transitive impact analysis with decay
- Thin-client HTTP mode (
--serverflag) - Configurable embedding models (infrastructure exists, UI incomplete)
- Integration tests against real repos
- Performance optimizations at scale
Phase 3.5: Production Hardening – In Progress
See docs/plans/production-hardening.md for details.
- Install protoc +
just setuprecipe (bobbin-1nv) - Clean up tambour references (bobbin-7pn)
- Fix production unwrap() calls (bobbin-ehp)
- Integration test foundation (bobbin-ul6)
- Add missing MCP tools — deps, history, status (bobbin-tnt)
- Add missing HTTP endpoints (bobbin-pid)
- Wire up incremental indexing (bobbin-thb)
- CI pipeline — GitHub Actions (bobbin-ola)
- Update AGENTS.md and CONTRIBUTING.md (bobbin-6lx)
Phase 4: Higher-Order Analysis – In Progress
Compose existing signals into capabilities greater than the sum of their parts.
See docs/plans/backlog.md for detailed exploration of each feature.
- Test coverage mapping via git coupling
- Claude Code hooks / tool integration
- Semantic commit indexing
- Refactoring planner (rename, move, extract)
- Cross-repo temporal coupling
Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[0.1.0] - 2026-02-07
Added
- Code indexing with tree-sitter parsing for Rust, TypeScript, Python, Go, Java, and C++
- Semantic search using ONNX Runtime embeddings (all-MiniLM-L6-v2)
- Full-text keyword search via LanceDB/tantivy
- Hybrid search combining semantic and keyword results with Reciprocal Rank Fusion
- Git history analysis for temporal context
- Coupling detection between files based on co-change patterns
- MCP server for AI agent integration
- CLI with
index,search,grep,mcp-server, andcompletionssubcommands - LanceDB as primary vector storage with SQLite for coupling metadata
- Support for
.bobbinignoreexclude patterns
Contributing to Bobbin
Bobbin is a local-first Rust code context engine.
Using Just
This project uses just as a command runner. Always prefer just commands over raw cargo commands - they’re configured with sensible defaults that reduce output noise and save context.
just --list # Show available commands
just setup # Install system deps (protoc, c++, verify rust)
just build # Build (quiet output)
just test # Run tests (quiet output)
just check # Type check (quiet output)
just lint # Run clippy (quiet output)
just run # Build and run
Verbose Output
All cargo commands run in quiet mode by default (-q --message-format=short). To see full output:
just build verbose=true
just test verbose=true
Rust Development
Prerequisites
- Rust (stable toolchain) — install via rustup
justcommand runnerprotoc(Protocol Buffers compiler) — required by lancedb- C++ compiler (
g++on Linux, Xcode CLT on macOS)
Run just setup to install system dependencies automatically.
Build Commands
just build # Build the project
just test # Run all tests
just check # Type check without building
just lint # Lint with clippy
Feature Integration Checklist
When adding new features to bobbin (new search signals, data sources, chunk types, or storage capabilities), review whether the bobbin context command should incorporate the new signal.
The context command is the “everything relevant in one shot” command. It combines hybrid search + temporal coupling to assemble task-aware context bundles. New retrieval signals should flow into it.
Before merging a new feature, check:
- Does this feature produce a new retrieval signal? (e.g., dependency graph, complexity scores)
- If yes, should
contextuse it during assembly? Updatesrc/search/context.rs - Does this change chunk types or storage schema? Update context output types if needed
- Does the MCP
contexttool need updating? Checksrc/mcp/server.rs - Are there new CLI flags that
contextshould also expose?
Task specs for the context command live in docs/tasks/context-*.md.