Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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 assemblybobbin context "fix the login bug" builds a budget-controlled bundle of the most relevant code, ready for an AI agent.
  • MCP serverbobbin serve exposes 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.

SectionWhat You’ll Find
Getting StartedInstallation, first index, core concepts, agent setup
GuidesSearching, context assembly, git coupling, hooks, multi-repo
CLI ReferenceEvery command with flags, examples, and output formats
MCP IntegrationAI agent tools, client configuration, HTTP mode
ConfigurationFull .bobbin/config.toml reference
ArchitectureSystem design, storage, embedding pipeline
EvaluationMethodology, 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.

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 TypeLanguagesExample
functionRust, TypeScript, Python, Go, Java, C++fn parse_config(...)
methodTypeScript, Java, C++class.handleRequest()
classTypeScript, Python, Java, C++class AuthService
structRust, Go, C++struct Config
enumRust, Java, C++enum Status
interfaceTypeScript, Javainterface Handler
traitRusttrait Serialize
implRustimpl Config
moduleRustmod auth
sectionMarkdown## Architecture
tableMarkdownMarkdown tables
code_blockMarkdownFenced 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:

ModeHow It WorksBest For
Hybrid (default)Combines semantic + keyword via RRFGeneral-purpose queries
SemanticVector similarity (ANN) onlyConceptual queries (“authentication logic”)
KeywordFull-text search (FTS) onlyExact 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 file
  • bobbin context <query> — automatically expand search results with coupled files

Context Assembly

The context command combines search and coupling into a single context bundle:

  1. Search: Find chunks matching your query
  2. Expand: Add temporally coupled files for each match
  3. Deduplicate: Remove redundant chunks across files
  4. 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:

StoreTechnologyContents
PrimaryLanceDBChunks, vector embeddings, full-text search index
MetadataSQLiteTemporal coupling data, file metadata

All data lives in .bobbin/ within your repository. Nothing is sent externally.

Next Steps

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

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:

  1. On every prompt (UserPromptSubmit): Search your codebase for code relevant to the prompt and inject it as context.
  2. 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 search tool)
  • “What files are related to src/main.rs?” (uses related tool)
  • “Find the definition of parse_config” (uses find_refs tool)

Prerequisites

Before connecting an agent, make sure your repository is initialized and indexed:

bobbin init
bobbin index

Next Steps

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.

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:

ModeBehavior
offNo bridging (baseline)
injectAdd discovered files as new results (default)
boostBoost scores of files already in results
boost_injectBoth: 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

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 doc chunks
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

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:

  1. Search — runs hybrid search for your query and collects the top-ranked code chunks.
  2. Coupling expansion — for each result, looks up files that frequently change together in git history (temporal coupling) and pulls in related chunks.
  3. 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:

  1. Takes each search result file.
  2. Looks up its top coupled files (limited by --max-coupled, default 3).
  3. Filters by --coupling-threshold (default 0.1).
  4. 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

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.

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

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

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.

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'

These three tools answer different questions:

ToolQuestionLevel
depsWhat does this file import / what imports it?File (import graph)
refsWhere is this symbol defined / used?Symbol (name resolution)
relatedWhat 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

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.

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

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 same config.toml settings (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

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:

  1. Rules — Glob patterns that assign tags to files during indexing
  2. Effects — Score adjustments applied when tagged chunks appear in results
  3. 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 paths
  • tags — 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.md matches both CHANGELOG.md and docs/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].

FieldTypeDescription
boostfloatScore multiplier. Positive = boost, negative = demote.
excludeboolRemove chunks with this tag from results entirely.
pinboolBypass relevance threshold; always include if budget allows.
budget_reserveintLines 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)

TagApplied to
auto:initGo init() functions
auto:testTest functions (Go, Rust, Python, JS)
auto:docsDocumentation files (markdown, rst, etc.)
auto:configConfig files (YAML, TOML, JSON, .env, etc.)
auto:generatedGenerated 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., canonicaluser: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:

PatternSpecificityExample match
aegis/crew/stryder3 (most specific)Exact agent
*/crew/stryder2Any rig’s stryder
*/crew/*1Any 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:

NamespacePurposeExamples
auto:Auto-assigned by bobbinauto:test, auto:init
type:Document/chunk typetype:changelog, type:design, type:eval
role:Agent instruction filesrole:claude-md, role:agents-md
domain:Domain/topic areadomain:iac, domain:lifecycle, domain:comms
criticality:Importance levelcriticality:high
feedback:Feedback-driven scoringfeedback: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 increase
  • boost = -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

MechanismWhen appliedEffectUse case
Exclude (exclude = true)Pre-search (SQL WHERE clause)Chunk never fetched from DBNoise that always wastes budget
Demote (negative boost)Post-search scoringScore reduced but chunk still includedSometimes useful, usually low-priority
Pin (pin = true)Assembly stage (reserved budget)Bypasses relevance threshold, injected firstCritical 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:

  1. Check [[effects_scoped]] entries matching both tag AND role glob
  2. Most specific role pattern wins (count non-wildcard segments)
  3. Fall back to global [effects.tag]
  4. 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:

  1. Reindex affected repos to apply new tag rules to chunks:

    bobbin index /path/to/data --repo <name> --source /path/to/repo --force
    
  2. 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 /search endpoint returns raw relevance scores without tag effects. To verify that tag boosts/demotions are working, test with the /context endpoint or the bobbin context CLI 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:

RatingMeaning
usefulThe injected code was relevant and helpful
noiseThe injection was irrelevant to the task
harmfulThe 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:

TagMeaning
feedback:hotFile is frequently rated as useful
feedback:coldFile 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

EndpointMethodDescription
/feedbackPOSTSubmit a rating: {injection_id, agent, rating, reason}
/feedbackGETList feedback with filters: ?rating=noise&agent=stryder&limit=50
/feedback/statsGETAggregated stats: total injections, ratings by type, coverage
/injections/{id}GETView injection detail: query, files, formatted output, feedback
/feedback/lineagePOSTRecord an action that resolves feedback
/feedback/lineageGETList 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

  1. Keep feedback_prompt_interval low (3-5) during initial deployment to build up data quickly
  2. Review noise patterns in the Feedback tab — repeated noise on the same files indicates missing tag rules or exclusions
  3. Use lineage to track what you changed in response to feedback — this closes the loop
  4. 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

FieldTypeDescription
namestringSource label — used as language tag in chunks and as a search filter
pathstringFilesystem path to the directory of markdown records
schemastringYAML frontmatter value to match (e.g., "agent-memory") — files without this in frontmatter are skipped
name_fieldstringOptional 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: telegram becomes field channel)
  • 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

EndpointDescription
GET /archive/search?q=<query>&source=<name>&limit=10Search 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).
  • Debounces rapid 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=simple with automatic restart on failure.
  • Sets RestartSec=5 and RUST_LOG=info.
  • Includes the resolved working directory and any --repo/--source flags.

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:

  1. Watch mode on your development machine for real-time updates during coding.
  2. Post-commit hook as a safety net in case the watcher wasn’t running.
  3. 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

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:

  1. 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.
  2. 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:

  1. Each injection records which chunks were sent, stored in .bobbin/session/<session_id>/ledger.jsonl
  2. On the next prompt, bobbin filters out chunks already in the ledger
  3. Only new or changed chunks are injected
  4. 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
ModeDescription
standardFile paths, line ranges, and code content
minimalCompact output, fewer decorators
verboseExtended metadata (scores, tags, chunk types)
xmlXML-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]:

SettingDefaultDescription
threshold0.5Minimum relevance score to include a result
budget300Maximum lines of injected context
content_mode"full"Display mode: full, preview, or none
min_prompt_length20Skip injection for prompts shorter than this (chars)
gate_threshold0.45Minimum top-result similarity to inject at all
dedup_enabledtrueSkip injection when results match previous turn
reducing_enabledtrueProgressive reducing (delta injection across session)
show_docstrueInclude 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_boost2.0Score multiplier for current repo files
feedback_prompt_interval5Prompt 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

FieldTypeDescription
namestringRule name (used for dedup and metrics)
toolstringTool name glob (e.g., "Edit", "Bash", "mcp__homelab__*")
matchtableParameter match conditions — keys are param names, values are regex
guidancestringText shown to the agent. Supports {args.X} and {file_stem} templating
search_querystringSearch query template for injecting related files
search_groupstringIndex group to search
search_tagslistTag filters for scoped search
max_context_linesintMax lines to inject (default: 50)
use_couplingboolUse git coupling instead of search
coupling_thresholdfloatMin coupling score (default: 0.1)
roleslistRole 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

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:

PrioritySourceExample
1--role CLI flagbobbin search --role human "auth"
2BOBBIN_ROLE env varexport BOBBIN_ROLE=human
3GT_ROLE env varSet by Gas Town automatically
4BD_ACTOR env varSet by Gas Town automatically
5Default"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:

  1. Exact match: aegis/crew/ian matches role named aegis/crew/ian
  2. Wildcard match: aegis/crew/ian matches aegis/crew/* (prefix /*)
  3. Less specific wildcard: aegis/polecats/alpha matches aegis/*
  4. Default fallback: if no pattern matches, uses role named default
  5. No config: if no [access] section exists, everything is visible

Deny Precedence

  • Deny always beats allow: deny = ["secret-repo"] wins even with allow = ["*"]
  • 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> --deep in sling args
  • Feature work: create a bundle as you explore, then attach it to the bead with b:<slug>
  • Onboarding: bobbin bundle list shows the knowledge map of the entire codebase

Bundle Structure

A bundle contains:

FieldPurposeExample
nameHierarchical identifiercontext/pipeline
descriptionOne-line summary“5-phase assembly: seed → coupling → bridge → filter → budget”
filesCentral source filessrc/search/context.rs
refsSpecific symbols (file::Symbol)src/tags.rs::BundleConfig
docsRelated documentationdocs/designs/context-bundles.md
keywordsTrigger terms for searchbundle, context bundle, b:slug
includesOther bundles to composetags

Depth Levels

Bundles support three levels of detail:

  • L0bobbin bundle list: tree view of all bundles (names + descriptions)
  • L1bobbin bundle show <name>: outline with file paths, symbol names, doc paths
  • L2bobbin 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::Symbol refs 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 list first 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:

FlagDescription
--jsonOutput in JSON format
--quietSuppress non-essential output
--verboseShow detailed progress
--server <URL>Use remote bobbin server (thin-client mode)

Commands

CommandDescription
bobbin initInitialize bobbin in current repository
bobbin indexBuild/rebuild the search index
bobbin searchHybrid search (combines semantic + keyword)
bobbin contextAssemble task-relevant context from search + git coupling
bobbin grepKeyword/regex search with highlighting
bobbin depsImport dependency analysis
bobbin refsSymbol reference resolution
bobbin relatedFind files related to a given file
bobbin historyShow commit history and churn statistics
bobbin hotspotsIdentify high-churn/complexity code
bobbin impactPredict which files are affected by a change
bobbin reviewAssemble review context from a git diff
bobbin similarFind semantically similar code or detect duplicates
bobbin statusShow index statistics
bobbin serveStart MCP server for AI agent integration
bobbin tourInteractive tour of bobbin features
bobbin primeGenerate LLM-friendly overview with live stats
bobbin benchmarkRun embedding benchmarks
bobbin watchWatch mode for automatic re-indexing
bobbin completionsGenerate shell completions
bobbin hookClaude Code hook integration

Supported Languages

Bobbin uses Tree-sitter for structure-aware parsing, and pulldown-cmark for Markdown:

LanguageExtensionsExtracted Units
Rust.rsfunctions, impl blocks, structs, enums, traits, modules
TypeScript.ts, .tsxfunctions, methods, classes, interfaces
Python.pyfunctions, classes
Go.gofunctions, methods, type declarations
Java.javamethods, constructors, classes, interfaces, enums
C++.cpp, .cc, .hppfunctions, classes, structs, enums
Markdown.mdsections, 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

FlagDescription
--forceOverwrite 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

FlagShortDescription
--incrementalOnly update changed files
--forceForce 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

FlagShortDescription
--type <TYPE>-tFilter by chunk type (function, method, class, struct, enum, interface, module, impl, trait, doc, section, table, code_block)
--limit <N>-nMaximum results (default: 10)
--mode <MODE>-mSearch mode: hybrid (default), semantic, or keyword
--repo <NAME>-rFilter to a specific repository

Search Modes

ModeDescription
hybridCombines semantic + keyword using RRF (default)
semanticVector similarity search only
keywordFull-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

FlagShortDescription
--budget <LINES>-bMaximum lines of content to include (default: 500)
--content <MODE>-cContent mode: full, preview (default for terminal), none
--depth <N>-dCoupling expansion depth, 0 = no coupling (default: 1)
--max-coupled <N>Max coupled files per seed file (default: 3)
--limit <N>-nMax initial search results (default: 20)
--coupling-threshold <F>Min coupling score threshold (default: 0.1)
--repo <NAME>-rFilter 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

ArgumentDescription
<FILE>File to show dependencies for (required)

Options

OptionShortDescription
--reverse-rShow reverse dependencies (files that import this file)
--both-bShow 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

  • refs — find symbol references across the index
  • related — find files related to a given file

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

FlagShortDescription
--ignore-case-iCase insensitive search
--regex-EUse extended regex matching (post-filters FTS results)
--type <TYPE>-tFilter by chunk type
--limit <N>-nMaximum results (default: 10)
--context <N>-CNumber of context lines around matches (default: 0)
--repo <NAME>-rFilter 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:

OptionShortDefaultDescription
--path <DIR>.Directory to search in
--repo <NAME>-rFilter results to a specific repository

Subcommands

refs find

Find the definition and usages of a symbol by name.

bobbin refs find [OPTIONS] <SYMBOL>
Argument/OptionShortDefaultDescription
<SYMBOL>Symbol name to find references for (required)
--type <TYPE>-tFilter by symbol type (function, struct, trait, etc.)
--limit <N>-n20Maximum 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>
ArgumentDescription
<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

  • deps — show import dependencies for a file
  • search — semantic search across the codebase

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

FlagShortDescription
--limit <N>-nMaximum 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

FlagShortDescription
--limit <N>-nMaximum 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

OptionShortDefaultDescription
--path <DIR>.Directory to analyze
--since <EXPR>1 year agoTime window for churn analysis (git date expression)
--limit <N>-n20Maximum number of hotspots to show
--threshold <SCORE>0.0Minimum 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

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

OptionShortDefaultDescription
--path <DIR>.Directory to analyze
--depth <N>-d1Transitive impact depth (1–3)
--mode <MODE>-mcombinedSignal mode: combined, coupling, semantic, deps
--limit <N>-n15Maximum number of results
--threshold <SCORE>-t0.1Minimum impact score (0.0–1.0)
--repo <NAME>-rFilter 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

  • related — find temporally coupled files
  • deps — show import dependencies

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

ArgumentDescription
RANGECommit range (e.g., HEAD~3..HEAD)
PATHDirectory to search in (default: .)

Options

OptionShortDefaultDescription
--branch <BRANCH>-bCompare branch against main
--stagedOnly staged changes
--budget <LINES>500Maximum lines of context to include
--depth <N>-d1Coupling expansion depth (0 = no coupling)
--content <MODE>-cContent mode: full, preview, none
--repo <NAME>-rFilter 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

  • context — task-aware context assembly
  • related — find temporally coupled files

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

OptionShortDefaultDescription
--scanScan entire codebase for near-duplicate clusters
--threshold <SCORE>-t0.85Minimum cosine similarity threshold
--limit <N>-n10Maximum number of results or clusters
--repo <NAME>-rFilter to a specific repository
--cross-repoIn 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

  • search — semantic and hybrid search
  • grep — keyword/regex search

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

FlagShortDescription
--detailedShow per-language breakdown
--repo <NAME>-rStats 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

FlagShortDescription
--samples <N>-nNumber 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]
--applyWrite best config to .bobbin/calibration.json
--fullExtended sweep: also tunes recency and coupling parameters
--resumeResume an interrupted --full sweep from cache
--bridge-sweepSweep bridge_mode + bridge_boost_factor only
--repo <NAME>Repo to calibrate (for multi-repo setups)
--source <DIR>Override source path for git sampling
--verboseShow detailed per-commit results

How It Works

  1. Sample N recent commits from git history (stratified across the time range)
  2. Build queries from commit messages — each message becomes a search probe
  3. Grid-sweep parameter combinations across all configured dimensions
  4. Score each combination by precision, recall, and F1 against ground truth (modified files)
  5. 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 --since window
  • 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:

ParameterValues
semantic_weight0.0, 0.3, 0.5, 0.7, 0.9
doc_demotion0.1, 0.3, 0.5
search_limit10, 20, 30, 40 (or CLI override)
budget_lines150, 300, 500 (or CLI override)
rrf_k60.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 parameterValues
recency_half_life_days7, 14, 30, 90
recency_weight0.0, 0.15, 0.30, 0.50
coupling_depth500, 2000, 5000, 20000
bridge_modeOff, Inject, Boost, BoostInject
bridge_boost_factor0.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

ToolDescription
searchSemantic/hybrid/keyword code search
grepPattern matching with regex support
contextTask-aware context assembly
relatedFind temporally coupled files
read_chunkRead 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-v2
  • bge-small-en-v1.5
  • gte-small

Models are automatically downloaded if not already cached.

Arguments

ArgumentDefaultDescription
[PATH].Directory containing .bobbin/ config

Options

OptionShortDefaultDescription
--query <TEXT>-qQueries to benchmark (required, can be repeated)
--model <NAME>-mall built-inModels to compare (can be repeated)
--iterations <N>5Number of iterations per query
--batch-size <N>32Batch 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 from bobbin.toml.
  • Deduplicates — files whose content hash hasn’t changed are skipped.

The process responds to Ctrl+C and SIGTERM for clean shutdown.

Arguments

ArgumentDefaultDescription
[PATH].Directory containing .bobbin/ config

Options

OptionDefaultDescription
--repo <NAME>Repository name for multi-repo indexing
--source <DIR>same as PATHSource directory to watch (if different from config dir)
--debounce-ms <MS>500Debounce interval in milliseconds
--pid-file <FILE>Write PID to this file for daemon management
--generate-systemdPrint 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=simple with automatic restart on failure.
  • Sets RestartSec=5 and RUST_LOG=info.
  • Includes the resolved working directory and any --repo/--source flags.

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

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

ArgumentDescription
<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

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.json that call bobbin hook inject-context and bobbin hook session-context automatically.
  • 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]
OptionDescription
--globalInstall 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:

  1. UserPromptSubmit — calls bobbin hook inject-context on every prompt, adding relevant code snippets.
  2. SessionStart (compact matcher) — calls bobbin hook session-context after context compaction to restore codebase awareness.

uninstall

Remove bobbin hooks from Claude Code settings.

bobbin hook uninstall [OPTIONS]
OptionDescription
--globalRemove from global settings instead of project-local

status

Show installed hooks and current configuration values.

bobbin hook status [PATH]
ArgumentDefaultDescription
[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]
OptionDescription
--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-dedupForce 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]
OptionDescription
--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/OptionDefaultDescription
[PATH].Directory to operate on
--forceRegenerate 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

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

ArgumentDescription
FEATURERun tour for a specific feature only (e.g., search, hooks)

Options

OptionDefaultDescription
--path <DIR>.Directory to tour
--non-interactiveSkip interactive pauses (run all steps continuously)
--listList 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

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

ArgumentDescription
PATHDirectory to check (default: .)

Options

OptionDefaultDescription
--briefShow 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

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:

FlagShortDescription
--description-dOne-line description
--keywords-kComma-separated trigger keywords
--files-fComma-separated file paths
--refs-rComma-separated file::Symbol references
--docsComma-separated documentation file paths
--includes-iComma-separated names of bundles to compose
--globalStore 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

FlagDescription
--jsonOutput in JSON format
--quietSuppress non-essential output
--verboseShow 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

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:

ToolDescription
searchSemantic/hybrid/keyword code search
grepKeyword and regex pattern matching
contextTask-aware context assembly with coupling expansion
relatedFind temporally coupled files
find_refsFind symbol definitions and usages
list_symbolsList all symbols defined in a file
read_chunkRead a specific code section by file and line range
hotspotsFind high-churn, high-complexity files
primeGet an LLM-friendly project overview with live stats

See Tools Reference for complete parameter documentation.

Resources

URIDescription
bobbin://index/statsIndex statistics (file count, chunk count, languages)

Prompts

NameDescription
explore_codebaseGuided 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

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:

ParameterTypeRequiredDefaultDescription
querystringyesNatural language search query
typestringnoallFilter by chunk type: function, method, class, struct, enum, interface, module, impl, trait
limitintegerno10Maximum number of results
modestringnohybridSearch mode: hybrid, semantic, or keyword
repostringnoallFilter 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:

ParameterTypeRequiredDefaultDescription
patternstringyesPattern to search for
ignore_casebooleannofalseCase-insensitive search
regexbooleannofalseEnable regex matching (post-filters FTS results)
typestringnoallFilter by chunk type
limitintegerno10Maximum number of results
repostringnoallFilter 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:

ParameterTypeRequiredDefaultDescription
querystringyesNatural language task description
budgetintegerno500Maximum lines of content
depthintegerno1Coupling expansion depth (0 = no coupling)
max_coupledintegerno3Max coupled files per seed file
limitintegerno20Max initial search results
coupling_thresholdfloatno0.1Minimum coupling score
repostringnoallFilter 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)

Find files related to a given file based on git commit history (temporal coupling).

Parameters:

ParameterTypeRequiredDefaultDescription
filestringyesFile path relative to repo root
limitintegerno10Maximum number of results
thresholdfloatno0.0Minimum 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:

ParameterTypeRequiredDefaultDescription
symbolstringyesExact symbol name (e.g., parse_config)
typestringnoallFilter by symbol type
limitintegerno20Maximum number of usage results
repostringnoallFilter 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:

ParameterTypeRequiredDefaultDescription
filestringyesFile path relative to repo root
repostringnoallFilter 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:

ParameterTypeRequiredDefaultDescription
filestringyesFile path relative to repo root
start_lineintegeryesStarting line number
end_lineintegeryesEnding line number
contextintegerno0Context 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:

ParameterTypeRequiredDefaultDescription
sincestringno1 year agoTime window (e.g., 6 months ago, 3 months ago)
limitintegerno20Maximum number of hotspots
thresholdfloatno0.0Minimum 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:

ParameterTypeRequiredDefaultDescription
sectionstringnoallSpecific section: what bobbin does, architecture, supported languages, key commands, mcp tools, quick start, configuration
briefbooleannofalseCompact 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:

ParameterTypeRequiredDefaultDescription
targetstringyesFile path or file:symbol reference
depthintegerno1Transitive expansion depth (0–3)
modestringnocombinedSignal mode: combined, coupling, semantic, deps
thresholdfloatno0.1Minimum impact score
limitintegerno20Maximum number of results

review

Assemble review context from a git diff. Finds indexed chunks overlapping changed lines and expands via temporal coupling.

Parameters:

ParameterTypeRequiredDefaultDescription
diffstringnounstagedDiff spec: unstaged, staged, branch:<name>, commit:<range>
budgetintegerno500Maximum lines of context
depthintegerno1Coupling expansion depth

similar

Find code chunks semantically similar to a target, or scan for duplicate clusters.

Parameters:

ParameterTypeRequiredDefaultDescription
targetstringnoChunk reference (file.rs:function_name) or free text
scanbooleannofalseScan entire codebase for near-duplicate clusters
thresholdfloatno0.85Minimum similarity score
limitintegerno10Maximum results
cross_repobooleannofalseInclude cross-repo matches

search_beads

Search for beads (issues/tasks) using natural language. Requires beads to be indexed via bobbin index --include-beads.

Parameters:

ParameterTypeRequiredDefaultDescription
querystringyesNatural language query
priorityintegernoallFilter by priority (1–4)
statusstringnoallFilter by status
assigneestringnoallFilter by assignee
limitintegerno10Maximum results
enrichbooleannotrueEnrich with live Dolt metadata

dependencies

Show import dependencies for a file. Returns forward and/or reverse dependencies.

Parameters:

ParameterTypeRequiredDefaultDescription
filestringyesFile path relative to repo root
reversebooleannofalseShow reverse dependencies (what imports this file)
bothbooleannofalseShow both forward and reverse
repostringnoallFilter to a specific repository

file_history

Show git commit history for a specific file, with author breakdown and churn rate.

Parameters:

ParameterTypeRequiredDefaultDescription
filestringyesFile path relative to repo root
limitintegerno20Maximum commits to return

status

Show current index status and statistics.

Parameters:

ParameterTypeRequiredDefaultDescription
languagesbooleannofalseInclude per-language breakdown

Search git commit history using natural language.

Parameters:

ParameterTypeRequiredDefaultDescription
querystringyesNatural language query
authorstringnoallFilter by author
filestringnoallFilter by file path
limitintegerno10Maximum results

feedback_submit

Submit feedback on a bobbin context injection. Rate injections as useful, noise, or harmful.

Parameters:

ParameterTypeRequiredDefaultDescription
injection_idstringyesInjection ID from [injection_id: inj-xxx]
ratingstringyesuseful, noise, or harmful
agentstringnoautoAgent identity (auto-detected from env)
reasonstringnoExplanation (max 1000 chars)

feedback_list

List recent feedback records with optional filters.

Parameters:

ParameterTypeRequiredDefaultDescription
ratingstringnoallFilter by rating
agentstringnoallFilter by agent
limitintegerno20Maximum 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:

ParameterTypeRequiredDefaultDescription
feedback_idsinteger[]yesFeedback record IDs to link
action_typestringyescode_fix, config_change, tag_effect, access_rule, or exclusion_rule
beadstringnoAssociated bead ID
commit_hashstringnoGit commit hash
descriptionstringyesWhat was done
agentstringnoautoAgent identity

feedback_lineage_list

List lineage records showing how feedback was acted on.

Parameters:

ParameterTypeRequiredDefaultDescription
feedback_idintegernoallFilter by feedback ID
beadstringnoallFilter by bead ID
commit_hashstringnoallFilter by commit hash
limitintegerno20Maximum results (max 50)

Search archive records (HLA chat logs, Pensieve agent memory) using natural language.

Parameters:

ParameterTypeRequiredDefaultDescription
querystringyesNatural language query
sourcestringnoallFilter: hla or pensieve
filterstringnoallFilter by name/channel
afterstringnoOnly records after date (YYYY-MM-DD)
beforestringnoOnly records before date (YYYY-MM-DD)
limitintegerno10Maximum results
modestringnohybridhybrid, semantic, or keyword

archive_recent

List recent archive records by date.

Parameters:

ParameterTypeRequiredDefaultDescription
afterstringyesOnly records after date (YYYY-MM-DD)
sourcestringnoallFilter: hla or pensieve
limitintegerno20Maximum 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

MethodPathDescription
GET/searchSemantic/hybrid/keyword search
GET/grepKeyword/regex pattern search
GET/contextTask-aware context assembly
GET/chunk/{id}Read a specific chunk by ID
GET/readRead file lines by path and range
GET/relatedFind temporally coupled files
GET/refsFind symbol definitions and usages
GET/symbolsList symbols in a file
GET/hotspotsIdentify high-churn complex files
GET/impactPredict change impact
GET/reviewDiff-aware review context
GET/similarFind similar code or duplicate clusters
GET/primeProject overview with live stats
GET/beadsSearch indexed beads/issues
GET/statusIndex statistics
GET/metricsPrometheus metrics
POST/webhook/pushTrigger re-indexing (for CI/CD)
ParameterTypeDefaultDescription
qstringrequiredSearch query
modestringhybridSearch mode: hybrid, semantic, keyword
typestringallFilter by chunk type
limitinteger10Maximum results
repostringallFilter by repository
curl "http://localhost:3030/search?q=error+handling&limit=5"

GET /grep

ParameterTypeDefaultDescription
patternstringrequiredSearch pattern
ignore_caseboolfalseCase-insensitive search
regexboolfalseEnable regex matching
typestringallFilter by chunk type
limitinteger10Maximum results
repostringallFilter by repository
curl "http://localhost:3030/grep?pattern=handleAuth&limit=5"

GET /context

ParameterTypeDefaultDescription
qstringrequiredTask description
budgetinteger500Max lines of content
depthinteger1Coupling expansion depth
max_coupledinteger3Max coupled files per seed
limitinteger20Max initial search results
coupling_thresholdfloat0.1Min coupling score
repostringallFilter by repository
curl "http://localhost:3030/context?q=refactor+auth+flow&budget=300"

GET /read

ParameterTypeDefaultDescription
filestringrequiredFile path (relative to repo root)
start_lineintegerrequiredStart line number
end_lineintegerrequiredEnd line number
contextinteger0Extra context lines before/after
curl "http://localhost:3030/read?file=src/main.rs&start_line=1&end_line=20"
ParameterTypeDefaultDescription
filestringrequiredFile path to find related files for
limitinteger10Maximum results
thresholdfloat0.0Min coupling score
curl "http://localhost:3030/related?file=src/auth.rs&limit=5"

GET /refs

ParameterTypeDefaultDescription
symbolstringrequiredSymbol name to find
typestringallFilter by symbol type
limitinteger20Max usage results
repostringallFilter by repository
curl "http://localhost:3030/refs?symbol=parse_config"

GET /symbols

ParameterTypeDefaultDescription
filestringrequiredFile path
repostringallFilter by repository
curl "http://localhost:3030/symbols?file=src/config.rs"

GET /hotspots

ParameterTypeDefaultDescription
sincestring1 year agoTime window for churn analysis
limitinteger20Maximum results
thresholdfloat0.0Min hotspot score
curl "http://localhost:3030/hotspots?since=6+months+ago&limit=10"

GET /impact

ParameterTypeDefaultDescription
targetstringrequiredFile or file:function target
depthinteger1Transitive depth (1-3)
modestringcombinedSignal: combined, coupling, semantic, deps
limitinteger15Maximum results
thresholdfloat0.1Min impact score
repostringallFilter by repository
curl "http://localhost:3030/impact?target=src/auth.rs&depth=2"

GET /review

ParameterTypeDefaultDescription
diffstringunstagedDiff spec: unstaged, staged, branch:<name>, or commit range
budgetinteger500Max lines of content
depthinteger1Coupling expansion depth
repostringallFilter by repository
curl "http://localhost:3030/review?diff=staged&budget=300"

GET /similar

ParameterTypeDefaultDescription
targetstring-Chunk ref or text (required unless scan=true)
scanboolfalseScan for duplicate clusters
thresholdfloat0.85/0.90Min similarity threshold
limitinteger10Max results or clusters
repostringallFilter by repository
cross_repoboolfalseCross-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

ParameterTypeDefaultDescription
sectionstring-Specific section to show
briefboolfalseCompact overview only
curl "http://localhost:3030/prime?brief=true"

GET /beads

ParameterTypeDefaultDescription
qstringrequiredSearch query
priorityinteger-Filter by priority (1-4)
statusstring-Filter by status
assigneestring-Filter by assignee
rigstring-Filter by rig name
limitinteger10Maximum results
enrichbooltrueEnrich 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:

PriorityLocationPurpose
1 (lowest)Compiled defaultsSensible out-of-the-box values
2~/.config/bobbin/config.tomlMachine-wide defaults (global config)
3.bobbin/config.tomlProject-specific settings (per-repo config)
4.bobbin/calibration.jsonAuto-tuned search parameters
5 (highest)CLI flagsPer-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.include replaces the entire global include list.
  • Scalars replace — a per-repo search.semantic_weight = 0.5 overrides 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.toml or ~/.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 **/*.md to 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

SectionDescriptionDetails
[index]File selection patterns and gitignore behaviorIndex Settings
[embedding]Embedding model and batch processingEmbedding Settings
[embedding.context]Contextual embedding enrichmentEmbedding Settings
[search]Search defaults and hybrid weightingSearch Settings
[git]Temporal coupling analysis from git historySee below
[hooks]Claude Code hook integration and injection tuningHooks Configuration

[git] Settings

KeyTypeDefaultDescription
coupling_enabledbooltrueEnable temporal coupling analysis
coupling_depthint5000How many commits back to analyze for coupling
coupling_thresholdint3Minimum co-changes to establish a coupling relationship
commits_enabledbooltrueEnable semantic commit indexing
commits_depthint0How 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

KeyTypeDefaultDescription
includestring[]See aboveGlob patterns for files to include
excludestring[]See aboveAdditional exclusion patterns (on top of .gitignore)
use_gitignorebooltrueWhether 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_gitignore is true, files matched by .gitignore are 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

KeyTypeDefaultDescription
default_limitint10Default number of results returned
semantic_weightfloat0.7Balance 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

KeyTypeDefaultDescription
modelstring"all-MiniLM-L6-v2"ONNX embedding model name. Downloaded to ~/.cache/bobbin/models/ on first use.
batch_sizeint32Number of chunks to embed per batch

[embedding.context] Options

Controls contextual embedding, where chunks are embedded with surrounding source lines for better retrieval.

KeyTypeDefaultDescription
context_linesint5Lines of context before and after each chunk
enabled_languagesstring[]["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_size may 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

KeyTypeDefaultDescription
thresholdfloat0.5Minimum relevance score to include a result in injected context
budgetint300Maximum lines of injected context per prompt
content_modestring"full"Display mode: "full", "preview", or "none"
min_prompt_lengthint20Skip injection for prompts shorter than this (chars)
gate_thresholdfloat0.65Minimum top-result semantic similarity to inject at all
dedup_enabledbooltrueSkip re-injection when results match previous turn
show_docsbooltrueInclude documentation files in output (false = code only, docs still used for bridging)
format_modestring"standard"Output format: "standard", "minimal", "verbose", or "xml"
reducing_enabledbooltrueTrack injected chunks across turns; only inject new/changed chunks
repo_affinity_boostfloat2.0Score multiplier for files from the agent’s current repo. Set 1.0 to disable
feedback_prompt_intervalint5Prompt agents to rate injections every N injections. 0 = disabled
skip_prefixeslist[]Prompt prefixes that skip injection entirely (case-insensitive)
keyword_reposlist[]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

ModeDescription
standardFile paths, chunk names, line ranges, and content. Default.
minimalFile paths and chunk names only. Lowest token cost.
verboseFull metadata including tags, scores, and match types.
xmlXML-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_weight from 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

PropertyValue
Modelall-MiniLM-L6-v2
Dimensions384
RuntimeONNX Runtime (CPU)
Model location~/.cache/bobbin/models/
DownloadAutomatic 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

LanguageExtensionsParserExtracted Units
Rust.rsTree-sitterfunctions, impl blocks, structs, enums, traits, modules
TypeScript.ts, .tsxTree-sitterfunctions, methods, classes, interfaces
Python.pyTree-sitterfunctions, classes
Go.goTree-sitterfunctions, methods, type declarations
Java.javaTree-sittermethods, constructors, classes, interfaces, enums
C++.cpp, .cc, .hppTree-sitterfunctions, classes, structs, enums
Markdown.mdpulldown-cmarksections, 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:

  1. Select a commit that fixes a bug and has a passing test suite
  2. Check out the parent of that commit (the broken state)
  3. Give the agent the bug description and test command
  4. 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:

ApproachDescriptionSettings
no-bobbinAgent works with only its built-in knowledge and the promptEmpty hooks (isolated from user config)
with-bobbinAgent receives semantic code context via bobbin’s hook systembobbin 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 RangeMeaning
1.0Perfect — agent modified exactly the same files as the ground truth
0.7-0.9Strong — agent found most files with minimal extras
0.4-0.6Partial — agent found some files but missed others or added extras
0.0-0.3Weak — 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:

ProjectFilesChunksCPU Index TimeGPU Index Time
flask210~700~2s~2s
polars3,089~50KPendingPending
ruff5,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:

EventSourceWhat It Captures
commandCLI dispatchEvery bobbin invocation: command name, duration, success/failure
hook_injectioninject-context hookFiles returned, chunks returned, top semantic score, budget lines used
hook_gate_skipinject-context hookQuery text, top score, gate threshold (when injection is skipped due to weak match)
hook_dedup_skipinject-context hookWhen 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:

SuiteProjectLanguageFilesCode LinesTasksDifficulty
flaskpallets/flaskPython21026K5easy-medium
polarspola-rs/polarsRust+Python3,089606K5easy-medium
ruffastral-sh/ruffRust+Python5,874696K5easy-medium

See Project Catalog for full LOC breakdowns and index statistics.

Adding a New Project

To add a new evaluation project:

  1. 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.

  2. Create task YAML files — Add eval/tasks/<project>-NNN.yaml with:

    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, ...]
    
  3. Run tokei on the cloned repo at the pinned commit and add a section to Project Catalog.

  4. Create a results page — Add eval/<project>.md to the book with task descriptions and a placeholder for results.

  5. Update SUMMARY.md — Add the new results page to the Evaluation section.

  6. Run evalsjust eval-task <project>-001 runs a single task. Results are written to eval/results/runs/.

Results Summary

Overall Comparison

Metricno-bobbinwith-bobbinwith-bobbin+blame_bridging=falsewith-bobbin+coupling_depth=0with-bobbin+doc_demotion=0.0with-bobbin+gate_threshold=1.0with-bobbin+recency_weight=0.0with-bobbin+semantic_weight=0.0
Runs3036333334
Test Pass Rate66.7%58.3%100.0%100.0%100.0%100.0%100.0%100.0%
Avg Precision85.1%90.1%33.3%55.6%55.6%77.8%77.8%23.6%
Avg Recall60.2%64.5%33.3%33.3%55.6%55.6%55.6%33.3%
Avg F168.3%71.9%33.3%38.9%55.6%61.1%61.1%25.2%
Avg Duration4.2m3.6m4.6m4.7m5.0m5.4m4.9m4.7m
Avg Cost$1.24$1.44$1.25$1.45$1.48$1.39$1.43$1.45
Avg Input Tokens1,250,1221,788,6331,517,0021,780,5061,882,0031,711,9551,813,4061,812,429
Avg Output Tokens7,4118,9187,5518,7238,9338,5728,4139,374

Metric Overview

summary_metrics.svg

F1 Score by Task

summary_f1_by_task.svg

Score Distribution

summary_f1_boxplot.svg

Duration

summary_duration.svg

Recent Trend

summary_trend.svg

Full historical trends

Per-Task Results

TaskLanguageDifficultyApproachTestsPrecisionRecallF1DurationCost
cargo-001rusteasyno-bobbin100.0%100.0%100.0%100.0%5.2m$1.04
cargo-001rusteasywith-bobbin100.0%100.0%100.0%100.0%4.6m$1.03
flask-001no-bobbin0.0%100.0%33.3%50.0%1.3m$0.00
flask-001with-bobbin0.0%100.0%33.3%50.0%1.3m$0.00
flask-002no-bobbin0.0%100.0%66.7%80.0%3.1m$0.00
flask-002with-bobbin0.0%100.0%55.6%70.0%3.5m$0.00
flask-003no-bobbin0.0%100.0%60.0%75.0%2.2m$0.00
flask-003with-bobbin0.0%100.0%60.0%75.0%2.4m$0.00
flask-004no-bobbin0.0%100.0%70.0%81.9%3.3m$0.00
flask-004with-bobbin0.0%100.0%60.0%75.0%3.2m$0.00
flask-005no-bobbin0.0%100.0%50.0%66.7%2.6m$0.00
flask-005with-bobbin0.0%100.0%58.3%73.0%1.9m$0.00
polars-004rustmediumno-bobbin100.0%100.0%66.7%80.0%4.4m$0.81
polars-005rustmediumno-bobbin100.0%100.0%66.7%79.4%6.4m$1.74
ruff-001rustmediumno-bobbin100.0%31.7%33.3%32.4%4.3m$1.23
ruff-001rustmediumwith-bobbin100.0%70.2%61.9%63.6%4.4m$1.52
ruff-001rustmediumwith-bobbin+blame_bridging=false100.0%33.3%33.3%33.3%4.6m$1.25
ruff-001rustmediumwith-bobbin+coupling_depth=0100.0%55.6%33.3%38.9%4.7m$1.45
ruff-001rustmediumwith-bobbin+doc_demotion=0.0100.0%55.6%55.6%55.6%5.0m$1.48
ruff-001rustmediumwith-bobbin+gate_threshold=1.0100.0%77.8%55.6%61.1%5.4m$1.39
ruff-001rustmediumwith-bobbin+recency_weight=0.0100.0%77.8%55.6%61.1%4.9m$1.43
ruff-001rustmediumwith-bobbin+semantic_weight=0.0100.0%23.6%33.3%25.2%4.7m$1.45
ruff-002rusteasyno-bobbin100.0%100.0%40.0%57.1%4.8m$0.00
ruff-002rusteasywith-bobbin100.0%100.0%40.0%57.1%4.3m$1.38
ruff-003rustmediumno-bobbin100.0%100.0%83.3%90.0%9.2m$0.00
ruff-003rustmediumwith-bobbin100.0%100.0%77.8%86.7%6.3m$1.92
ruff-004rusteasyno-bobbin100.0%46.7%66.7%54.2%3.9m$0.00
ruff-004rusteasywith-bobbin100.0%63.3%83.3%70.8%4.5m$1.67
ruff-005rusteasyno-bobbin100.0%100.0%100.0%100.0%3.6m$0.00
ruff-005rusteasywith-bobbin100.0%100.0%100.0%100.0%2.8m$0.63

Historical Trends

F1 Trend

trend_f1.svg

Test Pass Rate Trend

trend_pass_rate.svg

Duration Trend

trend_duration.svg

Run Comparison

RunCompletedPass RateAvg F1Avg Duration
202602102100.0%66.7%3.6m
202602102100.0%57.1%4.4m
202602102100.0%80.0%8.2m
202602101100.0%33.3%4.1m
202602101100.0%33.3%3.8m
202602102100.0%100.0%3.9m
2026021020.0%50.0%1.6m
2026021020.0%65.0%4.3m
2026021020.0%75.0%3.2m
2026021020.0%75.0%3.7m
2026021020.0%66.7%2.3m
202602102100.0%33.3%4.4m
202602102100.0%57.1%4.7m
202602112100.0%90.0%7.3m
202602112100.0%75.0%3.5m
202602112100.0%100.0%3.3m
2026021120.0%50.0%1.3m
2026021120.0%80.0%2.8m
2026021120.0%75.0%1.7m
2026021120.0%76.2%2.3m
2026021120.0%81.9%3.1m
2026021110.0%50.0%51s
2026021110.0%80.0%2.6m
2026021110.0%75.0%1.9m
2026021110.0%75.0%2.6m
2026021110.0%66.7%1.8m
202602163100.0%80.0%4.4m
202602163100.0%79.4%6.4m
202602261100.0%100.0%3.1m
202602271100.0%57.1%4.4m
202602271100.0%100.0%6.2m
202602271100.0%100.0%3.4m
202602271100.0%100.0%1.4m
202602281100.0%33.3%3.4m
202602281100.0%75.0%7.4m
202603021100.0%100.0%5.2m
202603022100.0%19.4%4.8m
202603022100.0%30.9%5.0m
2026030221100.0%48.8%4.9m
202603021100.0%100.0%4.6m

Task Heatmap

heatmap_f1.svg

Project Catalog

Projects used in bobbin evaluations, with codebase statistics.

astral-sh/ruff

Lines of Code

LanguageFilesCodeCommentsBlanksTotal
Rust1,750440,63325,75554,388520,776
Python3,690212,24812,84442,183267,275
JSON13415,80101015,811
TSX304,8101635535,526
TOML1453,6542204844,358
YAML253,1942923393,825
SVG281,30871661,445
TypeScript143636156480
CSS3322649377
Jupyter Notebooks3528915468511
Total5,874696,1041,212,699

Bobbin Index Stats

  • Index duration: 82.64s

pallets/flask

Lines of Code

LanguageFilesCodeCommentsBlanksTotal
Python8313,8568363,51518,207
ReStructuredText8411,08903,93915,028
SVG245122455
HTML20256018274
Forge Config3126720153
CSS2109125135
Autoconf9482050
INI1433450
Batch1261835
SQL2222428
Total21026,13234,921

Bobbin Index Stats

  • Index duration: 1.66s

pola-rs/polars

Lines of Code

LanguageFilesCodeCommentsBlanksTotal
Rust2,008361,68513,19946,759421,643
Python725228,9365,83932,248267,023
ReStructuredText1026,59301,4698,062
TOML504,2171413524,710
YAML27431011764
Makefile74601897575
Nix14095241502
JSON840800408
SVG327100271
HTML425301254
Total2,913606,877739,330

rust-lang/cargo

Lines of Code

LanguageFilesCodeCommentsBlanksTotal
Rust1,277263,82810,72122,803297,352
SVG36214,48501,06115,546
TOML6475,8343381,2097,381
JSON173,430003,430
JavaScript15096668643
Shell63194044403
Python2881618122
Dockerfile2442955
XML1217028
CSS163110
Total2,317291,133371,758

Bobbin Index Stats

  • Index duration: 28.29s

Cargo (Rust)

cargo-001 easy

Commit: a96e747227

Task prompt

Fix cargo check to not panic when using build-dir config with a workspace containing a proc macro that depends on a dylib crate. The code in compilation\_files.rs calls .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.

ApproachTests PassPrecisionRecallF1DurationCost
no-bobbin100.0%100.0%100.0%100.0%5.2m$1.04
with-bobbin100.0%100.0%100.0%100.0%4.6m$1.03

cargo-001_duration.svg

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\_str with multiple inputs. The optimizer incorrectly marks the output of concat\_str on 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.

ApproachTests PassPrecisionRecallF1DurationCost
no-bobbin100.0%100.0%66.7%80.0%4.4m$0.81

polars-004_f1_boxplot.svg

polars-004_duration.svg

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.

ApproachTests PassPrecisionRecallF1DurationCost
no-bobbin100.0%100.0%66.7%79.4%6.4m$1.74

polars-005_f1_boxplot.svg

polars-005_duration.svg

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.

ApproachTests PassPrecisionRecallF1DurationCost
no-bobbin100.0%31.7%33.3%32.4%4.3m$1.23
with-bobbin100.0%70.2%61.9%63.6%4.4m$1.52
with-bobbin+blame_bridging=false100.0%33.3%33.3%33.3%4.6m$1.25
with-bobbin+coupling_depth=0100.0%55.6%33.3%38.9%4.7m$1.45
with-bobbin+doc_demotion=0.0100.0%55.6%55.6%55.6%5.0m$1.48
with-bobbin+gate_threshold=1.0100.0%77.8%55.6%61.1%5.4m$1.39
with-bobbin+recency_weight=0.0100.0%77.8%55.6%61.1%4.9m$1.43
with-bobbin+semantic_weight=0.0100.0%23.6%33.3%25.2%4.7m$1.45

ruff-001_f1_boxplot.svg

ruff-001_duration.svg

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.Value to the list of functions excluded from the flake8-boolean-trap rule FBT003. The Value constructor 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 new is\_semantically\_allowed\_func\_call helper that uses qualified name resolution to match the call, and integrate it into the existing allow\_boolean\_trap logic.

ApproachTests PassPrecisionRecallF1DurationCost
no-bobbin100.0%100.0%40.0%57.1%4.8m$0.00
with-bobbin100.0%100.0%40.0%57.1%4.3m$1.38

ruff-002_f1_boxplot.svg

ruff-002_duration.svg

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 the is\_dunder helper instead of a simple starts\_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.

ApproachTests PassPrecisionRecallF1DurationCost
no-bobbin100.0%100.0%83.3%90.0%9.2m$0.00
with-bobbin100.0%100.0%77.8%86.7%6.3m$1.92

ruff-003_f1_boxplot.svg

ruff-003_duration.svg

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 like def \_\_new\_\_(cls) -> "MyClass" were not detected, while non-stringized versions were. Add a new is\_name\_or\_stringized\_name helper that uses match\_maybe\_stringized\_annotation to resolve both plain and stringized annotations, and use it for the return type checks.

ApproachTests PassPrecisionRecallF1DurationCost
no-bobbin100.0%46.7%66.7%54.2%3.9m$0.00
with-bobbin100.0%63.3%83.3%70.8%4.5m$1.67

ruff-004_f1_boxplot.svg

ruff-004_duration.svg

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 --silent flag was not being respected in format check mode, causing “Would reformat” messages to still appear. Fix by wrapping the output-writing match block in a log\_level > LogLevel::Silent guard so that no stdout output is produced in silent mode. Add integration tests for both --silent and --quiet modes to verify correct output behavior.

ApproachTests PassPrecisionRecallF1DurationCost
no-bobbin100.0%100.0%100.0%100.0%3.6m$0.00
with-bobbin100.0%100.0%100.0%100.0%2.8m$0.63

ruff-005_f1_boxplot.svg

ruff-005_duration.svg

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.

TypeDescriptionLanguages
functionStandalone function definitionsRust (fn), TypeScript, Python (def), Go (func), Java, C++
methodFunctions defined inside a class or typeTypeScript, Java, C++
classClass definitions (including body)TypeScript, Python, Java, C++
structStruct/record type definitionsRust, Go, C++
enumEnumeration type definitionsRust, Java, C++
interfaceInterface definitionsTypeScript, Java
traitTrait definitionsRust
implImplementation blocksRust (impl Type)
moduleModule declarationsRust (mod)

Markdown Chunk Types

These chunk types are extracted by pulldown-cmark from Markdown files.

TypeDescriptionExample
sectionContent under a heading (including the heading)## Architecture and its body text
tableMarkdown tables| Column | Column |
code_blockFenced code blocks```rust ... ```
docYAML 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

TypeDescription
commitGit commit messages (used internally for history analysis)
otherFallback 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):

ValueAliases
functionfunc, fn
method
class
struct
enum
interface
modulemod
impl
trait
docdocumentation
section
table
code_blockcodeblock
commit
other

Language-to-Chunk Mapping

LanguageExtensionsChunk Types Extracted
Rust.rsfunction, method, struct, enum, trait, impl, module
TypeScript.ts, .tsxfunction, method, class, interface
Python.pyfunction, class
Go.gofunction, method, struct
Java.javamethod, class, interface, enum
C++.cpp, .cc, .hppfunction, method, class, struct, enum
Markdown.mdsection, 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

  1. Run semantic search → get ranked list A
  2. Run keyword search → get ranked list B
  3. For each result, compute: score = w / (k + rank_A) + (1 - w) / (k + rank_B)
    • w = semantic_weight (default: 0.7)
    • k = smoothing constant (60)
  4. 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

FeatureHybridSemanticKeyword
Conceptual matchingYesYesNo
Exact identifier matchingYesWeakYes
SpeedModerateModerateFast
Requires embeddingsYesYesNo
Default modeYesNoNo

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:

Featurebobbin grepbobbin search --mode keyword
Regex supportYes (--regex)No
Case-insensitiveYes (-i)No
Matching lines shownYesNo
Output formatGrep-style with line highlightingSearch-style with scores

Exit Codes

Bobbin uses standard Unix exit codes. All commands follow the same convention.

Exit Code Table

CodeMeaningCommon Causes
0SuccessCommand completed normally
1General errorInvalid arguments, missing configuration, runtime errors
2Usage errorInvalid 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, or other.
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

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.
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

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

FeatureTraditional RAGBobbin
ChunkingFixed token windowsAST-based structural units
HistoryHEAD onlyFull git timeline
RelationshipsVector similarity onlyTemporal coupling + similarity
PrivacyOften cloud-basedStrictly local
RuntimeClient-serverEmbedded/serverless

Target Users

  1. AI Coding Agents - Claude Code, Cursor, Aider, custom agents
  2. Developers - Direct CLI usage for code exploration
  3. Tool Builders - Foundation for context-aware dev tools
  4. 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

ComponentChoiceRationale
LanguageRustPerformance, memory safety, ecosystem (Tree-sitter, LanceDB)
Vector StoreLanceDBEmbedded, serverless, git-friendly storage
ParserTree-sitterIncremental, multi-language, battle-tested
EmbeddingsLocal 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: init command
  • CLI: index command (full and incremental)
  • Configuration management
  • ONNX embedding generation
  • CLI: search command
  • CLI: grep command
  • CLI: status command

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 (--repo flag)
  • 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 (--server flag)
  • 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 setup recipe (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, and completions subcommands
  • LanceDB as primary vector storage with SQLite for coupling metadata
  • Support for .bobbinignore exclude 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
  • just command runner
  • protoc (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 context use it during assembly? Update src/search/context.rs
  • Does this change chunk types or storage schema? Update context output types if needed
  • Does the MCP context tool need updating? Check src/mcp/server.rs
  • Are there new CLI flags that context should also expose?

Task specs for the context command live in docs/tasks/context-*.md.