Architecture-as-code enforcement for the JVM — for CI, humans, and AI agents. candor reads JVM bytecode (Java, Kotlin, Scala, Groovy) and knows which functions reach the network, filesystem, a database, a subprocess — transitively, across packages — then turns invariants like "the domain layer does no I/O" into a policy that fails the build when an edit breaks them. The same spec runs as full engines in Rust, TypeScript and Swift, kept in agreement by an automated four-way conformance suite — so one mental model and one policy file work across your whole stack. candor-java is the reference implementation; the others are first-class, conformance-checked engines.
Site: candor.poly.io — the measured case in five minutes: the exhibits, the pre-registered evals, and the prove-it-on-your-own-repo path.
| repo | what |
|---|---|
| candor-java | the reference implementation — the JVM engine (Java, Kotlin, Scala, Groovy — reads bytecode): scanning, the policy gate, queries — jbang candor@tombaldwin/candor-java |
| candor-spec | the specification (report format, semantics, policy DSL, conformance suite) — implementable from its text alone |
| candor-rust | the Rust engine (cargo install candor-scan); a conformance engine — and where the eval harness lives |
| candor-ts | the TypeScript engine (on npm: npx -y candor-ts); a conformance engine |
| candor-swift | the Swift engine (SwiftSyntax); a conformance engine |
| candor-agents | effect analysis for agent fleets (declared-vs-observed drift), gated by the unmodified candor tools — exploratory |
AI agent? Install candor as an MCP server — npx -y candor-ts --mcp, or candor mcp install to write
the .mcp.json for you. It is published to the MCP Registry as
io.github.tombaldwin/candor, so clients can discover it without being handed a script; the registration
instructions come from the package you installed (candor mcp --help), not from a URL. Once an engine is
installed, --agents prints its contract version-matched to your build. AGENTS.md is the
written route for when you want to read rather than install.
Sceptical? Good. Each implementation ships a PROVE-IT self-experiment your own agent runs on your own repo (Rust · JVM · TypeScript) — manual blast-radius trace committed before the tool runs, every claimed miss verified at a file:line, and the negative outcome reported plainly if candor doesn't help on your codebase. The implementations also hold their own gates: each analyzes itself in CI under a declared policy (spec §7.12).
Code silently crosses architectural boundaries — a network call inside a domain layer, a DB query in a
UI handler. Reviews (human or LLM) catch this probabilistically; candor catches it
deterministically: a curated classifier at the I/O boundary, transitive propagation to a fixpoint,
an explicit Unknown wherever resolution fails (it discloses what it can't resolve rather than
guessing — held to it by adversarial fuzzers in CI), and a policy gate over the result. Purity is
undecidable, so this is a best-effort gate that catches far more than review and tells you where it
couldn't see — not a completeness proof; the gaps it's still closing are tracked in the open. For
agents it also answers the pre-edit question — "if I add an effect here, what's the blast radius and
does it break policy?" — in one query (measured on the Rust engine:
~1.8× faster than
tracing by hand at equal completeness).
Install the umbrella; it manages the analysis engines for you.
brew install tombaldwin/tap/candor # the one thing you install
candor update # fetch the engines — the JVM engine as a
# native binary (no JVM); ts runs via npxFirst result, in any repo:
cd your-project
candor scan . # analyse (engine picked by the manifest)
candor tour # the most surprising transitive reaches
candor where Db # which functions reach the databaseManage it:
candor engines # what's installed, and where each engine comes from
candor outdated # is a newer version available? (explicit check)
candor update rust # fetch one engine (no args = all of them)
candor init # stand up the effect gate for every language present
candor doctor # verify installs + spec agreementLatest version: brew upgrade candor && candor update. The umbrella pins the whole family to one
version, so every engine stays in spec agreement (candor doctor confirms it) — you move them together,
never one at a time. An optional, off-by-default notice (candor config update-check on) tells you when a
newer version is out; it only ever queries the public package registries (github.com · crates.io · npm) —
the analysis itself makes no network calls.
Every engine drives a query the same way (candor-spec §3.3.1): the report is discovered from a .candor/
ancestor, so candor where Net, candor path <fn> Net, and candor scan . are one command regardless of
language. bin/candor is a thin dispatcher that routes a query to the engine whose backend
the discovered report declares, and a scan to the engine whose manifest the target holds
(Cargo.toml/package.json/Gradle/Package.swift). It owns the bare candor name so the four language
engines — which keep their qualified names candor-query, candor-ts-query, candor-java, candor-swift —
don't collide on PATH. A polyglot project or a missing engine is a loud error, never a silent wrong-engine
run. Put bin/ on your PATH; the routing is covered by bin/candor.test.sh.
One command, no annotations or source changes (adopt/candor-init.sh). To get
it, clone this repo (or copy adopt/ plus integrations/github/candor-sarif — the script needs its
sibling files and exits 2 without them). Prerequisites: a JRE 17+, python3, and curl (Node.js
instead for the TypeScript engine, and for the fingerprint tool):
git clone https://github.com/tombaldwin/candor # (once, anywhere)
cd your-repo
mvn -q compile # (or ./gradlew classes) — candor reads bytecode
/path/to/candor/adopt/candor-init.shIt scans your compiled classes with the exact engine release the dropped workflow pins, proposes a
starter policy from what your code already does (every proposed rule currently passes — review it, keep
what matches your intent), records the regression-ratchet baseline, and drops the GitHub Action that
fails the build (pointing at the exact method) when an architecture rule is broken. It writes five
artifacts — arch.policy, .candor/baseline.json, .candor/config, .candor/candor-sarif (the
vendored SARIF reporter), and .github/workflows/candor.yml — commit all of them: the regression
ratchet bites only when the baseline is committed. Prefer to assemble it by hand?
adopt/ also has the pieces as a copy-paste starter — the annotated
policy template and the workflow. See it running end-to-end on
real Spring, Kotlin, and Quarkus apps in the case studies.
For coding agents, integrations/claude-code/ closes the loop at edit time: a
Claude Code Stop hook scans the agent's result, diffs the effects against a baseline, and hands back any
newly-introduced effect (with its transitive blast radius) or policy violation so the agent fixes it before
yielding to you — the deterministic counterpart to "the model will probably notice."
The reports are also served live, one npx away, by two servers in the candor-ts npm package —
candor-mcp (an MCP server: blast radius, whatif, the gate verdict, containment, blindspots — for
any MCP-speaking agent) and candor-lsp (a language server: per-function effect CodeLens, provenance
hover, the live gate verdict as diagnostics — helix/neovim natively, JetBrains via
integrations/jetbrains/). Both read any engine's report (JVM, Rust,
TypeScript, Swift), so one server covers the whole stack:
npx -y -p candor-ts candor-mcp · npx -y -p candor-ts candor-lsp.
fingerprint/ turns any engine's report into a fixed-size, text-free, deterministic
abstract mark of a project's effect profile — the effect mix as a colour nebula, effect-propagation
edges as weighted neon threads, and code structure as order-vs-chaos. Same report → same image. It also
emits the DNA as JSON — a single 0–100 structure descriptor (order vs chaos) plus its components
(effect smear, Unknown opacity, call-graph tangle, cycles). These are structural descriptors, not a
quality grade: candor deliberately doesn't score a codebase (spec §6.1). Works identically across all
engines. See fingerprint/README.md.
TESTING.md — the family-wide test standards every candor repo holds: the two-layer
doctrine (unit for pure logic, process for contracts), the non-negotiable pins (every documented gate
surface tested in-repo, every fail-closed path negative-tested, anti-fabrication twins, emission-path
tests), byte-identity-gated refactors, and the coverage policy (no percentage gate; the
zero-coverage-gate list stays empty).
Licensed under either of MIT or Apache-2.0, at your option.
