Guidance for Claude Code when working with this repository. Instructions here override default behavior.
Three tenets govern every change in this repo — to code, MCP tools, or
prompts. Read docs/PHILOSOPHY.md (mirrored as
mcp-steroid://skill/design-philosophy for runtime fetch via
steroid_fetch_resource) before proposing any of:
- a new
steroid_*MCP tool - a new method on
McpScriptContext - a "helper" that wraps an IntelliJ API
Short version: the MCP tool surface (10 today) stays narrow on
purpose; the IntelliJ capability surface stays full, exposed via
steroid_execute_code plus prompt resources. The strategy page's
"Give AI the whole IDE, not just the files" is delivered through that
combination — steroid_execute_code reaches every IDE API, and the
mcp-steroid:// prompt corpus teaches the agent how. New tools and new
context methods are not the lever.
Before acting on any task that touches files in a sub-folder, walk the directory tree from the changed
file's folder up to the project root and read every CLAUDE.md and AGENTS.md you find on the way
(including this one). Sub-folder guides take precedence over the root for their own scope; the root only
holds project-wide rules.
Recipe (run this in your head, or with a one-liner). Normalize the starting directory to an
absolute path first — relative paths converge on . and never match the repo root, infinite-looping:
# from any file path (relative or absolute), walk parents to repo root, collecting CLAUDE.md / AGENTS.md
file="<changed-file>"
dir=$(cd "$(dirname "$file")" && pwd)
root=$(git rev-parse --show-toplevel)
while [ "$dir" != "$root" ] && [ "$dir" != "/" ]; do
for f in CLAUDE.md AGENTS.md; do [ -f "$dir/$f" ] && echo "$dir/$f"; done
dir=$(dirname "$dir")
done
for f in CLAUDE.md AGENTS.md; do [ -f "$root/$f" ] && echo "$root/$f"; doneWhen changing files across multiple sub-folders, read the guides for each.
| Folder | Guide | Scope |
|---|---|---|
ij-plugin/ |
ij-plugin/CLAUDE.md | IntelliJ plugin code, services, threading, build, deployment, sandbox/index troubleshooting, registry keys |
prompts/ |
prompts/AGENTS.md | Prompt file format, IDE conditionals, mcp-steroid:// resources, KtBlocks |
test-integration/ |
test-integration/AGENTS.md | Stable Docker IDE smoke tests, shared infra, hung-test diagnosis, multi-version compat tests, playgrounds, Rider/.NET, Linux Docker CI gotchas |
test-experiments/ |
test-experiments/CLAUDE.md | DPAIA arena suite, debugger demos, prompt-quality comparisons, IMPROVEMENTS.md harness |
docs/ |
docs/CLAUDE.md | Autoresearch / prompt-optimization working notes, DPAIA history |
website/ |
website/CLAUDE.md | Hugo site sources, GitHub Pages deployment |
- Use IntelliJ MCP for everything where you can — see
ij-plugin/CLAUDE.mdfor the API patterns. - Never ignore warnings or errors — fix them properly.
- No test-only branches (
isUnitTestMode) — use correct IntelliJ actions (writeIntentReadAction,writeCommandAction). - Tests must show reality. Never remove, disable, or weaken a failing test; fix the underlying issue.
- No
@Suppress("DEPRECATION")— find the non-deprecated replacement. - Prefer JSON libraries for JSON parsing/manipulation; only static final JSON constants may be hand-written as raw strings.
- Log new ideas/tasks in
TODO*files (TODO.md,TODO-*.md). - Atomic commits with descriptive messages (what and why). Test and build before committing.
- Never include AI as co-author or mention AI in commit messages.
runCatching{}.onFailure{}— usetry { } catch (e: Exception) { }instead. OtherrunCatchinguses (.getOrNull(),.getOrDefault()) are fine.- Empty
catch/catch (_: Exception) {}. Fail fast and log: every catch must rethrow, log viaSystem.err.println/logger.error, or both. Silent failure hides root causes. run-agent.shreferences in production code or tests. It is a manual dev/peer-review tool only. NeverCOPYorchmod +xit inside Dockerfiles. Implement agent integrations directly via CLI flags.- Cross-subproject
build/directory access in Gradle build files. Use Gradle dependency configurations. Fail fast withrequire()/error()— no silent fallbacks. append("\n")tricks to bypass theNoLargeInlineStringsTestlint rule. When abuildString { }exceeds the consecutive-appendLinelimit, move the content toprompts/src/main/prompts/and reference it via the article URI.- Hardcoded
mcp-steroid://...URI literals in production Kotlin. Use the generated article class:XxxPromptArticle().uri(fromcom.jonnyzzz.mcpSteroid.prompts.generated.*). Enforced byNoHardcodedMcpSteroidUriUsageTest. SeeFetchResourceToolHandler.kt. - Infrastructure workarounds in tests. When a test fails due to missing Docker socket, missing CLI, wrong JDK, or missing native library, fix the infrastructure — never add detection-and-skip code.
- Detecting failures and skipping tests at runtime (
try { } catch { skip() },Assumptions.assumeTrue(isAvailable),TestAbortedExceptionon error). The only acceptable skip is at the Gradle task level (enabled = !condition) when an entire suite is structurally incompatible with the platform.- Single documented exception: Gemini API key on CI. TC has no Gemini token and there is no plan to
add one.
DockerGeminiSession.Companionopts intoskipTestWhenKeyMissing = true(seetest-helper/.../AISessionBase.kt), sorequireApiKey()throwsAssumptionViolatedExceptioninstead ofIllegalStateExceptionwhen the key is missing. Constraints when working in this area:- Session creation must stay lazy — called from inside test method bodies, never from
setUp()/ class init /@ClassRule.BasePlatformTestCase-backed tests route everyThrowablethroughJUnit38ClassRunnertofireTestFailure, so an early init failure shows up against the wrong test. - Do NOT add
excludeTestsMatchingor other test-class-level filters — that hides the test from reports. - Unresolved
%credentialsJSON:…%must still fail hard withIllegalStateException— that branch indicates a real TC misconfiguration and must stay visible. The contract is unit-tested in:test-helper:testAIAgentCompanionApiKeyTest. - Do NOT extend the opt-in to other agents. Anthropic / OpenAI keys ARE configured on TC; their tests must keep failing if the key disappears.
- Session creation must stay lazy — called from inside test method bodies, never from
- Single documented exception: Gemini API key on CI. TC has no Gemini token and there is no plan to
add one.
- Java threading primitives (
CountDownLatch,Semaphore,Object.wait()) in coroutine code. UseCompletableDeferred<T>+withTimeout(d) { deferred.await() },Channel<T>, orsuspendCancellableCoroutine. ./gradlew testat the repo root. It fans out to every module and can take hours. Always scope:./gradlew :ij-plugin:test,./gradlew :kotlin-cli:test,./gradlew :prompts:test --tests '<pattern>'. See per-module guidance inij-plugin/CLAUDE.md.
- NEVER run
:test-integrationor:test-experimentstests in parallel. Each test starts a full Docker IntelliJ container. Two concurrent runs exhaust RAM/CPU and OOM-kill both. Wait for completion before starting the next. Seetest-integration/AGENTS.mdfor the full Docker-test playbook. - Diagnose stuck/slow tests with JDK tooling BEFORE killing.
jps -l | grep GradleWorkerMain→jcmd <pid> Thread.print > /tmp/dump.txtwhile the JVM is alive; thengrep '<YourTest>Test' /tmp/dump.txt -A 5. Killing throws away evidence and forces guess-and-retry. Once you have the stuck test's name, iterate on just that test (--tests 'com.example.StuckTest'--rerun-tasks).
- 1-minute rule for integration tests. Any
:test-integration/:test-experimentscase that hasn't printed PASS/FAIL within ~60 s of> Task :*:testis suspicious — usually a modal dialog, indexing stall, or background task that won't finish. Capture the latest screenshot (ls -t test-integration/build/test-logs/test/run-*/screenshot/*.png | head -1) and an in-container thread dump (docker exec <id> jcmd <PID> Thread.print) before deciding. Full recipe and symptom→cause table intest-integration/AGENTS.md→ "Debugging a stuck/hung Docker test".
MCP Steroid — IntelliJ Platform plugin that exposes a standalone MCP server letting LLM agents drive the IDE via Kotlin code execution.
- Public repo: https://github.com/jonnyzzz/mcp-steroid
- Docs: README.md, docs/guides/AGENT-STEROID-GUIDE.md
- Modules: see
settings.gradle.kts. Plugin code lives inij-plugin/; prompt resources inprompts/; Docker IDE smoke tests intest-integration/; experimental/long-running tests intest-experiments/.
Gradle 9.5.1 / Kotlin 2.3.20 / Java 25 / IntelliJ Platform 2026.1+ / Ktor 3.3.2 (CIO+SSE) / kotlinx.serialization
The Gradle Daemon is pinned to JDK 25 via gradle/gradle-daemon-jvm.properties
(matches IDEA 2026.1's bundled JBR — see docs/262-EAP-PLAN.md). The
foojay-resolver-convention plugin in settings.gradle.kts is the auto-download fallback if no JDK 25 is
present locally. To change the daemon JVM: edit gradle-daemon-jvm.properties directly (one-line
toolchainVersion=N).
- Read requirements; ask if ambiguous.
- Add a failing test, then implement (test-first; integration tests preferred; never fake tests).
- Run Gradle build/test via the IDE's MCP, not shell — see
ij-plugin/CLAUDE.mdfor the run-config recipe. - Deploy:
./gradlew deployPlugin. - Test with IntelliJ MCP. Validate full Docker scenarios via
:test-integration:test/:test-experiments:test. - Use
steroid_execute_codeto verify warnings/errors are gone before declaring done. - Update
TODO*and commit.
Root build.gradle.kts defines ci-prefixed aggregator tasks for TeamCity and GitHub Actions.
./gradlew tasks --group ci lists them.
| Task | Subprojects | Notes |
|---|---|---|
buildPluginOnCI |
:ij-plugin (builds + publishes ZIP) |
Entry point for both GH Actions and TC |
ciBuildPluginTests |
All plugin modules except prompts + non-plugin | Per-OS matrix on TC |
ciBuildPromptsTests |
prompt-generator, prompts, prompts-api |
Linux only; full matrix takes 60–120+ min |
ciIntegrationTests |
:test-helper:test → :ij-plugin:integrationTest → :test-integration:test |
Strict sequential ordering via mustRunAfter; needs Docker + API keys |
:test-integration:test and :test-experiments:test have an onlyIf guard — plain root ./gradlew test
silently skips both. Direct ./gradlew :test-integration:test --tests '...' still works.
TeamCity DSL lives in a separate repo (~/Work/mcp-steroid-teamcity). See its own CLAUDE.md for the
generate→edit→regenerate→commit workflow. The TC VCS root pulls from jb, not origin — see "Git remotes" below.
GitHub Actions (.github/workflows/): builds the publishable plugin ZIP and deploys the website to
GitHub Pages. Plugin tests are intentionally NOT mirrored — full coverage stays on TC (3–5× faster
internal agents). Trigger PR builds via workflow_dispatch on the PR's head branch.
| Remote | URL | Role |
|---|---|---|
origin |
git@github.com:jonnyzzz/mcp-steroid |
Day-to-day development fork; source of truth for new commits |
jb |
git@github.com:JetBrains/mcp-steroid.git |
JetBrains-org mirror; consumed by TeamCity (mcp_steroid project) |
Sync direction:
- origin → jb: always via merge (the
jb-mergeprocedure below). - jb → origin: always via cherry-pick (individual commits, manual conflict resolution).
- Never fast-forward-push
maintojb—jb/maincarries org-specific commits that would be lost.
git fetch jb
git checkout -b jb-merge jb/main
git merge main --no-ff -m "Merge remote-tracking branch 'origin/main' into jb-merge"
git push jb jb-merge:main
git checkout main && git branch -D jb-merge--no-ff preserves jb/main's existing head as the merge's first parent so jb-only history stays
reachable. Why this matters for CI: TC pulls from jb. If your commit isn't on jb/main, TC builds
stale code.
The IntelliJ project at ~/Work/intellij is open in the IDE for research. Use steroid_execute_code with
project_name="intellij" and PSI APIs (FilenameIndex, JavaPsiFacade) — faster and more accurate than
grep. See test-integration/AGENTS.md → "Researching IntelliJ APIs" for the recipe.
run-agent.sh from ~/Work/jonnyzzz-ai-coder/ launches AI agents (Claude/Codex/Gemini) for peer reviews,
research, and consensus checks. Encouraged from agent sessions — the BANNED rule applies only to
production code/tests referencing it.
timeout / gtimeout are not available on this Mac. Use Gradle's own timeout mechanisms or the Bash
tool's timeout parameter.