zzop ( Zero Zone Of Pain )

zzop is built for an AI agent working in one repo β say the frontend β that needs to verify or
understand the other side of a contract (the backend) without reading it whole; a human reviewing the
same cross-repo change is the identical use case. Its core move is a cross-repo join: it parses each
repo into a language-neutral IR, exact-matches frontend fetch calls against backend routes across the
repo boundary, and names near-misses (a typo'd path segment, a version drift, a method mismatch) instead
of leaving you to diff two codebases by hand β cutting the read/context cost of confirming the other
side actually agrees. Alongside that cross-layer join it also runs a SAST-style layered rule system
(native whole-graph analyses + declarative JSON rule packs) over each repo individually, returning
structural findings, dependency/dead-code analysis, and health scores as one JSON document.
Every run is deterministic: same code in, same findings out β byte-stable output you can diff between
runs. That determinism is what makes zzop usable as a CI gate β fail a PR on contract drift by reading
the JSON severity counts β and as a substrate an agent can re-run and diff without chasing flaky
rechecks.
Quick start
zzop's primary distribution is two Node-free binaries β zzop (CLI) and zzop-mcp (MCP server) β no
Node.js, no npm, nothing to compile. Get them one of four ways:
- Download the binaries. Grab the
zzop-cli-<platform>[.exe] (CLI) and/or zzop-mcp-<platform>[.exe]
(MCP server) assets for your platform from GitHub Releases
and run them directly, or put them on PATH.
- Claude Code plugin.
/plugin marketplace add eezz4/zzop, then /plugin install zzop@zzop β
see Use in Claude Code below.
- Claude Desktop. One-click
.mcpb bundle (drag-and-drop install) β see
packages/mcpb/README.md.
- npm.
npm i -g @zzop/cli installs the exact same zzop binary above, fetched for your platform
as an npm dependency β same subcommands (analyze/cross/endpoint/manifest/diff/contract/
explain/validate-*),
same output, no Node runtime involved beyond a tiny launcher script and no separate JS implementation
that could drift from the native binary. Convenient when a project already manages its toolchain
through npm. See packages/cli/README.md.
Write a zzop.config.jsonc and run it, ESLint-style:
zzop analyze .
zzop cross --config zzop.config.jsonc
To track contract drift over time rather than at one instant, commit a structural manifest and diff
a later run against it β the same shape a lint baseline file has, kept by you, not by zzop:
zzop manifest ./api ./web > contracts.json
zzop diff contracts.json contracts.new.json
The manifest is deliberately uncapped and carries no file or line, so a pure refactor diffs empty while
a route leaving the join cannot hide above a summary's caps. diff refuses two manifests from different
zzop builds unless you pass --allow-tool-drift (which then discloses the drift), and tags a removal
attributable to a source that lost coverage as blindnessSuspect rather than calling it a deletion.
See crates/host/README.md for the full CLI and config reference.
To embed the engine instead of running the binary, depend on the zzop-facade/zzop-summary Rust
crates and call the JSON-in/JSON-out contract directly β or shell out to zzop-mcp's JSON subcommands,
no linkage required:
let report: serde_json::Value =
serde_json::from_str(&zzop_facade::analyze_json(r#"{"root":"."}"#)?)?;
Use in Claude Code (MCP plugin)
zzop-mcp is a self-contained binary with an MCP server built in:
/plugin marketplace add eezz4/zzop β then /plugin install zzop@zzop (two separate steps).
- Start a new session. The plugin downloads the binary for your platform on first run; nothing goes
on
PATH. That first session does not list the zzop tools yet β the tool list is settled
before the download finishes β so restart Claude Code once and they appear (the hook says so on
stdout too). Once installed, a newer release is reported to you, never installed behind your back.
See crates/host/README.md for the full install/build reference.
Result (abridged)
Every finding carries a rule id, severity, and a file:line location, e.g.:
{
"findings": [
{
"ruleId": "sql/nplus1",
"severity": "warning",
"file": "src/routes/orders.ts",
"line": 42,
"message": "await on a store/ORM call (`Repository`/`Store`/`prisma`/`db`/`orm`/`tx`/`trx`) verified structurally inside a for/for-of/for-in/while/do-while statement or an array-iteration callback β checked against the parser's projected loop spans, not merely co-occurring with loop syntax somewhere in the same function β N+1 query pattern. Batch the fetch (e.g. `findMany` with an `in` filter) instead of one call per item. Suppress a vetted case with `// nplus1-ok`."
}
],
"scores": { },
"health": { "pain": 12.4, "contributors": [ ] },
"recommendations": [ ],
"warnings": [ ]
}
analyzeTrees (multi-tree) additionally returns crossLayerFindings β frontend fetch <-> backend
route joins β which has no single-tree equivalent.
Supported languages
| Language | Support |
|---|
TypeScript / JavaScript (.ts, .tsx, .js, .jsx, .mjs, .cjs, .mts, .cts) | Native, full AST (swc): symbols, imports, calls, HTTP routes/egress, db-table consumes from ORM accessors AND from raw SQL statement strings |
Python (.py, .pyi) | Native, full AST (ruff, Python 3 β Python-2-only syntax falls back to lexical): symbols, imports, FastAPI route provides, requests/httpx consumes (module-level calls plus Session/Client/AsyncClient instances) β v1 scope |
Rust (.rs) | Native, full AST (syn 2): symbols, imports/mod tree (incl. same-workspace crate resolution), axum route provides, reqwest consumes β v1 scope |
Go (.go) | Native, full CST (tree-sitter-go 0.25): symbols, imports/dep graph (go.mod module resolution, package-directory-wide edges), gin + net/http route provides (cross-file mount composition β a function-parameter router mounted from another file's call site β incl. Go 1.22 "METHOD /path" mux syntax), net/http literal egress consumes (package free functions plus bound http.Client values) β v1 scope |
Java (.java) | Native, full CST (tree-sitter-java 0.23.5, Java 21 grammar): symbols (incl. nested types, dot-qualified method names, real visibility), imports/dep graph ((package, type)-indexed resolution, glob package-directory-wide edges), Spring MVC route provides (cross-file extends-chain + constant-prefix resolution) β v1 scope |
C# (.cs) | Native, full CST (tree-sitter-c-sharp 0.23.5): symbols (incl. nested types, dot-qualified method names, public visibility), imports/dep graph (namespaceβfiles index, using package-directory-wide edges), ASP.NET Core route provides (attribute controllers with [Route("api/[controller]")] + [HttpGet]/β¦ composition, plus same-file Minimal-API app.MapGet/MapGroup), HttpClient literal egress consumes β v1 scope |
Prisma schema (.prisma) | Native, lexical schema: models/fields (structural + usage-aware schema rules) + db-table provides joining the client-side consumes |
SQL (.sql) | Native, lexical: CREATE TABLE β db-table provides (migration files light up the db-table channel for MyBatis/JDBC-style stacks). The crate also owns the channel's consume-side statement reader, which other parsers call on the SQL strings they hold |
| Anything else (Ruby, JSP, ...) | Lexical fallback in-tree (line count + line-scan rules only), or first-class support via an external parser adapter conforming to the Normalized AST protocol |
Full precision-tier breakdown β exactly what each native parser extracts, Python's v1 scope note, and
each parser's fingerprint β in docs/ARCHITECTURE.md. (Those
fingerprints are not in zzop version's output, which prints the bare release number; the surface that
carries them is zzop manifest's tool field.)
A normal-sized file whose extension has no native parser also self-reports in the output's warnings
β naming the extension, a file count, and a path sample β instead of vanishing silently; point it at an
adapter (overlays: [...] in zzop.config.jsonc) if that language matters for the analysis.
Versioning & stability
zzop is pre-1.0 (0.x) and unstable β any release may change behavior, output, rules, or
defaults without notice, so pin an exact version (not a ^/~ range) and re-test before upgrading.
Semantic Versioning and a maintained changelog begin at 1.0.0. Full policy:
VERSIONING.md.
Layout
crates/core β engine library: Common IR, cross-layer linker, graph analyses, call graph, rule
DSL interpreter (line/method/symbol/io matchers), unified rule registry + gating
crates/metrics β score channels consumed by engine: roi/health/criticality/coupling/
seams/recommendations/diagnostics
crates/engine β fused execution pipeline: language dispatch (TS/Prisma/Python/Rust/Go/Java/C#/SQL) β rayon
per-file parse + per-file rules β AST drop β whole-graph passes; graceful degrade, cache
consumption, git/scores integration, multi-tree cross-layer join, rule profiling
crates/git β git history collection (single git log --numstat pass β per-file stats +
per-commit sets)
crates/cache β per-file IR/findings cache (content hash + parser fingerprint + ruleset
fingerprint)
crates/facade β pure-JSON analyze/analyzeTrees/analyzeEnvelope/validateEnvelopeOnly/validateRulePackOnly/queryIo/version contract
that crates/host calls directly β no Node, no native addon in between
crates/config β shared Rust config front end (zzop.config.jsonc discovery β JSONC strip β
configβfacade-request mapper β trees: "auto" workspace expansion), used by crates/host
crates/host (zzop-host) β lib-only shared dispatch + embedded contract docs consumed by the two
Node-free host products: zzop (CLI subcommands, package packages/cli-bin) and zzop-mcp (MCP
stdio server, package packages/mcp), both built on zzop-config + zzop-summary
parser/ β parser frontends: source β Common IR, including HTTP route/consume extraction across
languages and frameworks (parser/README.md)
rules/native/ β whole-graph native rules (rules-graph, rules-http, rules-cross-layer, rules-schema) plus rules/dsl/
declarative JSON rule packs (rules/README.md)
Development
Contributing? Start with CONTRIBUTING.md.
Build & test
cargo test --workspace
cargo clippy --workspace --all-targets # kept at 0 warnings
cargo fmt --all
See crates/host/README.md for building/running the zzop/zzop-mcp binaries
(cargo build -p zzop-cli-bin -p zzop-mcp --release).
Cold/warm benchmark over a real tree:
cargo run --release -p zzop-engine --example bench -- <root> --packs rules/dsl --cache <dir> --git
Other crates/engine/examples/ ad hoc harnesses: cross_layer_rule_counts (per-cross-layer/*-rule
finding counts across 1+ tree roots; set ZZOP_DUMP_MESSAGES=<n> to print sample messages),
dep_graph_export (exports the file-level dependency graph as Graphviz DOT or Mermaid), and
fastapi_overlay_adapter (reference external adapter β a lexical FastAPI/Python router scanner feeding
EngineConfig::adapter_overlays, Mode B; now the reference for what native Python v1 deliberately skips
β non-literal prefixes, Flask/Django, custom conventions β since native FastAPI extraction covers the
common literal shapes directly; also reachable via the adapterOverlays config field; see
docs/NORMALIZED_AST.md's "Adapter overlays" section).
Run the English-only source guard (OSS-facing files must be English; Korean is confined to the internal
notes directory, which is gitignored and not part of this repo's published contents):
bash scripts/check-english-source.sh
License
MIT β see LICENSE.