Skip to content

fizyxbt/grafly

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

grafly

Scan · Map · Detect modules · Analyze

Grafly turns a codebase into a dependency map — a directed graph of artifacts (files, classes, functions) and their dependencies. It parses source files locally with tree-sitter, detects cohesive modules with the Leiden algorithm, and emits an interactive HTML visualization, a JSON map, and a Markdown report — with no API calls required for code files.

Designed to work as a standalone CLI, a Rust library, and an MCP server that any LLM coding assistant can call as a tool.


Ubiquitous Language

Grafly uses the architect's vocabulary — what software architects actually call things:

Term Meaning
Artifact A unit of code (file, class, function, ...)
Package A buildable unit declared in a manifest (Cargo.toml, pyproject.toml, package.json, go.mod). Sits above File in the containment hierarchy
Dependency A directed relationship between artifacts
Dependency Map The full graph of artifacts and dependencies
Module A cohesive cluster of artifacts (detected by Leiden)
Hotspot An artifact with disproportionately high centrality
Coupling A dependency that crosses module boundaries
Visibility Source-level access of a declared symbol — Public, Crate, Private, Unknown. Drives the public-surface filter on hotspots, couplings, and the artifact HTML
Scan Discovering artifacts and dependencies from source
Insight A finding the analysis surfaces about the architecture

Features

  • Local-first — all code scanning runs with tree-sitter, fully offline
  • Fast — parallel file scanning via Rayon; single-pass map construction
  • Package layer — discovers buildable units from project manifests (Cargo.toml, pyproject.toml, package.json, go.mod), links each source file to its declaring package, and flags binary entry points
  • Visibility-aware — detects Public / Crate / Private on each declared symbol (Rust pub, Python underscore, JS export, Go capitalisation, Java modifiers, TS accessibility) and filters internal helpers out of the architecture view by default
  • Module detection — Leiden algorithm runs both globally (cross-package modules) and within each package (fine-grained subsystems), so you can see both "where the cross-cuts are" and "what lives inside each crate"
  • Architecture insights — hotspots, cross-module couplings, suggested insights
  • Interactive path queries — weighted shortest paths that prefer runtime call chains (Calls=1) over file-level import shortcuts (Imports=5), and BFS subgraphs with a supernode cap to keep neighborhoods focused
  • Interactive HTML — vis-network map with module colours, click-to-inspect
  • MCP server — expose grafly as a tool to Claude Code, Cursor, and any MCP-compatible LLM

Supported Languages

Language Extensions
Python .py
Rust .rs
JavaScript .js .mjs .cjs
TypeScript .ts .tsx
Go .go
Java .java

Installation

cargo install grafly-cli   # CLI
cargo install grafly-mcp   # MCP server

Or build from source (requires Rust ≥ 1.85):

git clone https://github.com/gianlucaciocci/grafly
cd grafly
cargo build --release

CLI Usage

# Analyze the current directory (writes ./grafly-out/)
grafly analyze .
# `grafly .` is shorthand for the same.

# Specific project
grafly analyze ~/projects/myapp

# Tune module resolution (default 1.0 — higher = more, smaller modules)
grafly analyze . --resolution 0.5

# Deterministic run
grafly analyze . --seed 42

# Choose output formats
grafly analyze . --formats json,html

grafly analyze flags

Flag Default Description
<PATH> (positional) . Directory to scan
-r, --resolution <FLOAT> 1.0 Leiden resolution — higher → more, smaller modules
-s, --seed <INT> Random seed for deterministic module detection
-f, --formats <CSV> json,html,html-modules,html-packages,md Comma-separated output formats: json, html, html-modules, html-packages, md
--max-html-nodes <N> 800 Cap on artifacts in the artifact-level HTML (0 = unlimited)
--max-html-modules <N> 100 Cap on modules in the module-level HTML (0 = unlimited)
--html-include-ambiguous false Show Ambiguous-confidence edges in the artifact HTML (always kept in JSON)
--no-ignore false Disable all path filtering — scan every file, including hidden dirs, .gitignored paths, node_modules, target, .venv, tests, examples
--include-tests false Keep test/example files (tests/, __tests__/, examples/, *_test.go, *.test.ts, *Test.java, etc.). Excluded by default — they're not runtime architecture
--include-imports false Keep Imports edges in the output. Used for clustering either way; dropped after clustering by default because they create misleading A → shared_file → B path shortcuts and inflate hotspot degrees
--no-intra-package-modules false Skip the intra-package Leiden pass. By default grafly clusters within each Package separately (in addition to the global cross-package modules), surfacing fine-grained subsystems inside each crate/package
--leiden-thorough false Use leiden-rs's stock high-quality defaults (max_iter=100, epsilon=1e-10) instead of grafly's fast defaults (max_iter=30, epsilon=1e-8, min_iter=3). Adds time on large codebases for a sub-percent quality gain
--include-private false Show Visibility::Private symbols in the artifact HTML, hotspots, and couplings. Hidden by default so the architecture view stays focused on the public surface; always kept in grafly_knowledge.json regardless

Run grafly analyze --help for the same list straight from the binary.

Output files (always written to ./grafly-out/ — hardcoded so the rules file, MCP server, and /grafly-* skills all agree on where to look):

File Description
README.md Index of all output files, written for both humans and LLM agents
grafly_report.md Markdown analysis: packages, modules, hotspots, cross-module couplings, suggested questions — LLM-discoverable
grafly_knowledge.json Full directed dependency map with source_file:line on every edge
grafly_modules.html Interactive module-level overview (Leiden modules as nodes, edges grouped by relationship kind)
grafly_packages.html Interactive package-level overview (Cargo/pyproject/package.json/go.mod packages as nodes, cross-package edges; binaries coloured distinctly from libraries)
grafly_artifacts.html Interactive artifact-level graph (top-N by degree, Ambiguous edges suppressed for clarity)
SUGGESTED_QUESTIONS.md Kickoff list of architectural questions grouped by which grafly file answers them, with a section for an LLM to fill in project-specific versions

Wire Grafly into Your LLM

After analyzing, run grafly install to wire grafly into your LLM tool of choice. One command does everything that target supports — the rules file (so agents know to consult ./grafly-out/), the MCP server registry (so they can query the graph live), and (for Claude Code) the /grafly-* slash commands.

grafly install                          # default: Claude Code
grafly install --platform cursor        # Cursor
grafly install --platform claude-desktop # Claude Desktop (MCP only)
grafly install --platform vscode        # VS Code (MCP only)
grafly install --all                    # every supported target

grafly list                             # show what's currently installed
grafly uninstall --all                  # cleanly remove everything everywhere

Per-target capability matrix:

Target Rules file MCP config Claude Code skills
claude (default) CLAUDE.md .mcp.json /grafly-ask, /grafly-suggest-questions
claude-desktop claude_desktop_config.json (global)
cursor .cursor/rules/grafly.mdc .cursor/mcp.json
windsurf .windsurfrules ~/.codeium/windsurf/mcp_config.json
vscode .vscode/mcp.json
agents AGENTS.md (Codex/Aider/OpenCode/Factory)
copilot .github/copilot-instructions.md
gemini GEMINI.md

Each install is all-or-nothing per target. There's no flag to install just the rules or just the MCP server — if a target supports a surface, it gets it.

Rules-file installs append a marker-bracketed section (<!-- grafly-section-start --> / <!-- grafly-section-end -->); MCP installs merge a grafly-mcp entry into the existing JSON registry. Other content in those files is preserved. grafly uninstall reverses each surface cleanly.

The MCP server

grafly-mcp exposes ten tools — analyze, get_artifacts, get_modules, get_hotspots, get_couplings, get_insights, export, find_path, get_neighbors, get_dependents. The binary path is auto-detected (prefers the bare name grafly-mcp on PATH; falls back to the sibling of the current grafly executable); override with --bin.

The /grafly-* slash commands (Claude Code only)

Installing the claude target also wires two skill briefs into ~/.claude/skills/ plus a registration in ~/.claude/CLAUDE.md:

Slash command What it does
/grafly-ask Ask any architectural / structural question — overview, modules, hotspots, cross-module couplings, "how does X connect to Y", "what depends on Z" — and the skill routes to the right grafly-mcp:* MCP tool. Falls back to ./grafly-out/grafly_report.md if the MCP server isn't connected.
/grafly-suggest-questions Bootstrap a project-specific question list: reads ./grafly-out/SUGGESTED_QUESTIONS.md + grafly_report.md, resolves the placeholders (<ARTIFACT> / <MODULE> / <PACKAGE>) to real names from this codebase, appends a dated section, and surfaces the top 10 as a menu in chat. Useful as a kick-off for onboarding or code review.

The same suggested-questions workflow is also wired into the install templates for every other LLM tool, so agents that don't support Claude Code skills still know to consult SUGGESTED_QUESTIONS.md when the user asks "what can I ask about this codebase?" or "where do I start?".


MCP Server

Grafly ships a first-class MCP server (grafly-mcp) so LLMs can call it directly as a tool.

Claude Code setup

Add to your ~/.claude/settings.json (or project .claude/settings.json):

{
  "mcpServers": {
    "grafly-mcp": {
      "command": "grafly-mcp",
      "args": []
    }
  }
}

Available tools

Tool Description
analyze Full pipeline — returns summary with artifact/dependency/module counts, quality score, hotspots, and insights
get_artifacts List artifacts, optionally filtered by kind or module_id
get_modules Module breakdown — sizes and representative artifacts
get_hotspots High-centrality artifacts that may be architectural bottlenecks
get_couplings Cross-module couplings — unexpected dependencies between modules
get_insights Suggested insights for architectural review
export Write JSON / HTML / Markdown files to a directory
find_path Weighted shortest path between two artifacts — prefers Calls chains over Imports shortcuts
get_neighbors Depth-limited BFS subgraph around an artifact (default: runtime edges only, supernode cap at degree 200)
get_dependents The artifacts that depend on a given artifact (incoming-direction subgraph)

Example tool call

{
  "tool": "analyze",
  "arguments": {
    "path": "/home/user/projects/myapp",
    "resolution": 1.0,
    "seed": 42
  }
}

Response:

{
  "artifacts": 312,
  "dependencies": 847,
  "modules": 7,
  "quality": 0.4821,
  "hotspots": [
    { "label": "DatabaseClient", "degree": 34, "source_file": "src/db/client.py" }
  ],
  "insights": [
    "`DatabaseClient` (src/db/client.py) is a hotspot with 34 connections — consider splitting it.",
    "`AuthMiddleware` (module 2) couples to `ReportGenerator` (module 5) via `Imports` — is this intentional?"
  ]
}

Path queries

{
  "tool": "find_path",
  "arguments": {
    "path": "/home/user/projects/myapp",
    "from": "DataActorCore",
    "to": "ExecutionEngine"
  }
}

By default the path is weighted so Calls edges (weight 1) are strongly preferred over Imports edges (weight 5) and References/Uses (weight 10). In a message-bus architecture this routes the answer through the actual mediation chain instead of taking a file-level import shortcut. Set weighted: false for raw shortest path by hop count. Each hop in the response carries its DependencyKind, Confidence, and source_line so callers can distinguish hard-evidence chains from inferred ones.


Rust Library Usage

Add to Cargo.toml:

[dependencies]
grafly = "0.1"
use std::path::Path;

// 1. Scan
let scan = grafly::scan::scan_dir(Path::new("./src"))?;

// 2. Build dependency map
let mut builder = grafly::MapBuilder::new();
builder.add_scan(scan);
let mut map = builder.build();

// 3. Detect modules
grafly::cluster::detect_modules(&mut map, 1.0, None)?;

// 4. Analyze
let analysis = grafly::analyze::analyze(&map);

// 5. Query — weighted shortest path between two artifacts
let from = grafly::query::resolve(&map, "DataActorCore")?;
let to   = grafly::query::resolve(&map, "ExecutionEngine")?;
if let Some(path) = grafly::query::find_path(&map, from, to, &Default::default()) {
    println!("{} hops, weight {}", path.total_hops, path.total_weight);
}

// 6. Export
grafly::export::write_html(&map, Path::new("map.html"))?;

Architecture

grafly/
├── crates/
│   ├── core/      Artifact, Dependency, DependencyMap, MapBuilder — shared types
│   ├── scan/      tree-sitter parsers (Python, Rust, JS, TS, Go, Java) + Rayon walker
│   ├── cluster/   Leiden module detection via leiden-rs (detect_modules)
│   ├── analyze/   Hotspots · couplings · insights
│   ├── query/     find_path · neighbors · ancestors · descendants
│   ├── report/    Markdown report generator
│   ├── export/    JSON + interactive HTML (vis-network)
│   ├── cli/       grafly binary (clap)
│   └── mcp/       grafly-mcp binary (rmcp stdio server)

Pipeline:

scan_dir(path)
  → ScanResult { artifacts, dependencies }
  → MapBuilder           → DependencyMap (petgraph DiGraph)
  → detect_modules()     → module_id assigned to each artifact
  → analyze()            → Analysis { hotspots, couplings, insights }
  → query()              → Path / Subgraph — weighted shortest path, BFS
  → export / report

Contributing

Prerequisites:

  1. Rust ≥ 1.85
  2. Build:
git clone https://github.com/gianlucaciocci/grafly
cd grafly
cargo build

All dependencies (including leiden-rs for module detection) resolve from crates.io.

Adding a new language

  1. Add the tree-sitter-<lang> crate to crates/scan/Cargo.toml and the workspace Cargo.toml.
  2. Create crates/scan/src/<lang>.rs following the pattern in python.rs.
  3. Register the extension in crates/scan/src/lib.rs.
  4. Update the supported languages table in this README and in CLAUDE.md.

License

MIT — see LICENSE.


grafly.net · me@gianlucaciocci.com

About

Extract, cluster and analyze codebases as knowledge graphs.

Resources

License

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Rust 100.0%