A living document. DIRECTIVES.md says what is decided and why; this says in what order, with
every task, and what is done. It is updated in the same turn the state changes.
Legend: ☐ to do · ◐ in progress · ☑ done and committed.
Sprint exit: green on Tool: tests kotlin, Tool: tests frontend and Tool: lint, read from the tail
of .claudetools/run/out/<name>.log; clean diagnostics on every file touched; a signed commit.
Every API named below was read from the platform source at idea/253.29346.138. Where a task says an
API is forbidden, the replacement is named. Nothing here is written from memory.
Two rules that apply to every sprint. At most four tools per domain — a domain that does not fit is
split. And every tool returns the smallest answer that settles the question, with a hard limit on anything
that enumerates.
First, because everything else rests on the compiler telling the truth.
- ☑
sinceBuildpinned to the current stable. Already253.29346.138,untilBuild263.*. - ☑
allWarningsAsErrors = trueinbuild.gradle.kts. A deprecated API emits a warning and@ApiStatus.Experimentaldoes not, so this forbids exactly what is forbidden and tolerates exactly what is tolerated, with no list to maintain. - ☑
FileRollbackoff the two-argumentrunWriteCommandAction, which is@TestOnlyand names the undo entryUndefined, ontowriteCommandAction(project).withName(…). - ☑
TerminalLauncheroff the reflected five-argumentcreateNewSession, which is@ApiStatus.Internal, onto the publiccreateShellWidget(workingDirectory, tabName, requestFocus, deferSessionStartUntilUiShown)plusTerminalWidget.sendCommandToExecute(String). - ☑ The terminal contract test stops pinning the reflected signature and pins the public one, and asserts reflection does not come back.
- ☑
InternalPlatformApiContractTest: scans production sources for the internal symbols the compiler cannot flag, each with its public replacement named in the failure message. - ☐ Coverage thresholds reviewed for the packages about to appear. Carried into sprint 1, because there is nothing to measure until the packages exist.
Commit: ☑ build: warnings are errors, and no internal platform API gets in
- ☐
IdeServerbecomesCODE,RUN,VCS,OPS,JETBRAINS. The two hechtcarmel entries, their plugin ids, ports, install buttons and per-server rules are deleted. - ☐ Delete
IdeRule.ktand theknownToolsfield. The rules existed to describe someone else's tools. - ☐
IdeMcpPromptshrinks to one paragraph: the four servers, what each is for, and that you start withdomains(). The injected block names no tool at all, and its character ceiling is tightened, not relaxed. - ☐
GodModeis our servers plus the common rules. The JetBrains server is not part of it. - ☐
IdeMcpStateloses the third-party ports and flags, tolerantly parsing old persisted JSON. - ☐
SettingsIdeMcpSection: one checkbox for our integration, with the JetBrains server kept as an optional extra behindIdeServerControls. - ☐
McpConfigBuilderemits four stdio entries.LaunchOptionsreplaces the index/debugger flags. - ☐
SessionLauncherandJcefSettingsMenufollow. - ☐
PluginInstallerTeststops naming the removed servers; README and the settings documentation follow in the same commit.
Commit: refactor(mcp): the IDE integration is servers of our own; the third-party servers are gone
The delicate one. The codec lands before any tool does, because everything downstream is measured in it.
- ☐ A TOON codec twice: Kotlin in the pure core, and dependency-free Java for the helper, which is launched
by a bare
javaand cannot see Kotlin. - ☐ All four forms: inline, list, tabular and keyed tabular, plus nested field groups. Tabular is where the saving is and is the shape of nearly everything returned.
- ☐ Validated against the specification's published reference fixtures, not invented examples, plus a round-trip test of our own.
- ☐
JsonRpc,ToolSpec,ToolArgs,ToolResult,OutputBudgetin the pure core, which joins the platform-free package list and therefore may not namecom.intellij. - ☐ A dual-era
McpServer: modern fields always emitted, branching only oninitializeversusserver/discover. One server instance per catalogue, not four implementations. - ☐
StdioBridge: first line of stdin is the credential, then JSON-RPC; translates to TOON at the boundary and pumps against the socket. Its jar path resolves from its own code source location. - ☐ Four Unix sockets under a 0700 directory named with 128 random bits, removed on dispose. No TCP.
- ☐ The three meta-tools, with the test that
tools/listreturns exactly three entries per server. - ☐ A queue per server: the socket reader never executes a tool; immediate acknowledgement; replies
correlated by
idand therefore allowed out of order; timeout and cancellation on every tool; a bounded depth that refuses with an actionable message instead of growing. - ☐ The auth token: generated in code, handed to each server at
init, kept in memory, carried in the per-request metadata the specification reserves, rotated every 30 minutes with a short overlap so in-flight requests survive. Tests: a missing or expired token is refused without saying why, the secret never reaches a log or an error, and it is not in the helper process environment. - ☐ A notification when a connection arrives that is not a known chat tab, with a setting to require approval. In the hacked-developer scenario this is worth more than the token.
- ☐ The guard evaluated at the
run(tool, args)dispatcher — one place, not twenty-six — with a test that a dangerous nested argument is refused. Nothing under the permission package is touched, and nothing needs to be: the classifier never sees a tool name and the input scanner already recurses. - ☐ Every tool names its parameters with the keys the guard recognises, so they inherit every existing verdict rather than needing new rules.
- ☐ The permission card reads the inner tool name from the dispatcher's argument, so the user sees
read_filerather thanrun. That is view work, not guard work. - ☐ Domains
readandsearchof thecodeserver.read_fileresolves through the local file system and reads text via the immutable character sequence, which needs no lock.search_textgoes through the IDE's find-in-project machinery with a model carrying the query, regex, case and file mask.find_filesuses the filename index, and falls back to iterating content with the exclusion check for globs; case-insensitive name lookup is documented as a full scan.
Commit: feat(mcp): four MCP servers inside the plugin, each asked for its tools on demand
- ☐
definitionandsymbol_info: the PSI file for the document, the reference at the offset, resolved. Coordinates validated before use so a bad line is an actionable error, not an exception. - ☐
references: the reference search query, consumed by a processor rather than an iterator, because the iterator forms are deprecated. - ☐
implementations: the definitions-scoped search. - ☐
call_hierarchyandtype_hierarchy, grouped under one tool with a direction argument so the domain stays within four. - ☐
find_symbolsthrough the symbol and class contributor extension points, processing names and then elements. Not the short-names cache, which would tie the plugin to Java. - ☐
file_outlinefrom the structure view builder for the file's language, walked as a tree. - ☐
problems: the daemon's already-computed highlights, iterated under the markup lock with no heavy work inside the processor. The file has to be open for this to be meaningful, and the tool says so. - ☐
project_problems: the problems collector, whose listener methods are expected on the UI thread. - ☐
inspect: running an inspection on a file through the public engine entry point, with the current profile and a new global context. This does not depend on the daemon, which is what covers the gap left by the highlight path being marked for future deprecation.
Commit: feat(mcp): navigation, the file outline, the IDE's problems and its inspections
- ☐ Every write wrapped in a named command, so the user's undo says what Claude did. Several files in one command by marking it global and declaring the affected files, which is required when touching the virtual file system rather than a document.
- ☐ A single local-history label around the same span, started and finished in a
finally. The history service never returns null; availability is checked with its enabled flag, not a null comparison. - ☐ The virtual-file-system requestor is never null, because null is interpreted as an external change and detaches our edits from the history that groups them.
- ☐
replace_text,insert_at,create_filethrough the document API and the directory creation utility. - ☐
renamethrough the refactoring factory, set non-interactive so it never opens a dialog. - ☐
move_filethrough the move processor, because the refactoring factory has no move. - ☐
safe_deletethrough the safe-delete processor. - ☐
reformatand import optimisation through the code style manager and the optimise-imports processor. - ☐
quick_fixesandquick_fix_apply: the intentions pass collects actions, cached intentions expose them, and application happens on the UI thread inside a write command when the action asks for one. The offset comes from the caret. - ☐ Editor state: selected editor, caret movement and selection, all on the UI thread and outside a write action.
- ☐ Every edit opens the existing review diff, reusing the plugin's own diff opener.
Commit: feat(mcp): edits, refactors, quick fixes and formatting, each opening the review diff
- ☐
buildthrough the project task manager. Its result reports only aborted and has-errors, so the error list comes from the problems collector. The build-view problems service does not exist in this platform. - ☐
run_configurationslisted from the run manager;run_configurationexecuted through an execution environment built for the run executor, with the process listener attached before notification starts, the exit code taken from the process event, and system output filtered out. - ☐
run_tests: there is no "run this test and give me a verdict" API. The run configuration is executed and the public test-status topic is listened to. The test proxy is documented as UI-thread-only, so it is copied into an immutable value inside that thread. The declarative status-listener extension point is not usable for this. - ☐
shell: a command line with a coloured process handler, attached to a terminal execution console in the tool window, keyed on the content so a tab is reused. This is what the platform itself does, and it gives reliable output and exit codes. The interactive tab stays on the public shell widget. - ☐ Long work reports through background progress rather than going silent and dumping at the end.
Commit: feat(mcp): builds, tests, run configurations and a shell in the IDE's terminal
Nine drafted tools do not fit in four, so the domain is split into debug and breakpoints, and stepping
becomes one tool with a kind argument.
- ☐
debug_start,debug_status,debug_step(kind),debug_stopin one domain. - ☐
breakpoint_set,breakpoint_remove,breakpoints, plus evaluation, in the other. - ☐ Breakpoints are added by file URL, not by virtual file, and the deprecated single-breakpoint lookup is replaced by the plural one.
- ☐ Waiting for a pause is a session listener callback, never polling, and suspension is checked after subscribing to close the race.
- ☐ Evaluation and variables through the evaluator and stack frame, copied out into values before crossing a thread boundary.
Commit: feat(mcp): the debugger as tools
- ☐ Reading stays in the existing git gateway, the only file allowed to name the git plugin's types, and the read-only contract test that pins that stays green.
- ☐ Writing lives outside that package, because the contract test scans it: commit, pull, push, fetch, merge, rebase, tag, reset and stash through the line handler, with the message written via the public commit-message file helper.
- ☐ Branches through the brancher, using the reference overload, not the deprecated string one.
- ☐ Fetch through the fetch support, which is synchronous and blocking and therefore never called on the UI thread.
- ☐ Push through the git facade, because the push support has a private constructor.
- ☐ Status for any version control system through the change list manager, with its asynchronous update callback, because the state is not immediately current.
- ☐ The forge is not a command-line wrapper: the IDE's own git actions are invoked by id and its tool window activated, reusing the plugin's existing navigator and action invoker.
Commit: feat(mcp): git, and the forge through the IDE's own views
The DevOps piece, and the reason no container or cluster command line is wrapped anywhere in this roadmap.
- ☐
services(): the tree as the user sees it, walked recursively through the service view contributor extension point. - ☐
service_actions(path): the descriptor for a node exposes its toolbar and popup action groups, so the actions the IDE itself offers on that node can be enumerated. - ☐
service_action(path, action): invoked through the existing action invoker, passing the guard. - ☐
service_open(path): select the node and activate the tool window. - ☐ Honest limitation recorded in the tool descriptions: presentation and actions are available, typed domain objects are not, and no supported path to them is invented.
Commit: feat(mcp): the Services panel, and every action the IDE offers on its nodes
The fragile sprint. Each domain degrades to absent rather than breaking startup.
- ☐ All database internals in a single gateway file, reached by reflection from the context class loader, wrapped so a changed signature degrades the domain instead of throwing. A contract test fails if any file outside that package names the database plugin.
- ☐ Recorded in the gateway's tests: the data-source classes moved in a recent release, the official thread about it went unanswered, and the extension-point documentation for that plugin is a dead link. There is no contract, which is exactly why it is isolated.
- ☐ Decide whether the SQL argument is renamed so it inherits the guard's command verdict. Today it would not.
- ☐ HTTP through the IDE's own request run configuration, located at runtime by configuration type because its id is undocumented and the plugin is not in Community. Absent plugin means absent domain, and the message names the plugin. The command-line HTTP client is not wrapped.
- ☐ SSH read-only through the credential provider extension point, which is the supported way to enumerate what the IDE knows. Executing over SSH would be an external process and stays out.
Commit: feat(mcp): the IDE's databases and HTTP client as tools
- ☐
ide_action(id): the existing action invoker moves into the MCP package, which gives reach to every registered action in the IDE, with enablement and visibility checked before performing. - ☐
tool_window(id): activation by id, with the verified ids for Services, Problems, Version Control, Commit, Database, Run, Debug, Project and Structure. Terminal is not among the platform constants. - ☐
settings_open(name)plus the typed settings services for the editor, the general settings, the user interface — refreshing it after mutation — and code style. There is no generic set-by-key writer; the model that would offer one is internal, so none is faked. - ☐
modules, dependency changes through the root-modification utility the javadoc recommends, and SDKs through the JDK table and project root manager, both of which genuinely require a write lock. - ☐
plugins()from the plugin manager. - ☐
notify: a notification group registered in the plugin descriptor, with actions rather than the deprecated listener, and no links in the content, as the javadoc asks. - ☐
index_status: dumb-mode state, knowing that running when smart does not block and waiting does.
Commit: feat(mcp): Claude configures the IDE it works in, and speaks up inside it
- ☐ The core knows nothing about Claude Code. Everything specific to it — the launch configuration, the project directory variable, its output ceilings — lives in one adapter behind a setting.
- ☐ The connection configuration is published so another client can read it, behind a setting that enables it.
- ☐ Recorded as debt: a minimal MCP client in the repository is the only thing that would actually prove the claim. It is not in this release.
Commit: feat(mcp): any MCP client can drive the IDE
A passphrase for the plugin, not for the IDE: entered when it opens, held in memory only, and used to encrypt everything the plugin stores. It is also what arms the MCP servers.
- ☐ The envelope goes into the single function pair that touches the credential store, which is the only place in the plugin that does, so every consumer is covered without being edited.
- ☐ What that protects, and why it is worth it: the authentication token, the API key, the credentials blob, the account profile and status, the environment variables, the settings blob, the open chats, the agent index, the review prompt, and the guard log.
- ☐ No home-made cryptography. Key derivation with a standard password-based function from the runtime, a random salt and a high iteration count; encryption with authenticated symmetric encryption and a fresh random nonce per record, so a tampered record is detected.
- ☐ A versioned record format, so the scheme can change without orphaning what is stored.
- ☐ The salt is not secret and lives in ordinary settings. The derived key lives only in memory, is cleared when the project closes or the passphrase is disarmed, and its buffers are wiped after use.
- ☐ A verifier record, so a wrong passphrase is detected without decrypting everything else.
- ☐ The same passphrase arms the servers. They are born disarmed and are disarmed by hand. No passphrase, no key; no key, no token; no token, the sockets answer nobody. That is what actually stops the attacker in the hacked-developer scenario.
- ☐ Migration both ways: enabling re-encrypts what exists, disabling decrypts it. A wrong passphrase refuses and changes nothing — never wipe because you could not read.
- ☐ Tolerate the platform's known behaviour of emptying the store on a password-token mismatch: a secret may simply be gone, and what can be regenerated is regenerated.
- ☐ Forgetting the passphrase loses what was encrypted. There is no recovery and none is invented. It is said plainly when the feature is turned on, alongside the fact that most of it is recoverable by signing in again — what is genuinely lost is the guard log's history.
- ☐ This is new security code, not a change to the guard. It ships with its own tests and touches nothing under the permission package.
Commit: feat(security): a passphrase of the plugin's own encrypts what it stores and arms the servers
- ☐ The first-run tutorial on the flame, reworded: one switch, nothing to install.
- ☐ Version text across the changelog, the release notes and the README.
- ☐ Manual pass: God Mode on, a fresh chat, the card lists four servers with three tools each, a read goes
domains()thentools("read")thenrun("read_file", …), the permission card shows the inner tool name, an edit opens the review diff, a container is stopped and started from the chat through the Services panel, and no new listening port appears.
Commit: the tutorial and the version text
Already researched; becomes sprints once the release ships. Same standard: no signature written without having been read.
| Area | Tools | Verified ground |
|---|---|---|
| Templates | templates, template_apply, file_templates, file_from_template |
Live template settings and manager; file templates and the creation utility. Risk: the live-template settings classes live in an implementation package |
| Injected languages and documentation | injections, inject_at, docs |
The injected language manager is fully public core API. Documentation goes through the modern target provider, not the older provider whose javadoc already asks for migration |
| Bookmarks and the project view | bookmarks, bookmark_add, project_view_select, structure_select |
The bookmarks manager is experimental and therefore tolerated; the project view selects and changes panes. All UI thread |
| Workspace model, read-only | workspace |
The current snapshot is documented as readable without locks from any thread — the cheapest read available. Updating the project model through it is obsolete; writes stay on the root-modification utility |
| PSI as a tree | psi_tree, psi_at, psi_replace, psi_insert |
Create through the file factory with an explicit language on an in-memory file and graft the subtree; mutate with the element add/replace/delete API; navigate with the tree utility; whitespace and comments language-agnostically through the parser facade. Pattern matching is a predicate for extension points, not a finder |
| The indexes | index_keys, index_query, stub_query |
The file-based index is an application service; stub elements come from the plural getter. Other plugins' indexes are queryable because index ids resolve by name globally; the limit is the class loader. Real trap: several overloads silently discard the id filter, and one throws on a scope without a project |
| UAST | uast_tree, uast_at |
Present in Community in this build. The facade is a Kotlin object plus extension functions. Read-only: mutation goes through the underlying source PSI |
Conditional, and may never ship: a completion tool. The service entry point is public, but the convenient path is test-framework only and the progress indicator is internal. If there is no stable public way to build completion parameters, it is not done — a tool that needs internal API to exist fails the policy.
Out of scope, documented: language server integration. That module is not in Community and the official documentation confirms it is a commercial-IDE extension, so such a domain would not start at all.
This is what makes Claude visible in the IDE rather than only active in it, and it is what sustains the proactive working style the plugin is for.
| Area | Tools | Verified ground |
|---|---|---|
| Editor markup | mark_add, mark_remove, marks, hint_add |
Range and line highlighters taken with the colour-scheme key overloads, which is what survives a theme change; gutter icons through the highlighter's renderer, whose base class declares equality and hashing abstract, so they must be implemented; inline, block and after-line-end hints through the inlay model, added in batch mode and removed by disposal |
| The editor banner | banner_show, banner_clear |
Only through the project-level notification provider extension point, whose single method runs under a read lock, off the UI thread and returns a function the platform applies on the UI thread. Refresh through the notifications service; the older nested provider class and the per-provider refresh are deprecated |
| Diffs, scratches, the status bar | diff_show, scratch_create, status |
Diff contents from the content factory, including the empty content that represents a created or deleted file, wrapped in a simple request and shown on the UI thread; a chain for several files. Scratch files are one call, and the platform already wraps it in a global-undo write command, so it must be called where a write action is legal. Status text through the window manager, with a fixed widget requiring the widget factory extension point |
| Split and remote IDE | — | Not yet considered, and a real risk. With a split frontend and backend, a local Unix socket may not be where the client believes. The remote-procedure and shared-API documentation is read before anything is promised |
| Other products | — | Databases with a supported API if one ever appears, plus the framework integrations, each as a degradable domain |
Three pieces from this horizon that earlier sprints already need: background progress is the modern suspending API and exists in this build, so the blocking progress manager is legacy and the project-first asynchronous overload is scheduled for removal and banned; the file chooser factory moved and nearly all its create methods are obsolete, so the short names are used; and a tool window of our own adds content through the content manager, never by injecting components into the window's component.
Files rewritten: the server enumeration, the injected prompt, the God Mode predicate, the launch configuration builder, the session launcher, the launch options, the integration's persisted state, its settings section, and the settings menu in the page.
Files deleted: the per-server rule type and the known-tools inventory field.
Reused as they are: the plugin installer and its server controls, now serving only the JetBrains server; the single UI-thread helper; the installed-plugin lookup; the plugin id helper; the wire JSON model; the git history service; the action invoker; the forge navigator; the review diff opener; and the anchored allowlist pattern used by the vulnerability-prompted actions, including its per-token and cardinality ceilings.
Gates that move with it: the package dependency contract gains constants for the two new packages and their allowed edges, with the pure core added to the platform-free list; the file size ceiling applies as everywhere else; the no-comments and reachability contracts apply; and the coverage floors need their exclusions reviewed before the first MCP sprint, because the controller package will be IDE-bound and therefore hard to unit-test at the same level as pure code.
Capability degradation: a domain whose plugin is missing does not appear in its server's domain list at all, and a server with no live domains is not started. Discovery is by plugin id against the verified list for Docker, Kubernetes, the database plugin, the HTTP client, the configuration-language plugin, the cloud toolkit, the JetBrains MCP server, the terminal and git.
- The TOON codec against the specification's published fixtures, plus a round trip of our own.
- One-line framing with a payload containing an escaped newline.
- The same server answering a legacy handshake and a modern discovery.
- An unknown tool answered as a protocol error; a failed execution answered as a tool error with actionable text, never an escaping exception.
- Truncation that announces itself.
- A stable order in the tool listing.
- The four sockets removed on dispose.
- No tool present in two catalogues; a server with no live domains does not start.
- A dangerous input inside a nested argument of the dispatcher is refused by the guard.
- No file outside the database gateway names the database plugin; no file in the git read package gains a write API.
tools/listreturns exactly three entries per server, the injected block names no tool, and no domain exceeds four tools. If any of those grows, on-demand discovery has broken.
- The exact split of domains under the four-tool ceiling, taken at the start of each sprint.
- Whether the SQL argument is renamed so it inherits the guard's existing verdict.
- A stronger key derivation for the plugin passphrase, which would add a dependency and is decided separately.
- Whether a completion tool is viable at all.