Initial commit: rustytokens — native codebase indexer + MCP server

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
endergebnisandCopilot committed 2026-09-04 02:43:00 +02:00
commit 354132e887
43 files changed
+3783

No files matched your search

+172
View File
@@ -0,0 +1,172 @@
# rustytokens
A native Rust CLI + MCP server that indexes a codebase with
[`universal-ctags`](https://github.com/universal-ctags/ctags) and lets an AI
coding agent retrieve **symbols and functions** instead of whole files — saving
context tokens.
Language coverage (via ctags): Rust, Java, Python, C/C++, TypeScript,
JavaScript, HTML. HTML import edges are extracted directly (not via ctags), and
`<link>`/`<script>`/`<img>` references to local `.css`/`.js`/image files are
tracked as imports. CSS files are discovered and import-tracked, but ctags has
no CSS parser — `search symbols` returns nothing for `.css` files.
`rustytokens` is an independent, clean-room implementation. It does not share
code with any AGPL-licensed project.
## Prerequisites
- Rust (MSRV: current stable; built and tested on nightly)
- `universal-ctags` on `PATH` (`ctags --version` must report Universal Ctags)
```bash
# Debian/Ubuntu
sudo apt install universal-ctags
# macOS
brew install universal-ctags
```
No system SQLite needed — `rusqlite` bundles SQLite with FTS5.
## Build
```bash
cargo build --release
# binary at target/release/rustytokens
```
## Commands
| Command | Purpose |
|---|---|
| `index create <path>` | Index a project from scratch |
| `index update <path>` | Re-index only changed files (content-hash diff) |
| `search symbols <query>` | Symbol search by name (exact > prefix > full-text) |
| `search text <query>` | Full-text search over file contents |
| `inspect symbol <id>` | Source of a single symbol |
| `inspect outline <file>` | Symbol outline of a file |
| `inspect file <file> [--lines a-b]` | Read a file or line range |
| `inspect tree [--depth n]` | Indexed file tree |
| `inspect blast-radius <file> [--depth n]` | Files transitively affected by a change |
| `find importers <file>` | Who imports this file |
| `find dead` | Files nobody imports (entry points excluded) |
| `inspect cycles` | Circular imports |
| `serve` | MCP server over stdio |
| `stats` | Index statistics |
Global options: `--project <dir>` (default `.`), `--format json|table`
(default `json`), `--compact`.
## Examples
```bash
# Index a project
rustytokens index create ./myproject
# Find the `authenticate` function
rustytokens --project ./myproject search symbols authenticate
# Get only that function's source (id from the search result)
rustytokens --project ./myproject inspect symbol 42
# Which files import auth.rs?
rustytokens --project ./myproject find importers src/auth.rs
# What's dead / circular?
rustytokens --project ./myproject find dead
rustytokens --project ./myproject inspect cycles
# Human-readable output
rustytokens --project ./myproject --format table search symbols authenticate
```
## MCP integration
Register the binary as a stdio MCP server (e.g. in Claude Code / Finir
`mcp.json`):
```json
{
"mcpServers": {
"rustytokens": {
"command": "/path/to/rustytokens",
"args": ["--project", "/path/to/your/project", "serve"]
}
}
}
```
Exposed tools: `index_create`, `index_update`, `search_symbols`,
`search_text`, `inspect_symbol`, `inspect_outline`, `inspect_file`,
`inspect_tree`, `find_importers`, `find_dead`, `inspect_cycles`,
`inspect_blast_radius`, `stats`.
## Why it saves tokens
Instead of sending a whole file, the agent fetches only the symbol it needs.
Measured on `rustytokens`' own `src/ops.rs` (396 lines, ~11.7 KB):
- Whole file: 396 lines, 11,675 chars (~2,900 tokens)
- `inspect symbol` for `build_tree`: 14 lines, 437 chars (~110 tokens)
That single retrieval is ~96% smaller than reading the file. Across a large
codebase and a long session, per-symbol retrieval keeps context focused on the
code actually being edited.
## Performance
Measured on a Xeon E5-2667 v3 @ 3.2 GHz (16 cores), `cargo build --release`,
`universal-ctags` on `PATH`. Wall time, CPU time and peak RSS via
`/usr/bin/time -v`.
| Operation | Repo (files) | Wall | CPU | Peak RSS |
|---|---|---|---|---|
| `index create` | nlohmann/json (497) | 1.61 s | 0.98 s | 22 MB |
| `index create` | gson (264) | 0.92 s | 0.46 s | 13 MB |
| `index create` (warm) | nlohmann/json (497) | 1.55 s | — | — |
| `index update` (no-op) | nlohmann/json (497) | 0.02 s | — | — |
| `search symbols` | — | 8.7 ms | — | 7.8 MB |
| `find dead` | — | 4.4 ms | — | — |
| `inspect cycles` | — | 3.7 ms | — | — |
Notes:
- `index update` is ~50× faster than a full rebuild when nothing changed: it
hashes every file in parallel (rayon) and re-indexes only changed ones, while
`index create` drops and rebuilds the whole database. A warm `index create`
(≈1.55 s) shows that hashing dominates the cost.
- Hardware cycle counters were not available (`perf` tools missing on this
kernel), so cycle figures are estimates: CPU time × 3.2 GHz nominal.
## Development
```bash
cargo fmt
cargo clippy
cargo test --release # unit (in-memory SQLite) + CLI integration tests
```
Test fixtures live in `tests/fixtures/` (own small Rust/Python/TS repos).
## Licensing
`rustytokens` is an independent, clean-room implementation — no code is taken
from any AGPL-licensed project, so the project's own source is yours to license
however you choose (MIT, proprietary, commercial, closed-source). Decide and
set it once: add a `license` field to `Cargo.toml` and, if you ship the source,
drop in a `LICENSE` file.
Dependencies are all permissive (MIT / Apache-2.0 / BSL-1.0 / Unlicense):
`clap`, `rusqlite`, `ignore`, `rayon`, `xxhash-rust`, `serde`, `serde_json`,
`anyhow`, `regex`. None is copyleft — commercial use and closed-source
distribution are fine. The only obligation is to ship each crate's copyright
notice and license text (e.g. a `THIRD_PARTY_NOTICES.md`).
`universal-ctags` is GPL-licensed, but `rustytokens` invokes it only as an
external program over `PATH` (subprocess + stdio, no linking, no bundling). That
keeps it a separate program and does not impose GPL on `rustytokens` itself. Do
not bundle the `ctags` binary into a distributed product — that is what would
trigger GPL obligations for that binary. Keep it a user-installed prerequisite.
Naming: `rustytokens` is distinct from `toktoken`. If this goes commercial,
avoid "toktoken-compatible" or similar wording in marketing.