An AI toolkit for developers building high-performance code with an LLM in the loop. It gives your coding assistant real performance tooling it can drive itself, a built-in project tracker so plans never get lost between sessions, and the discipline to keep raw tool output out of the model's context. Built for Claude Code, and it works just as well with Cursor or any other MCP client.
Reusable helpers are available directly from devtools_mcp.evidence for bounded
artifact inventory, content bindings and strict result parsing. The
mining report documents their
provenance and scope. Use the maintained MCP client
(devtools-call, or python -m devtools_mcp.client from the source checkout)
for structured tool-call batches. The integration roadmap
tracks process supervision through existing recipes.
analyze_ring_arrival_boundaries joins a dedicated native ring boundary trace,
ordered arrival CSV and exact benchmark report. It validates complete event
counts, clock-offset bounds and latency percentiles before returning bounded
phase summaries. The same read-only callable is exposed as devtools_ring_arrival.
Use DTrace -o to keep records separate from target stdout. Input byte/event
and report-row budgets are explicit; analysis success does not qualify an
instrumented performance result. Dedicated B,pid,tid,event,phase,timestamp,arg
records may uniformly append an eighth vtimestamp_ns column. Seven-column
captures return thread_cpu: null; mixed formats refuse. Optional
preceding_events=0 independently budgets a chronological measured-event window
before the single worst event, separate from top_n. Requested/available counts
and first/last event IDs expose a short window near measurement start; warm
events and the worst event itself are excluded. CPU records must
advance monotonically over each actual producer/consumer sequence, including
warmup and consumer EOF. CPU spans never subtract across threads. Reports retain
raw wall/CPU spans and signed residuals, including negative residuals; residuals
are not pure off-CPU time or proof of preemption/algorithmic cause. The accounting
includes platform probe and clock-reading differences: see Apple XNU DTrace
and the DTrace variable reference.
DTrace run summaries display collection diagnostics before samples or target output, even when the collector exits0 and the benchmark reportsPASS. The runner scans captured stderr bytes before output truncation, separately reports capture/scan completeness, and retains bounded error/drop examples. SIP notices remain advisory. Legacy results with no recorded stderr inspection are explicitly UNVERIFIED. These diagnostics preserve actual exit codes, benchmark JSON and raw artifacts; absence of reported errors does not certify complete probe coverage.
Serious performance tools demand two kinds of expertise: the command-line incantations to run them, and the experience to read what comes back. VTune, Valgrind, PerfView and ETW, perf, JFR, the debuggers, all of them. An LLM has both. What it doesn't have is room. One raw profiler dump is tens of thousands of symbol-heavy lines, and pasting that into a conversation destroys the context you're working in. ai-grind absorbs that noise. Every run is parsed into a queryable Polars DataFrame, so the model gets a bounded summary and a run_id, then queries for exactly what it needs instead of taking the whole dump back at once.
The same problem applies to the plan itself. Plans made in chat evaporate between sessions. Pause, resume, and the knowledge is gone. So the server carries a built-in project tracker, a mini-JIRA in one SQLite file. The LLM writes its plan straight into it: epics, stories, punch-card subtasks, acceptance criteria tied to real tests, dependencies that resolve into "what should happen next". That state persists on disk across restarts, sessions, and tools.
This is a working version, not a final one. It's the toolkit I'm using to build the next one.
devtools-mcp, the MCP server. 17 backends spanning profilers (VTune, ETW/PerfView, perf, DTrace, Valgrind, JFR/async-profiler, py-spy, V8, RenderDoc), debuggers (LLDB, CDB), and build/package systems (Maven, Gradle, npm, pnpm, yarn, Cargo), all behind one normalized vocabulary, with flame graphs and a local browser dashboard for the human in the loop.- The tracker, persistent project management driven entirely through MCP tools: tasks with
PROJ-123keys, hierarchy, status workflow with an acceptance-test close gate, commit linking, auto-tagging, GitHub issue sync, and a dependency resolver. - The skills library (
skills/), 109 skills that teach the assistant how to use all of this and more: driving each profiler and reading its output, interpreting flame graphs, the tracker workflows, making PowerShell behave on Windows (5.1 vs 7), uv for Python, and general debugging workflows.
Design rule (everywhere): the LLM is never flooded with raw, symbol-heavy tool output. Every run is stored as a queryable Polars DataFrame; tools return only bounded summaries (top-N, percentages, a
run_id) and large artifacts (flame-graph SVGs, raw traces) are written to disk and returned as a path. Drill in on demand withdevtools_analyze/devtools_query.
Instead of dozens of individual tool wrappers, devtools-mcp provides a few categories of tools that work uniformly across all backends:
| Category | Tools | Purpose |
|---|---|---|
| Batch | devtools_check, devtools_run, devtools_list, devtools_raw |
Detect tools, run analyses, list results, view raw output |
| Analysis | devtools_analyze, devtools_query, devtools_compare |
Filter, group, query, and diff run results |
| Ring evidence | devtools_ring_arrival |
Validate same-execution native event boundaries and attribute bounded wall intervals |
| Search | devtools_search, devtools_correlate |
Cross-run text search and join runs on shared columns |
| Flame | devtools_flamegraph |
Render any sampling run as an SVG flame graph + bounded text tree |
| Visualize | devtools_dashboard |
Launch a local browser "visualization terminal" for all runs |
| Debug | debug_start, debug, debug_inspect, debug_stop |
Interactive LLDB sessions with structured snapshots |
| Track | tracker_project, tracker_task, tracker_status, tracker_criteria, tracker_tag, tracker_commits, tracker_deps, tracker_issue, tracker_query, tracker_sync, tracker_files |
Persistent plans, tasks, acceptance gates, and "what's next" |
| Suite | Tools | Platform |
|---|---|---|
| Valgrind | memcheck, helgrind, drd, callgrind, cachegrind, massif | Linux |
| LLDB | Interactive debugging with backtrace, variables, breakpoints, registers, memory, disassembly | macOS, Linux |
| DTrace | cpu, syscall, trace | macOS, Solaris |
| perf | cpu, stat, annotate | Linux |
| ETW (PerfView) | cpu (CPU hotspots Exc%/Inc% + flame graph) | Windows |
| VTune (Intel) | cpu (hotspots), threads (threading), alloc (memory-consumption), memory (memory-access), uarch (top-down), snapshot | Windows, Linux (oneAPI) |
| JVM | cpu (JFR), alloc (async-profiler), threads (jstack), heap (jmap/jcmd) | any (JDK) |
| CDB | stacks (~*k), analyze (!analyze -v), inspect. Batch-mode Windows debugger |
Windows |
| Python | cpu (py-spy sampling), threads (py-spy dump), cprofile (deterministic) | any (cProfile stdlib; py-spy = pip install py-spy) |
| Node/JS | cpu (--cpu-prof), alloc (--heap-prof). V8 profiles, turned into a flame graph |
any (Node.js) |
| RenderDoc | capture, analyze, counters, resources, thumb. GPU frame capture (D3D/Vulkan/OpenGL) giving a per-drawcall tree and a GPU-time flame graph | Windows, Linux (GPU + interactive session) |
| Maven | build, test, deps, sync | any (mvn or project mvnw) |
| Gradle | build, test, deps, sync, tasks | any (gradle or project gradlew) |
| npm | build, test, deps, sync, audit, outdated, tasks | any (Node.js) |
| pnpm | build, test, deps, sync, audit, outdated, tasks | any (pnpm) |
| yarn | build, test, deps, sync, audit, tasks | any (yarn classic) |
| Cargo | build, check, test, deps, sync, audit | any (Rust / rustup) |
One vocabulary, many backends. Every build and package-manager backend speaks the
same verbs: deps (dependency tree plus subdependencies), sync (resolve /
install / refresh / fetch), build, test, plus audit / outdated / tasks
where the tool supports them. So devtools_run(suite="npm", tool="deps") and
devtools_run(suite="cargo", tool="deps") behave identically; only the backend
implementation differs.
Profiling verbs are unified too. cpu means a CPU profile whether the backend is
perf, dtrace, etw, jvm, py, or node; alloc is allocation profiling (jvm, node);
threads is a thread dump (jvm, py). Any sampling run can be turned into a flame
graph with devtools_flamegraph(run_id=...). Folded stacks are the universal
currency, so flame graphs work the same for native code (Linux perf script,
macOS dtrace ustack, Windows ETW), the JVM, Python (py-spy), and JavaScript
(V8).
devtools_dashboard(action="start") launches a local, 127.0.0.1-only web UI. The browser becomes a window onto everything the LLM sees:
/lists every run across workspaces.- A run page shows the bounded summary, the full queryable data table, and raw logs.
- The interactive flame graph re-roots on any frame you click, and hovering gives the name and percentage. Pure server-rendered SVG, no JS framework or CDN.
It reads the live workspace, so runs appear as you create them. This is also the seam where agentic/visual tooling can be added later.
A persistent, SQLite-backed task tracker built into the server, so the LLM can
put its plan straight into durable storage instead of leaving it in chat.
Pause a session, resume tomorrow, switch from Claude Code to Cursor, and the
epics, subtasks, acceptance criteria, and "what's next" are all still there.
Eleven tracker_* tools over one global database (~/.devtools-mcp/tracker.db,
override with DEVTOOLS_MCP_TRACKER_DB):
| Tool | Role |
|---|---|
tracker_project |
Projects: key namespaces (GRIND-123), close policy (advisory/strict) |
tracker_task |
Tasks: create / get / update / move / breakdown (punch-card subtasks), 6-level hierarchy |
tracker_status |
Status workflow + the acceptance close gate |
tracker_criteria |
Acceptance criteria linked to tests (file::test_name), pass/fail recording |
tracker_tag |
Tags + auto-tag rules (kind / regex / parent-kind, applied at creation) |
tracker_commits |
Commit links: manual or git log scan for task keys in messages (scan runs the subprocess off the event loop and batches links in one transaction, so it never blocks the server) |
tracker_deps |
Task dependencies + execution-plan resolver: what's ready now, what's blocked by what, parallelizable order |
tracker_issue |
GitHub issue bridge (create from task with criteria checklist, sync drift, close); provider-abstracted, GITHUB_TOKEN/GH_TOKEN |
tracker_query |
Bounded reporting: tasks / tree / rollup / criteria / commits / tags / deps / issues / activity / claims views |
tracker_sync |
Local-first collaboration: CRDT sync with another replica (see below) |
tracker_files |
Local agent collaboration: report file touches, place advisory claims, list contested files |
Same no-token-flood contract as the profiling tools: every response is bounded
markdown; the data lives in SQLite (WAL, indexed for the tag/kind/rollup and
reverse-dependency probes) and is paged through Polars-backed views.
Workflow skills live in skills/authored/skills/tracker/. The tracker also
renders as cards in the dashboard: project overview, a per-project board with
status columns and the execution plan, and task detail pages at /tracker.
The tracker is a replicated, local-first store rather than a local file.
Every replica has a site id and a hybrid logical clock; SQLite triggers
capture every mutation into an op-log with site-independent payloads (rows
carry global uids, and references use uids, never local rowids). Merging is
last-writer-wins per row, idempotent, and converges regardless of sync order
or topology. Ops propagate transitively, so two laptops that only ever talk
to a third machine still end up identical. Concurrent allocation of the same
human key (both sides mint PROJ-7 offline) is resolved by deterministic
re-keying, so no replica ever loses a task.
Syncing is one tool call: tracker_sync(action="sync", url="http://other-box:8765")
The peer is any machine running devtools_dashboard, which serves the sync
API at /api/crdt/. Work fully offline, sync when you meet the network again.
(v1 is row-level LWW with full-exchange transport; field-level merge and
incremental vector-clock exchange are the documented upgrade path.)
The dashboard binds 127.0.0.1 by default, so a fresh install is loopback-only
and no peer can reach it. Sharing a replica is an explicit act — start the peer
with --dashboard-host 0.0.0.0 (env: DEVTOOLS_MCP_DASHBOARD_HOST), which keeps
loopback working and admits the machine's hostname, its hostname-resolved
addresses and, for an unqualified hostname, <hostname>.local to the Host
allowlist that guards state-changing requests. Additional interface addresses,
DNS aliases and 127.0.0.1:8766 seen through an SSH tunnel are listed explicitly
in DEVTOOLS_MCP_DASHBOARD_ALLOWED_HOSTS.
--host is a different knob — it binds the MCP transport and should stay on
loopback. Anything that can reach the shared dashboard port can read and write
that tracker replica, so share it on a trusted LAN or over SSH. Step-by-step
two-machine recipe (firewall rules included): the tracker-usage skill.
Run several agents on one machine, say Claude Code, Cursor, and Codex side by
side, without them silently stepping on each other's edits. Claude Code hooks
(hooks/, wired up automatically by the plugin) report every Edit/Write to
the local service; the tracker records who touched what, and if another
session is on the same file the agent gets a warning injected straight into
its context. Agents can also take advisory claims (self-expiring leases,
heartbeated by their own edits) on files before a big refactor via the
tracker_files tool. By default a contested file warns, and with
DEVTOOLS_MCP_COLLAB_MODE=ask it surfaces a permission prompt to the human
instead. Everything links to tracker tasks (DEVTOOLS_MCP_TASK=GRIND-42), the
/collab dashboard page shows sessions, claims and contested files live,
and tracker_query(view="activity"|"claims") gives the bounded tables. See
the agent-collab skill for the workflow.
Coming soon: a multi-user team collab server that extends this across machines and teammates. The local layer is its single-machine precursor and will sync into it.
Skills don't have to be static files. A live skill is a SKILL.md whose source of
truth is a CRDT text document, pycrdt with
Yrs under the hood. Agents edit it in place with the skill_live tool
(create / append / patch with find-replace semantics), and every change
is materialized to a real ~/.claude/skills/<name>/SKILL.md
(DEVTOOLS_MCP_LIVE_SKILLS_DIR overrides), so the file Claude loads is always
the current merged state. skill_live(action="sync", url="http://other-box:8765")
exchanges state-vector diffs with any peer dashboard, and concurrent edits from
different agents or machines merge at character level instead of one side
clobbering the other. An agent that learns a better incantation appends it to
the skill; after the next sync, every machine's copy teaches it.
Prefer append/patch over rewriting whole documents: concurrent full
rewrites both survive a CRDT merge (that's the point) and duplicate content.
skills/ is the other half of the toolkit: the knowledge that makes the tools
usable. 109 Claude Code skills covering how to drive each profiler and read its
output (etw-profiling, vtune-profiling, flamegraph-reading, jvm /
python / node profiling), the tracker workflows (tracker-usage, -breakdown,
-acceptance, -github-sync), build tooling, a full set of
PowerShell-on-Windows survival guides (5.1 vs 7 idioms, errors, native
commands, non-interactive automation), and uv for Python projects. Skills are
managed as a library: harvested and hand-authored sources merged into a loadable
mirror by skills/sync.py. See skills/README.md.
Requires Python 3.12+. Durable recipes also require that Python's sqlite3
module is linked against SQLite 3.38 or newer. Check the active interpreter with
uv run python -c "import sqlite3; print(sqlite3.sqlite_version)". On older
SQLite, the server reports that durable recipes are unavailable; other tools
remain available. Upgrade to a Python build with a newer SQLite library.
Installing pysqlite3-binary alone does not change the standard-library module.
Tests requiring durable workflows are skipped on older SQLite; unrelated tests still run.
# Clone and install
git clone https://github.com/Ugbot/ai-grind.git
cd ai-grind
uv syncThis repo is a self-contained Claude Code plugin marketplace
(.claude-plugin/marketplace.json) shipping one plugin, devtools-mcp, that
bundles the MCP server and the full skills library (109 skills, 1 command,
4 agents), plus the agent-collab hooks. Install it in Claude Code:
/plugin marketplace add Ugbot/ai-grind
/plugin install devtools-mcp@ai-grind
The plugin wires up the MCP server through ${CLAUDE_PLUGIN_ROOT}, so no
hand-edited .mcp.json path. It still needs uv on PATH and the repo's
Python deps (uv sync runs on first launch). To iterate on bundled skills, edit
skills/authored/ (or skills/catalog/) and regenerate the committed bundle:
python skills/sync.py --target plugin # rebuild plugin/{skills,commands,agents}The marketplace also ships project-map (plugins/project-map/), which
templates any repository for coding agents: a root CLAUDE.md, an AGENTS.md
pointer, a PROJECT_MAP.md that says what lives where and why, a CLAUDE.md
per area, engineering principles, and a local SQLite BM25 index with MCP tools.
/plugin install project-map@ai-grind
Then ask Claude to "map this project", or run
python3 plugins/project-map/skills/project-map/scripts/kb.py template from the
target repo. See plugins/project-map/README.md.
The marketplace can be installed in Codex CLI. The repository ships
Codex manifests under .codex-plugin/. Its MCP configuration sets cwd to the
plugin root and runs uv run devtools-mcp, so it does not depend on
Claude-only variable substitution:
codex plugin marketplace add Ugbot/ai-grind
codex plugin add devtools-mcp@ai-grind
codex plugin add project-map@ai-grindThis installs the stdio devtools-mcp server and the skills library.
The Claude-specific hooks and agent definitions remain in the Claude bundle.
The Codex project-map manifest explicitly declares no MCP servers, which prevents
fallback to the Claude .mcp.json. Its skills remain available.
For the optional project-kb MCP server, put this in the target project's
.codex/config.toml, using absolute paths. The explicit root prevents a plugin
cache or another working directory from being indexed:
[mcp_servers.project-kb]
command = "python3"
args = ["/path/to/ai-grind/plugins/project-map/skills/project-map/scripts/kb_mcp.py"]
cwd = "/path/to/your-project"
[mcp_servers.project-kb.env]
CLAUDE_PROJECT_DIR = "/path/to/your-project"Codex loads project configuration for trusted projects. codex mcp add writes
user configuration; running that command after cd does not make it
project-scoped. Install either the plugin's stdio server or the shared HTTP
server configuration for devtools-mcp to avoid duplicate registrations.
To use the skills inside this repo without installing the plugin, generate the
.agents/skills mirror (picked up by clients that follow the agents.md
convention, including Codex):
python skills/sync.py --target agents # writes .agents/skills (gitignored)Run one instance on the machine, and every project and client talks to it over HTTP. The dashboard comes up with it, so the UI is always there:
.\scripts\devtools-service.ps1 start # MCP at http://127.0.0.1:8010/mcp, dashboard at :8765
.\scripts\devtools-service.ps1 status # is it up?
.\scripts\devtools-service.ps1 install # also start automatically at loginThe equivalent manual command is uv run devtools-mcp --transport http --port 8010. Network transports auto-start the dashboard; pass --no-dashboard to opt
out.
If an older Windows login shortcut still starts port 8000, migrate it explicitly before starting the new service. This rewrites the shortcut and stops the old listener so the MCP endpoint and dashboard belong to the same process:
.\scripts\devtools-service.ps1 uninstall
.\scripts\devtools-service.ps1 stop -Port 8000
.\scripts\devtools-service.ps1 install -Port 8010Keep a custom port if needed, but use that same port in each client config.
Both the MCP port and the dashboard bind loopback; add --dashboard-host 0.0.0.0 only if another machine must sync with this tracker replica.
Then point clients at the URL once, at user scope, so all projects get it:
# Claude Code (all projects):
claude mcp add --transport http devtools-mcp http://127.0.0.1:8010/mcp --scope userOr per project, in .mcp.json (Claude Code) and .cursor/mcp.json (Cursor),
both already in this repo:
// .mcp.json (Claude Code)
{ "mcpServers": { "devtools-mcp": { "type": "http", "url": "http://127.0.0.1:8010/mcp" } } }
// .cursor/mcp.json (Cursor)
{ "mcpServers": { "devtools-mcp": { "type": "streamable-http", "url": "http://127.0.0.1:8010/mcp" } } }Why this shape: one process owns the tracker DB and the run workspace, so every
project's profiling runs and tasks land in the same dashboard; restart the
service and Claude Code reconnects automatically (exponential backoff). If the
service is down, the MCP server shows as unavailable, and /mcp in Claude
Code retries it once you've started the service.
uv sync(once).\scripts\devtools-service.ps1 start- Open Cursor, then Settings, then MCP. Project
.cursor/mcp.jsonregistersdevtools-mcpathttp://127.0.0.1:8010/mcp - Reload MCP if the service was started after Cursor opened
- Smoke test: ask the agent to run
devtools_check - Optional login autostart:
.\scripts\devtools-service.ps1 install
Dashboard, which auto-starts with the service: http://127.0.0.1:8765. It has Runs, Search, the Tracker board, and installed-tools status.
The classic spawn-per-session model still works (this is also what the plugin
install above wires up automatically), in .mcp.json:
{
"mcpServers": {
"devtools-mcp": {
"command": "uv",
"args": ["run", "--directory", "C:\\path\\to\\ai-grind", "devtools-mcp"]
}
}
}Then in Claude Code:
> Check what dev tools are installed on this system
> Run memcheck on ./my_binary and show me the top memory leaks
> Start a debug session on ./crash_repro, set a breakpoint at main, and inspect variables
> Profile ./app with VTune hotspots and show me a flame graph
> Plan this feature in the tracker: epic, stories, subtasks with dependencies, then tell me what to do first
The server speaks three transports. Pick one with --transport, or with env vars:
uv run devtools-mcp # stdio (pipes), the default
uv run devtools-mcp --transport http --port 8010 # streamable HTTP at /mcp
uv run devtools-mcp --transport sse --port 8010 # SSE at /ssestdiocarries MCP over stdin and stdout, which is how editor and CLI clients (Claude Code, Cursor) spawn it. Nothing is printed to stdout except the protocol.http(streamable-http) is a long-lived server athttp://<host>:<port>/mcp. Connect multiple clients, run it remotely, or share one instance.sseis the legacy Server-Sent-Events transport at/sse, for older clients.
Host/port/transport can also come from env: DEVTOOLS_MCP_TRANSPORT,
DEVTOOLS_MCP_HOST, DEVTOOLS_MCP_PORT (default 127.0.0.1:8010). To connect an
MCP client to the HTTP server instead of spawning stdio:
{
"mcpServers": {
"devtools-mcp": { "type": "http", "url": "http://127.0.0.1:8010/mcp" }
}
}Any MCP client works: give it either the stdio spawn command or the HTTP URL.
Stdio, in the client's mcpServers config (Windsurf mcp_config.json, Claude
Desktop claude_desktop_config.json, or Cline):
{
"mcpServers": {
"devtools-mcp": {
"command": "uv",
"args": ["run", "--directory", "/path/to/ai-grind", "devtools-mcp"]
}
}
}HTTP, start the shared service first (devtools-service.ps1 start or
devtools-service.sh start), then point the client at
http://127.0.0.1:8010/mcp. Goose, in ~/.config/goose/config.yaml:
extensions:
devtools-mcp:
name: devtools-mcp
type: streamable_http
uri: http://127.0.0.1:8010/mcp
enabled: trueCodex without the plugin, per project in .codex/config.toml (this repo ships
one as an example):
[mcp_servers.devtools-mcp]
url = "http://127.0.0.1:8010/mcp"from devtools_mcp.server import mcp
# The FastMCP server instance is importable for testing
# or embedding in other applicationsClaude Code ←→ MCP Protocol ←→ devtools-mcp server
│
┌───────────────┼───────────────┐
│ │ │
Valgrind LLDB DTrace/perf
│ │ │
Parse output PTY session Parse output
│ │ │
└───────┬───────┘───────────────┘
│
Polars DataFrames
│
┌───────────┼───────────────┐
│ │ │
Filtering Comparison Unified Index
& Sampling (A vs B) (cross-run search)
Key design decisions:
- Normalized interface. Every backend registers a
BackendSpecwithdetect(),run(),df_builders, andformat_summary(). Adding a tool suite means implementing one module, not 10 tools. - Polars DataFrames. Every backend converts its results into a DataFrame for filtering, grouping, correlation, and comparison. The
FilterSpecengine supports regex patterns, thresholds, pagination, sampling (random, stratified, every-nth), and sort overrides. - Unified search index. Every run in a workspace lands in one DataFrame with normalized columns (
function,file,kind,value, and so on), which is what makes cross-tool queries work: "find all functions that both leak memory and are CPU hotspots." - Structured LLDB sessions. A debug session drives a PTY-based interactive process. The parser turns each snapshot (backtrace, variables, breakpoints) into structured models and stores it as a workspace run, so the same analysis tools can query it.
1. devtools_run(suite="valgrind", tool="memcheck", binary="./server")
→ run_id: "abc123"
2. devtools_run(suite="valgrind", tool="callgrind", binary="./server")
→ run_id: "def456"
3. devtools_correlate(run_id_a="abc123", run_id_b="def456", join_on="function")
→ Table showing functions that both leak memory AND are CPU-hot
# Run full test suite (735 tests)
uv run pytest tests/ -v
# Tests cover:
# - All parser suites (valgrind, lldb, dtrace, perf, etw, jvm, cdb)
# - The flame-graph engine (fold -> tree -> SVG/text invariants)
# - Polars analysis and comparison functions
# - Rich filtering and sampling engine
# - MCP server endpoints (via in-memory client/server session)
# - Workspace, registry, index, and formatter internals
# - Cross-run correlation
# - The progress tracker (schema/migrations, hierarchy + close gate,
# tag rules, git-scan against real temp repos, GitHub via MockTransport,
# end-to-end tracker_* tools)Windows, JVM, and CDB backends are tested against synthetic tool output (PerfView CSV, JFR JSON, jstack/jmap text, CDB backtraces) so the suite needs none of the real tools installed.
Factory functions randomize all test data. No hardcoded fixtures.
src/devtools_mcp/
├── server.py # FastMCP server, lifespan, shared helpers
├── models.py # RunBase, shared base for all results
├── registry.py # Backend auto-registration and tool detection
├── workspace.py # Run storage, caching, temp file management
├── index.py # Unified cross-run search index
├── filters.py # Declarative FilterSpec engine
├── formatters/ # Markdown table and summary formatters
├── tools/ # MCP tool definitions
│ ├── batch_tools.py # check, run, list, raw
│ ├── analysis_tools.py # analyze, query, compare
│ ├── search_tools.py # search, correlate
│ ├── flame_tools.py # devtools_flamegraph
│ ├── viz_tools.py # devtools_dashboard
│ ├── tracker_tools.py # tracker_*, the progress tracker (mini-JIRA)
│ └── debug_tools.py # start, debug, inspect, stop
├── tracker/ # Tracker domain layer (SQLite, WAL, migrations)
│ ├── schema.py db.py # versioned DDL + connection/transactions
│ ├── tasks.py # projects, PROJ-123 keys, hierarchy, close gate
│ ├── criteria.py # acceptance criteria <-> tests, gate evaluation
│ ├── tags.py # tags + auto-tag rules engine
│ ├── commits.py # manual links + git-log key scanning
│ ├── issues.py # task -> external issue lifecycle
│ ├── frames.py # Polars views (tasks/tree/rollup/...)
│ └── providers/ # IssueProvider ABC, GitHub REST, GitLab stub
├── viz/ # Browser visualization terminal (stdlib HTTP, dark UI)
├── flamegraph/ # Shared engine: fold -> tree -> SVG + text
│ ├── fold.py # Brendan-Gregg folded-stack I/O
│ ├── tree.py # call tree + per-function exc/inc stats
│ ├── render_text.py # bounded text flame-tree + top table
│ └── render_svg.py # pure-Python interactive SVG (no deps)
├── valgrind/ # Valgrind backend (6 tools)
│ ├── parsers/ # XML and text parsers
│ └── analysis/ # DataFrame builders and comparisons
├── lldb/ # LLDB backend (PTY sessions + parsers)
├── dtrace/ # DTrace backend (3 tools)
├── perf/ # perf backend (3 tools)
├── etw/ # Windows ETW backend (PerfView), CPU hotspots + stacks
├── vtune/ # Intel VTune backend, hotspots/threading/memory/uarch + flame graph
├── jvm/ # JVM backend, JFR, threads, heap, async-profiler
├── cdb/ # Windows debugger backend (batch CDB)
├── py/ # Python backend, py-spy, thread dumps, cProfile
├── node/ # Node/JS backend, V8 --cpu-prof / --heap-prof
├── renderdoc/ # RenderDoc backend, GPU frame capture/analyze/counters/resources/thumb
├── build/ # shared build core, models, JUnit, JS dep/audit parsers, frames
├── maven/ gradle/ # JVM build backends
├── npm/ pnpm/ yarn/ # JS package-manager backends
├── cargo/ # Rust/Cargo backend
└── hotspots.py # shared stacks -> hotspots DataFrame (jvm/py/node)
Build & package tools are normalized too: mvn dependency:tree / npm ls --all
/ cargo tree (thousands of lines) become a queryable dependency frame with
depth + conflict detection; reactor/task results, JUnit tests, and audit
advisories become bounded summaries. The binary argument for these backends is
the project directory (Maven/Gradle prefer a mvnw/gradlew wrapper, else
the global tool).
A backend is one module exposing detect(), run(), df_builders,
format_summary(), and an optional stacks builder (which unlocks flame graphs).
See any of etw/, jvm/, cdb/ for the pattern.
MIT
devtools_instruments_threads and the reusable
devtools_mcp.evidence.instruments_threads.analyze_instruments_thread_policy
read existing xctrace thread-info, ThreadQoSTable and ThreadPriority XML exports.
Supply an explicit PID and half-open trace-relative nanosecond window. The reader
validates UTF-8 XML, per-file ID/ref DAGs, exact column schemas, identities and
nonoverlapping intervals before reporting target-thread coverage. Combined input
byte/row/node caps, fixed depth 64, target-thread and per-summary detail caps are
explicit. Input bytes are not an aggregate resident-memory budget.
Sparse observations return analysis PASS with coverage PARTIAL; uncovered
intervals stay unknown. Raw exporter QoS codes (not OS QoS constants), formatted
labels and mismatch-state text are retained. Bounded pair details include total
and omitted counts/durations; mismatch-code and exporter-state durations include
all observed groups, including omitted details. Gap lists retain total counts.
No capture UUID exists in these exports: caller association and shared PID/TID
cannot authenticate the same execution. The tool performs no process launch,
scheduling changes or artifact writes, and makes no native clock mapping,
off-CPU/latency causal claim or performance qualification (UNVERIFIED).
devtools_instruments_ring and the reusable
devtools_mcp.evidence.instruments_ring.analyze_instruments_ring_arrival validate
native benchmark JSON, ordered arrival CSV, collected RingClockAnchor signpost,
thread-state and thread-info XML. The exact emitted native bracket/payload and
marker-main identity must match. Identical marker projections across distinct
exported table paths are deduplicated; within-table duplicates refuse.
Default assume_constant_offset=False performs all validation but reports clock
join UNVERIFIED and returns no joined event windows. Explicit True enables
CONDITIONAL single-finish-anchor extrapolation, with drift UNMEASURED. Signed
trace-minus-native offset bounds produce possible envelopes and guaranteed
cores for bounded worst due-to-terminal and preceding-measured-terminal gaps;
the first measured event's prior gap remains unavailable. Intersected scheduler
coverage separates missing time from observed raw Unknown state, and preserves
raw state/core/priority labels without inferring QoS or producer roles. Coverage
and state totals precede detail truncation, with explicit omitted counts and
durations. Overlapping envelopes are not additive exact CPU time or causal
latency/off-CPU evidence. Native performance FAIL is retained separately from
analysis PASS; the CSV check has no independent DTrace boundary-stamp proof.
Combined budgets cover all input bytes and rows, XML elements, JSON values and
CSV cells; XML/reference depth64 and native JSON1MiB additionally bound parsing.
Per-event interval/state/gap details are bounded by state_details, event output
by top_n. These are not aggregate resident-memory budgets. Caller-associated
files and a matching marker do not authenticate a capture UUID or establish
clock drift. The analyzer does not launch processes, change policy or write files.