Files
fleetd/CLAUDE.md
T
Dai Ha 457458437f
CI / contract (pull_request) Successful in 1m12s
CI / build (pull_request) Successful in 1m31s
#362: make the plugin visible, and fix the drift that made it unusable
CB-527 shipped a Claude Code plugin and a marketplace in this repo. Nothing in
CLAUDE.md or docs/ ever named it, so a later session planned the same feature
from scratch. The wiki Features entry existed and was correct, but wiki/ is a
submodule whose pointer is never advanced, so no session reads it.

Visibility:
- CLAUDE.md addendum now names plugin/ and both structural limits, so every
  session sees it. This is the change that stops the rebuild happening again.
- wiki/11-Features.md records the rename and why the entry alone was not enough.

Drift (each measured against the code, not assumed):
- mount name fleetd -> fleet, matching PeerLauncher.MCP_MOUNT_NAME. The old name
  gave a lead with both a project .mcp.json and the plugin two mounts of one
  daemon and a duplicated fleet_* tool set.
- url is now ${FLEETD_MCP_URL} instead of a hardcoded address, so one plugin can
  serve hosts running the daemon on different ports. Plain ${VAR}, the form
  kb-alms proves works here; ${VAR:-default} is untested and not used.
- plugin claude-bridge -> fleet, marketplace claude-bridge -> fleetd, version
  0.2.0. Breaking for a 0.1.0 install: mcp__fleetd__* becomes mcp__fleet__*.
- README install path ltms/claude-bridge -> the fleet/fleetd remote.
- the setup skill's §5 told operators to pin primary.terminal:. CB-579 replaced
  that with fleet.leaders.*.tab. Replaced, with the duplicate-tab warning (#359).

Scope: the plugin is lead-side only, and cannot be otherwise. The launcher adds
--agent only when <worktree>/.claude/agents/<role>.md exists in the member's own
tree (ClaudeCodeLauncher.java:371,391), and a member's CLAUDE_CONFIG_DIR points
at its profile's config dir (ClaudeCodeLauncher.java:285), so a member never
reads the operator's plugin store. On this Mac all four Claude profiles set
configDir, and the four ccs instances hold four separate copies of the plugin
store -- same md5, different inodes. Seeding member skills through the worktree
is #362 scope item 3, implemented separately.

Note for anyone verifying a plugin: `claude plugin validate` does NOT read
.mcp.json. Replacing it with `{ this is not json at all` still passes, exit 0.

Refs #362, #359
2026-09-05 12:42:20 +07:00

28 KiB
Raw Blame History

claude-bridge — project instructions

Bridge communication (enforced — read this first)

Canonical block. Everything down to §Layering is the portable bridge charter, copied verbatim into every project that mounts the bridge MCP. Keep it byte-identical with the template in the wiki (Use Cases → The portable CLAUDE.md block); improvements go to the template first, then out to each project. Anything specific to this repo lives under §Project addendum below, never inline above it.

If no fleet_* MCP tools are mounted in this session, this section does not apply — skip it.

fleetd is the sole communication gateway between agents here. The orchestrating session (the primary) and every delegated peer (a member) mount the same MCP server and talk only through its fleet_* tools. No session addresses a peer, a broker, or the network directly.

Which role am I? — settle this before acting

Every role reads this file. A member runs in a git worktree of this same repo, so it inherits this CLAUDE.md verbatim, and every rule below is role-conditional.

Call fleet_whoami. It returns primary, worker, or architect, resolved by the daemon from your connection — unforgeable, and the same resolution its authorization gate uses. A worker also carries its sessionId, profile, worktree and branch; an architect carries the slot name it was bound to. Don't infer what you can ask.

Only if that call is unavailable, fall back to these — each is one-way, so keep reading until one fires: the reply charter in your system prompt ("You are a spawned member in the claude-bridge fleet") ⇒ spawned member; fleet tools prefixed mcp__fleet__* ⇒ spawned member (the launcher fixes that mount name; a primary's mount is named by whoever wrote its .mcp.json, so it varies — and a member spawned before CB-632 still says mcp__bridge__*); ANTHROPIC_BASE_URL set ⇒ spawned member (Claude-model members run on a clean env, so its absence proves nothing). None of these separate a worker from an architect — only fleet_whoami does. Still unsure ⇒ act as a worker, the most restricted member role. The two mistakes are not symmetric: a primary acting as a worker is refused by the authorization gate — loud and self-correcting — while a member acting as the primary ends its turn with no fleet_reply, and the sender silently receives nothing. Fail toward the recoverable error.

Invariants — both roles, no exceptions

  1. Never set, export, or forward ANTHROPIC_BASE_URL (or ANTHROPIC_AUTH_TOKEN). The primary stays on subscription; only the bridge puts a member off it, at spawn. Mounting the bridge must never move a session across that boundary.
  2. The bridge is the only channel. Text you print in your terminal reaches nobody — the other side cannot see your screen. An answer that isn't in a fleet_* call is silently discarded.
  3. Identity comes from the connection, never an argument. Workers never pass a target; you cannot act as another session. Spawn/stop/drain are lead-only; send is lead or architect; reply/ask are only-as-itself — any peer may answer for its own pane, and for no other. A call outside your role is refused, not queued.
  4. Delivery is status-gated: one message per turn. Don't busy-poll a peer's terminal and don't re-send because a call looks slow — the bridge delivers when the peer is idle, blocked or done. A spawned member must also have mounted the bridge MCP: until it has, it is not deliverable, and a send waits on that gate for ~60s and then fails without ever reaching its pane.
  5. Never drive the terminal multiplexer directly (no herdr CLI, no socket). The bridge owns policy; the multiplexer owns PTYs. Going around the bridge bypasses every rule above.

Primary (lead) — run this on every task, in order

Delegate by default — that is the job. With the bridge mounted you are an orchestrator on a metered subscription, and workers are cheap, parallel, and disposable. The default answer to "who does this?" is a worker, not you. Reach for fleet_send before you reach for Edit. The steps below are the procedure — run them in order, every task, not only the big ones.

  1. Know your role — fleet_whoami, once per session, before anything else.
  2. Split. Write the unit list. Every unit carries: scope · the files or PR in question · acceptance criteria · exactly what to report back. A unit with no acceptance criteria is not ready to delegate — refine it or keep it.
  3. Gate each unit on one question: "can I write a brief good enough for a worker to succeed?" — not "could I do this faster myself?" (usually you could; doing it yourself costs your context and your subscription, while a wasted worker turn costs a worker turn). Yes ⇒ delegate. The keep-list is closed: the conversation with the user, decomposition and planning, the final judgment call, verification, merges, and anything that depends on context only you hold. Nothing else is yours by default.
  4. Spawn every delegated unit first — fleet_spawn{profile, worktree:true, ticket}, one per unit, before sending any. Pass profile explicitly: profiles differ in model and cost, not in tier, so the default is rarely what you want.
  5. Then send them all — fleet_send{sessionId, content, wait:false}. Line 1 of every brief is Load the <name> skill. naming the worker's playbook; those skills are opt-in and that line is what makes them reliable. Where the project ships no such skill, spell the procedure out in the brief instead. The brief is self-contained — the worker sees your message and the repo, nothing of your context, your plan, or your screen.
  6. Collect — fleet_poll{ticket} → fleet_ack{target, msgId}. Answer a worker's fleet_ask with fleet_send{turnId, content} — not sessionId. A worker gone quiet is diagnosed with fleet_status, never by reading its terminal; it also reports an open question and the turnId that answers it. A worker's ask waits ~55 seconds, and no nudge makes that longer — so never brief a worker to "ask me". Decide before you delegate, or give it an explicit default.
  7. Verify yourself. Re-run the build and the checks. A worker cannot run your IDE tooling, any forge tools it appears to have hold a blocked credential and fail, and a piped command (… | tail) hides failures behind a zero exit — never promote a worker's "clean" to a fact.
  8. Review — fan out. Spawn reviewers against the diff, one per dimension or per file, with wait:false. Never the implementer of the scope it reviews, and brief them from the diff — not from the implementer's rationale, which carries its own blind spot. Dispatch each PR's reviewers as it lands; don't wait for the last implementer. Under ~50 changed lines, skip the fan-out and read it yourself.
  9. Adjudicate, merge, tear down — yours alone. Read the diff yourself: fully if it is small, targeted at the reported findings and the risky paths if it is large. Reviewer findings direct your attention; they never substitute for it. Then merge, then fleet_stop{paneId}.

Steps 3 and 4 are separate on purpose — spawning and sending in one loop is how parallel work silently becomes serial, and it is the most common way this layer is wasted. For the same reason, prefer wait:false + fleet_poll for anything non-trivial: a blocking fleet_send is capped by your own MCP client call timeout (~60s), well below the task's real runtime.

Delegating does not delegate responsibility. Workers open PRs; you are the gate. Never delegate the merge — and merging on a reviewer's word is delegating it by proxy.

Intent Tool
Confirm your own role fleet_whoami
See backends available fleet_profiles
Start a member fleet_spawn{role?, profile?, cwd?, worktree?, ticket?, sessionName?, resumeSessionId?} → sessionId + paneId
See the fleet fleet_list → leads (your peers) + members (each carries agentSessionId when its backend knows one) · one peer's state: fleet_status{sessionId}
Delegate (blocking) fleet_send{sessionId, content}
Delegate (long task) fleet_send{sessionId, content, wait:false} → ticket → fleet_poll{ticket}
Answer a member's fleet_ask fleet_send{turnId, content} — not sessionId
Message a peer lead on this host fleet_send{sessionId: <their terminal>, content} — fleet_list → leads reports it. Coordination only, never a task
Message a peer lead on another daemon or host fleet_send{coordId: <their coord-id>, content} — needs a coordinator: block; your own coord-id is in fleet_list. Coordination only, never a task
Answer a peer lead that messaged you fleet_reply{content} — the one case a lead replies
Collect a held reply fleet_poll{target} · then fleet_ack{target, msgId}
Tear down a member fleet_stop{paneId}

Lead ↔ lead — coordinate, never delegate

fleet_list returns leads alongside members; your own row carries self: true. Every other row is a peer — an orchestrator with its own context, its own members, and its own judgment. An empty members array means no members are spawned; it says nothing about peers.

A lead never assigns a task to another lead. Work goes to members — only ever downward, never sideways. Sending a peer a brief with acceptance criteria is a category error: a brief is a member's artefact, and a peer is not yours to task. If a unit needs doing and it falls in your area, spawn a member and delegate it yourself; if it falls in the peer's area, say so and let the peer assign it. The traffic between leads is coordination and nothing else:

  1. Divide the map, not the work. Agree who owns which area, then each of you assigns inside your own. Split by context ownership — whoever already holds the context owns that area — and say who takes what, in one message, before either of you starts. Two leads silently working the same unit is the failure mode here, and neither notices until the merge.
  2. Share findings, hazards, and corrections. What you have already discovered, what broke, what the next person will trip on. This is the traffic that actually pays for the channel: it costs one message and saves a peer a rediscovery.
  3. Verify a peer exactly as you verify yourself. Peer status buys nothing: check the claim against the code, and re-run the build. A peer's correction gets the same treatment — right or wrong on the evidence, not on who said it. Neither of you merges the other's work unreviewed.

Being messaged by a peer does not make you its worker: answer with fleet_reply, and push back on the substance if it is wrong. A peer that simply complies has thrown away the reason there are two of you.

Member (worker or architect) — the turn contract

  1. Load the playbook skill the lead named before doing anything else.
  2. Do the assigned scope only. Note anything you spot outside it in one line; don't go hunt it.
  3. fleet_ask{question} when a decision is genuinely the lead's (ambiguous requirement, two defensible fixes, "bug or intended?"). It blocks and you resume the same turn with the answer. Don't ask what you could decide yourself.
  4. End the turn with exactly one fleet_reply{content}, carrying your complete answer. This is the whole handoff. No fleet_reply ⇒ the sender gets nothing and the exchange stalls. Do not lean on the completion fallback to carry your answer for you: when you end a turn without replying, the bridge scrapes your pane, and it can return only the last 4000 characters. A clipped scrape is marked as partial, but the missing text is gone — your report reaches the lead with its end cut off.
  5. Report honestly. State only what you actually ran and its real output, including failures, and never claim the result of a check you had no way to run. Measure your own tools; do not assume them. What you mount depends on your backend: an opencode member gets the bridge and nothing else, while a Claude Code member also inherits the operator's user-scope MCP servers, which the bridge never chose for you. Two rules follow. The primary's IDE tooling is still not yours, whatever you see. And a mounted tool is not a working tool — the forge server you may find there holds a deliberately blocked credential and fails every call, by design.
  6. Never merge. Stage files explicitly — never git add -A — and leave alone anything the project marks as not-yours-to-commit.

Where each rule lives (don't duplicate — extend the right layer)

Layer Scope Reaches
the launcher's reply charter the one rule that must survive with no repo: end every turn with fleet_reply every spawned member, at launch, every peer kind — never a lead
this section protocol + orchestration policy primary and every member that reads the repo — tracked in git, so worktrees inherit it
role agent definition files role contract and per-job procedure a member whose launcher binds its role to the matching file in its worktree
role playbook skills per-job procedure (commit/PR recipe, finding format) a member told to load one
the bridge's own docs design detail, flows, error model on demand

A rule belongs in exactly one layer — the outermost one that must obey it. A member without a repo checkout still gets the launcher's reply charter, which is why that one rule stays there. Peers that don't read CLAUDE.md (non-Claude adapters) get the charter only, so any rule they must obey belongs in the charter, not here.

Project addendum — claude-bridge (not part of the canonical block)

  • This repo is the bridge. The daemon is fleetd, its MCP mount is http://127.0.0.1:8765/mcp, and the code behind the rules above is mcp/FleetMcp (tools), auth/Authz (the role table), mcp/ConnectionIdentity (connection→role), and worker/*Launcher (REPLY_CHARTER).
  • fleet_profiles/fleet_list report two separate outage states, and they are not the same thing. Quarantined (CB-578) means the backend told us it is out of capacity — a long, 1800s-default cooldown. Cooling off (fleetd #201/#227) means a profile's credential threw two distinct backend errors (a non-exhaustion failure such as an HTTP 5xx) within 60 seconds — a short, fixed 60s cooldown, not configurable per profile. Each check runs independently, so a profile can show both at once. In the JSON: a cooling profile carries credentialId and coolingOffForSeconds; a quarantined profile carries quarantinedForSeconds; a profile hit by both carries all three fields, and either state alone already sets that profile's free to 0. A fleet_spawn naming a cooling-off profile is refused before it ever reaches the backend adapter, with a message naming the credential and the remaining seconds ("cooling off after repeated backend errors") — distinct wording from a quarantine refusal, so don't conflate the two when reading a spawn failure.
  • Skills available to delegate: implementer (worktree → commit → push → own PR), reviewer (one diff → one structured finding) and hunter (sweep a package → several ranked findings, change nothing). Name exactly one in every delegation. reviewer and hunter are not interchangeable — reviewer caps the answer at one finding in about 90 words, so naming it for a multi-finding sweep hands the worker two contradictory output contracts. That has already cost three workers' turns: each wrote a good report to its terminal and ended the turn with no fleet_reply, and the scrape returned the tail of the brief instead.
  • Primary-side skills (not delegation playbooks — a worker cannot use them): port-to-opencode (make an OpenCode session a participant in this workspace) and fleets-status (report every fleet that shares one LavinMQ instance).
  • This repo is also a Claude Code marketplace, and ships a plugin. .claude-plugin/marketplace.json points at plugin/, which carries the MCP mount and the setup skill (/claude-bridge:setup — make any project bridge-ready). It was added in CB-527 and then went unmentioned by every instruction file, so it drifted and a later session planned it from scratch (#362). Read plugin/ before designing anything about onboarding a project. Two limits are structural, not bugs: a plugin cannot carry the role agent files, because ClaudeCodeLauncher.java:371 requires <cwd>/.claude/agents/<role>.md in the member's own worktree; and a plugin cannot deliver anything to members at all, because ClaudeCodeLauncher.java:285 exports CLAUDE_CONFIG_DIR and every Claude profile here sets it, so a member never reads the operator's plugin store. The plugin is the lead-side surface; member-facing assets travel in the worktree.
  • Never commit .mcp.json (the primary's local copy, flagged --skip-worktree) or wiki/ (a submodule with its own remote).
  • A provisioned worktree neutralizes .mcp.json, opencode.json and .autoenv — the repo's committed copies would otherwise mount the primary's IDE and forge servers (fleetd #134). The worktree's copy of each is a stub, not the repo's real file, so a worker that reads one and reports what it found is reporting on the stub. The daemon logs a per-spawn summary, but the worker cannot see that log. From inside its own worktree a worker — or a lead debugging one — reads the list with git config --worktree --get-all fleet.neutralizedConfig, and the consequence with git config --worktree --get fleet.neutralizedConfigNote. Never brief a worker to edit one of these files: the edit cannot be committed, and it will not tell you so.
  • Flows and the error model — rendezvous, fleet_ask, detached delivery, the turn-done fallback and status gating — are diagrammed in docs/MCP-Contract.md. That page is now flows only: its pre-build tool catalogue, parameter tables and REST paths were deleted rather than corrected, because a hand-maintained second copy of the tool surface is what drifted for a month while this line pointed every session at it (CB-609 / #114). The live MCP schema is the tool reference, with the intent→tool table above as the short form. McpContractDocTest fails if that page names a fleet_* tool the server does not register. The flows are kept out of this file because this file loads into every session's context.

Redeploying the daemon — the lead may do this (primary only)

A merge is not a deployment. The running fleetd holds the jar it was started with, so a feature merged to main does nothing until the daemon is rebuilt and restarted. Saying "shipped" about code the live daemon has never loaded is a false report. The lead may and should redeploy rather than hand the job back to the operator.

Workers must never do this. A worker has no business restarting the daemon it is talking through, and stopping it kills the worker's own channel mid-turn.

Use the script — do not hand-roll the steps.

scripts/redeploy-fleetd.sh --check   # report state, change nothing
scripts/redeploy-fleetd.sh           # build, confirm drain, restart, verify
scripts/redeploy-fleetd.sh --yes     # skip the drain prompt (fleet already checked)

It builds before it stops anything, so a failed build never leaves the fleet down; it waits for the old process to exit rather than assuming; it polls /healthz; and it anchors its log checks to a line marker taken before the restart, so old errors cannot be misread as new ones. Run --check first — it is read-only and reports whether the forge token resolves, which nothing else tells you.

The script encodes the five things below, each of which has gone wrong here before. Read them anyway: if the script is unavailable or a step fails, this is what it was protecting you from.

  1. Login shell, or workers silently lose their forge token. The daemon inherits WORKER_GITEA_TOKEN from the shell that starts it, and that comes from ${SHARED_ENV}/tools/secrets.sh. Start it from a non-login shell and the variable is empty, the daemon starts fine, and the failure appears much later as workers that cannot open a PR. Nothing logs this at startup — the script's --check is the only thing that reports it, and it checks whether the name resolves without ever printing the value.
  2. Drain live members first. fleet_list, then fleet_stop each member, and collect anything you still want with fleet_poll before you kill anything. A restart drops in-flight tickets and rendezvous, and a member's report is not recoverable once its ticket is gone.
  3. A restart is the only way deferred config keys take effect. That is usually the reason to do it. The startup log names which keys it accepted and which it deferred — read those lines rather than assuming.
  4. Re-check identity afterwards. Call fleet_whoami and confirm it still answers primary. The lead is found by its tab label (fleet.leaders.*.tab), and a lead whose tab no longer matches is demoted to worker, which refuses every orchestration call.
  5. Prove the new jar is the one running. Confirm a fresh fleetd listening line at the end of fleetd/fleetd.out, dated after the restart. An old daemon that never died looks identical from the outside.

Permission. A CLAUDE.md rule grants intent, not tool permission — the command classifier refuses a bare kill on the daemon whatever this file says. The script is the seam that fixes that: it is one auditable command, so the operator allow-lists it once instead of approving a stop and a start every time. The rule lives in the operator's Claude Code settings:

{ "permissions": { "allow": ["Bash(scripts/redeploy-fleetd.sh:*)"] } }

Granted by the operator on 2026-08-15. If a call is still refused, do not route around it by running the stop and start as separate commands — that is exactly the approval the script replaced. Say what you were going to run and why, and let the operator decide.

The prompt is part of the product — update it with the code (mandatory)

This repo is the bridge, so the canonical block above is not documentation about someone else's system: it is the instruction surface this codebase ships. Every change here must end by asking whether the block still tells the truth. A code change that silently invalidates it is an incomplete change — the agents reading it have no other source.

Before you call any work done, check the row that matches what you touched:

You changed… Re-read and update…
a fleet_* tool — added, removed, renamed, or its params/semantics the primary's intent→tool table; any rule that names that tool
Authz / the role table invariant 3, and the primary-only vs worker-only claims
ConnectionIdentity / how a caller is resolved the fleet_whoami paragraph and the fallback ladder
REPLY_CHARTER, or a launcher's mount/flags the fallback ladder (mcp__fleet__*), and the layering table's top row
the injector / status gating invariant 4
worktree provisioning or the parity overlay the "both roles read this file" premise — it rests on the worker's worktree being a checkout of this repo
.claude/skills/** the addendum's skill list, and the "name the playbook" rule
a new peer kind (non-Claude adapter) what that peer can read — anything it must obey belongs in its charter, not in the block
anything an operator can use, configure, or observe — an MCP tool, a fleetd.yaml knob, an endpoint, a visible behaviour Features — one entry: what it does · the knob that turns it on · why it exists · the gotcha

That last row is not bookkeeping. Chapters 1–10 answer how is this built and why this way; none of them has a home for what can it do and how do I turn it on, so for twenty tickets a shipped capability landed nowhere and the Roadmap went on claiming the stage was finished. The why line is the one that matters — without it a decision gets re-litigated from scratch a month later. Internal contract changes go to wiki/9-Implementation.md instead; test and coverage work is a Roadmap line. A change that touches none of the three earns no entry, and that is a normal outcome rather than an omission.

Then propagate: the block in this file and the template in the wiki (Use Cases → The portable CLAUDE.md block) must stay byte-identical, and other projects carrying the block need the same edit. Verify rather than trust:

python3 - <<'PY'
import pathlib
c = pathlib.Path("CLAUDE.md").read_text()
w = pathlib.Path("wiki/7-Use-Cases.md").read_text()
S, E = "## Bridge communication (enforced", "## Project addendum — claude-bridge"
block = c[c.index(S):c.index(E)].rstrip() + "\n"
i = w.index("```markdown\n") + len("```markdown\n")
print("in sync:", w[i:w.index("\n```\n", i) + 1] == block)
PY

IDE MCP tools & validation workflow (enforced)

Primary only. Workers have no IDE MCP mount — if you are a worker, skip this section and report the build/test output you actually ran (see §Bridge communication → Worker).

Two IDE MCP servers are connected: intellij-index (semantic code intelligence) and jetbrains (file problems, reformat, debugger). IntelliJ has multiple projects open; our module is fleetd. Always pass these to IDE MCP tools:

  • project_path = /Users/dai.ha/LTMS/claude-bridge/fleetd
  • IDE paths are relative to fleetd/ (e.g. src/main/java/dev/ltms/fleet/...)

After editing any file — mandatory

  1. ide_sync_files{paths} — the built-in Edit/Write tools write to disk; the IDE index is stale until synced, or IDE nav/refactor/diagnostics give wrong results.
  2. ide_diagnostics{file} (or jetbrains get_file_problems) — clear all errors and warnings. IDE inspections catch what a build won't (unused params/fields, redundant modifiers, resource leaks, "always same arg", …). These diagnostics are per-file.
  3. mvn clean install (Bash) — required for overall project health (clean build + full test run). A per-file-clean file can still break the build or another module. This is the whole-project gate before declaring work done or committing.

Whenever dependencies change (or a pom.xml edit), validate CVEs with jetbrains get_file_problems{filePath: "fleetd/pom.xml"} — its Mend.io check reflects the dependencies on disk. (Note: ide_diagnostics / intellij-index does NOT re-resolve dependencies after a pom edit without a full Maven reimport, so it reports stale CVE results — don't trust it for this.) Treat a CVE warning like any other: bump to a patched version and confirm mvn clean install still passes. If the latest available version is still flagged (EOL line, "insufficient information", or config-file-only advisories), document it as accepted in the pom rather than chasing a fix that doesn't exist.

Use IDE MCP tools for navigation, refactoring, and diagnostics only

  • Navigate (prefer over Grep/Read for symbols): ide_find_definition, ide_find_class, ide_find_file, ide_find_references, ide_find_implementations, ide_find_super_methods, ide_call_hierarchy, ide_type_hierarchy, ide_search_text (regex: jetbrains search_in_files_by_regex).
  • Refactor (prefer over multi-file Edit / rm / mv): ide_refactor_rename (position-based, updates all refs/overrides/tests), ide_refactor_safe_delete, ide_move_file, jetbrains reformat_file.
  • Diagnose: ide_diagnostics / jetbrains get_file_problems.

Do NOT route build/test/one-offs through the IDE

Keep mvn (compile/test/package/clean install) and all one-off shell commands on Bash. Do not use jetbrains build_project, execute_run_configuration, or execute_terminal_command to replace them.

Java 25 notes

Prefer the unnamed lambda parameter _ for required-but-unused params; a non-public static void main(String[]) is valid (JEP 512) and boots via java -jar.