zzop
Built for the AI-agent era: a context window can't hold your codebase, so zzop condenses it — or several codebases at once — into one deterministic JSON map of security findings, structural debt, and cross-layer drift. A multi-language SAST and architecture analysis engine, written in Rust.
npm i -D @zzop/cli # add to your project
npx zzop init # writes an annotated zzop.config.jsonc
npx zzop # analyze and print a report
# Embedding instead? Depend on @zzop/native:
# JSON.parse(zzop.analyze(JSON.stringify({ root: '.' })))
{
"findings": [ /* DSL rules + native analyses */ ],
"crossLayerFindings": [ /* frontend fetch <-> backend route joins */ ],
"scores": { /* structural subscores, 0-100 */ },
"health": { "index": 78 },
"recommendations": [ /* refactor-first candidates, ROI-ordered */ ],
"warnings": [ /* anything this run could not provide */ ]
}
Why zzop
An agent working in a large repository cannot read every file — the context window is its scarcest resource. zzop condenses the whole codebase into one compact, machine-readable report: what is risky, what is dead, what drifted between frontend and backend, and where to refactor first. Agents and humans alike start from the map, not the territory.
That is also where it parts ways with traditional static analyzers. A classic linter or SAST tool hands you a flat list of findings and stops; zzop is built to give direction — every finding carries its fix and its exclude config, structural results are ranked by refactor ROI, and cross-repo joins turn "two codebases quietly disagree" into a first-class, actionable finding.
One engine, three lenses
Security (SAST)
SQL injection, weak hashing, SSRF, hardcoded secrets — DSL rules plus native analyses catch vulnerable patterns across languages.
Structure
Circular dependencies, dead code, risk scores, extraction seams — structural debt, quantified per file and per repository.
Cross-layer
Frontend calls joined to backend routes: unconsumed endpoints, method mismatches, version skew, path drift — even across repositories.
What makes it different
Cross-layer, cross-repo
analyzeTrees accepts multiple roots and joins them: one repo's frontend fetches against another repo's backend routes. Drift between layers becomes a finding, not a production incident.
Customizable in any environment
Rules are data, not engine code. Write your own packs in a JSON DSL, extend or override the bundled ones with packsDir, and feed languages the engine does not parse natively through the Normalized AST adapter protocol.
Deterministic, honest output
Same input, byte-identical output — no timestamps, no unstable ordering. A capability a run cannot provide is absent and self-reported in warnings, never stubbed. An empty array always means analyzed, found nothing.
Zero config, actionable findings
{ root: "." } is the entire required configuration. Every finding reports three things: the problem, the fix, and the exact exclude config to suppress it deliberately.
Ready to run it?
Zero config to start, full config reference when you need it.