Semantic code index and gatekeeper for AI coding agents. Builds a compiler-grade symbol graph (defs/refs) via SCIP and a file-level dependency graph via import extraction. Exposes queries via CLI and MCP tools.
No LLMs involved — just static analysis.
- Indexes your codebase (TypeScript, Python, Go, Rust, Java/Kotlin, Scala, C#, Ruby) into a SQLite graph of symbols, references, imports, and definitions
- Queries let you search symbols, jump to definitions, find all references, trace impact of changes, explore call graphs, and compute structural metrics
- Analyzes transitive impact with risk scoring — BFS across the symbol graph to find all affected files, packages, public API breaks, and relevant tests
- Detects structural drift — dependency cycles (Tarjan's SCC), module coupling (Ca/Ce/instability), and public API surface tracking with snapshot-based diffing
- Enforces architectural policies via
ariadne.policies.json— deny new cycles, cap API growth, limit coupling increases - Verifies repo consistency: stale index detection, empty index detection, missing test runs, type errors, architecture boundary violations, and policy compliance
- Indexes non-code artifacts —
.envvars,package.jsonscripts, SQL migrations, OpenAPI schemas — and links them to source references - Integrates with Claude Code via hooks (auto-update on edit, gatekeeper on stop) and MCP tools
- Works on any existing project — run
setupand it indexes everything from disk
Download the prebuilt binary for your platform:
curl -fsSL https://raw.githubusercontent.com/simonrueba/ariadne/main/scripts/install.sh | bashOr specify a version: ARIADNE_VERSION=v0.2.0 curl -fsSL ... | bash
Then set up on your project:
ariadne setup /path/to/your/projectRequires Bun >= 1.0
git clone https://github.com/simonrueba/ariadne.git && cd ariadne && bun install
# Set up on your project
bun run packages/cli/src/index.ts setup /path/to/your/projectSCIP indexers are optional — structural import analysis works without them. Install per language as needed (see Toolchains by language below). Run ariadne doctor to check what's available.
# Set up on your project — does everything in one step:
# init → index → generate hooks + MCP config
bun run packages/cli/src/index.ts setup /path/to/your/projectThis creates:
.ariadne/— SQLite database, hook scripts, SCIP cache.claude/settings.json— Claude Code hooks (impact context on edit, auto-update, verify gate).mcp.json— MCP server config (14 read-only tools)ariadne.boundaries.json— auto-generated architecture boundary config (monorepos)
Then restart Claude Code in your project to pick up the hooks and MCP server.
Add structural analysis to your pull requests with a single workflow file:
# .github/workflows/ariadne.yml
name: Ariadne Structural Guard
on:
pull_request:
branches: [main]
permissions:
contents: read
pull-requests: write
jobs:
structural-analysis:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: simonrueba/ariadne-action@v1
with:
comment: 'true'
fail-on-policy: 'true'This posts a PR comment with impact radius, risk score, public API breaks, new cycles, test coverage, and policy status. See action/ for full configuration options.
# Initialize .ariadne/ directory
bun run packages/cli/src/index.ts init /path/to/your/project
# Build the full index (structural imports + SCIP symbols)
bun run packages/cli/src/index.ts index /path/to/your/project
# Check prerequisites
bun run packages/cli/src/index.ts doctor /path/to/your/project
# Run gatekeeper checks
bun run packages/cli/src/index.ts verify /path/to/your/project# Search for a symbol
bun run packages/cli/src/index.ts query search "MyFunction"
# Find definition
bun run packages/cli/src/index.ts query def "<symbol-id>"
# Find all references
bun run packages/cli/src/index.ts query refs "<symbol-id>"
# Impact analysis — what's affected by changes to a file?
bun run packages/cli/src/index.ts query impact src/foo.ts
# Detailed impact — includes symbol defs, docs, and reference snippets
bun run packages/cli/src/index.ts query impact src/foo.ts --details
# Call graph — who calls a function, and what does it call?
bun run packages/cli/src/index.ts query call-graph "<symbol-id>"
bun run packages/cli/src/index.ts query call-graph "<symbol-id>" --depth 2
# File dependency graph (imports only)
bun run packages/cli/src/index.ts query module-graph
# Hybrid module graph (imports + SCIP semantic edges with weights)
bun run packages/cli/src/index.ts query module-graph --mode hybrid
# Scoped to a directory
bun run packages/cli/src/index.ts query module-graph --path packages/core/All commands output JSON. Symbol IDs are SCIP symbol strings returned by search.
# Full transitive impact with risk scoring
bun run packages/cli/src/index.ts impact src/core.ts
# Limit BFS depth
bun run packages/cli/src/index.ts impact src/core.ts --max-depth 2
# Include call graph traversal
bun run packages/cli/src/index.ts impact src/core.ts --call-graph
# Multiple files
bun run packages/cli/src/index.ts impact src/core.ts src/utils.tsReturns: changed symbols (with public API flag), affected files (with depth and reason), affected packages, public API breaks (with downstream consumers), test files (direct vs transitive), risk score and category (low/medium/high/critical), boundary violation risk.
# Current metrics (coupling, cycles, API surface)
bun run packages/cli/src/index.ts metrics
# Save baseline snapshot
bun run packages/cli/src/index.ts metrics --snapshot
# Diff current vs baseline
bun run packages/cli/src/index.ts metrics --diffCreate ariadne.policies.json in your project root:
{
"policies": {
"deny_new_cycles": true,
"max_public_api_growth": 5,
"max_coupling_increase": 3
}
}Policies are checked automatically by ariadne verify. They compare current metrics against the saved baseline snapshot — run ariadne metrics --snapshot to establish the baseline.
| Command | Description |
|---|---|
setup [path] [--quick] |
One-command setup: init + index + generate hooks & MCP config. --quick skips SCIP. |
init [path] |
Create .ariadne/ directory and SQLite database |
index [path] [--structural-only] |
Full index: structural imports + SCIP analysis. --structural-only skips SCIP. |
update [--full] [--reingest] [--files path...] |
Incremental update: structural imports + auto SCIP when dirty source files exist. --files processes only the specified files (used by hooks for fast single-file updates). --full forces SCIP even when clean. --reingest forces SCIP re-ingestion even when file contents haven't changed (needed after package renames where symbol IDs change). |
query search <query> |
Fuzzy search symbols by name |
query def <symbol-id> |
Get definition location, docs, and code snippet |
query refs <symbol-id> |
Find all references across the codebase |
query impact <path>... [--details] |
Changed files → impacted symbols → dependent files → recommended tests. --details adds symbol defs, docs, and up to 3 reference snippets per symbol. |
query call-graph <symbol-id> [--depth N] |
Approximate call graph: callers and callees at the given depth (default 1) |
query module-graph [--mode] [--path] [--format] |
File dependency graph (imports, semantic, or hybrid) |
impact <path>... [--max-depth N] [--call-graph] |
Transitive impact analysis with risk scoring. BFS across symbol graph up to N depths (default 5). --call-graph includes caller traversal. |
metrics [--snapshot] [--diff] |
Structural metrics: coupling (Ca/Ce/I), dependency cycles, API surface. --snapshot saves baseline, --diff compares against it. |
context <path>... [--depth N] [--mode] |
Compile agent-friendly context for files: symbols, imports, importers, semantic refs. Modes: compact (default), full, symbols-only. |
preflight <path>... [--depth N] |
Pre-edit blast radius analysis: symbols defined in files, their call sites across the codebase, boundary violations, and risk assessment. |
ci [--base branch] [--markdown] [--root path] |
CI command: runs impact + verify + metrics on changed files vs base branch. Outputs JSON (default) or --markdown for PR comments. |
verify |
Run gatekeeper checks including policy enforcement (exit 0 = OK, exit 1 = FAIL) |
status |
Index stats (file count, symbol count, edge count, dirty count) |
dirty mark <path> |
Mark a file as needing re-index |
ledger log <event> <json> |
Append event to execution ledger |
doctor [path] |
Check prerequisites (Bun, Node, SCIP indexers for all languages) and warn about stale legacy directories |
The MCP server exposes 14 read-only tools for AI agents:
| Tool | Description |
|---|---|
ariadne.search_symbol |
Fuzzy search symbols by name |
ariadne.get_def |
Get symbol definition with docs and code snippet |
ariadne.find_refs |
Find all references to a symbol (optionally scoped) |
ariadne.impact |
Blast radius analysis for changed files (with optional details for symbol defs and key refs) |
ariadne.transitive_impact |
Full transitive impact with risk scoring, public API break detection, and affected package mapping |
ariadne.metrics |
Structural metrics: module coupling (Ca/Ce/instability), dependency cycles, API surface per package |
ariadne.cycles |
Dependency cycle detection using Tarjan's SCC algorithm |
ariadne.module_graph |
File dependency graph (imports/semantic/hybrid, json/dot/mermaid) |
ariadne.symbol_graph |
Dependency subgraph centered on a specific symbol |
ariadne.file_symbols |
List all symbols defined in a file |
ariadne.status |
Index stats: files, symbols, dirty count, timestamps |
ariadne.call_graph |
Approximate call graph: callers and callees for a symbol |
ariadne.plan_context |
Agent-friendly file context: symbols, imports, importers, semantic refs via BFS graph traversal |
ariadne.preflight |
Pre-edit blast radius: symbols in target files, call sites, boundary risks, and blast radius summary |
ariadne setup generates this automatically. To configure manually, add .mcp.json to your project root:
{
"mcpServers": {
"ariadne": {
"command": "bun",
"args": ["run", "/path/to/ariadne/packages/mcp/src/index.ts"],
"env": { "ARIADNE_ROOT": "." }
}
}
}If you installed via npm (bun add --global ariadne-mcp), use:
{
"mcpServers": {
"ariadne": {
"command": "bunx",
"args": ["ariadne-mcp"],
"env": { "ARIADNE_ROOT": "." }
}
}
}The MCP server starts gracefully even if .ariadne/ doesn't exist yet — it creates the directory and logs a helpful message.
ariadne setup generates .claude/settings.json automatically. The hooks reference portable scripts in .ariadne/hooks/ that resolve the ariadne binary dynamically at runtime.
- Pre-edit impact hook (
Edit|WritePreToolUse): runsariadne impactbefore every source file edit and injects the blast radius (changed symbols, dependent files, recommended tests) as context so Claude sees what will be affected. Silently skips test files, non-source files, and uninitialized projects. ~240ms latency. - Post-edit hook (
Edit|WritePostToolUse): marks edited source files dirty, runsariadne update --files <path>for fast single-file updates (no full repo walk), logs the edit to the ledger. Non-source files (README, config, etc.) are skipped to avoid false-positive freshness failures. - Post-test hook (
BashPostToolUse): detects test runner commands (vitest, jest, pytest, bun test, mocha, ava, cargo test, go test, mvn test, dotnet test, sbt test, bundle exec, playwright), logstest_runto the ledger - Stop hook: runs
ariadne update(auto-triggers SCIP when dirty source files exist), thenariadne verify. On failure, outputs{"decision":"block","reason":"..."}to prevent Claude from stopping until issues are fixed
The stop hook uses atomic mkdir-based locking to prevent concurrent runs, checks stop_hook_active from stdin to prevent infinite loops, and has a 120s stale lock timeout.
All hooks guard against missing .ariadne/ — they silently exit if the project hasn't been initialized.
ariadne verify runs these checks:
- Empty index — fails if zero files are indexed (prevents vacuous pass on uninitialized projects)
- Index freshness — checks the dirty set (files marked changed by hooks or
update), filtering to source files only. Fails if any dirty source files exist that haven't been covered by a SCIP index pass. Non-source files are ignored since SCIP can't index them. - Missing test runs — checks the ledger for a
test_runevent after the most recentedit. Fails if no tests were run after editing. - Typecheck — runs language-appropriate type checkers (
tsc --noEmit,go vet,cargo check,mvn compile,sbt compile,dotnet build) against detected projects. Fails on any type errors. On failure, includes recommendations withariadne query impactandariadne query searchcommands for the affected files and identifiers. Skipped if no supported project is detected. - Architecture boundaries — reads
ariadne.boundaries.jsonand checks allimportsedges against layer allowlists. Fails if any file imports from a layer not in itscanImportlist. Skipped if no config file exists. - Policy compliance — reads
ariadne.policies.jsonand compares current structural metrics against the saved baseline snapshot. Checks:deny_new_cycles(no new dependency cycles),max_public_api_growth(cap on new exported symbols per package),max_coupling_increase(cap on coupling metric increases per module). Skipped if no config file or no baseline snapshot exists.
graph TD
subgraph Indexing Pipeline
A[Source Files<br>.ts .tsx .js .jsx .py .go .rs<br>.java .kt .scala .cs .rb] --> B[Structural Pass<br>import/export extraction]
A --> C[SCIP Pass<br>scip-typescript / scip-python<br>scip-go / rust-analyzer<br>scip-java / scip-dotnet / scip-ruby]
D[Artifact Files<br>.env package.json *.sql] --> E[Artifact Pass<br>pseudo-symbol extraction]
end
subgraph SQLite Store
B --> F[(index.db<br>WAL mode)]
C --> F
E --> F
F --- G[files · symbols · edges<br>occurrences · dirty · ledger]
end
subgraph Consumption
F --> H[CLI Queries<br>search · refs · impact<br>call-graph · module-graph<br>transitive-impact · metrics<br>context · preflight]
F --> I[MCP Server<br>14 read-only tools]
F --> J[Verify Engine<br>freshness · tests · typecheck<br>boundaries · policies]
end
subgraph Claude Code Hooks
K[Pre-edit] -->|impact context| H
L[Post-edit] -->|dirty mark + update| F
M[Post-test] -->|ledger log| F
N[Stop] -->|update + verify| J
end
Three-pass pipeline:
- Structural pass (instant, per-file) — regex-based extraction of
import/export/from/use/require/usingstatements across all supported languages. Creates file-levelimportsedges with resolved file paths (e.g../utils→src/utils.ts) so edges point to actual file nodes. Runs on everyupdate. - SCIP pass (slower, full project) — runs the appropriate SCIP indexer per detected sub-project, producing a protobuf index. The parser decodes it and extracts symbols, occurrences (with line:col ranges), and definition/reference roles. Creates symbol-level
defines,references, and approximatecallsedges (via enclosing-definition heuristic). Runs onindex,update(when dirty source files exist), andupdate --full(forced). - Artifact pass (instant, per-file) — extracts pseudo-symbols from non-code files:
.envvars (env_var),package.json/tsconfig.jsonkeys (config_key), SQL migrations (table,index), and OpenAPI specs (api_endpoint,api_schema). Scans dirty source files for references (process.env.KEY,os.environ, SQL table names) and createsconfig_refedges. Runs on everyupdate.
All passes write to the same SQLite database (.ariadne/index.db). The database uses WAL mode, busy_timeout=5000 for concurrent access from hooks, and transactions for atomic SCIP ingestion. Bulk ingestion uses index-drop/recreate and PRAGMA synchronous=OFF for faster writes, and skips unchanged files by content hash comparison.
Ariadne auto-detects sub-projects in monorepos by scanning for language-specific config files (tsconfig.json, pyproject.toml/setup.py, go.mod, Cargo.toml, pom.xml/build.gradle, build.sbt, .sln/.csproj, Gemfile). Each sub-project is indexed independently with correct path prefixing so that SCIP-relative paths map correctly to repo-root-relative file paths.
The module-graph query supports three modes:
- imports (default) — structural import edges only
- semantic — SCIP-derived edges with occurrence weights (how many times symbols from file A are referenced in file B)
- hybrid — union of both, with source tagging (
import,semantic, orimport+semantic)
Output formats: json (default), dot (Graphviz), mermaid.
packages/
core/ ariadne-core library (636 tests)
src/
store/ SQLite schema, queries (7 focused query classes + facade), types, transactions
scip/ protobuf parser + SCIP types + call graph derivation
indexers/ scip-typescript, scip-python, scip-go, scip-rust, scip-java, scip-csharp, scip-ruby, import extractor, project detector, artifact extractor, config ref scanner
graph/ refs, impact analysis (basic + transitive with risk scoring), call graph, module graph (import/semantic/hybrid), structural metrics (coupling, cycles, API surface), context compiler, preflight analyzer, shared utils
verify/ gatekeeper engine + checks (freshness, tests, typecheck, boundaries, policies)
ledger/ execution event log
cli/ CLI command router + hooks
src/
commands/ init, setup, index, update, query, verify, status, dirty, doctor, ledger, impact, metrics, ci, context, preflight
hooks/ pre-edit-impact.sh, post-edit.sh, post-test.sh, stop-verify.sh
lib/ context, output helpers
mcp/ MCP stdio server (14 read-only tools)
Benchmarked on Ariadne's own codebase (95 files, 3 sub-projects). Apple M3.
| Operation | Time | Notes |
|---|---|---|
| Full index (structural + SCIP + artifacts) | ~17s | One-time; 3 SCIP sub-projects indexed in parallel sequence |
| Incremental update (no changes) | ~120ms | Hash comparison, no I/O |
| Single-file update (hook path) | ~155ms | Targeted file only, no repo walk |
| Pre-edit impact hook | ~170ms | Includes blast radius + symbol details |
| Symbol search | ~80ms | SQLite LIKE query |
| Impact analysis (--details) | ~95ms | Symbols + dependents + test recommendations |
| Verify (all checks) | ~250ms | Freshness + test coverage + typecheck + boundaries |
Hook latency stays under 200ms — fast enough to run on every edit without noticeable delay in Claude Code.
bun install
bun test # 636 tests across 29 files
bunx tsc --noEmit # type-check all packages
bun run packages/cli/src/index.ts doctor # check prerequisitesRequired: Bun >= 1.0
Optional: SCIP indexers add symbol-level analysis (defs/refs/call graph) on top of structural imports. Without them, you still get file-level dependency graphs, import edges, and artifact indexing.
| Language | Extensions | Structural imports | SCIP indexer | Type checker | Install SCIP |
|---|---|---|---|---|---|
| TypeScript / JS | .ts .tsx .js .jsx |
ES modules, CJS, dynamic | scip-typescript | tsc --noEmit |
npx @sourcegraph/scip-typescript (auto) |
| Python | .py |
import, from, relative |
scip-python | — | uvx scip-python or pip install scip-python |
| Go | .go |
import "pkg", blocks |
scip-go | go vet |
go install github.com/sourcegraph/scip-go@latest |
| Rust | .rs |
use, mod, extern crate |
rust-analyzer | cargo check |
rustup component add rust-analyzer |
| Java / Kotlin | .java .kt |
import, static imports |
scip-java | mvn compile / gradle |
coursier install scip-java |
| Scala | .scala |
import pkg._, selective |
scip-java | sbt compile |
coursier install scip-java |
| C# | .cs |
using, static/aliased |
scip-dotnet | dotnet build |
dotnet tool install --global scip-dotnet |
| Ruby | .rb |
require, require_relative |
scip-ruby | — | gem install scip-ruby |
SCIP indexers exist for these languages and could be integrated:
| Language | SCIP Indexer | Status |
|---|---|---|
| C / C++ | scip-clang | Not yet integrated |
| PHP | scip-php | Not yet integrated |
| Swift | sourcekit-lsp SCIP export | Not yet integrated |
Contributions welcome — see CONTRIBUTING.md.
MIT — see LICENSE