## 0.95.0

### `McpEditingGuard` — Memory-vs-Disk Modal Hardening

Both `steroid_execute_code` and `steroid_apply_patch` now run their bodies through a single guard that owns the before/after flow: modality fail-fast (DialogKiller dismisses leftover modals, otherwise we abort with a clean MCP error instead of parking on the EDT), pre-flight commit + `saveAllDocuments` + `awaitRefresh` (every open document's mod-stamp matches disk before the body runs, so the platform's `MemoryDiskConflictResolver` short-circuits naturally), prefer-disk reload override scoped to the body's lifetime (belt-and-braces against the rare race where the user edits a file while the agent writes it), and post-flight `awaitRefresh` (picks up disk changes from external commands the body invoked). The modal "memory differs from disk" dialog no longer blocks the IDE — [#27](https://github.com/jonnyzzz/mcp-steroid/issues/27).

### New MCP Resource: `mcp-steroid://ide/find-duplicates`

Typed PSI-based recipe for extracting clone pairs from IntelliJ's `DuplicatedCode` global inspection (`LocalInspectionToolWrapper(DuplicateInspection())` → `InspectionEngine.runInspectionOnFile`). Direct `import com.jetbrains.clones.DuplicateProblemDescriptor` + `dpd.textClone` access — the duplicates-detector module is already on the script classpath in IDEA Ultimate, so no private-field reflection or `setAccessible(true)` is needed. Previously agents probed `JavaPsiFacade` (which only sees user-project classes, not plugin classes), concluded the class was unavailable, and reached for reflection — [#33](https://github.com/jonnyzzz/mcp-steroid/issues/33).

The supporting reflection policy is now stated in `mcp-steroid://skill/mcp-steroid-info` (the startup-instructions text every agent reads) and the top-level `coding-with-intellij` skill guide: typed imports first, probes for classpath-presence are allowed, private-field reflection in shipped recipes is not.

### Three Agent-First Design Tenets Codified

New canonical statement at `docs/PHILOSOPHY.md`, mirrored at runtime as `mcp-steroid://skill/design-philosophy` (fetch via `steroid_fetch_resource`). Three tenets that until now were implicit:

1. **The MCP tool surface stays narrow.** 10 tools today; new tools require a three-gate test (rule out the IntelliJ API, rule out a richer recipe, three-agent quorum).
2. **Power lives in prompts and direct IntelliJ APIs** inside `steroid_execute_code`, not in new tools.
3. **`McpScriptContext` methods are last-resort.** The whole IntelliJ API surface is reachable from scripts; helpers there compete for attention with everything else.

Per-folder `CLAUDE.md` / `AGENTS.md` guides (ij-plugin, prompts, test-integration, test-experiments, docs, website) and the README now cite the tenets in their own scope. The README's tool table was genuinely stale (claimed 9 tools, listed an obsolete `Capabilities Discovery` row) — it now lists all 10 tools with their `steroid_*` API names. `steroid_input`'s description was also stale ("OCR analysis"); fixed.

### `DialogWindowsLookup` — Synchronous Modality Check

The old timeout-driven probe dispatched to the EDT with a 100 ms ceiling: false positives under IDE load (a busy EDT looked exactly like a parked one, so `steroid_list_windows` and the dialog-killer path flagged a modal whenever the IDE was just slow), and a permanent +100 ms tax on every call when the EDT was actually parked. Replaced by `isModalDialogShown()` — one EDT dispatch with `ModalityState.any()` reading `ModalityState.current()`. Non-equal to `ModalityState.nonModal()` means elevated modality, the only condition we need before enumerating dialog windows. Progress modals (Task.Modal, write-action queues) — which elevate modality but are not user-consent prompts — still get filtered out by the existing `isModal` predicate downstream.

### Tool-Handler Architecture: `Spec + Handler + IJ Service`

Internal refactor. Each MCP tool now has a `Spec` class that owns the MCP schema and JSON-arg parsing, an interface for the handler entry point, and an `@Service` IDE-level implementation. Typed `@Serializable` param classes replace the raw `JsonObject` plumbing through `ExecutionStorage` and the handlers. Per-handler local `errorResult` helpers are gone — shared `ToolCallResult.errorResult` / `successTextResult` extensions instead. Tool handlers also use lazy initialization via lambda + a `PromptsContextHandler` interface for prompts-context injection. Improves testability and per-tool isolation.

### `steroid_execute_code`: Drop `required_plugins` Parameter

The feature was never useful in practice — scripts that reference plugin classes either work (the plugin is loaded) or fail at compile time with a clear error. The parameter is removed from the tool schema; existing scripts that didn't use it are unaffected.

### Other

- `steroid_fetch_resource` requires `project_name` (was already required for the IDE-conditional filtering but the test helper omitted it on a few cases — now consistent).
- `McpScriptContext.params` field dropped (was unused).
- Editor-config added; `.*` imports eliminated across the codebase.
- `McpServerIntegrationTest` pins protocol correctness under concurrent `tools/call` — 4 parallel `steroid_execute_code` requests on a single session must keep JSON-RPC ids, error states, and result payloads cleanly separated (regression guard for Claude's 60 s MCP-tool timeout + retry-storm path).
- `ExecutionStorageTest.findExecutionId` now exercises `writeNewExecution`, `writeToolCall`, and `writeExecutionFeedback` — the previous gap let a `params.json`-write regression slip through.

### Plugin Verifier: Still Zero Internal-API Usages

Compatible against both IntelliJ 2025.3 (IU-253.28294.334) and 2026.1 (IU-261.22158.277), **0 internal-API usages** (hard gate). Experimental API usages: **4** (up from 3 in 0.94.0). The new entry is the suspending overload `RefreshQueue.refresh(boolean, List, Continuation)` invoked from `VfsRefreshService.awaitRefresh` — a public replacement for the old callback API. The four entries are: `IdeaPluginDescriptorExtensions` (class reference + `getContentModules` invocation in `ScriptClassLoaderFactory`), `RefreshQueue.refresh(suspend)` in `VfsRefreshService`, and suspending `CoroutinesKt.writeAction` in `McpScriptContextImpl`. All are public IntelliJ APIs that IntelliJ itself flags as experimental.

### Test Infrastructure

The KtBlock compilation tests (under `:prompts`) — which compile every kotlin code fence in the prompt corpus against each downloaded IntelliJ distribution's classpath via an external `kotlinc` subprocess — now derive `-jvm-target` from the running JVM (`java.specification.version`) and pin the test JVM to JDK 25 to match the JBR bundled inside every tracked IDE. A new `assertTestJreMatchesIdeBundled` canary fails loudly if a future IDE bumps its bundled JBR major. Fixes the 2026.1.x EAP `cannot inline bytecode built with JVM target 25 into bytecode that is being built with JVM target 21` error on TC.

### Fixed Issues

- [#27](https://github.com/jonnyzzz/mcp-steroid/issues/27) — VFS Modal Dialog blockage (now `McpEditingGuard`).
- [#33](https://github.com/jonnyzzz/mcp-steroid/issues/33) — `DuplicatedCode` inspection: agents forced to use private-field reflection to extract clone pairs (now `mcp-steroid://ide/find-duplicates` recipe + reflection policy in root prompts).
