Skip to content

About

Distributions of maddi: CLI, build systems, IDE integrations

Resources

Code of conduct

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Latest commit

 

History

284 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

maddi-dist

What users install from maddi: the command-line distributions, the Gradle and Maven plugins, and the IDE plugins with the analysis daemon behind them. maddi's README explains what maddi computes; this one explains how to run it.

The analyzer needs JDK 25 or later to run. The Kotlin distribution needs JDK 26 or later.

Command line

Two self-contained zips on the GitHub release. Unpack and run: every jar is in lib/, and the launcher carries the JVM flags the analyzer needs.

Zip Launcher Analyses
maddi-<version>.zip (maddi-cli) bin/maddi Java
maddi-kotlin-<version>.zip (maddi-cli-kotlin) bin/maddi-kotlin Java and Kotlin

bin/maddi-kotlin takes the same command line as bin/maddi and does strictly more. On a project with no .kt file, it runs the Java analyzer and behaves identically. Take the small zip for a Java-only build: the Kotlin one carries about 85 MB of K2 compiler jars. Kotlin support ships only this way, because it depends on JetBrains artifacts that are not on Maven Central.

An option the mixed Java/Kotlin pipeline cannot honour yet is refused by name, with exit code 7, never run as a no-op. Writing analysis results, incremental analysis and the hints compiler are among those options. --translate is one too: a project with Kotlin sources has no single Java program to translate. --skip-kotlin-sources analyses only the Java part of a mixed project, and accepts an incomplete result.

To point the CLI at a project, capture what the build compiled and hand it over. No build-tool integration is needed:

./gradlew :your-module:compileJava --debug 2>&1 | grep 'Compiler arguments:' > build.log
maddi --compile-log build.log --analysis-steps modification --analysis-results-dir out

--compile-log also reads Maven and Ant logs. The analysis loads the JDK and library results that ship with maddi; --preload-analysis-results-dirs none turns that off.

Translation

The same command line translates the project's main sources:

maddi --compile-log build.log --translate kotlin --translate-output kotlin-src
maddi --compile-log build.log --translate csharp --translate-output csharp-src   # experimental; with a Translated.csproj

The modification analysis and the nullability pass run first, and their verdicts decide T or T?, where !! (or !) is needed, and which collections are read-only. --analysis-steps none translates without them. translation-report.txt in the output lists the printer's crashes and messages; a crash leaves that one file out and exits non-zero.

Gradle plugin

io.codelaser.maddi.analyzer on the Gradle Plugin Portal:

plugins {
    java
    id("io.codelaser.maddi.analyzer") version "0.9.5"
}

maddi {
    analysisSteps = "modification"   // parse -> prep -> modification -> write results
}
./gradlew maddi-analyzer     # one result .json per package, under build/maddi

The plugin takes the source sets, the classpath, the language level and --enable-preview from the Gradle model, and forks a worker JVM with the flags the analyzer needs. Its options are in maddi-gradleplugin/README.md.

Maven plugin

io.codelaser:maddi-mvnplugin on Maven Central, goal prefix maddi, five goals: run, write-input-configuration, statistics, write-analysis-hints and compile-analysis-hints. The JVM that runs Maven needs javac's --add-exports flags for the analysis goals. Set them once in .mvn/jvm.config or MAVEN_OPTS; the user manual lists them.

IDE plugins (build from source)

The IntelliJ, Eclipse and VS Code plugins are not published yet. Each shows maddi's computed annotations in the editor. All three talk to the same analysis daemon (maddi-ide-daemon), which runs out of process, so the IDE can stay on any JVM.

IDE Module Build Notes
IntelliJ IDEA maddi-intellij ./gradlew :maddi-intellij:buildPlugin, or runIde to try it Java and Kotlin. The Kotlin front end is downloaded on demand from the release and checked against its SHA-256.
Eclipse 2025-09+ maddi-eclipse task eclipse:build (a p2 update site) maddi-eclipse/README.md
VS Code maddi-vscode task vscode:package (a .vsix) Needs redhat.java. maddi-vscode/README.md

task --list shows the other tasks, and task doctor checks the toolchain.

maddi-intellij-j2k is not a plugin. It is a headless starter that runs IntelliJ's own J2K converter on a corpus, so that maddi's Kotlin translation can be compared with it.

Coding agents (MCP)

maddi-mcp is an MCP server on stdin/stdout, for a coding agent to configure a project, analyse all or part of it, and ask for the verdicts. It drives the same daemon as the IDE plugins, bundled in its daemon/ directory. It runs on a JDK 25 or later, as the daemon does: by default the daemon uses the same one, or the one --jdk names. On macOS and Linux, brew install codelaser/tap/maddi-mcp installs it with its JDK (MADDI_JDK_HOME names another, Temurin for example), and claude mcp add maddi -- $(brew --prefix)/bin/maddi-mcp registers it with Claude Code. Or download maddi-mcp-<version>.zip from the GitHub release, unzip it, and register its bin/maddi-mcp with the agent: claude mcp add maddi -- <dir>/maddi-mcp-<version>/bin/maddi-mcp. From source: ./gradlew :maddi-mcp:installDist, then maddi-mcp/build/install/maddi-mcp/bin/maddi-mcp.

Tool Does
configure_project, list_projects a Gradle build by its directory alone; or an inputConfiguration.json (the Gradle plugin's task maddi-write-input-configuration, the Maven plugin's goal maddi:write-input-configuration, the CLI's --write-input-configuration); or directories and a class path
analyze, run_status, stop_run a run in the background: a large project takes minutes. sourceSets and packages analyse part of the project
overview a project, package or type in one answer: types by immutability level, modifying methods, modified fields and parameters, findings by category, per package
verdicts, explain, findings the annotations of types and members, the properties behind them, why they hold, the findings with their why-chains
element_at the element at a file and line, explained
diff_runs what changed between two runs: analyse, edit, analyse again, compare
library_annotations what the last run knows of JDK and library types

A Gradle build needs no maddi plugin: given its directory, the server compiles every Java source set with javac's arguments logged (an init script, run with the build's own wrapper) and has the daemon turn that log into the configuration, as the CLI's --compile-log does, with the build files hashed at that moment. The first analyze does this (or analyze with configureOnly); it takes a full compile.

A configured project is stored (in ~/.maddi/mcp, or --store), with the hashes of the build files that shaped it, and survives a restart. Every run first hashes those files again. When one changed, the configuration is computed again before the run: with the plugin's task or goal, or with the command given at configuration (a CLI-written file needs one). Computing it for a large build takes minutes; this way it happens only when the build changed. A project configured from directories has no such command: a change to its Gradle or Maven files fails the run, unless analyze is told allowStale.

A project can name the JDK its daemon runs on (configure_project with project and daemonJdk, a JDK 25 or later), when the server's does not suit it: an annotation processor such as Lombok runs inside the daemon's javac, and an older one fails on a newer JDK, leaving its source sets parsed without the members it generates. run_status says so when it happened.

A run that finished, or was stopped with values, is stored too, the last 8 per project: after a restart its verdicts can still be asked and compared with diff_runs. A stored run says when it was analysed and how many source files changed since.

Every answer carries the run's certainty. Only FINAL values are certified; PROVISIONAL (a run going on or stopped) and BEST_AVAILABLE values hold, but may be weaker than the code allows.

explain says why a verdict holds, as a chain of statements (whys), the element's own first, then into what it calls: why a method, field or parameter is @Modified, why something is @Dependent or @Nullable, why a method has @StaticSideEffects. For a type, why it stops where it does on the way to @Immutable (@Mutable, @FinalFields, @Immutable(hc=true)): the first rule it fails, at the member that fails it. And why a type is not @Container (marked "holds": false): one step per parameter that is modified, at the statement that modifies it. The IDEs show that last one only for a type at most 2 parameters away from @Container; the server gives it for every type.

The server runs until the agent closes its standard input, and its daemon exits with it. After an installDist, reconnect the agent to the server (/mcp in Claude Code) to run the new build. Options, each also an environment variable:

Option Environment Default
--jdk MADDI_JDK_HOME the JDK the server runs on; the daemon needs 25 or later
--daemon-install MADDI_DAEMON_INSTALL the bundled daemon/ directory
--xmx-mb MADDI_DAEMON_XMX_MB the daemon's own default heap
--log MADDI_DAEMON_LOG maddi-mcp-daemon-<pid>.log in the temporary directory
--store MADDI_MCP_STORE ~/.maddi/mcp

How the server is built on the daemon, and what comes next (asking what an edit would do to the verdicts, and whether it would break a contract, before making it): docs/roadmap/mcp-server.md.

Building

Check out the three repositories side by side, as described in maddi's README:

./gradlew build
./gradlew :maddi-cli:installDist           # the `maddi` CLI, in maddi-cli/build/install/maddi
./gradlew :maddi-ide-daemon:installDist    # the daemon the IDE plugins bundle
./gradlew :maddi-mcp:installDist           # the MCP server, with that daemon in daemon/

release-cli.sh <tag> builds both zips and publishes them, with their checksums, on the GitHub release for that tag.

dogfood/ is a standalone build that applies the Gradle plugin to maddi's own CST. It is the proving ground for eventual immutability; see dogfood/README.md.

Three repositories

Repository Holds Depends on
maddi the base: CST, front ends, printers, the AnalysisEngine interface nothing above it
maddi-mod the analyses the base only
maddi-dist what users install compiles against the base only; carries maddi-mod at run time

Every module here reaches the analyses through io.codelaser.maddi.analysis.api.AnalysisEngine. maddi-mod modules may appear only in runtimeOnly configurations, or shadeRuntime in the plugins. The maddi-tier-guard plugin, in maddi's build-logic, fails the build when a compile class path resolves one.

Licence: LGPL-3.0-or-later (COPYING, COPYING.LESSER), as maddi.

About

Distributions of maddi: CLI, build systems, IDE integrations

Resources

Code of conduct

Security policy

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages