# Changelog

Upgrade with `jdx upgrade`. Unreleased changes since v1.6.1: https://github.com/MohammadMD1383/jdx/compare/v1.6.1...main

## v1.6.1 (2026-10-12)

## 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](https://github.com/MohammadMD1383/jdx/issues/7)).

### Install

```bash
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](https://github.com/MohammadMD1383/jdx/releases/tag/v1.6.0): two
  setup backends still missing (Windsurf
  [#38](https://github.com/MohammadMD1383/jdx/issues/38), Aider
  [#41](https://github.com/MohammadMD1383/jdx/issues/41)); version-probe
  caveats
  [#84](https://github.com/MohammadMD1383/jdx/issues/84) /
  [#85](https://github.com/MohammadMD1383/jdx/issues/85); `MULTI_RELEASE_VARIANT`
  provenance
  [#29](https://github.com/MohammadMD1383/jdx/issues/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](https://github.com/MohammadMD1383/jdx/pull/93) | Clarify throw as throws declarations (closes #7) |

**Full Changelog**: https://github.com/MohammadMD1383/jdx/compare/v1.6.0...v1.6.1

## v1.6.0 (2026-10-08)

## 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

```bash
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](https://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:

```json
{ "mcpServers": { "jdx": { "command": "jdx", "args": ["mcp"] } } }
```

```console
$ 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](https://github.com/MohammadMD1383/jdx/issues/38)) and Aider
  ([#41](https://github.com/MohammadMD1383/jdx/issues/41)), tracked under
  [#32](https://github.com/MohammadMD1383/jdx/issues/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](https://github.com/MohammadMD1383/jdx/issues/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](https://github.com/MohammadMD1383/jdx/issues/85)).
- Attribution still covers reader warnings; the `MULTI_RELEASE_VARIANT`
  *provenance* line does not say which variant was selected
  ([#29](https://github.com/MohammadMD1383/jdx/issues/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](https://github.com/MohammadMD1383/jdx/pull/89) | GitHub Pages site generated from the docs and the real CLI |
| [#90](https://github.com/MohammadMD1383/jdx/pull/90) | `jdx body` survives a bad Vineflower token |
| [#91](https://github.com/MohammadMD1383/jdx/pull/91) | Antigravity setup support, agy-verified |

**Full Changelog**: https://github.com/MohammadMD1383/jdx/compare/v1.5.0...v1.6.0

## v1.5.0 (2026-09-28)

## 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

```bash
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:

```console
$ 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)
```

```json
{ "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:

```json
{ "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:

```console
$ 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:

```toml
[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:

```console
$ 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.diffFixturesDir`,
named once so they cannot drift from the fixture constants) and all four fixture
jars into every PIT module, and — the part that stops it recurring — by
`verifyPitestWiring`, a cheap assertion **in every `check`** that fails if a PIT
module loses either property or a jar dependency. A wiring regression now reads
as one clear red line in seconds instead of four confusing PIT aborts twenty
minutes in.

The same lesson is now written down where it will be read next: a new rule in
`AGENTS.md` §7 and `CONTRIBUTING.md` (any precondition a gate needs gets its own
assertion in `check`), plus `docs/TESTING.md` §10.1 and a new
"ask, then resolve" convention — a missing input is a property of the machine, so
helpers expose an availability probe and the suite `assumeTrue`s on it, making
absence a visible SKIP rather than a red that points at product code.

### CI: the `/opencode` agent can read CI logs (#36)

The job's `permissions:` block gained `actions: read` (a whitelist, not
additive), and its `GITHUB_TOKEN` is now exposed to the agent as `GH_TOKEN` —
read-only, this repo only, minted per job and dead when the job ends. So an
agent debugging a red build reads `gh run view <id> --log` instead of guessing,
and never needs a PAT.

### Known limitations

- **Three agents from the umbrella are still missing**: Antigravity
  ([#34](https://github.com/MohammadMD1383/jdx/issues/34)), Windsurf
  ([#38](https://github.com/MohammadMD1383/jdx/issues/38)) and Aider
  ([#41](https://github.com/MohammadMD1383/jdx/issues/41)), tracked under
  [#32](https://github.com/MohammadMD1383/jdx/issues/32). The seam is one enum
  case plus a merge function; each lands the way these six did, verified against
  a real install rather than a guess.
- **Cline has one global file, so its two scopes are the same file.** A
  project-scoped Cline run still edits `~/.cline/...`. This is Cline's verified
  layout, and the output says so — but it is not checkout-local, and pretending
  otherwise would be the wrong kind of convenience.
- **Codex CLI only reads `.codex/config.toml` in a trusted checkout.** jdx
  writes it either way; Codex decides whether to load it.
- **`jdx setup` is still not served by the daemon** and has no MCP tool or HTTP
  route: it writes to your config, not to a workspace. It is a local maintenance
  command like `jdx upgrade` and `jdx cache`.
- **Version detection is a best-effort probe** and a binary that answers with
  something unparseable reports the raw output (`claude-code version unknown
  (<raw>)`, `codex not found on PATH`) so a human can judge — jdx never guesses a
  line. Two caveats found by dogfooding the released binary while writing these
  notes, both filed and both pre-existing rather than regressions: 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](https://github.com/MohammadMD1383/jdx/issues/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](https://github.com/MohammadMD1383/jdx/issues/85)) —
  so overriding `HOME` does not redirect `setup`/`doctor`, and there is currently
  no flag to point them at a different home.

- Attribution still covers reader warnings; the `MULTI_RELEASE_VARIANT`
  *provenance* line does not say which variant was selected
  ([#29](https://github.com/MohammadMD1383/jdx/issues/29)).
- Follow-ups found while doing this, both open: the fixture-corpus wiring is
  still copy-pasted across seven module build files
  ([#82](https://github.com/MohammadMD1383/jdx/issues/82)), and `:index:pitest`
  reports bogus surviving mutants under the parallel `javap` fan-out
  ([#81](https://github.com/MohammadMD1383/jdx/issues/81)).

### Testing

**185 new tests and 18 new goldens**, none of which can touch your real home:

- **98 service tests + 49 adapter tests** for the six new backends, covering the
  merge matrix per backend (fresh install, merge-preserves-unrelated,
  remove-keeps-others, byte-exact idempotent re-run, corrupt config → 5, project
  walk-up, `$CODEX_HOME`/`$COPILOT_HOME` overrides, `type: stdio` probed true
  where `local` is written, Cline's legacy flat shape, Kilo's `.kilo/`
  preference, hostile command words) and each backend's `--json` payload.
- **18 byte-pinned goldens** — fresh-install, merge-preserves and
  remove-keeps-others for all six new backends. Rewrite with
  `-Pgolden.update=true`, and read the diff before committing it.
- **23 `doctor` tests** covering the new `appcds` row (present, absent, empty,
  stale, unresolvable, unreadable) and the widened `setup` row, plus **2**
  Windows probe tests.
- **7 launcher/install round-trips in `:app`** running the **real** scripts: the
  multiple-fat-jar exit 6, the garbage-archive silent-degradation path, the
  `install.sh` no-archive note, and the Windows `.exe` probe.
- **6 fixture-ownership tests** in `testfixtures`/`core` making the
  resolver-ownership pin path- and source-set-correct, so the corpus the
  mutation gate now depends on is the *same* corpus the tests read.

`userHome`, `projectDir`, `$CODEX_HOME` and `$COPILOT_HOME` are all temp dirs or
constructor args, and every version probe is a `ProcessRunner` fake, so the suite
never runs a real agent and never reads your real config.

### Docs

`docs/COMMANDS.md` (the `jdx setup` and `jdx doctor` rows rewritten) ·
`docs/PROPOSAL.md` §15 (launcher tuning) + §17.2 (the release-bundle caveat) +
Appendix B (the widened `setup` line) · `README.md` (one setup line per agent) ·
`docs/TESTING.md` §10.1 (the gate-wiring rule) · `AGENTS.md` §7/§11,
`CONTRIBUTING.md` and the `cli`, `app`, `testfixtures` module notes.

### PRs in this release

| PR | What |
|---|---|
| [#70](https://github.com/MohammadMD1383/jdx/pull/70) | Kilo Code setup support |
| [#71](https://github.com/MohammadMD1383/jdx/pull/71) | Claude Code setup support |
| [#72](https://github.com/MohammadMD1383/jdx/pull/72) | Stale fat-jar version bug fixed |
| [#73](https://github.com/MohammadMD1383/jdx/pull/73) | AppCDS `doctor` row + `install.sh` note |
| [#74](https://github.com/MohammadMD1383/jdx/pull/74) | Stale CDS noise silenced + pinned + docs |
| [#77](https://github.com/MohammadMD1383/jdx/pull/77) | Codex CLI setup support |
| [#78](https://github.com/MohammadMD1383/jdx/pull/78) | Cline setup support |
| [#79](https://github.com/MohammadMD1383/jdx/pull/79) | Cursor setup support |
| [#80](https://github.com/MohammadMD1383/jdx/pull/80) | GitHub Copilot CLI setup support |
| [#83](https://github.com/MohammadMD1383/jdx/pull/83) | The `core` mutation gate can get a green pre-scan |

**Full Changelog**: https://github.com/MohammadMD1383/jdx/compare/v1.4.0...v1.5.0

## v1.4.0 (2026-09-27)

## 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

```bash
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:

```console
$ 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:

```json
{
  "$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:

```console
$ 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).

```console
$ 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:

```console
$ 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**:

```console
$ 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:

```console
$ 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](https://github.com/MohammadMD1383/jdx/issues/36)–[#42](https://github.com/MohammadMD1383/jdx/issues/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 other than
  the CI runner's ([#54](https://github.com/MohammadMD1383/jdx/issues/54)) —
  output and exit codes are correct, you only lose the shared-archive speedup;
  and the `core` mutation gate still cannot run in CI
  ([#66](https://github.com/MohammadMD1383/jdx/issues/66)).

### Testing

**60 new tests and 3 new goldens**, none of which can touch your real home:

- **30 service tests + 12 adapter tests** for `setup` in `cli`, covering the
  merge matrix (fresh install, v1-only completed, v2-only completed, unrelated
  servers preserved in *both* maps, a v1 server named `servers` left alone,
  remove-keeps-others, byte-exact idempotent re-run, JSONC read, corrupt config
  → 5, project walk-up, system `jsonc` preference, hostile command words) and the
  `--json` payload.
- **7 property-based families** over the two pure functions that can be wrong
  quietly: version classification and `describeVersion` (generated minors and
  patches, so `1.x` vs `0.0.0-next|beta|dev|alpha|rc` vs `2.x` vs garbage is
  never a guess), the binary probe's preference of `opencode` over the legacy
  shim (with generated PATH noise), and the JSONC stripper — 200 generated
  strings that must keep comment-like text *inside* strings intact, plus
  generated comment bodies that must vanish deterministically.
- **3 byte-pinned goldens**: fresh install, merge-preserves, remove-keeps-others.
  Rewrite with `-Pgolden.update=true` — and read the diff before committing it.
- **6 `doctor` tests** for the setup row (unwired, v1, v2, the `opencode2` shim,
  an unparseable version, wired project) and **5 in `JdxCliTest`**.
- **4 launcher round-trips in `:app`** that run the **real** launcher script and
  fat jar: a missing argument, an unknown command and an unknown option each
  exit 3, plus a service-level usage error that still exits 3 — which pins the
  fix at the process boundary, not just at the Clikt seam.
- **2 `index` tests** for the diff attribution: a corrupt class in both jars is
  reported once per side with each side named, and a self-diff still reports
  once.

The probe is a `ProcessRunner` fake and every `userHome`/`projectDir` is a temp
dir, so the suite never runs `opencode` and never reads your real config.

### Docs

`docs/COMMANDS.md` (a `jdx setup` row + the rewritten `jdx doctor` row) ·
`docs/PROPOSAL.md` Appendix B (per-command highlights) · `README.md` (the
one-line OpenCode setup hook) · and the `cli` module notes for `SetupService`
and the doctor row.

**Full Changelog**: https://github.com/MohammadMD1383/jdx/compare/v1.3.0...v1.4.0

## v1.3.0 (2026-09-27)

## 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:

```console
$ 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:

```console
$ 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

```bash
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:

```bash
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](https://github.com/MohammadMD1383/jdx/issues/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](https://github.com/MohammadMD1383/jdx/issues/65)). Service-level exits
  are correct.
- The `core` mutation gate cannot currently run in CI
  ([#66](https://github.com/MohammadMD1383/jdx/issues/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, determinism, the
truncation law, text ⊆ JSON, hostile names), 19 snapshot tests, 21 renderer
tests, 44 service/dispatch/rule-coverage/golden tests in `index`, and 37
adapter tests in `cli`.

Plus a tier-3 `javap` differential over six real version pairs in the local
corpus, which pins two things nothing else can: the snapshot's member sets equal
`javap -p -s` **exactly**, and the differ is neither blind to a `javap`-visible
change nor noisy on an unchanged class.

`core`'s mutation gate is 84.0% module-wide against a ≥ 80% threshold, with the
new `core/diff` package at 82.2% — the six remaining gaps in that package were
real unexercised branches (the enum-constant access flags, the ambiguous-bucket
guard's mirror case, field `Signature`/`ConstantValue`/annotations, a
non-`stripAppliesTo` Kotlin view, and each comparator tiebreak), not incidental
equivalent mutants.

New corpus: `testfixtures/src/diffV1` and `src/diffV2` — two versions of the
same package, real `javac` and `kotlinc` output, byte-deterministic across a
clean rebuild. It self-describes through a **rule-coverage** test rather than
per-type annotations: the property worth pinning belongs to the *pair* — that
every rule fires against real compiler output. A rule with no fixture fails the
build, and a fixture with no declared expectation fails it too, so neither can
grow silently. The Kotlin rules are proven against real `@Metadata` there, which
matters because `KOTLIN_SUSPEND_CHANGED` depends on a reader flag
(`stripTrailingContinuation`) that nothing else would catch if it stopped being
set.

### Docs

`docs/PROPOSAL.md` §7.7 (the full rule set and the exit-code reasoning),
Appendix B, §16, §21 · `docs/COMMANDS.md` · `docs/TESTING.md` §11.2 · `README.md`
· and the `core`, `index`, `cli`, `server` and `testfixtures` module notes.

**Full Changelog**: https://github.com/MohammadMD1383/jdx/compare/v1.2.1...v1.3.0

## v1.2.1 (2026-09-26)

## 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

```bash
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**: https://github.com/MohammadMD1383/jdx/compare/v1.2.0...v1.2.1

## v1.2.0 (2026-09-26)

## 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

```bash
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**: https://github.com/MohammadMD1383/jdx/compare/v1.1.0...v1.2.0

## v1.1.0 (2026-09-24)

## What's new: `jdx upgrade` self-update

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

```bash
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:

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

**Full Changelog**: https://github.com/MohammadMD1383/jdx/compare/v1.0.0...v1.1.0

## v1.0.0 (2026-09-24)

## 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

```bash
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

