Ripplet
See the ripple before you make the change.
A precise, local code graph for coding agents.
Benchmarks · Languages · Agents · Change safety · UI · License
A precise, local code graph for coding agents. Ripplet indexes a repository
into a call / type / data graph and answers the questions agents otherwise
answer with grep: who calls this, what breaks if it changes, which lines a
rename touches, which tests to run. Every answer comes with file:line, and
a link the graph cannot prove is labelled as such instead of being guessed.
- Precise. Checked call by call against compiler-grade indexes (SCIP): zero wrong edges across 12 repositories, 0.92 call-site recall.
- Built for agents. MCP server, always-on rules for Claude Code, Codex, Gemini CLI, Cursor, Copilot, OpenCode and more, a Claude Code skill, and hooks that steer an agent to the graph before it greps.
- Change safety. Impact of an edit, a dry-run rename, broken callers after an edit, the exact tests that reach a change, PR review, architecture rules.
- Local. One SQLite file, no LLM, no cloud, no vector database. Python 3.12+.
pip install "ripplet[treesitter-multilang]"
ripplet setup . # index + MCP config + agent rules/skill/hooks, in one step
ripplet ui . # browse the graph
At a glance
| Ripplet | Graphify | |
|---|---|---|
| Callers found (site recall, 258 functions) | ~93% | ~23% |
| Right file first for a task (hit@1, 389 tasks) | 62.5% | 44.5% |
| Broken change caught by selected tests | 68-92% | 10-20% |
| Wrong call edges vs a compiler (12 repos) | 0 | not measured |
Details and per-repository numbers below.
How it compares
Measured on real open-source repositories with harnesses that score every
tool the same way (ground truth: textual call sites, the files real commits
changed, or mutants a test suite kills). Graphify 0.9.92 was run with its
defaults (graphify extract --code-only, graphify query).
Finding every caller of a function
Site recall = share of the real call sites (file:line) the tool reports.
| Set | Functions | Ripplet | Graphify |
|---|---|---|---|
| fresh (13 repos: TS, Java, C#, Go, PHP, Python, Rust, Lua) | 150 | 93% | 23% |
| dev (7 repos: Java, Lua, Python, TS, C#) | 67 | 96% | 29% |
| extra (9 repos: Lua, Ruby, Rust) | 41 | 89% | 16% |
Per language, all three sets: Java 99% vs 66%, TypeScript 91% vs 14%, Python 96% vs 29%, Go 98% vs 48%, C# 93% vs 11%, Lua 94% vs 6%, Rust 90% vs 56%, Ruby 86% vs 25%, PHP 100% vs 79%.
Finding where a task should be done
The commit subject of a real later commit is the task; the files that commit
changed are the answer. task_context vs graphify query.
hit@1 = the first file named is one the commit changed; hit@5 = one of the first five. 389 tasks on 13 repositories (Python, Java, Go, TypeScript, PHP); the last 9 were added later as hold-out sets, to catch tuning that only fits the first four.
| Repository | Tasks | hit@1 Ripplet | hit@1 Graphify | hit@5 Ripplet | hit@5 Graphify |
|---|---|---|---|---|---|
| httpx (Python) | 38 | 53% | 34% | 87% | 76% |
| marshmallow (Python) | 29 | 69% | 41% | 90% | 83% |
| jsoup (Java) | 40 | 60% | 30% | 78% | 55% |
| chi (Go) | 40 | 62% | 50% | 80% | 80% |
| attrs (Python) | 15 | 80% | 33% | 87% | 53% |
| zustand (TypeScript) | 11 | 91% | 91% | 91% | 100% |
| gson (Java) | 39 | 46% | 36% | 79% | 49% |
| cobra (Go) | 40 | 82% | 52% | 100% | 88% |
| monolog (PHP) | 40 | 68% | 55% | 78% | 70% |
| javapoet (Java) | 37 | 70% | 70% | 95% | 84% |
| commons-text (Java) | 18 | 78% | 72% | 94% | 83% |
| commons-csv (Java) | 7 | 71% | 14% | 86% | 86% |
| JsonPath (Java) | 35 | 26% | 11% | 60% | 34% |
| all | 389 | 62.5% | 44.5% | 83.8% | 69.9% |
Average answer size: about 1,500 tokens.
Picking the tests to run after a change
Mutation test selection: a mutant is caught when one of the selected tests fails on it.
| Repository | Mutants | Ripplet | Graphify | grep |
|---|---|---|---|---|
| click (Python, 578 tests) | 40 | 68% caught | 10% | 30% |
| conduit (C#, 38 tests) | 25 | 92% caught | 20% | 52% |
Ripplet selects the tests that reach the changed symbol through calls, instantiation and inheritance, so a broken change is far more likely to fail a selected test; it also selects more tests (43% of click's suite, 16% of conduit's).
Precision against a compiler
Ripplet's call edges compared with SCIP indexes (scip-typescript, scip-python, rust-analyzer, scip-go, scip-dotnet, scip-java) — every call site the compiler resolves, and every edge Ripplet claims:
| Repository | Language | Recall | Wrong edges |
|---|---|---|---|
| hono | TypeScript | 0.955 | 0 |
| h3 | TypeScript | 0.978 | 0 |
| axios | JavaScript | 1.000 | 0 |
| starlette | Python | 0.954 | 0 |
| requests | Python | 0.974 | 0 |
| ripgrep | Rust | 0.938 | 0 |
| axum | Rust | 0.839 | 0 |
| eShopOnWeb | C# | 0.920 | 0 |
| gin | Go | 0.959 | 0 |
| echo | Go | 0.940 | 0 |
| spring-petclinic | Java | 0.962 | 0 |
| commons-lang | Java | 0.905 | 0 |
| all 12 | 0.923 of 77,408 call sites | 0 |
A call Ripplet cannot prove (an untyped receiver whose method name exists in
the project) is not turned into an edge. Agents see it in an UNVERIFIED
section: possible caller, check the line.
With a coding agent
Claude Haiku 4.5, headless, 13 repositories in 13 languages; the same tasks with and without Ripplet (without = no MCP server, no rules, no hooks).
| Task | With Ripplet | Without |
|---|---|---|
| Rename a function everywhere (complete, compiles) | 13 / 13 | 12 / 13 |
| … cost / turns for all 13 | $0.81 / 85 | $1.82 / 420 |
| List every caller (recall) | 0.985 | 0.995 |
| Files read or grepped (both tasks, all repos) | 14 | 229 |
Renames are as reliable at less than half the cost and a fifth of the turns. Caller lists are about as complete; where the graph has a gap, the agent is told so (UNVERIFIED) rather than given a confident wrong answer.
Languages
Deep analysis (symbols, imports, calls with receiver types, inheritance, overrides) for:
| Language | Notes |
|---|---|
| Python | AST; imports, __all__, properties, __call__, typed attributes |
| TypeScript / JavaScript | tree-sitter; namespaces, re-export barrels, workspaces, callbacks |
| Java, Kotlin | packages, nested/anonymous classes, overloads, extension functions |
| C# | usings, generics, extension methods, local functions, await Task<T> |
| Go | packages, interfaces, embedding, build tags |
| Rust | modules, re-exports, workspaces, traits, macros |
| PHP | namespaces, Laravel container/facades, Blade views, docblock types |
| Ruby | modules, mixins, Rails conventions |
| C / C++ | includes, namespaces, templates, #if branches, macro bodies |
| Swift | modules, extensions, closures typed by their callee |
| Dart, Lua, GDScript | imports / requires, classes and modules |
Also indexed: SQL schemas and code migrations (Laravel, Rails, Django, Alembic, EF Core, Prisma) as tables and columns, HTTP routes and the client calls that hit them across languages, Docker Compose, Kubernetes, CI workflows, Terraform, Markdown docs, config files.
Using it with agents
ripplet setup . # init, index, .mcp.json / opencode.json, rules, skill, hooks, guard
ripplet rules on . --target all # rules for every agent below
ripplet rules on . --strict # block the first raw read/grep until a Ripplet tool is used
| Agent | Always-on guidance | Enforcement |
|---|---|---|
| Claude Code | CLAUDE.md + skill .claude/skills/ripplet/SKILL.md |
PreToolUse / PostToolUse hooks |
| Codebuddy | Claude-style | same hooks |
| OpenCode | AGENTS.md |
plugin .opencode/plugins/ripplet.js |
| Gemini CLI | GEMINI.md |
BeforeTool hook |
| Codex, Hermes, Amp, Devin, Jules | AGENTS.md |
|
| Cursor | .cursor/rules/ripplet.mdc |
|
| GitHub Copilot | .github/copilot-instructions.md |
|
| Kiro, Windsurf, Antigravity, Cline | their rules folders |
This repository ships the same guidance as files you can copy:
CLAUDE.md, AGENTS.md, GEMINI.md and the
Claude Code skill skills/ripplet/SKILL.md.
MCP tools
ripplet mcp serves these over stdio (ripplet tool <name> key=value runs any
of them from the shell):
| Tool | Use it for |
|---|---|
task_context |
the start of a task: the code it is about, callers, routes / tables, files to edit, tests |
repo_map |
an unfamiliar repository: entry points, most-used files and symbols |
explain_symbol |
a known name: source, every caller and callee with the call's code |
get_neighbors, analyze_change_impact |
who calls it / what breaks, with every call line |
change_plan, rename_symbol |
renames and signature changes: every line to edit, same-named code to leave alone |
investigate_error |
a stack trace or log line mapped to functions, code and callers |
check_change |
after editing: broken callers, leftovers, the exact tests to run |
list_routes |
routes, their handlers and the permission checks they make |
feature_slice |
building something like an existing feature: all its touch points |
find_related_tests, find_path, search_symbols, query_graph |
the rest |
Answers fit a token budget and say how to get more; read tools re-index changed files first, so line numbers follow the agent's edits.
Change safety from the command line
ripplet impact --symbol Store.save # what depends on it
ripplet tool change_plan symbol=Store.save change=rename new_name=persist
ripplet check . --run-tests # broken callers + the tests that reach the change
ripplet review . --base origin/main # PR review (Markdown or --format sarif)
ripplet arch # architecture rules in ripplet.arch.yaml
ripplet test-plan --working-tree # tests to run, with the evidence for each
Architecture rules are checked on the graph, not on text:
rules:
- id: writes-need-permission
must_call: [requireRole, requireNotViewer]
for: {path: "app/Controllers/*.php", type: [method], public: true, name: "^(store|update|delete)"}
depth: 3
- id: ui-not-db
forbid: {from: "src/ui/**", to: "src/db/**"}
The UI
ripplet ui . opens a local, read-only explorer:
- Map of module dependencies, Explore a symbol's neighbourhood (graph or list, large neighbourhoods grouped by file), Impact ring by ring with the tests that reach it, Changes of your working tree, Path between two symbols.
- Routes with their handlers, Data: tables, columns and the queries on them (SELECT / INSERT / UPDATE / DELETE), Index health: coverage per language, stale files, agent setup.
- Every caller with the call's code, proven vs inferred links, possible (unverified) callers, and a dry-run rename preview.
Install
pip install ripplet # Python, Markdown, config, SQL
pip install "ripplet[treesitter-multilang]" # + every language above
pip install "ripplet[watch]" # ripplet watch: re-index on save
ripplet index . # build / update the graph (incremental)
ripplet search Store # find symbols
ripplet explain Store.save # source, callers, callees
ripplet status # what is indexed
Optional local semantic search with Ollama (ripplet embed --model
qwen3-embedding:0.6b, then ripplet query ... --mode hybrid_full); everything
else works without a model.
Limitations
- Static analysis: calls made through reflection, string-built names or
dependency injection containers configured at runtime are not followed
(Laravel's
app(X::class)is). - Dynamic languages (Ruby, Lua, PHP without types) have more UNVERIFIED sites than typed ones; Ripplet reports them instead of guessing.
- Generated code that only exists after a build is not indexed.
- Single SQLite file per workspace.
License
Ripplet is source-available under the Functional Source License 1.1, Apache 2.0 future license (FSL-1.1-ALv2).
- Free to use for anything except a competing product: personal projects, work, commercial companies, internal tools, CI, education, research.
- Free to change, and to share your changes, as long as the license and copyright notices stay.
- Not allowed: offering Ripplet, or a copy or fork of it, as a commercial product or service that competes with it.
- Becomes Apache 2.0 two years after each release.
- The name "Ripplet" and its logo are trademarks and are not licensed.
For a commercial or team license, contact the author.
Author
Mehmet Emin Madanoglu
Metadata
Release files for ripplet 0.1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| ripplet-0.1.0.0.tar.gz | 623.5 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| ripplet-0.1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 1.3 MB
Release files / ripplet-0.1.0.0.tar.gz
| Download URL | ripplet-0.1.0.0.tar.gz |
|---|---|
| Size | 623.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
e90d79ec9b715a8c4c3e17fc6ea6ea8be40a44599d96287c07742361ee2cabf6
|
|
BLAKE2b-256 checksum How to use checksums |
88362e197e5f485da11cc06e02f672024fb70a02ae1b0a9dd330c80ab37c80ea
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Release files / ripplet-0.1.0.0-py3-none-any.whl
| Download URL | ripplet-0.1.0.0-py3-none-any.whl |
|---|---|
| Size | 715.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
81ba579ee32ed5a1cd67210f8de2e84e185bcbcfb3ac50796d1111fec36796b1
|
|
BLAKE2b-256 checksum How to use checksums |
623cf5c1df4faa3bab9edbcf2c8424edd12d1e7d8c0218cba6051ee12ad6b111
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Security for ripplet 0.1.0.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.