Skip to content

release: 6.0.0 — Claude becomes one with your IDE - #76

Merged
serialexperimentslainnnn merged 264 commits into
developfrom
feature/release_6.0.0
Sep 12, 2026
Merged

serialexperimentslainnnn merged 264 commits into
developfrom
feature/release_6.0.0

Conversation

@serialexperimentslainnnn

Copy link
Copy Markdown
Owner

Summary

Release 6.0.0. The plugin runs four MCP servers of its own inside the IDE (code, run, vcs, ops, 178 tools in 55 domains, on by default as Claude God Mode), mirrors every action in the IDE without taking the focus, draws the IDE calls as cards with links into the IDE, drives pull requests and releases through the IDE's GitHub account, and passes every IDE call through the guard inside the servers. Underneath: the code is restructured one responsibility per file, the page is TypeScript, the plugin log has a Log view, and no deprecated or internal platform API remains. Full text in CHANGELOG.md and RELEASE_NOTES.md under 6.0.0.

Related issue

n/a

Type of change

  • New feature
  • Refactor (no behavioural change)
  • Bug fix
  • Docs / build / CI
  • Security fix

Risk and rollback

Risk: the IDE integration is on by default; a session that could not reach a server says so and falls back to the native tools. Settings gain ideMcp fields with a catalogue marker; an older state is adopted into God Mode on first load. The permission surface is unchanged: every own call is judged by the same guard.

Rollback: a user stays on 6.0.0 until they update; turning God Mode off (the flame, or Settings) disables the servers and the rules without reinstalling. Settings written by 6.0.0 are read by older builds as unknown keys and ignored.

Checklist

  • PR targets the develop branch.
  • Commits follow Conventional Commits.
  • verifyPlugin is Compatible across 253 → 263.* on IU and PY, experimental API only.
  • detekt, spotless, eslint and prettier pass locally.
  • No new deprecated or scheduled-for-removal IntelliJ Platform APIs.
  • Tests: 4799 JVM tests, 681 frontend tests, green.
  • CHANGELOG.md and RELEASE_NOTES.md carry the 6.0.0 block, dated.
  • No secrets, tokens, transcripts or personal absolute paths in the diff.

How was this tested?

  • Unit tests and frontend tests
  • Smoke test on a real IDE install: every tool of every domain exercised once through the reinstalled plugin, with the fixes committed on this branch.

Notes for reviewers

This pull request was opened, will be commented on, watched and merged through the plugin's own pull_request_ops tools from inside the IDE.

The twelve onX handlers and their helpers move out of the orchestrator into ConversationEvents, ToolEvents, TaskEvents, SignalEvents and ControlEvents; onEvent is now the seven-arm dispatch the map always described it as. agentStatusOf becomes AgentStatus.parse next to the enum it names. The collaborators the handlers read become internal instead of private; nothing else about them changes.
…e signals leave the orchestrator

SessionLifecycle owns the process, the launch gates, the credential question and what a terminated process means; BinaryCatalog owns what initialize told us and the request that fills it; SessionSignals holds the rate limits, session state, auth status and the last cost and context usage. ClaudeSession keeps its verbs as one-line delegations and is down to wiring, state holders and the event dispatch. The three contract tests that scan the lifecycle follow it to its file.
requestInitialize hand-rolled its control request, so its callback ran on the process reader thread: it wrote the five non-volatile catalogue fields there and, when the binary's model list lacked the pinned default, called changeModel, whose fireState walks the Swing hierarchy and the tab list from that thread. Every other question already rides SessionQueries.ask, which hops to the EDT before the callback; initialize now does too, as one more entry in the Asks catalogue.
stop() left the three PollSchedule timers running: with an unfinished subagent on disk the revival poll kept re-reading every agent transcript every five seconds for the rest of the tab's life, and the quota poll kept asking a process that was gone. The next launch re-arms what it needs the moment a prompt goes out, so stopping them costs nothing.
Disposing a session terminated its process but never remembered it was disposed, and never cancelled a sign-in in flight. A user who pressed Sign in and closed the tab while the browser leg was open got, on success, restartSession() -> start() -> a fresh claude process for a session no manager listed any more: alive until the IDE exited, burning quota and holding the project directory. shutdown() now records the disposal and cancels the login, start() refuses on a disposed session, and the sign-in's restart is skipped once the chat is gone.
…hdogs

failAll cleared the pending map and invoked the callbacks, but the thirty-second watchdog each request had scheduled was only reachable from inside the pending entry, so every in-flight request left a task on the scheduled executor to fire against nothing. A restart loop accumulated them. The watchdog travels with the entry now, and one settle path cancels it whichever way the request ends.
EditSnapshotStore kept the full pre-image of every edited file for as long as the session lived and nothing ever cleared it, while the transcript that could still show a Restore for those rows is capped at two thousand entries. The store is an LRU of five hundred snapshots now, which is more than the cards a transcript can still reach; a Restore on an older card takes the existing 'could not revert' path. Disposing the session empties it outright.
Safety guardrails were at risk of reading work on the guard as offensive
tooling and refusing or interrupting it, which stalls maintenance of the
control itself. The directive now says explicitly what the guard defends
against and that working on it is authorised defensive engineering.
It is itself a guardrail — the last one between a compromised or
careless session and the machine — so treating work on it as suspect
weakens the layer that stops bypasses.
SessionPersistence takes restore, the open-tab record and the title (rename and the generated one). The forwarders are gone and every caller reaches the owner: cards for pending permissions and edit snapshots, tokens for the counters, diffs.refreshAfterRewind, rollback, lifecycle for isStarting/refreshBootState/dismissLoginCard, turnControl.interrupt, agentScanner.scan, and ClaudeSettings — through the now-public project — for provider, checkpointing and the guard state. The two constants with a single user move there (EXPIRED_TOKEN_NOTICE to ConversationEvents, the control timeout to SessionControlClient).

The detekt baseline is emptied: neither LargeClass nor TooManyFunctions holds on ClaudeSession any more, which was the agreed signal that the split is done.
SessionProcess owns the ClaudeProcess handle, the launch generation, spawning for one generation and what an exit code means; SessionLifecycle keeps the policy — when to start, the gates, the boot state, stop and shutdown. The generation is still bumped at the same points (start, the first line of stop and of shutdown), so a late exit callback is ignored exactly as before. refuseRemoteProject no longer clears a starting flag that is never set when the gates run.
LoginCoordinator keeps the public surface, the route decision and the sign-in state. LoginAttempt (LoginFlows.kt) is the single PTY flow; the card and the dialog are two LoginUi implementations of it, so the two near-identical flows collapse into one. SignInCompletion is the verify-and-vault step. SessionNotifier gains the two notifications with an action that were built inline.

Deltas from folding the dialog flow into the shared one: its code now goes through submitCode, so the 45 s verify watchdog covers it too; the dialog path stores the setup token like the card path always did; and a card sign-in that fails verification no longer raises a balloon on top of the card's own error.
…n the EDT

Pressing Sign in ran resolveEnv() — which sources the user's env script and waits up to 15 s for it — and PtyProcessBuilder.start() on the EDT; verifying a finished sign-in resolved the env on the EDT again. The attempt now starts on the pool and reports back through edt; a cancel that lands while the spawn is in flight terminates the fresh PTY instead of leaving it orphaned, and a second press while one is starting is ignored. EdtAuthContractTest pins both hops.
…reader

The whole read loop sat inside one runCatching, so an exception from a listener callback (onToken writing to a locked PasswordSafe) ended the reader: the code prompt that followed was never parsed, and the flow then reported the exit code as a clean success. Each callback is now isolated and logged at warn, the reader keeps going, and scanning stops once the URL, the prompt and the token have all been seen instead of re-parsing the accumulated output on every chunk. The PTY spawn is injectable so the reader has a test.
cancel() destroyed the PTY and the reader then reported the killed process's non-zero exit as a failed login: the card flipped from idle to an error after the user had cancelled, and the dialog route showed a 'Login failed' balloon right after 'Login canceled'. A cancelled flow now reports nothing.
SessionListing owns 'Open previous session': the newest files with title, first prompt, branch and timestamp. It reads each line through SessionTranscriptReader.parseRecord — the one JSONL line parser, now exposed — instead of carrying its own Json instance, and still stops at the first line that completes the three fields. EntryDTO and SessionRef move to their own file.
ProtocolParser keeps the top-level and system tables and the two status notices. MessageParsers takes assistant, user and stream_event; ControlParsers takes the control frames and the rate-limit event, with control_request going through a subtype table like everything else, so a new control subtype is a row and not another branch in the one function that grows per feature. unwrapToolError had no caller outside the parser and is private.
Msg is its own sealed hierarchy (ui/jcef/Msg.kt) and JcefBridge only parses. SessionControl gains three sealed sub-families — Vuln, Navigation, Onboarding — so the router's else-chain onto three boolean handlers is gone: every when is exhaustive and a new message that nobody handles is a compile error, not a warn at runtime. ChatBridgeRouter is the seven-arm dispatch; each family has a Bridge* handler in ui/. The whitelist and suspension arithmetic moves to GuardWhitelists and SecuritySuspensions as pure operations on State; the live half of a settings-menu toggle is the SettingsMenuApply table; the panel registry and its broadcasts are LivePanels; VulnPromptedActions lives in vuln/ next to what it quotes; pooled payloads go through JcefHost.execBuilt, which skips a disposed browser.

A settings toggle now learns whether the key is known by applying it to the live state once, then persists through update {}, so the block handed to update is pure on State.
The card's file_path comes from the tool input, and View diff read it synchronously on the EDT with no containment check and no size cap: a card naming a file under the home directory opened it in a diff, and a large one froze the UI while it was read. DiffPresenter.readCurrent decides — inside the project root, under 1 MB, absent files diff against nothing — and the read happens on the pool; openDiff is then handed the text it already has.
… the page are their own files

PageAssembly builds the document and its CSP (appNames and CSS_PARTS live there now, which is where the frontend test loader and the guard-view contract read them). PageRoute is the ladder's pure part. SchemePageServer is the custom-scheme registration and its resource handler. PageDelivery is the ladder itself: the watchdogs, promotion, the loopback server and which rung has been proven. JcefHost keeps the browser, the JS query, exec and its queue, the deferred blocks and the load and navigation handlers. util/Edt.kt gains edtNow — run inline on the EDT, hop otherwise — which replaces the host's private copy.
The replacement chat for a closed last tab is built into a component that is not on screen yet. JBCefOsrComponent.addNotify is what creates the browser, and the 2.5 s ready watchdog was armed in deliver(), before that — so on a cold CefApp, or right after disposing the previous browser, the deadline fired before the first navigation could start and promote() walked the ladder. Its last two rungs, INLINE and NOTICE, loaded through loadHTML, which navigates to file:///jbcefbrowser/… and is refused by the host's own navigation guard: ready stayed false, the queue never drained, no script ran — no composer, no tabs, 'loading' forever — until Open previous session built a browser on a warm CefApp that met the deadline.

The watchdog is now armed on the main frame's first onLoadStart, when a browser exists to load; the two dead rungs and the RemoteDevNotice page they served are gone (PageRoute is SCHEME → LOOPBACK → nothing); exec runs nothing in a disposed host and the unreachable JcefHost.dispose() is folded into the Disposer registration; and ChatTabsPanel.close opens the replacement in a finally, so a teardown that throws still leaves a chat on screen.
… switch on

GitAction.behaviour is sealed — InitRepository, Prompt(text), Ide(actionId), Host(run) — and perform() is one exhaustive when over it. The two when(action.id) blocks that duplicated the catalogue as arms (and answered a new row with 'No prompt is wired') are gone: a row now says what it does where it is declared, and a row without a behaviour does not compile. Kind and ideActionId are derived so the catalogue's readers and its test keep their vocabulary. GitInit owns the one process the plugin spawns and the VCS mapping that follows; IdeActionInvoker owns the platform-action invocation. IdeActionApiContractTest follows both.
refresh() was reentrant only by a flag: a second caller arriving while a snapshot was being collected had its callback dropped, and the re-run answered the first caller twice. With two chats open, the one that asked second never repainted its Git card after a branch change. Callers now queue; each is answered by a collection that started after it asked, exactly once.
Four files in main and three tests still carried prose: the mechanism each one held (what UntrustedState disarms and why, why the trust gate leaves the binary paths out, why session-scoped suspensions are keyed per project, what the two reachability scans can and cannot see) now sits in the package maps next to the file it describes. One line in ClaudeProcess was the orphaned half of a sentence the previous sweep cut.
SettingsMenuRows builds the JSON the page draws; JcefSettingsMenu keeps the key vocabulary and applies a toggle to State. Both were one 300-line object with two unrelated reasons to change.
…cefHost

PageLoadHandler holds the main-frame failure verdict and reports three outcomes: started, arrived, missed. installNavigationGuards is the pair of CEF handlers that refuse any navigation off the plugin's own page. JcefHost keeps the browser, the queue and the deferred blocks. PageStateRecoveryContractTest follows the handler.
… session

The two forSession factories take the lambda plumbing out of ClaudeSession, which is left with declarations and its verbs. The injected-lambda constructors stay, because the tests drive both through them.
GuardFixture holds the base policy and the read/bash/edit builders; GuardProbe is the base class the guard tests extend for v/rule/why against their own policy. The same five helpers were copied into 27 files. No assertion changes: every test keeps the policy it had, spelled as basePolicy().copy(...) where it differed from the base.
Thirteen names promised an ASK, a card, a third-party distinction or a per-caller behaviour that the suite deleted releases ago while the assertion underneath said DENY. The verdicts are untouched; the names now say what is asserted, so the next reader does not trust the sentence over the code. SecurityRuleFamiliesTest also joins the shared fixture, the one file the previous commit missed.
…ALLOW'

Eight fuzz tests asserted only that a vector did not pass, so a vector caught by the wrong rule — whitelistable under a different label, reported under a different reason — read as green. Each now asserts DENY and the rule (or, where the bases mix families on purpose, the small set of rules they belong to). The unreadable-script fuzz pins PRIVILEGE_ESCALATION for its sudo variant, because the command families are judged before the script is opened; that order is the guard's, and the test says so.
…lank

A dash stood for false, so an enabled column full of false read as "not
applicable" and truncated=false as nothing at all; Lain, 2026-09-12,
looking at service_actions. True stays a tick.
… the 250-line ceiling

tools.css had reached 255 lines and the frontend size gate was red.
…ns says which context judged

Overriding the selection keys on top of the tree's context replaced
what the platform's selection rules had derived from the real selection,
which is what a plugin's actions read. The tree is matched by its
selected item, by identity or by name, and used untouched; the synthetic
context stays for a view that is not showing. The answer now carries
context=tree|synthetic so a disabled column can be read.
… Requests view

Github.PullRequest.Show is enabled only by the list's own context, which
derives the request id from the selected row; the view model behind it
is Internal at 262, so the row is selected in the list itself and the
action fired with the list's context, as a click would. The browser is
the fallback when the view does not list the number; shown says which.
Lain, 2026-09-12: "abrela en el ide".
…ata, and what a release leaves behind

Two vcs domains. pull_request_ops creates, comments on, watches and
merges a pull request through the IDE's GitHub account: GHGQLRequests
create and mergeabilityData, the REST comment and merge-commit requests,
all public in the GitHub plugin at 262. pr_checks polls until the checks
settle; pr_merge refuses unless the merge state is clean and no check
failed, and says that merging can publish. release reads tags, workflow
runs and a Release through the executor's generic GET with the same
account, and the plugin's Marketplace versions through the public API
with no token. Lain's condition for publishing 6.0.0, 2026-09-12: the
whole chain from the IDE, never gh.
The previous README described a chat with an optional MCP switch that
was off by default; 6.0.0 is a different product. The manual is now
organised by what the user asks for, in their own words, and opens with
the part nobody could infer from a tool list: Claude opens files, takes
you to a line, shows a commit in the Log, a diff in the diff viewer, a
pull request in its view, and drives the IDE's windows, settings and
actions for you without moving your focus. Counts (178 tools, 55
domains) come from IdeRule; setting labels match the settings page; the
tool-by-tool reference stays in docs/SKILL_INVENTORY.md.
Both blocks still called 6.0.0 a polish release, which is what the
release job would have put in the GitHub Release body and the
Marketplace's What's New. They now name the plugin's own MCP servers,
God Mode, the mirror, the cards, pull requests and releases through the
IDE's account, the guard inside the servers and the three guard
behaviours of this release, and are dated. The Problems view and the
opt-in log stay out because they did not ship.
@serialexperimentslainnnn

Copy link
Copy Markdown
Owner Author

This Release is being driven by Claude Code Native Plugin.

Comment thread src/main/kotlin/dev/lain/claudejb/controller/mcp/IdeMcpService.kt Dismissed
… the guard sees an own call flat

The guard reads a tool call's path and content keys at the top level,
which is where the native tools carry them; an own call carried them one
level down, under args, so a write judged through the meta-tool did not
look like a write to it. OwnTools.guardInput lifts the arguments beside
the tool name before either gate (the server's GuardGate and the
session's broker) evaluates the call, so both shapes get the same
verdict. Independently, the tools that create or move files now refuse a
destination outside the project root, as delete_file and the VCS write
tools already did: write_file, create_file, replace_text, insert_text,
move_file, file_from_template and worktrees add. GuardGateTest pins the
nested write with a probe that reports the target missing.
CodeQL on the release pull request: the per-session socket directory was
created owner-only, but its parent under the system temp directory took
the default permissions, so other local users could list the session
ids. The parent is now created owner-only and, when an older build left
it open, tightened on the next start.
…instead of opening the browser

The GitHub plugin replaces the list with the request's details inside
the same content, so the second open of the same number found no list
row and fell back to the browser. When no row is found, the tool now
looks for the component of the Pull Requests window whose context
enables Refresh Pull Request and fires it there (shown=refreshed); the
browser is only for an IDE without the view.
A Unix socket takes the process umask at bind, which the plugin does not
control; the owner-only directory around it was the only thing keeping
other local users from connecting. The endpoint now sets the socket to
rw------- right after bind, so the file defends itself whatever the
umask, and ServerEndpointTest pins it.
@serialexperimentslainnnn

Copy link
Copy Markdown
Owner Author

Re the CodeQL alert Local information disclosure in a temporary directory (SocketHome.kt): solved in 477f269 and b87f3d3.

  • 477f269 — the parent directory under the system temp directory is created owner-only (rwx------), and one left open by an older build is tightened on the next start. SocketHomeTest pins both.
  • b87f3d3 — the socket file itself is set to rw------- right after bind, so it does not depend on the process umask any more. ServerEndpointTest pins it.

Three owner-only layers now sit around every socket (parent, session directory, socket file), plus the per-request token.

…still without the focus

ToolWindow.show marks the window visible but leaves another window
selected on the same side, so 'show me the pull request' answered shown
while the user kept looking at Bookmarks. Every reveal of a tool window
now activates it with autoFocusContents=false: the window takes its
place in front like a selected tab, the keyboard focus stays where it
was, and the focus contract keeps refusing activate(..., true).
…n it is already open

View Pull Request opens the request as an editor tab named by its
number; asked again while that tab sat behind others, the tool refreshed
the details panel instead of putting the tab in front, which is what
'show me the pull request' means. The tool now selects the open tab
first (openFile without focus, like a source file), opens it from the
list otherwise, and answers none rather than refreshing anything.
…without its defaults

The models were only ever decoded, so the encoders the serialization
plugin generates never ran and the package sat under the branch floor.
Each model is now encoded and decoded back, full and empty, under the
protocol's Json and under one that omits defaults; the usage window and
rate-limit helpers get their branch cases too.
OsvScanner reached OsvHttp through a plain call, so nothing could stand
in for the network and its 45 lines were never run. The call is now an
injected function, OsvHttp.post by default, and the test drives the
batch, the hydration, the silences, a malformed answer, cancellation at
both points and the cut above the hydration cap.
…udit reads it first

EnvScriptLoader.load ran a shell nobody tested, and SourceScriptAudit
had no test at all. A temporary script is now sourced on POSIX and its
export comes back; a blank, missing or exiting script yields nothing;
the audit passes a harmless script, names a destructive one and ignores
what it cannot read.
…ugh a real control client

SessionQueries and RemoteControl were only reached through the live
session. They now run against SessionControlClient with a fake
scheduler: each ask writes its subtype and decodes its reply on the EDT,
a stopped session answers nothing, the usage reply feeds the quota
warnings, and Remote Control records state, error and the notice for
every outcome. The protocol round-trip test takes the formatter's shape.
…d the gate probes the stand-in binary

The login coordinator built its attempt and the attempt built its PTY,
so no test could stand between them. Both take a factory now, the real
ones by default. A scripted process plays the binary's own output: the
url and the code prompt reach the ui, the pasted code reaches the pty, a
verified login restarts the chat, a failed one carries the binary's
wording, cancelling forgets the attempt, and another provider is told to
use its key. The gate's probe, renewal short-cuts and the dialog login
ui are driven against fake-claude and the test dialog manager.
…d tool window

A Pull Requests tool window with a list of rows is registered in the
headless fixture: the request's row is selected and yields the list's
context, an unknown number yields none, the window opens with and
without focus, and an open timeline tab is selected while a missing one
is reported.
…un against the headless IDE

A target's parsing and the contexts it yields (a file with its editor,
PSI and caret element, a directory, the project), the reveal of a file
and of a registered tool window and its content, an action dispatched
and a toggle flipped through IdeActions, and every verb of the card
places against the windows the fixture has and lacks.
…eveal test stops depending on test order

NoticeNarrator had no test of its own: status notices, notifications
loud and quiet, denied permissions with their best reason, recalled
memories, persisted and failed files, plugin installs by status, both
refusal shapes, informational notices by level and the logged rest. The
reveal test no longer asserts on windows another fixture may have
registered in the shared light project.
…nd the coverage gate measures what can run

MarketplaceGateway takes its fetch as a parameter, the HTTP one by
default, so the update rows, their defaults, a dead endpoint and a
reply that is not a list are all tested; GitHubAvailability runs
headless with and without the plugin. Two corrections to what kover
measures: the uiTest source set was read as production code and showed
up as a package at zero, and GitHubGateway joins the gateways that need
a live plugin, an account and the network, on the same grounds as the
database and OSV ones. No floor moves.
@serialexperimentslainnnn
serialexperimentslainnnn merged commit f7c0b19 into develop Sep 12, 2026
11 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants