>- Ranked content search over any text corpus using BM25 (via xhluca/bm25s). files/archives, and any local directory. Stateless — builds an in-memory index each invocation, no cache, no persistence. Use when you need ranked multi-word content search beyond grep, or when picking the "most relevant files for these terms" across a corpus. Triggers on "rank these documents", "search this corpus", "find content about X", "which files are most about Y", or multi-word concept queries against a known body of text.
npx skills add https://github.com/oaustegard/claude-skills --skill bm25
Ranked content search over any text corpus. One CLI, in-memory BM25 index
per process, with a session-local disk cache so repeat invocations against
the same corpus load in tens of milliseconds instead of rebuilding.
uv pip install --system --break-system-packages bm25s
Install is sub-second on a warm uv cache. That's the entire dependency.
BM25=/mnt/skills/user/bm25/scripts/bm25.py
# Local directory
python3 $BM25 ./repo 'csrf middleware'
# Multiple queries against the same in-memory index (build once, query many)
python3 $BM25 ./repo 'csrf middleware' 'session backend' 'queryset filter'
# Cloned GitHub repo via tarball (one HTTP call)
python3 $BM25 'github.com/django/django' 'atomic transaction'
python3 $BM25 'github.com/django/django@stable/5.0.x' 'atomic transaction'
# Project knowledge or uploads
python3 $BM25 project 'RAG scaling laws'
python3 $BM25 uploads 'tax loss harvesting'
# Filters
python3 $BM25 ./repo 'auth flow' --exclude 'tests/*' --exclude '*/tests/*'
python3 $BM25 ./repo 'config' --include '*.py' --include '*.toml'
# Interactive (REPL — single corpus, many queries)
python3 $BM25 ./repo --interactive
# JSON output for piping
python3 $BM25 ./repo 'auth flow' --json
| Spec | Meaning |
|------|---------|
| ./path or /abs/path | Local directory |
| uploads | /mnt/user-data/uploads/ |
| project | /mnt/project/ |
| github.com/owner/repo[@ref] | Tarball fetch via GitHub API (GH_TOKEN used if set) |
| Option | Default | Description |
|--------|---------|-------------|
| --top-k N | 10 | Results per query |
| --include GLOB | (auto) | Repeatable. If set, only files matching one of these globs are indexed |
| --exclude GLOB | | Repeatable. Skip files matching these globs |
| --snippet-lines N | 3 | Lines of snippet context per hit (0 = none) |
| --max-file-bytes N | 2,000,000 | Skip files larger than this |
| --json | | Machine-readable output |
| --interactive / -i | | REPL mode for ad-hoc querying within one session |
| --stats | | Print discover + index timings as JSON |
| --no-cache | | Bypass the session-local index cache; build in-memory only |
With no --include, a default set of text/code extensions is indexed (Python,
JS/TS, Go, Rust, Markdown, JSON, YAML, etc.). Standard noise dirs are skipped
unconditionally: .git, node_modules, __pycache__, .venv, dist, etc.
| Question shape | Tool |
|----------------|------|
| "Find lines matching class.*Error" | grep / ripgrep |
| "Show me where parse_input is defined" | tree-sitting (find:/source:) |
| "Which files are about CSRF handling?" | bm25 |
| "Rank these docs by relevance to 'rate limiting strategies'" | bm25 |
| "What's the implementation of the atomic transaction context manager?" | bm25, then tree-sitting source: |
| "Find code by natural-language concept (in a code repo)" | searching-codebases (which has its own TF-IDF mode) |
The boundary with searching-codebases: that skill is code-specific (routes
between regex and TF-IDF, expands via tree-sitting AST). bm25 is the simpler
general-purpose tool — any corpus, no AST awareness, no routing. Prefer
searching-codebases for code; reach for bm25 when the corpus is mixed
(docs + code), non-code (notes, transcripts, PDFs converted to text), or when
you specifically want BM25's length-normalized scoring.
/home/claude/.bm25-cache/<key>/. Thekey is a hash of `(resolved_corpus_path, include_globs, exclude_globs,
max_file_bytes)` — any change invalidates naturally. First invocation
builds and saves; subsequent invocations against the same corpus and
filters load in tens of milliseconds. The cache lives in /home/claude,
which is ephemeral, so it expires at the session boundary — same
lifetime as the corpus state itself, no cross-session staleness.
~5–35MB per cached index, depending on corpus size.
--no-cache bypasses both load and save — useful only if you'vemutated the corpus mid-session (rare) or want to confirm a rebuild matches.
between queries in one process. Passing multiple queries positionally,
or using --interactive, amortizes any rebuild cost across queries.
code, combine with tree-sitting queries on the same paths.
bm25s.tokenize with stopwords disabled — over asmall Django sample, AST-derived token streams (identifiers/strings/
comments only) gave near-identical rankings, so we don't bother.
Default (human-readable):
QUERY: csrf middleware
----------------------------------------------------------------------
1. 5.51 django/core/checks/security/csrf.py
def _csrf_middleware():
return "django.middleware.csrf.CsrfViewMiddleware" in settings.MIDDLEWARE
2. 5.34 docs/howto/csrf.txt
...
--json produces {"query": ..., "results": [{"path", "score", "snippet"}, ...]}.
bm25.py CLI
├── resolve_corpus(spec) → local Path (downloads tarball if github.com/...)
├── cache_key(...) → 16-hex sha256 of inputs that determine the index
├── CorpusIndex.load(cache_dir) → returns cached index if present, else None
├── CorpusIndex.build(...) → walks files, tokenizes, indexes with bm25s
├── CorpusIndex.save(cache_dir) → persists to /home/claude/.bm25-cache/<key>/
├── query(q, k) → ranked (doc_idx, score) pairs
└── best_snippet(doc, q, lines) → pick line w/ most query-term hits + context
Cache contents per directory:
bm25/ — bm25s.BM25.save() output (NumPy arrays + vocab)corpus.pkl — pickled {paths, docs} so we can render snippets withoutre-reading the source files
manifest.json — corpus root, files count, built_at timestampNo network beyond optional tarball fetch on github.com/... corpora. No
state outside /home/claude/, which is ephemeral.
Comprehensive document creation, editing, and analysis with support for tracked changes, comments, formatting preservation, and text extraction. When Claude needs to work with professional documents (.docx files) for: (1) Creating new documents, (2) Modifying or editing content, (3) Working with tracked changes, (4) Adding comments, or any other document tasks
Comprehensive PDF manipulation toolkit for extracting text and tables, creating new PDFs, merging/splitting documents, and handling forms. When Claude needs to fill in a PDF form or programmatically process, generate, or analyze PDF documents at scale.
Presentation creation, editing, and analysis. When Claude needs to work with presentations (.pptx files) for: (1) Creating new presentations, (2) Modifying or editing content, (3) Working with layouts, (4) Adding comments or speaker notes, or any other presentation tasks
Create beautiful visual art in .png and .pdf documents using design philosophy. You should use this skill when the user asks to create a poster, piece of art, design, or other static piece. Create original visual designs, never copying existing artists' work to avoid copyright violations.
Use this skill whenever the user wants to do anything with PDF files. This includes reading or extracting text/tables from PDFs, combining or merging multiple PDFs into one, splitting PDFs apart, rotating pages, adding watermarks, creating new PDFs, filling PDF forms, encrypting/decrypting PDFs, extracting images, and OCR on scanned PDFs to make them searchable. If the user mentions a .pdf file or asks to produce one, use this skill.
Use this skill whenever the user wants to create, read, edit, or manipulate Word documents (.docx files). Triggers include: any mention of 'Word doc', 'word document', '.docx', or requests to produce professional documents with formatting like tables of contents, headings, page numbers, or letterheads. Also use when extracting or reorganizing content from .docx files, inserting or replacing images in documents, performing find-and-replace in Word files, working with tracked changes or comments, or converting content into a polished Word document. If the user asks for a 'report', 'memo', 'letter', 'template', or similar deliverable as a Word or .docx file, use this skill. Do NOT use for PDFs, spreadsheets, Google Docs, or general coding tasks unrelated to document generation.
Use this skill any time a .pptx file is involved in any way — as input, output, or both. This includes: creating slide decks, pitch decks, or presentations; reading, parsing, or extracting text from any .pptx file (even if the extracted content will be used elsewhere, like in an email or summary); editing, modifying, or updating existing presentations; combining or splitting slide files; working with templates, layouts, speaker notes, or comments. Trigger whenever the user mentions \"deck,\" \"slides,\" \"presentation,\" or references a .pptx filename, regardless of what they plan to do with the content afterward. If a .pptx file needs to be opened, created, or touched, use this skill.
Create and edit Obsidian Flavored Markdown with wikilinks, embeds, callouts, properties, and other Obsidian-specific syntax. Use when working with .md files in Obsidian, or when the user mentions wikilinks, callouts, frontmatter, tags, embeds, or Obsidian notes.
Take oaustegard/bm25 from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference pip, uv.
Without those the skill loads but fails at the first command.