Skip to content

Releases: MohammadMD1383/jdx

v1.6.1

Choose a tag to compare

@github-actions github-actions released this 12 Oct 10:05
eac000b

jdx v1.6.1 — usages --kind throw says what it means

Patch release: no behaviour change, no output shape change, no exit code
change. jdx usages --kind throw has always read throws declarations
(declaration-level — "who declares this type"), never ATHROW throw sites
("who actually throws it"). Attributing a throw site needs data-flow analysis
the reference extractor deliberately skips. Until now that took source-diving
to learn; now the help text, the MCP tool description, and the docs say it
everywhere (closes
#7).

Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash

Windows users who prefer an Explorer-first bundle: download
jdx-1.6.1-windows.zip from the assets below and run
sh install-release.sh --tarball jdx-1.6.1-windows.zip.
Release installs self-update with jdx upgrade.

Releases are unsigned: expect a first-run warning — Unblock-File on
Windows (PowerShell), xattr -d com.apple.quarantine <install-dir>/jdx on
macOS.

What changed

throw = throws declarations, not ATHROW sites — stated explicitly in:

  • jdx usages --help and the --kind flag help (UsagesCommand)
  • the MCP usages tool kind parameter description
  • docs/COMMANDS.md (command table), docs/PROPOSAL.md (§7.3),
    docs/guide/navigating-code.md (kind table + Limitations bullet)
  • KDoc on JdxService (UsageKindFilter, T-075 comment,
    collectMetadataUsages), ReferenceExtractor (ATHROW intentionally not
    extracted), RpcDispatch (usages kind contract)

The follow-up commit also fixes the guide naming a nonexistent jdx flow:
throw-site attribution is deferred to future work without naming a command
that does not exist yet.

Known limitations

  • usages --kind throw remains declaration-level: methods whose throws
    clause declares the type. Throw-site (ATHROW) attribution needs data-flow
    analysis and is deferred to future work.
  • Same standing notes as
    v1.6.0: two
    setup backends still missing (Windsurf
    #38, Aider
    #41); version-probe
    caveats
    #84 /
    #85; MULTI_RELEASE_VARIANT
    provenance
    #29.

Testing

  • Existing UsagesServiceTest.kind throw shows throws declarations already
    pins the semantics; UsagesServiceTest (34 passed), UsagesCommandTest
    (6 passed), lint clean (per PR #93).
  • Release gate: full ./gradlew check -Pci plus per-OS distribution builds
    and launcher smoke tests, all green.

PRs in this release

PR What
#93 Clarify throw as throws declarations (closes #7)

Full Changelog: v1.6.0...v1.6.1

v1.6.0

Choose a tag to compare

@github-actions github-actions released this 08 Oct 13:58
c57c8b8

jdx v1.6.0 — docs website live, Antigravity setup, sturdier body

Minor release: the documentation website is live (generated from the docs and
the real CLI, with gates that stop it drifting), jdx setup grew its eighth
agent backend (Antigravity, verified against a real install), and jdx body
no longer gives up on one bad Vineflower token. No query command changes
behaviour, no output shape changes, and no command exit code changes — if you
script against jdx, nothing here needs re-learning.

Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash

Windows users who prefer an Explorer-first bundle: download
jdx-1.6.0-windows.zip from the assets below and run
sh install-release.sh --tarball jdx-1.6.0-windows.zip.
Release installs self-update with jdx upgrade.

Releases are unsigned: expect a first-run warning — Unblock-File on
Windows (PowerShell), xattr -d com.apple.quarantine <install-dir>/jdx on
macOS.

Docs website is live

The GitHub Pages site (mohammadmd1383.github.io/jdx)
is generated from the repository's Markdown files and the freshly built jdx
binary by a new build-time-only :site Gradle module — never shipped in the
product distribution.

What keeps it honest, on every PR:

  • Exec blocks: console exec code blocks in the docs are executed against
    the built binary and their real output is embedded — if an exit code or an
    output line changes, the build fails instead of publishing stale docs.
  • Doc lint: every jdx <cmd> --flag mention in the docs is validated
    against the real CLI's --help, so a renamed flag breaks the build, not a
    user's copy-paste.
  • Link checker: all internal links and anchors must resolve.
  • Autogenerated reference: the command reference pages are rendered from
    actual help output, and every HTML page gets a Markdown twin for
    language-model consumption.

Riding along in the same PR: layered user guides and FAQ, the green accent
colour, release-note heading nesting without skipped levels, and Lighthouse as
a warn-only gate that never blocks deploy.

jdx setup --agent antigravity

The eighth backend, verified against the real agy 1.3.0 binary (installed via
the official installer — not just the docs):

--agent Project scope System scope Version probe
antigravity .agents/mcp_config.json, walk-up ~/.gemini/config/mcp_config.json agy --version

The entry is the minimal shape agy mcp list recognises:

{ "mcpServers": { "jdx": { "command": "jdx", "args": ["mcp"] } } }
$ jdx setup --agent antigravity --scope project
antigravity project setup installed (mcpServers entry)
  /home/you/project/.agents/mcp_config.json
next: restart antigravity (config loads once at startup)

Real-binary findings: system scope writes the user-level file only (all of
agy mcp add/list/enable/disable/remove operate there); agy mcp
never writes the project file, so jdx hand-edits it in the same mcpServers
shape. Bidirectional interop was proven with the built jdx both ways —
jdx-written files pass agy mcp list, agy-written bytes pass jdx --check
(exit 0, byte-identical no-op) — and jdx --remove leaves agy mcp list
empty. Same merge discipline as the other seven backends: unrelated servers
survive, re-runs are byte-exact no-ops, --check reports without writing
(exit 1 when absent).

Fix: jdx body survives one bad Vineflower token (#87)

body / source --around locate members by parsing decompiled text with
JavaParser. A single rejected token — Vineflower emitting import foo.1; /
this.1 for synthetic $1 siblings (Found ".1" expected "." ";") — failed
the whole file, so a member that members lists and javap proves was
reported not found, while source served the same file fine unparsed.

The fix adds a lenient seam (parseJavaUnitLenient / loadJavaUnitLenient /
findJavaBodiesLenient): the offending Found "..." token is blanked to
spaces — line breaks and lengths preserved, so ranges still address the
original lines and slices stay verbatim — and parsing retried, up to 10x.
Paired -sources.jar input stays strict (still an honest ParseError /
exit 5). If even lenient parsing fails, the error now says the member exists
in bytecode and names the file view that still works
(jdx source 'TYPE' --engine vineflower) alongside the --engine javap
hatch. Exit codes are unchanged, so the default-ladder retry still works.

Test infrastructure: categories, budgets, progress

Also in this release: tests are categorised, per-test time budgets are
enforced (test stays a fast unit loop), and the runner logs live progress —
so a slow or hung suite points at the culprit instead of a silent minute.

Known limitations

  • Two agents from the umbrella are still missing: Windsurf
    (#38) and Aider
    (#41), tracked under
    #32. Antigravity (#34)
    closed with this release.
  • Version detection is a best-effort probe: the raw probe output is
    not collapsed to one line, so a chatty binary spills a multi-line,
    absolute-path-bearing WARNING across the detected: line in text mode
    (#84; the --json
    envelope escapes it correctly and is fine); and the agent-config root comes
    from the JVM's user.home, which on Linux comes from getpwuid() and
    therefore ignores $HOME
    (#85).
  • Attribution still covers reader warnings; the MULTI_RELEASE_VARIANT
    provenance line does not say which variant was selected
    (#29).

Testing

  • Website: the pages workflow builds and validates the site on the PR;
    lint, :site:test and :site:buildSite green.
  • Antigravity: 20 new SetupServiceTest cases (incl. a real-agy-bytes
    golden pin and generative fixed-point families), 9 new SetupCommandTest
    cases, 3 new byte-pinned goldens, 3 new doctor tests — all with fake homes;
    545 unit tests, full integration suite, lint, :site:test,
    :site:buildSite green.
  • Lenient slicing: 8 new JavaBodiesLenientTest tests (incl. a property
    test: injected bad imports still slice via lenient over generated members),
    plus BodyServiceTest (repairable forced-vineflower succeeds exit 0 with
    vineflower provenance; unrecoverable names the file view + javap in text
    and JSON) and SourceServiceTest (--around with one bad token succeeds);
    :sources:integrationTest 140 pass, :index:check 651 pass, :cli:check
    green.

PRs in this release

PR What
#89 GitHub Pages site generated from the docs and the real CLI
#90 jdx body survives a bad Vineflower token
#91 Antigravity setup support, agy-verified

Full Changelog: v1.5.0...v1.6.0

v1.5.0

Choose a tag to compare

@github-actions github-actions released this 28 Sep 12:11

jdx v1.5.0 — jdx setup: every agent you actually use

Minor release: jdx setup grew from one agent backend to seven,
jdx doctor gained an AppCDS row, and three defects were fixed — two of which
could make jdx report the wrong thing, and one of which had a quality gate
silently not running at all. No query command changes behaviour, no output
shape changes, and no command exit code changes — if you script against jdx,
nothing here needs re-learning. jdx setup --agent opencode behaves exactly as
it did in v1.4.0, byte for byte.

In v1.4.0 you could paste an opencode.json snippet by hand and jdx would
write it correctly. The obvious follow-up question was the one everybody
actually has: which config file does my agent read? This release answers
it for the seven agents the project verified against real installs, writes the
entry in that agent's own shape, and reports which line it found — rather than
guessing a shape and hoping.

--agent Project scope System scope Version probe
opencode (v1.4.0) opencode.json/opencode.jsonc, walk-up ~/.config/opencode opencode --version → v1/v2 line
claude-code (alias claude) .mcp.json, walk-up ~/.claude.json claude --version
cursor .cursor/mcp.json, walk-up ~/.cursor/mcp.json cursor-agent --version (legacy agent shim fallback)
kilo .kilo/kilo.json[c], else kilo.json[c], walk-up (fresh default .kilo/kilo.json) ~/.config/kilo/kilo.json[c] none (Kilo has no classified line)
cline ~/.cline/data/settings/cline_mcp_settings.json same single global file none
codex .codex/config.toml, walk-up $CODEX_HOME/config.toml, else ~/.codex/config.toml codex --version
copilot .mcp.json, walk-up, .github/mcp.json fallback $COPILOT_HOME/mcp-config.json, else ~/.copilot/mcp-config.json copilot --version

Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash

Windows users who prefer an Explorer-first bundle: download
jdx-1.5.0-windows.zip from the assets below and run
sh install-release.sh --tarball jdx-1.5.0-windows.zip.
Release installs self-update with jdx upgrade.

Releases are unsigned: expect a first-run warning — Unblock-File on
Windows (PowerShell), xattr -d com.apple.quarantine <install-dir>/jdx on
macOS.

jdx setup --agent claude-code

The shape is the one claude mcp add jdx -- jdx mcp would write, so an agent
that already knows the entry recognises it:

$ jdx setup --agent claude-code --scope project
claude-code project setup installed (mcpServers entry)
  /home/you/project/.mcp.json
  detected: claude-code (2.1.283 (Claude Code))
next: restart claude-code (config loads once at startup)
{ "mcpServers": { "jdx": { "command": "jdx", "args": ["mcp"] } } }

Project scope walks up for an existing .mcp.json and writes one in the
checkout root if there is none; system scope targets ~/.claude.json.

jdx setup --agent cursor

Same mcpServers.jdx shape — Cursor reads it from .cursor/mcp.json (project)
or ~/.cursor/mcp.json (system), verified against
cursor-agent 2026.09.26-dd393fe via agent mcp list.

jdx setup --agent kilo

Kilo Code is an OpenCode fork sharing the mcp map shape, so it gets the
single mcp.jdx entry (not OpenCode's v1+v2 pair — there is no second line
to cover). Fresh kilo.json[c] files carry no $schema. Project scope walks
up, preferring the .kilo/ variant at each level — within one directory the
order is .kilo/kilo.jsonc, .kilo/kilo.json, kilo.jsonc, kilo.json — and
a checkout with no Kilo config gets a fresh .kilo/kilo.json.

Kilo Code and Cline are the two backends that report no detected: line:
neither has a config-line version to classify, and jdx does not invent one.

jdx setup --agent cline

Cline's entry is nested one level deeper — it is a transport object:

{ "mcpServers": { "jdx": { "transport": { "type": "stdio", "command": "jdx", "args": ["mcp"] } } } }

Verified against the real install (cline CLI 3.0.65 + extension 4.1.21:
resolveMcpSettingsPath; cline/cline#2418 open): Cline keeps no
project-level MCP file
, so --scope project and --scope system name the
same global ~/.cline/data/settings/cline_mcp_settings.json. That is the
verified layout, not a fallback, so the output says so out loud rather than
letting a project-scoped run look checkout-local:

$ jdx setup --agent cline --scope project
cline project setup installed (global file — Cline keeps no project-level MCP config)
  /home/you/.cline/data/settings/cline_mcp_settings.json
next: restart Cline (config loads once at startup)

The probe also accepts the legacy flat command/args shape, so --check and
doctor report a Cline entry written by an older jdx.

jdx setup --agent codex

Codex CLI config is TOML, so the entry is a [mcp_servers.jdx] table and the
merge is line-based rather than a JSON tree rewrite — every other key in
config.toml survives as written:

[mcp_servers.jdx]
command = "jdx"
args = ["mcp"]

Verified against codex-cli 0.157.1. Project scope targets
.codex/config.toml (Codex only reads it in a trusted checkout); system scope
honours $CODEX_HOME.

jdx setup --agent copilot

The GitHub Copilot CLI entry is mcpServers.jdx with "type": "local", in
.mcp.json (project — the nearest one walking up, .github/mcp.json as
fallback, trusted directories) or $COPILOT_HOME/mcp-config.json, else
~/.copilot/mcp-config.json. Verified against GitHub Copilot CLI 1.0.88. A
hand-written "type": "stdio" entry probes as installed; jdx writes local.

Every backend still merges, never clobbers

Unchanged from v1.4.0 and now true for all seven: unrelated servers, models,
providers and every unrelated top-level key survive; only the jdx entry is
added, replaced, or removed. Re-runs are byte-exact no-ops, so comments and
formatting survive. --check reports without writing (exit 1 when absent),
--remove uninstalls cleanly, and exit codes are unchanged: 0
installed/removed/present, 1 checked-absent, 3 bad usage, 5
unreadable config.

One backend-specific fix worth calling out: isJdxCommand accepts Windows
shims (jdx.exe, jdx.cmd, jdx.bat) and backslash paths on every host,
so --check and the doctor row stay truthful on Windows instead of reporting
"not installed" for an entry jdx itself wrote.

jdx doctor grew an appcds row

The AppCDS archive (libs/jdx.jsa, trained per-OS by :app:installDist) was
invisible to doctor — the one thing users asked about when cold start felt
slow. It is now a stat-only row (size + mtime against the fat jar, no JVM
probe, no network):

State Reported
present ok — path and size
absent warn — cold start without AppCDS; rebuild with ./gradlew :app:installDist
empty (0 bytes) warn — corrupt or truncated; rebuild
older than the fat jar warn — stale; rebuild
unresolvable (dev layout, no jar) warn — ship with :app:installDist

Absent or stale is a warn, never a fail: the archive only affects speed, and
-Xshare:auto degrades to a plain run. Only the generic probe wrapper turning a
thrown exception into fail can make this row fail.

install.sh now also prints a one-line note when it installs from a build with
no archive, pointing at this row rather than failing.

The setup row was widened in the same change: it now reports wiring for all
seven
backends, on both scopes, with each backend's version classification.

Fix: a stale fat jar can no longer report the wrong version (#68)

A versioned -PjdxVersion build on top of a default one left both
jdx-0.1.0-SNAPSHOT-all.jar and jdx-1.3.0-all.jar in app/build/libs. The
launcher globbed and took the first, so jdx version could report the wrong
version — and jdx upgrade, which decides what to do from that version, was
misled with it. Fixed on both sides:

  • the app build evicts any superseded jdx-*-all.jar after writing its own,
    so libs holds exactly one;
  • the launcher treats more than one fat jar as an ambiguous install and
    fails loudly, naming every jar it found, instead of picking one:
$ jdx version
jdx: multiple jdx fat jars in '/opt/jdx/libs': '/opt/jdx/libs/jdx-1.4.0-all.jar' '/opt/jdx/libs/jdx-1.5.0-all.jar' — rebuild to leave exactly one: ./gradlew :app:clean :app:installDist

Fix: a stale AppCDS archive is now silent (#54, #22)

The archive is keyed to the exact JDK build, so on a different vendor or
update the JVM rejects it and logged a [warning][cds] version-mismatch notice.
Output and exit codes were always correct — but the notice lands on stdout,
where it corrupts piped and --json output, and on stderr it reads like a
crash for a command that then works fine. Every launcher now passes
-Xlog:cds=off (after -Xshare:auto, before -XX:SharedArchiveFile), so the
degradation is silent: exit 0, correct output, empty stderr, just without the
archive speedup. Pinned end-to-end by a launcher test that feeds a garbage
archive and still requires a clean jdx version.

CI: the core mutation gate can run again (#66)

The core ≥ 80 % mutation gate has never once run in CI. PIT aborts in its
pre-scan — before generating a single mutant, with no score produced at all —
when the fixture corpus is missing, and on a clean runner it always was. Two
independent causes: a PIT minion is a fresh JVM that inherits neither the
test/tier2Test system properties nor a dependsOn on :testfixtures:build,
so it found no corpus. This is worse than no gate: a gate that cannot run
looks exactly like a passing one.

Fixed by wiring both corpus properties (jdx.fixturesDir, `jdx.diff...

Read more

v1.4.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 14:35
405cd2d

jdx v1.4.0 — jdx setup: jdx in your agent, in one command

Minor release: one new command, one exit-code fix that closes a hole in the
contract, one diff provenance fix. No query command changes behaviour and no
output shape changes — if you script against jdx, the only thing to re-learn is
that a mistyped command now fails instead of exiting 0.

Two things an agent could not do before: wire jdx into its own editor's
config
(paste a JSON snippet from the README in the right shape for the right
OpenCode line, and get mcp.servers vs mcp right), and trust that a usage
error is an error
— jdx nosuchcommand exited 0, so a typo looked like a
successful run, in every release up to v1.3.0.

Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash

Windows users who prefer an Explorer-first bundle: download
jdx-1.4.0-windows.zip from the assets below and run
sh install-release.sh --tarball jdx-1.4.0-windows.zip.
Release installs self-update with jdx upgrade.

Releases are unsigned: expect a first-run warning — Unblock-File on
Windows (PowerShell), xattr -d com.apple.quarantine <install-dir>/jdx on
macOS.

jdx setup --agent opencode

One command wires jdx into OpenCode, in the checkout or for the whole machine:

$ jdx setup --agent opencode --scope project
opencode project setup installed (v1+v2 entries)
  /home/you/project/opencode.json
  detected: opencode v2 (opencode v2.0.18)
next: restart opencode (config loads once at startup)

Both OpenCode lines get wired, always, because they disagree about the shape
of an MCP entry and only one of them is what a given install actually reads:

  • v1 (opencode 1.x): servers sit directly under the top-level mcp
    object keyed by name, each
    { "type": "local", "command": ["jdx","mcp"], "enabled": true }.
  • v2 (0.0.0-next-* / 0.0.0-beta-* / 0.0.0-dev-* / 2.x): servers sit
    under mcp.servers, and enabled is replaced by its inverse disabled.

So setup writes both entries and reports the line it detected rather than
guessing which one your install reads:

{
  "$schema": "https://opencode.ai/config.json",
  "mcp": {
    "jdx": { "type": "local", "command": ["jdx", "mcp"], "enabled": true },
    "servers": {
      "jdx": { "type": "local", "command": ["jdx", "mcp"], "disabled": false }
    }
  }
}

It merges; it never clobbers. Every other server in mcp and in
mcp.servers, every model/provider/theme key, and every unrelated top-level key
survives — only mcp.jdx and mcp.servers.jdx are added, replaced, or removed.
The file is read leniently (JSONC comments and trailing commas, which is what a
real opencode.jsonc carries), and a second run is a byte-exact no-op, so
your comments and formatting survive a re-run:

$ jdx setup --agent opencode --scope project
opencode project setup already installed (no changes)
  /home/you/project/opencode.json
  detected: opencode v2 (opencode v2.0.18)

A v1 server entry literally named servers cannot share the key with the v2
namespace, so the foreign entry wins untouched and only the v1 jdx entry is
written. Merge discipline beats completeness.

Flags and exit codes

Flag Meaning
--agent <name> Only opencode so far; parsed case- and separator-insensitively (OpenCode, open-code). Other backends follow the same seam — see the limitations.
--scope project|system project (default): the nearest opencode.json[c] walking up from the checkout, else a fresh opencode.json there. system: the existing ~/.config/opencode/opencode.jsonc (what a real install carries), else a fresh opencode.json in ~/.config/opencode.
--check Report without writing anything. Exit 1 when absent, 0 when present — a CI step can assert wiring without changing it.
--remove Uninstall: deletes both jdx entries, drops an emptied mcp.servers and then an emptied mcp, keeps every other server. Idempotent.
--json The same information in the standard envelope.

Exits: 0 installed / removed / checked-present, 1 checked-absent, 3 bad
usage, 5 an unreadable or corrupt config (the file is named in the message and
is never rewritten).

$ jdx setup --agent opencode --scope project --check
opencode project setup not installed
  /home/you/project/opencode.json
  detected: opencode v2 (opencode v2.0.18)
next: jdx setup --agent opencode --scope project
$ echo $?
1

$ jdx setup --agent cursor
usage error: unsupported --agent 'cursor' (only --agent opencode so far)
$ echo $?
3

Restart the agent afterwards — it reads its config once at startup.

jdx doctor grew a setup row

jdx doctor now answers "is jdx wired into my agent, and which line is reading
it?" without you having to ask:

$ jdx doctor
jdx doctor
  jdk: ok (21 (/usr/lib/jvm/java-21-openjdk))
  …
  setup: ok (opencode v2 (opencode v2.0.18): installed (/home/you/project/opencode.json); no system config (/home/you/.config/opencode/opencode.json))

The row is ok when either scope is wired and warn when neither is — setup is
optional, so it never fails — and it names the file it looked at plus the exact
command to fix it. The version probe is the same pure seam setup uses, so
doctor and setup can never disagree about which line is installed.

Fix: a usage error is now an error (exit 3)

This closed the largest hole in the exit-code contract. Clikt reports a usage
error by handing a status code to its own Context.exitProcess seam, and
clikt-core's default seam is a no-op (the real one lives in the mordant flavor
jdx excludes), so every one of these printed a perfectly correct message and
exited 0:

$ jdx members ; echo $?
Usage: jdx members [<options>] <ref>

Error: missing argument <ref>
0                                  # v1.3.0 and earlier — v1.4.0 prints 3

$ jdx nosuchcommand ; echo $?
Usage: jdx [<options>] <command> [<args>]...

Error: no such subcommand nosuchcommand
3

$ jdx members java.lang.String --nope ; echo $?
Usage: jdx members [<options>] <ref>

Error: no such option --nope. (Possible options: --no-daemon, --no-jdk, --no-color, --repo)
3

They now exit 3 — the code AGENTS.md §6 has reserved for usage errors since
v1.0.0 — while --help and --version still exit 0 and every service-level
code is untouched (3 for a bad flag value, 5 for an unreadable artifact, …). The
mapping is 1 → 3, installed once on the root Context so every subcommand
inherits it; nothing about the message format changed.

If a script of yours ran jdx … and checked only for 0, a typo in it now
stops the pipeline instead of passing silently. That is the point.

Fix: a diff warning now names the artifact it came from (#64)

Found by dogfooding jdx diff on the gson 2.11.0 → 2.14.0 upgrade. gson's
nest-marker classes have binary names that start with an empty $ segment
(com/google/gson/internal/$Gson$Types.class), which the reader cannot model, so
it degrades. With two inputs the warning could not say which side produced it —
and "was this class dropped from the old jar, or is it new in the new one" is
exactly the question a diff exists to answer:

$ jdx diff ~/.gradle/caches/…/gson-2.11.0.jar ~/.gradle/caches/…/gson-2.14.0.jar
api diff gson-2.11.0.jar -> gson-2.14.0.jar (public surface)
…
warning MULTI_RELEASE_VARIANT: in gson-2.11.0.jar: multi-release gson-2.11.0.jar serves version-specific variants for this JDK (21)
warning UNNAMEABLE_CLASS: in gson-2.11.0.jar: unnameable class name (empty name segment in [, Gson, Types]): com/google/gson/internal/$Gson$Types.class

That single word — in gson-2.11.0.jar: — immediately disproved the assumption
the report used to hide behind: those classes are in the old jar, not the
new one. Both sides' warnings are now attributed before they are unioned, and
the consequences are deliberate in both directions:

  • the same unreadable class in two differently-named jars is two facts, so
    it now reports once per side instead of collapsing into one anonymous line;
  • a self-diff (jdx diff a.jar a.jar) still prints one line, because the two
    attributed messages are then identical.

The reader's own warning text is unchanged — it is shared by every command and
pinned in other goldens. Only diff's union copies it, so no other command's
output moved a byte.

Known limitations

  • Only OpenCode is wired. jdx setup --agent cursor|copilot|cline|windsurf| kilo-code|aider|codex exits 3 naming what is supported. The eight follow-ups
    are filed and tracked (#36–#42);
    each is one enum entry plus a merge rule behind the same seam.
  • The v1 MCP entry is deprecated upstream and slated for removal by
    OpenCode. jdx keeps writing both entries until v1 support is dropped from jdx
    — a deliberate, temporary compatibility cost, not an oversight.
  • jdx setup is not served by the daemon and has no MCP tool or HTTP
    route: it writes to your config, not to a workspace, and there is nothing for a
    remote adapter to answer. It is a local maintenance command like jdx upgrade
    and jdx cache.
  • Version detection is a best-effort probe of opencode on PATH (falling back
    to the legacy opencode2 shim only when no opencode exists). A binary that
    answers with something unparseable reports opencode version unknown (<raw output>) so a human can judge — jdx never guesses a line.
  • Attribution now covers reader warnings; the MULTI_RELEASE_VARIANT provenance
    line still does not say which variant was selected, only which side produced
    the warning.
  • Unchanged since v1.2.x and still shipped: the per-OS libs/jdx.jsa logs
    shared archive file version … does not match noise on a JDK build ...
Read more

v1.3.0

Choose a tag to compare

@github-actions github-actions released this 27 Sep 11:40

jdx v1.3.0 — jdx diff: public-API diff with binary-compat warnings

Minor release: one new command, and nothing that already shipped changes
behaviour. jdx diff answers the question a dependency upgrade poses — what
changed, and can I still compile and link against it?
— with every difference
named by a rule code an agent can filter on, most breaking first.

A real upgrade, on the surface a consumer compiles against — this one is clean, so
the CI gate passes and the answer is that you can take the upgrade:

$ jdx diff gson-2.11.0.jar gson-2.14.0.jar --fail-on breaking
api diff gson-2.11.0.jar -> gson-2.14.0.jar (public surface)
info  TYPE_ADDED  com.google.gson.internal.GsonTypes
info  TYPE_ADDED  com.google.gson.internal.bind.JsonElementTypeAdapter
info  MEMBER_ADDED  com.google.gson.FieldNamingStrategy#alternateNames(java.lang.reflect.Field)
…
0 breaking · 0 suspicious · 17 informational · 2 types added · 0 types removed
fail-on breaking: no breaking changes (exit 0)

The same two jars on the all surface, where the internal churn a consumer
never sees is also reported — 118 breaking findings, which is the point of the
flag for refactors and mod/mixin work:

$ jdx diff gson-2.11.0.jar gson-2.14.0.jar --visibility all --severity breaking
api diff gson-2.11.0.jar -> gson-2.14.0.jar (all surface)
breaking  TYPE_REMOVED  com.google.gson.Gson$1
breaking  TYPE_REMOVED  com.google.gson.Gson$2
…
118 breaking · 11 suspicious · 101 informational · 32 types added · 29 types removed

Both sides may be a jar, a class directory, a glob, or a Maven coordinate
(--fetch to download; ~/.gradle/caches and ~/.m2 are used without it). No
workspace is needed and no index is built — a diff names its own two artifacts.
Warm, two real jars compare in 230–250 ms.

Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash

Windows users who prefer an Explorer-first bundle: download
jdx-1.3.0-windows.zip from the assets below and run
sh install-release.sh --tarball jdx-1.3.0-windows.zip.
Release installs self-update with jdx upgrade.

Releases are unsigned: expect a first-run warning — Unblock-File on
Windows (PowerShell), xattr -d com.apple.quarantine <install-dir>/jdx on
macOS.

What jdx diff reports

47 rules at three severities (16 breaking, 11 suspicious, 20
informational). The rule id is the CompatRule enum name, so a caller filters
on a code and never on prose — MEMBER_REMOVED, RETURN_TYPE_CHANGED,
STATIC_TO_INSTANCE, ENUM_CONSTANT_REMOVED, KOTLIN_SUSPEND_CHANGED, and so
on. Full set in docs/PROPOSAL.md §7.7.

A member's identity is its name plus the erased parameter list, so a
covariant return is one RETURN_TYPE_CHANGED rather than a removal plus an
addition, and a field's type is part of its identity. Two pairing passes keep
the report free of remove/add avalanches: shape (name + arity) first, then
Kotlin name and Kotlin arity for JVM renames and suspend changes. An
ambiguous bucket is never paired
— with two removals of one shape, guessing
which addition replaced which is worse than reporting both.

The inheritance check refuses to guess. A member gone from its type but
declared by one of the type's new supertypes is MEMBER_MOVED_TO_SUPERTYPE,
not a removal — the caller's invokevirtual still resolves through the chain.
When the walk reaches a supertype the artifact does not hold, absence proves
nothing, so the removal stays MEMBER_REMOVED and the detail names the
supertype it could not read. A diff never downgrades a breaking change on a
maybe. The universal roots (java.lang.Object and friends) are excluded from
that walk, because counting java.lang.Object as "missing from the artifact"
put an "inheritance not checked" caveat on every removal in every jar.

Structure comes from bytecode alone — no sources jar, no decompilation, no
classpath — because a diff has no "flesh" to recover.

Exit codes — one documented exception

A comparison that ran exits 0 whatever it found. The answer is in the
report, not the process status, because "no differences" and "differences that
do not matter to you" are the same outcome and only you can tell them apart.
--fail-on breaking (or any) is the CI gate and the only way a successful
diff exits non-zero:

jdx diff old.jar new.jar --fail-on breaking   # exit 1 when a breaking change is present

The verdict is also in the envelope as result.gate, so a consumer that cannot
read exit codes still gets the answer. This is the single documented exception
to the exit-code table in AGENTS.md §6 / PROPOSAL.md §16; everything else is
unchanged (3 for a bad spec or flag, 5 for an unreadable artifact).

Flags

Flag Meaning
--visibility public|all public (default) is the surface a consumer compiles against; all compares every declared member, for refactors and mod/mixin work.
--severity all|suspicious|breaking Cumulative: suspicious = breaking + suspicious. The per-severity tally always describes the whole comparison, never only what was printed.
--fail-on none|breaking|any Turn the report into a process status.
--limit <int> Maximum findings shown (default 200); the tail this cuts is by construction the least severe.
--include-synthetic Compare bridge/synthetic members too, hidden by default.
--fetch, --repo Maven coordinate resolution, as on every other command.

All four interfaces

CLI, MCP (jdx_diff), the daemon wire, and HTTP (GET /v1/diff?query=<old>&new=<new>)
— the same JdxService.diff call, verified byte-identical between the CLI and
HTTP adapters.

jdx diff is deliberately not forwarded to the daemon, and there is
deliberately no --no-daemon flag: a daemon warm on one workspace's
classpath holds nothing a two-artifact comparison can reuse, and a --fail-on
verdict has no way back over the v1 wire. A flag that does nothing is a lie, so
it is absent rather than inert — jdx --no-daemon diff a b parses,
jdx diff --no-daemon a b does not. The daemon still serves diff.

Fixes that shipped with it

Found by the new test families, not by inspection — each is a case where the
report was confidently wrong:

  • A spurious caveat on every removal. MEMBER_REMOVED carried "supertype
    java.lang.Object is not in new.jar; inheritance not checked" on every
    removal in every jar, because every class extends java.lang.Object. The
    universal roots are now skipped in the inheritance walk, and a private removal
    skips the walk entirely (JLS §8.2).
  • SUPERTYPE_REMOVED java.lang.Object. A class that becomes an interface, or
    that now names a real superclass, drops java.lang.Object from its header —
    reported as a lost supertype, on a change already reported as
    TYPE_KIND_CHANGED or SUPERTYPE_ADDED.
  • FIELD_TYPE_CHANGED printed its ref twice. A field's canonical ref carries
    no type, so the detail read now: pkg.Type#field on both sides of the arrow.
    It now reads int -> long, which is the whole news.
  • ANNOTATION_VALUES_CHANGED kotlin.Metadata on every Kotlin edit. The
    compiler's own bookkeeping changed value on every declaration touched, so
    every Kotlin diff carried a line that is always true, burying the real
    findings. The internal annotations are now excluded; a JetBrains @Nullable is
    still compared, because that one is real declared API.
  • Every degradation warning was printed twice on a self-diff.
  • macOS and Windows could not build the new test fixtures. The Kotlin plugin
    contributes a default src/diffV1/kotlin for source set diffV1; an added
    src/diffv1/kotlin is a different path on Linux and the same directory on a
    case-insensitive filesystem, so every fixture reached the compiler twice
    (CLASSIFIER_REDECLARATION). Fixed with setSrcDirs and source-set-cased
    directory names.
  • Windows could not run the glob tests. * is not a legal character in a
    Windows path, so four tests that built a glob argument with
    Path.resolve("*.jar") threw before their first assertion. A spec is matched,
    never opened — a glob is a perfectly good argument.

Known limitations

  • A public member that is no longer declared but was an override of a
    universal root's method (toString, equals, …) still links through that
    root, and is reported as MEMBER_REMOVED. Resolving it needs the roots' member
    names, which is a table, i.e. a guess — so the case stays conservative and
    documented on the rule.
  • A degradation warning does not yet say which of the two artifacts produced it
    (#64); a multi-release jar
    trips MULTI_RELEASE_VARIANT for the side that carries it.
  • VARARGS_CHANGED is only reachable from hand-modified bytecode: javac gives
    f(String) and f(String...) different descriptors, so that change is
    reported as PARAMETER_TYPE_CHANGED. Pinned by unit tests and listed as
    unreachable-from-javac in the coverage test, so the gap is deliberate rather
    than invisible.
  • Unrelated to diff, and pre-existing in every release so far: a Clikt-level
    usage error — a missing argument, an unknown command, an unknown flag — prints
    the right message but exits 0
    (#65). Service-level exits
    are correct.
  • The core mutation gate cannot currently run in CI
    (#66): the PIT pre-scan
    needs the fixture corpus, which three modules do not hand to the minion JVM, so
    PIT aborts before mutating anything on a clean checkout. Pre-existing since at
    least v1.0.0.

Testing

206 tests and 22 goldens for this feature alone: 73 per-rule examples and 12
property-based families in core (self-identity, total order,...

Read more

v1.2.1

Choose a tag to compare

@github-actions github-actions released this 26 Sep 19:25

jdx v1.2.1 — Windows correctness + full CI on all three OSes

Patch release: no new commands, no output-shape changes on Linux. Windows
users get working --jars globs, Kotlin KDoc from CRLF checkouts, and JDK
sources; everyone gets the CI suite proving it — full check (tests + lint

  • coverage gates) green on Linux, macOS and Windows on every push.

Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash

Windows users who prefer an Explorer-first bundle: download
jdx-1.2.1-windows.zip from the assets below and run
sh install-release.sh --tarball jdx-1.2.1-windows.zip.
Release installs self-update with jdx upgrade.

Releases are unsigned: expect a first-run warning — Unblock-File on
Windows (PowerShell), xattr -d com.apple.quarantine <install-dir>/jdx on
macOS.

What's changed since v1.2.0

  • JDK sources resolve (body/source/doc over java.*): JDK 9+
    src.zip nests files under the module directory
    (java.base/java/util/ArrayList.java), which the source mapping missed —
    every JDK query errored instead of serving sources on JDKs that ship it.
  • Windows --jars globs work: *?[{ are illegal path characters on
    Windows, so expansion threw InvalidPathException instead of matching;
    glob roots now split with string math, mismatches exit 5, hits sort
    deterministically on every filesystem.
  • Kotlin KDoc from CRLF checkouts: the PSI doc association misses every
    KDoc under CRLF text while bodies still slice — CRLF sources (every
    Windows text=auto checkout) silently lost all Kotlin KDoc. Sources now
    read LF-pinned, so parse offsets and served text agree on all platforms.
  • Windows output spellings: source-scan mention paths join with /
    (one spelling for agents), directory listings sort by name string
    (Windows orders case-insensitively), fault JSON names JSON-escaped paths.
  • jdx doctor Kotlin row probes the resolved cache root, honouring
    JDX_CACHE_DIR instead of the ambient home.
  • Full CI/CD suite (#55–#61): Linux/macOS/Windows check legs with
    JUnit + coverage uploads, coverage gates riding check, nightly
    soak/bench and weekly mutation tiers, workflow hardening (concurrency,
    minimal permissions, timeouts) and a release check-gate. CI mode (-Pci)
    keeps slow-runner timing report-only instead of red.

Full Changelog: v1.2.0...v1.2.1

v1.2.0

Choose a tag to compare

@github-actions github-actions released this 26 Sep 11:56

jdx v1.2.0 — full multiplatform support

Every OS gets a native bundle in this release: jdx-<version>-<os>.tar.gz and
jdx-<version>-<os>.zip (<os> = linux, macos, windows), each with a
.sha256 checksum and a jdx.jsa AppCDS archive trained on that OS
(the archive only engages when your JDK build matches the CI JDK —
otherwise it logs [cds] noise and runs normally; see #54). The bare
jdx-<version>.tar.gz/.zip aliases are the Linux assets, kept so the installer
and jdx upgrade default URLs keep working.

Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash

Windows users who prefer an Explorer-first bundle: download
jdx-1.2.0-windows.zip from the assets below and run
sh install-release.sh --tarball jdx-1.2.0-windows.zip.
Release installs self-update with jdx upgrade.

Releases are unsigned: expect a first-run warning — Unblock-File on
Windows (PowerShell), xattr -d com.apple.quarantine <install-dir>/jdx on
macOS.

What's changed since v1.1.0

  • Windows launcher (jdx.bat + jdx.ps1): JDK discovery via %JAVA_HOME%,
    where java.exe, registry and %ProgramFiles% probes; same flags and exit-6
    contract as the POSIX launcher (#46).
  • Per-OS directories: cache/config/daemon-socket/install roots follow the
    platform table (Linux XDG, macOS ~/Library, Windows LOCALAPPDATA/APPDATA);
    old dot-dir locations migrate automatically; a missing XDG_RUNTIME_DIR falls
    back instead of exiting 3 (#44, #45).
  • Daemon starts on Windows/macOS with portable detach, sun_path length
    guard, start mutual exclusion, and pid-validated stop (#49, #50).
  • POSIX hardening for macOS: no GNU-only flags in the launcher or
    installers; JDK via /usr/libexec/java_home (#47).
  • Tool + filesystem correctness on Windows: .exe probes, drive/UNC-safe
    globs, case-insensitive .jar, USERPROFILE fallback (#51).
  • Per-OS release matrix with zip bundles, per-leg checksums, and per-OS
    jdx.jsa (#48).
  • README rework (hook/demo/install/quickstart, command catalogue moved to
    docs/COMMANDS.md) plus reproducible benchmark proof.

Full Changelog: v1.1.0...v1.2.0

v1.1.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 11:13

What's new: jdx upgrade self-update

Release installs can now self-update from GitHub Releases — no reinstall script
needed:

jdx upgrade --check        # report only, never touches the install
jdx upgrade                # update to the latest release
jdx upgrade --version v1.1.0 --json   # pinned version, machine-readable

The updater resolves the target tag, verifies the SHA-256 checksum, and swaps
the release tree with a backup-restore guard. Source builds (no .jdx-release
marker) are refused with exit 3 — they were never release installs.

One-time note for v1.0.0 installs

v1.0.0 predates the .jdx-release marker, so jdx upgrade on a v1.0.0 install
reports "not a release install". Reinstall once with the script below; from
v1.1.0 on, self-update works:

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash

Full Changelog: v1.0.0...v1.1.0

v1.0.0

Choose a tag to compare

@github-actions github-actions released this 24 Sep 10:45

jdx v1.0.0 — initial release

jdx is an IDE for AI agents, as a command line tool. It answers the questions
agents working on JVM codebases ask dozens of times per task — what members a
class has, what a method does, who calls it, what implements an interface —
directly from .jar, -sources.jar, class/source directories, Maven
coordinates, and the JDK itself. Every command returns the smallest correct
answer with text + --json output and stable exit codes.

Read: declaration, members, structure

  • jdx show, members, outline, signature, doc, body, source
  • Structure from bytecode (ASM, never class-loaded), flesh from sources;
    no sources jar → Vineflower decompile (labelled ⚠ reconstructed) →
    javap fallback. Kotlin signatures from @Metadata; Kotlin bodies/KDoc via
    the jdx kotlin install sidecar.

Search and browse

  • jdx search (incl. camel-hump + fuzzy), resolve, ls, tree

Graph: usages, hierarchy, calls, samples

  • jdx usages (call/read/write/ref/new/throw/annotation), hierarchy,
    implementors, callers, calls, samples — live over bytecode roots,
    plus --src textual scan of project source dirs

Environment, maintenance, serving

  • jdx version, doctor, ws (named workspaces + Gradle/Maven
    auto-discovery), cache, kotlin install, help --agent, bench
  • jdx daemon (warm JVM), mcp (20 typed tools), serve (HTTP/JSON), batch
    (NDJSON) — all byte-identical to --json

Install

curl -fsSL https://raw.githubusercontent.com/MohammadMD1383/jdx/main/install-release.sh | bash
jdx members java.util.HashMap --limit 5

Requires JDK 21+. Verify with jdx version / jdx doctor.

Full Changelog: https://github.com/MohammadMD1383/jdx/commits/v1.0.0