# AGENTS.md — jdx

> **Entry point.** This file is the rules. Then read only what your change needs:
> the module `AGENTS.md` (`core/`, `index/`, …), GitHub Issues (roadmap),
> `docs/TESTING.md` (before writing tests), `CONTRIBUTING.md` (before writing
> code). The full design lives in `docs/PROPOSAL.md` — read it lazily.
> (The v1 task board, session logs, and decision/lesson shards were removed
> post-v1; their durable content is distilled here and in the module notes,
> full history in git.)

## 1. What this project is

`jdx` is **an IDE for AI agents, as a command line tool.**

An agent working on a JVM codebase constantly needs to know things that today cost it many
shell round-trips: what methods a class has, what a method actually does, who calls it, what
implements an interface. Today it does this with `unzip`, `javap`, `grep`, and by reading
1000-line source files into its context window. That is slow, noisy, and burns context.

`jdx` answers those questions directly, from `.jar`, `-sources.jar`, class directories,
source directories, Maven coordinates, and the JDK itself:

```
jdx members com.google.gson.Gson --inherited     # what can I call on this? (IDE ".")
jdx body 'com.google.gson.Gson#toJson(Object)'   # show me ONLY that method body
jdx usages com.google.gson.TypeAdapter           # find usages
jdx hierarchy java.util.Map                      # who implements this?
jdx search '*Http*Client' --kind class           # symbol search
```

**Design north star:** every command answers one precise question with the *smallest correct
output*, and tells the agent exactly what to type next. Never dump a whole file when a
member was asked for. **v1 is complete** (all commands in `jdx help --agent` ship with
text + `--json`, exit codes, goldens).

## 2. Non-negotiable principles

1. **Minimal correct output.** Consumed by a model with a finite context window.
   Truncate intelligently and say how to get more — never silently, never by dumping more.
2. **Never guess a symbol.** Ambiguous input exits `2` with every candidate as a
   copy-pasteable canonical ref. Guessing wrong is worse than asking.
3. **Every output line names its symbol canonically**, so the next command is a
   mechanical copy-paste, not a reconstruction.
4. **Provenance always available.** Which jar, which sources jar, bytecode vs source vs
   decompiled, which decompiler. An agent must be able to judge confidence.
5. **Deterministic.** Same inputs → byte-identical output. Sorted. No ANSI unless TTY.
   No timestamps, no absolute paths, no hash values in default output.
6. **Meaningful exit codes** (see §6) so agents can branch without parsing prose.
7. **Fast.** Cold CLI under ~250 ms; warm daemon under ~20 ms. Called dozens of times
   per task.
8. **Degrade, don't fail.** No sources jar → decompile. Decompiler fails → `javap`.
   Kotlin metadata corrupt → JVM view. Always emit *something*, and say what it is.
9. **Heavily tested, by construction.** `core` test-first; everything else backed by
   *generative* families — differential against `javap`, property-based, metamorphic,
   fault-injection, corpus soak. Hand-written examples alone are not sufficient.

## 3. Locked rules (owner-settled — do not re-litigate; to change one, open an issue,

get an explicit reversal, record it here and in the commit message)

- **Truth model:** structure from bytecode, flesh (bodies, param names, docs) from
  sources; disagreement warns `SOURCES_VERSION_MISMATCH`, never silently wins.
- **Read-only:** parse with ASM, never load inspected classes (no static init ever
  runs). Write only inside the platform dirs (`docs/PROPOSAL.md` §17.1 table).
- **Per-OS dirs (D-044, issue #44):** cache/config/socket/install defaults per OS —
  Linux `~/.cache|~/.config|$XDG_RUNTIME_DIR` (honouring `XDG_*`); macOS
  `~/Library/Caches|~/Library/Application Support|$TMPDIR/jdx-$UID`; Windows
  `%LOCALAPPDATA%/jdx/cache|%APPDATA%/jdx|%LOCALAPPDATA%/jdx/run` (AF\_UNIX,
  Win10 17063+ floor); install `~/.local/share/jdx` + `~/.local/bin` (same on macOS),
  `%LOCALAPPDATA%/Programs/jdx` + PATH shim on Windows. Supersedes D-013/D-027; a
  missing `XDG_RUNTIME_DIR` falls back per table, never `exit 3`. Precedence: CLI flag
  > `JDX_*` \> `XDG_*` \> OS default. Migration: move (never merge) the old location on
  > first run, warn if both exist. Full table in `docs/PROPOSAL.md` §17.1.
- **Output:** text default, `--json` envelope complete (no text-only information).
- **Exit codes are a public contract** — changing them breaks agents.
- **Graph queries are live-roots-first:** `usages`/`hierarchy`/`callers`/`calls`/
  `samples` re-scan bytecode roots per query (no persistent-index acceleration yet).
- **Network is opt-in per invocation** (`--fetch`); coordinates resolve from
  `~/.gradle/caches` + `~/.m2` first.
- **Push rule:** no `git push` / `gh repo create` without explicit owner go-ahead in
  the current conversation.

|Area|Rule|
|---|---|
|Language / build|**Kotlin + Gradle** (JDK 21 toolchain target)|
|Bytecode reading|**ASM**|
|Decompiler|**Vineflower bundled** (default); `--engine javap` for raw opcodes|
|Java sources|**JavaParser**|
|Kotlin sources|**kotlin-compiler-embeddable PSI** — side-loaded into an isolated classloader, never a compile dep, never in the fat jar|
|Kotlin binaries|`@Metadata` via **kotlin-metadata-jvm** → true Kotlin signatures|
|Index|**Persistent on-disk index** (SQLite), content-hash keyed, behind an `IndexStore` interface|
|Usages scope|**All workspace jars + the project's own source dirs**|
|Classpath input|explicit paths/globs/dirs + **named workspaces** + **project auto-discovery** (never run Gradle/Maven) + **Maven coords (auto-download opt-in)** + **JDK stdlib via jrt-fs**|
|Interfaces|one-shot **CLI**, **MCP** stdio server, **daemon** (5-min idle auto-shutdown), **HTTP/JSON** — all thin adapters over one `JdxService`, no logic in adapters|
|Testing|**TDD mandatory in `core`** + generative families; Pitest mutation gate ≥ 80 % on `core`|
|Deferred|mappings/remap, resources \& services inspection, `--since`/API-level reporting|
|Repo|local git; public GitHub repo ([github.com/MohammadMD1383/jdx](https://github.com/MohammadMD1383/jdx)), Apache-2.0|

## 4. Repository layout

```
jdx/
├── AGENTS.md                  ← you are here
├── README.md                  user-facing intro (hook, demo, install, quickstart, links)
├── docs/COMMANDS.md           user-facing command catalogue (moved out of README)
├── CONTRIBUTING.md            conventions, code style, definition of done
├── <module>/AGENTS.md         per-module notes — read only the one(s) you touch
├── docs/
│   ├── PROPOSAL.md            full design document (the spec)
│   ├── TESTING.md             testing strategy — read before writing tests
│   ├── guide/                 user guides (website "Getting started" + "Guides" sections)
│   └── FAQ.md                 user FAQ (website /faq/, FAQPage structured data)
├── gradle/libs.versions.toml  version catalog (single source of dependency versions)
├── core/                      model, symbol refs, resolution, rendering. Pure Kotlin, no IO.
├── index/                     ASM readers, Kotlin metadata, SQLite store, indexer, JdxService
├── sources/                   sources-jar handling, JavaParser, Kotlin PSI (isolated CL)
├── decompile/                 Vineflower + javap engines
├── cli/                       Clikt commands, text/JSON renderers
├── mcp/                       MCP stdio server
├── server/                    HTTP/JSON API + daemon
├── app/                       fat-jar assembly + `jdx` launcher script
├── testfixtures/              nasty Java/Kotlin classes → binary jar + -sources.jar
└── site/                      GitHub Pages website generator (build-time only; site/AGENTS.md)
```

**Dependency rule:** `core` depends on nothing project-local. Everything depends on `core`.
`cli`/`mcp`/`server` are ADAPTERS and must contain no behaviour; logic tempted into a
Clikt command belongs in `core` or a service class so all four interfaces share it.

## 5. Symbol reference syntax (canonical)

Javadoc style. Accept generously, print canonically.

```
com.google.gson.Gson                              type
com.google.gson.Gson$TypeAdapterRuntime           nested (also accepts .Inner)
com.google.gson.Gson#toJson                       all overloads
com.google.gson.Gson#toJson(Object)               simple param names OK
com.google.gson.Gson#toJson(java.lang.Object)     fully-qualified params
com.google.gson.Gson#toJson(Ljava/lang/Object;)Ljava/lang/String;   exact JVM descriptor
com.google.gson.Gson#<init>(...)                  constructor
Gson#toJson                                       short form → resolved, or exit 2 w/ candidates
```

`::` and `.` are accepted in place of `#`. **Always quote the ref in shell examples** —
`#` and `(` are shell-hostile. `$` always separates nesting; the final dot-segment is
always the class. `parse(print(ref)) == ref` is a pinned property — don't break
round-tripping.

## 6. Exit codes (contract — do not change casually)

|Code|Meaning|
|---|---|
|`0`|Success, results found|
|`1`|Valid query, **no results** (class/member not found)|
|`2`|**Ambiguous** reference — candidates printed|
|`3`|Usage / argument error|
|`4`|No index or workspace resolved for this query|
|`5`|Artifact read error (corrupt jar, unreadable file)|
|`6`|Internal error|

**The one documented exception** (owner-approved, issue #23): `jdx diff` exits `0`
whenever the comparison ran, whatever it found, and `--fail-on breaking|any` exits `1`
when the gate trips. A diff has no "not found" outcome, and the verdict is also in the
envelope as `result.gate`, so a consumer that cannot read exit codes still gets the
answer. Nothing else may bend this table.

## 7. Build, test, lint

- Build only via the Gradle wrapper (`./gradlew`); JDK 21+ required. Categories: `test`
  (fast unit loop, per-test budget \<= 2.5 s) · `integrationTest` (integration suites,
  per-test budget \<= 120 s) · `check` (pre-commit: unit + integration + lint + coverage) ·
  `soak`/`bench`/`mutationTest` on demand (`bench` and `mutationTest` require explicit
  confirmation via `-PconfirmTier4=true` or `-PallowHeavy=true`).
- `core` TDD-mandatory, mutation gate ≥ 80%; `index`/`sources`/`decompile` ≥ 85% line;
  adapters ungated (parity + goldens instead — gating thin code incentivises padding).
- **A gate that cannot run is not a gate.** `check` runs `verifyPitestWiring` (alongside
  `lint`) because the `core` mutation gate aborts in PIT's *pre-scan* — silently, with no
  score produced at all — when the fixture corpus is missing, and that only shows up on a
  clean checkout. Any precondition a gate needs gets its own cheap assertion in `check`;
  don't let a red nightly be the thing that discovers it. `docs/TESTING.md` §10.1.
- Every behaviour needs a *generative* family (property / `javap`-differential /
  metamorphic / fault-injection / corpus), not just examples.
- Root `lint` (runs in every `check`): no trailing whitespace, no tabs, no bare
  `TODO`/`FIXME` without a reference, no `println`/`System.exit`/`printStackTrace` in
  library mains, `allWarningsAsErrors` on main + test compiles.
- `-Pgolden.update=true` is the most dangerous command here: **read every golden diff
  before committing it.** An unread golden update is a deleted test.
- CI mode `-Pci` (also auto-detected via `CI`/`GITHUB_ACTIONS`): wall-clock test gates
  relax on slow runners — per-test budget report-only, PIT timeouts 10 s → 60 s.
  Local loop unchanged. Product timeouts untouched. See `docs/TESTING.md` §2.
- Line counting is POSIX: one trailing `\n` is a terminator, not a line.

## 8. Working agreements

- **Keep GitHub Issues current** — they are the roadmap. Move finished items out, record
  newly found ones (limitations, follow-ups, caveats). A change that alters behaviour
  or finds a caveat and leaves them stale is incomplete.
- **Keep the `AGENTS.md` notes true.** If a change invalidates a module note (new
  invariant, new gotcha, new key file), update that note in the same change. A
  contributor who trusts a stale note pays twice.
- **Ask the owner about genuine ambiguity** rather than picking. A silent wrong guess
  propagates through a codebase nobody fully reads.
- **Work in the cadence: one small change → commit → push → next.** Conventional Commits
  with a module scope: `feat(index): parallel artifact indexer`. If a change can't be
  finished and committed in one sitting, it is too big — **split it first.** An unpushed
  working tree is invisible to every other contributor.
- **Definition of done for a command:** behaviour in `core`/`index`/`sources`/`decompile`,
  thin adapter in `cli`, `--help` text, text renderer, JSON renderer, exit codes,
  truncation, golden tests for both renderers, at least one *generative* test family,
  docs/COMMANDS.md table row, Appendix B flag entry, GitHub Issue filed/updated if behaviour or
  limitations changed, and `./gradlew :site:buildSite` green (the website executes doc
  examples and checks every documented flag against `--help` — see `site/AGENTS.md`).
- **Report honestly.** Failing tests get pasted, not summarised away. Documented
  half-finished work is useful; half-finished work reported as done is a trap.
- **Write for a stranger.** Explicit over clever, named over inlined, invariants stated
  in comments where types don't enforce them.
- **Batch independent tool calls in a single turn.** Sequence calls only when one
  genuinely depends on another's output.

## 9. Specs and roadmap

- `docs/PROPOSAL.md` — design spec (§6 ref grammar, §8 output, §14 serving,
  Appendix B flag reference, §19 licenses). Code cites `PROPOSAL.md §X`; keep true.
- `docs/TESTING.md` — testing strategy; read before writing tests.
- GitHub Issues — known limitations + phase-2 backlog (the roadmap).
- docs/COMMANDS.md is the user-facing catalogue; every shipped flag must appear in
  `jdx <cmd> --help`, the docs/COMMANDS.md table, and Appendix B.
- **Website** ([mohammadmd1383.github.io/jdx](https://mohammadmd1383.github.io/jdx/)) is
  generated from README.md, docs/ and the built binary by `site/` and deployed by
  `.github/workflows/pages.yml` on every push to `main` and every release. Never edit it by
  hand; change the source. Hierarchy and URLs live in `site/.../SiteManifest.kt`.

## 10. Dogfood rule — built by agents, for agents

`jdx` is its own proof of work: when working on this repo, use `jdx` on this
repo first (own source dirs / built jars, third-party jars, the JDK) instead of
`javap` / `grep` / `unzip` / full-file reads for structural questions.

- A wrong answer, crash, or missing capability found while dogfooding is a bug
  or feature gap in `jdx` — file it as a GitHub Issue in the same session
  (`gh issue create`), with: the exact `jdx` command + inputs, when/where/why
  it happened, expected vs actual, the fallback used, and the proposed fix.
- Never block on the fix: fall back to whatever works (`javap`, `grep`, direct
  reads) and finish the task. Never work around silently without filing — an
  unfiled dogfood bug is an incomplete task.

## 11. Environment notes (this machine)

- Arch Linux. JDK at `/usr/lib/jvm/default` (`java`, `javac`, `javap`, `jar`, `jdeps`).
  `JAVA_HOME` is **unset** — the launcher must resolve the JDK itself and not assume it.
  Other OSes (CI-verified via `windows.yml` / `macos.yml`, issue #52): macOS JDK via
  `/usr/libexec/java_home -v 21` (override with `$JDX_JAVA_HOME_HELPER` in tests);
  Windows JDK via `%JAVA_HOME%\bin\java.exe` → `where java.exe` → registry
  (`JavaSoft` / `Adoptium`) → `%ProgramFiles%\Java` (see `app/AGENTS.md`). Windows
  symlinks need Developer Mode, otherwise launcher/installer symlink suites skip
  with a reason; goldens stay LF (`*.sh eol=lf`, `*.bat eol=crlf`) with CRLF-tolerant
  compare (`GoldenFiles.normaliseLineEndings`, `UnifiedDiff.splitLines`).
- **No `mvn`/`gradle` on PATH.** Use the Gradle **wrapper**.
- Maven Central and GitHub are reachable. `git` and `gh` are installed.
- In the `/opencode` GitHub Actions job, `GH_TOKEN` is the job's own
  `GITHUB_TOKEN` scoped to `actions: read` on this repo — so `gh run list`,
  `gh run view <id> --log`, `gh run watch` work. **Read the logs instead of
  guessing at a CI failure**, and never ask for a PAT to do it.
- Rich local test corpus: ~2183 jars in `~/.gradle/caches`, including `-sources.jar`
  artifacts (gson, msal4j, lwjgl, mojang-logging) and a large obfuscated/remapped
  `minecraft-client.jar` under `~/.gradle/caches/fabric-loom/` — the scale/perf
  benchmark; gson is the everyday correctness fixture.
- ASM and Vineflower already exist in the local Gradle cache.
