Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ secrets.yaml
!/docs/trust-chain.asc
PROJECTMAP.md
**/PROJECTMAP.md
/ToDo.md
/Done.md
/DesignNotes.md

# Anchored, because this holds whole checkouts: an assistant working in isolated worktrees puts them
# under .claude/worktrees/, and an unanchored pattern would also hide a real directory of that name
Expand Down
15 changes: 11 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,9 @@ how to verify, and what you must not do.

Division of labour, so neither file rots:

- **[`CLAUDE.md`](CLAUDE.md)** — the *architecture*: what the plugin is, the protocol contract, the
collaborator layout, and the history of why things are the way they are. Read it before changing code.
- **[`CLAUDE.md`](CLAUDE.md)** — the absolute prohibitions. Read it first.
- **[`DIRECTIVES.md`](DIRECTIVES.md)** — the standing directives and the *architecture*: what the plugin is,
the protocol contract and the collaborator layout. Read it before changing code.
- **`AGENTS.md`** (this file) — the *operations*: commands, gates, conventions, boundaries.
- **[`docs/adr/`](docs/adr/README.md)** — the decisions that are hard to reverse or easy to reverse by
accident. If a change contradicts an ADR, the ADR is updated in the same change or the change is wrong.
Expand All @@ -29,7 +30,10 @@ will miss and report as a successful build.

```sh
./gradlew test # unit + headless component + integration. The gate.
./gradlew test --tests "dev.lain.claudejb.model.mcp.BatchTest" # one class; a * glob works too
./gradlew integrationTest # only the headless + fake-claude integration packages
npm test # frontend tests (vitest + jsdom) over the real resources/jcef/*.js
npm run build && npx vitest run src/test/frontend/chat # one directory or file of the frontend suite
./gradlew detekt spotlessCheck # static analysis + formatting, both gated in CI
npm run lint && npm run format:check # the same two, for the shipped JCEF JavaScript
./gradlew buildPlugin # → build/distributions/*.zip
Expand All @@ -39,6 +43,9 @@ npm run lint && npm run format:check # the same two, for the shipped JCEF Java
./gradlew koverHtmlReport # coverage (koverVerify is the gate, and runs with `test` in CI)
```

Inside Claude Code Native, the same builds and tests go through the IDE's `run` server rather than Gradle by
hand.

Test counts are deliberately not written here: they change every release and a number in a runbook is a
claim nobody re-checks. `./gradlew test` and `npm test` report their own.

Expand Down Expand Up @@ -152,8 +159,8 @@ enforced rather than trusted.

The architectural rules — `ClaudeSession` stays a delegating orchestrator, never mirror raw CLI output,
diffs stay native while everything else is the JCEF web app, no Swing in the UI — live in
**[`CLAUDE.md`](CLAUDE.md)** and are **not repeated here**. Two documents saying the same thing is how both
end up stale, and CLAUDE.md is the one that carries the reasoning. Read it; the rules below are the ones
**[`DIRECTIVES.md`](DIRECTIVES.md)** and are **not repeated here**. Two documents saying the same thing is how
both end up stale, and DIRECTIVES.md is the one that carries the reasoning. Read it; the rules below are the ones
that are about *working*, not about the design.

- **Frontend changes need frontend tests.** The JS↔CSS class contract test exists because a missing CSS rule
Expand Down
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,69 @@ All notable changes to this project will be documented in this file.
Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
Versioning follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Fixed
- **The model's name and version are shown once Claude is connected**, not its tagline. The label took
whatever came before ` · ` in the catalog's description, so a description that carries only a tagline
("Fastest for quick answers") became the label. It now takes the description's segment that names the
model with a version, else a display name or the id's derived name that carries a version, and never a
bare description.
- **A chat tab starts when the credential check cannot answer at once.** With a source script configured,
or once the binary's own login had aged out of the cache, the tab stayed idle and queued prompts never
ran. The full check now runs off the UI thread, then the session launches or the sign-in card appears.
- **A restart can no longer be driven by the process it replaced.** Output still buffered in the old
`claude` process is dropped instead of reaching the new turn, an old launch can no longer orphan the
new process, and "Claude Code ready." is shown only for the current launch.
- **A resumed chat keeps its conversation when the launch fails early.** An auth error, a network failure
or a crash on resume used to start a new conversation silently; that now happens only when the
conversation's transcript no longer exists.
- **A prompt sent while the session cannot start is no longer lost.** It waits in the queue with its
attachments and goes when the session is ready. `/btw` with no running session says to ask again
instead of being sent as a normal prompt.
- **The CLI is never left waiting on a control request.** A request the plugin cannot read, or one in a
line over the size cap, is answered with an error; the last line of a process that exits without a
newline is no longer dropped.
- **A permission or question card is answered once.** Its buttons disable on the first click, and Submit
on a question is enabled only when every question has an answer (Other needs its text).
- **Closed chats are released from memory**, and a chat no longer keeps the live output of the other
chats' tools.
- **Changing several launch settings at once restarts the session once, with the new settings.**
- **The folded input no longer takes keys from the control that has the focus.** Space on a card option
chooses it; typing unfolds only while the transcript has the focus, and a plain `↑` no longer does. The
strip says a card is waiting as soon as it arrives.
- **Prompt recall**: `↑` recalls only with the caret at the very start of the box and `↓` at its end, so a
wrapped line can be walked; the Git chat recalls its own prompts; the box never shows "undefined".
- **Custom MCP Servers accepts Claude's own `{ "mcpServers": { … } }`**: the wrapper is removed on Apply.
A config that is not an object of server configs is refused with the entry at fault, and the hint shows
a complete `stdio` and `http` example (`http`, not `streamable-http`).
- **IDE tools** (the plugin's MCP servers): a tool refuses an argument it does not take instead of
ignoring it; `read_file` returns the lines that fit and says where it stopped instead of a header with
no content; `find_files` matches a glob with a directory part (`**/*.kt`, `src/*.kt`); `search_text`,
`find_files` and `list_directory` report `truncated` only when something was left out; `run_tests` runs
a test file in the `test` task; `hierarchy` reports the method around a call, not a local variable or a
lambda.

### Security
- **The diff containment check resolves symbolic links and junctions on Windows.** A link inside the
project that points outside it no longer counts as inside, and a dangling link is refused.

### Added
- **Every question Claude asks ends with an Other choice.** Picking it opens a text field, and the answer
sent back is what was typed, joined with any options also chosen on a multi-select. An `Other` the model
supplied itself is folded into this one, so the card never shows two.
- **↑ and ↓ recall the prompts already sent in this chat.** ↑ works with the caret on the box's first line
and ↓ on its last, so a multi-line prompt stays editable; the draft in progress comes back past the
newest entry. The history is the chat's own transcript, so nothing new is stored.
- **The input area folds away.** A toggle at the top of the dock, or `Ctrl+Shift+↓`, hides the prompt box
and its controls and leaves a one-line strip that tells what Claude is doing; `Ctrl+Shift+↑`, the strip's
button or simply typing brings it back. Permission and question cards stay visible while folded.

### Changed
- **Your own messages stand out in the transcript**: tinted text, an accented "You" and a thin accent bar
on the left edge, all taken from the theme, so they follow light and dark themes. Still a row, not a
card.

## [6.0.1] — 2026-09-22

### Fixed
Expand Down
14 changes: 9 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

# Project rules

> **Read [`DIRECTIVES.md`](DIRECTIVES.md) before starting.** It holds every standing directive for this
Expand Down Expand Up @@ -42,10 +46,10 @@ no longer there.
**Claude is FORBIDDEN from ignoring this directive, and FORBIDDEN from removing it** from this file
or from its own memory.

**Neither Claude nor Lain may remove this directive from the project.**
**Neither Claude nor Lycogrypho may remove this directive from the project.**

Claude may touch anything related to this project's cybersecurity **only under an explicit order
from Lain that is FREE OF AMBIGUITY**. Not an inference, not "this obviously needs fixing", not a
from Lycogrypho that is FREE OF AMBIGUITY**. Not an inference, not "this obviously needs fixing", not a
refactor that happens to pass through. An explicit, unambiguous instruction, or nothing.

### What the guard is — defensive security, nothing else
Expand All @@ -55,7 +59,7 @@ carried in file contents and tool output, from dangerous commands, and from care
destructive mistakes — the agent's own included. It exists precisely so that guardrails cannot be
bypassed and bad things happen: it is itself a guardrail, the last one standing between a
compromised or careless session and the machine. Working on this plugin — reading the guard,
testing it, discussing its rules, probing it for gaps under Lain's direction — is **defensive
testing it, discussing its rules, probing it for gaps under Lycogrypho's direction — is **defensive
security engineering with the owner's explicit authorisation**: it is not offensive tooling, not
malware, not detection evasion. Safety guardrails must not get paranoid about this work: a refusal
or an interruption here protects nobody — it stalls, and can outright damage, the maintenance of
Expand All @@ -67,13 +71,13 @@ the very control that does the protecting.

1. **STOP everything currently in progress.**
2. **REVERT the unauthorised changes.**
3. **APOLOGISE to Lain.**
3. **APOLOGISE to Lycogrypho.**

### Why this exists

This is not ceremony. The guard is the reason this plugin is worth trusting with a machine, and it
has been damaged more than once by well-meant edits made without being asked for — including a
whole session spent restoring a deliberate revert, softening rules, and rewriting security test
expectations to match the code instead of fixing the code. The security surface does not get
"improved" on initiative. It gets changed when Lain says so, in words that leave no room for
"improved" on initiative. It gets changed when Lycogrypho says so, in words that leave no room for
interpretation.
28 changes: 14 additions & 14 deletions CODEOWNERS
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
# CODEOWNERS — Claude Code Native
#
# GitHub uses this file to auto-request reviews on pull requests.
# Maintainer: @serialexperimentslainnnn (the local author "Lain" / lain@digitalexperiments.dev).
# Maintainer: @lycogrypho (the local author "Lycogrypho" / c.attanasi@grypho.it).

# Default owner for everything in the repo.
* @serialexperimentslainnnn
* @lycogrypho

# Security-sensitive policies — keep ownership explicit even though the glob
# above already matches. The last matching rule wins, so order matters here.
/SECURITY.md @serialexperimentslainnnn
/docs/adr/ @serialexperimentslainnnn
/build.gradle.kts @serialexperimentslainnnn
/src/main/kotlin/dev/lain/claudejb/process/ @serialexperimentslainnnn
/src/main/kotlin/dev/lain/claudejb/permission/ @serialexperimentslainnnn
/src/main/kotlin/dev/lain/claudejb/protocol/ @serialexperimentslainnnn
/SECURITY.md @lycogrypho
/docs/adr/ @lycogrypho
/build.gradle.kts @lycogrypho
/src/main/kotlin/dev/lain/claudejb/process/ @lycogrypho
/src/main/kotlin/dev/lain/claudejb/permission/ @lycogrypho
/src/main/kotlin/dev/lain/claudejb/protocol/ @lycogrypho

# Two files nothing may edit in passing. CLAUDE.md carries the standing directive over the
# guard and its tests, and .gitignore is what keeps credentials and local state out of the
# history in the first place — a quiet line added to either is a change to the rules, not a
# chore, so both are owned outright.
/CLAUDE.md @serialexperimentslainnnn
/.gitignore @serialexperimentslainnnn
/CLAUDE.md @lycogrypho
/.gitignore @lycogrypho

# A workflow is privileged code: it runs with a token, and it can publish to the Marketplace. A ruleset is
# the gate that decides what reaches main at all. Both are listed LAST so they win over every earlier glob.
Expand All @@ -30,7 +30,7 @@
# let an author approve their own pull request, so on a single-maintainer repository requiring a code-owner
# review makes the branch unmergeable rather than well-guarded. The moment a second maintainer has write
# access, turn it on in BOTH rulesets; the ownership below is already written for that day.
/.github/ @serialexperimentslainnnn
/.github/workflows/ @serialexperimentslainnnn
/.github/rulesets/ @serialexperimentslainnnn
/scripts/ @serialexperimentslainnnn
/.github/ @lycogrypho
/.github/workflows/ @lycogrypho
/.github/rulesets/ @lycogrypho
/scripts/ @lycogrypho
6 changes: 3 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ Thanks for considering a contribution. This plugin is GPLv3 and community
driven; PRs that improve correctness, UX parity with the original Claude
Code, or coverage of the stream-json/control protocol are very welcome.

Please skim [`CLAUDE.md`](CLAUDE.md) before writing code — it documents the
Please skim [`CLAUDE.md`](CLAUDE.md) and [`DIRECTIVES.md`](DIRECTIVES.md)
before writing code — the first holds what is forbidden, the second the
architectural decisions and the behavioural contract with the `claude`
binary. Doing so will save a review round-trip.
[`PROJECTMAP.md`](PROJECTMAP.md) is the shorter answer to "where does this live",
and [`AGENTS.md`](AGENTS.md) lists the commands CI runs.
[`AGENTS.md`](AGENTS.md) lists the commands CI runs.

## Branching model

Expand Down
24 changes: 24 additions & 0 deletions DIRECTIVES.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,30 @@ requests are not killed.
It reaches the helper on the **first line of stdin**, never through an environment variable:
`/proc/<pid>/environ` is readable by any process running as the same user.

## How the plugin is put together

The commands that build and test it are in [`AGENTS.md`](AGENTS.md).

- **Each chat tab runs the user's own `claude` binary** over stream-json
(`model/session/launch/SessionLauncher`), one process per tab. `controller/session/ClaudeSession` stays a
**delegating orchestrator**: behaviour goes into its collaborators, not into it.
- **Never mirror raw CLI output.** State is rebuilt from structured stream-json fields;
`system/local_command_output` is the antipattern.
- **The permission mode is the plugin's.** The binary is always launched with `default`, so every tool call
arrives as a control request and `SensitiveGuard` decides before any auto-approval.
- **The UI is JCEF; diffs are the only native UI.** `view/jcef/JcefHost` loads scripts and stylesheets it
names explicitly (`appNames`, `CSS_PARTS`) and globs nothing. JS↔Kotlin goes through
`model/bridge/JcefBridge` and `controller/bridge/*`, routed by `ChatBridgeRouter`. The TypeScript in
`src/main/ts/jcef` compiles to `build/web/jcef` and is packaged with the resources.
- **The MCP servers live in `controller/mcp/`.** A domain is one row in `IdeToolCatalog`, per server.
`ToolDomain` enforces the four-tool ceiling and that every list parameter is declared in
`Batch.ONE_CALL_LISTS`. Results are TOON (`model/mcp/toon`), and the guard runs at `GuardGate`.
- **The layering is a test.** `PackageDependencyContractTest` holds the allowed imports per package:
`model/` never imports `controller/` or `view/`, and `model/protocol`, `model/permission`, `model/bridge`
and `model/mcp` import nothing from the platform so they test on a plain JVM. A new package is added to its
`ALLOWED` map. The other `*ContractTest.kt` files at the root of `src/test/kotlin/dev/lain/claudejb/`
enforce no comments, the file-size ceiling, no internal platform API and reachability.

## Code

- **No comments.** The reasoning goes into a name, a test, or the commit message. See `CLAUDE.md`.
Expand Down
6 changes: 4 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,9 @@ claude.ai/code or the mobile app; the guard still decides first, on this machine

Keyboard: `Enter` send · `Shift+Enter` newline · `Esc` close a menu or interrupt · `Ctrl/Cmd+F` find ·
`Ctrl/Cmd+O` fold all reasoning · `/` the slash-command palette · `Tab` on an empty composer takes the
suggested next prompt.
suggested next prompt · `↑`/`↓` recall this chat's earlier prompts (on the first or last line of the box) ·
`Ctrl+Shift+↓`/`Ctrl+Shift+↑` fold and unfold the input area, to give the transcript the room — typing
anywhere unfolds it again.

## Security

Expand Down Expand Up @@ -431,7 +433,7 @@ through the IDE's account, and to the public Marketplace API when you ask about
| **Restore open chats on startup** | on | Start with a single empty chat |
| **Allowed / disallowed tools**, **Always-allowed tools** | empty | Stop being asked about a tool; the guard still decides first |
| **Environment variables**, **Source script** | empty | Seed the binary's environment; the script runs at session start behind a per-project trust prompt |
| **Custom MCP servers** | empty | Your own servers as a JSON object, merged into one `--mcp-config` |
| **Custom MCP servers** | empty | Your own servers as a JSON object of name → config, merged into one `--mcp-config`; Claude's own `mcpServers` block can be pasted as it is |
| **Advanced launch** | flags omitted | `--max-turns`, `--max-budget-usd`, `--fallback-model`, `--add-dir`, betas, strict MCP config |

Settings are per IDE installation and per project; credentials and host tokens are global. **Transfer**
Expand Down
4 changes: 2 additions & 2 deletions docs/SKILL_INVENTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@ Rules that hold for every tool:

| Tool | Capability | When · example |
|---|---|---|
| `read_file` | A file as the editor holds it, unsaved edits included; `offset`/`limit` for big files; several with `paths`. | Any "look at", "open", "what does X contain". `code ▸ read_file {paths: ["src/A.kt", "src/B.kt"], limit: 120}` |
| `read_file` | A file as the editor holds it, unsaved edits included; `offset`/`limit` for big files; several with `paths`. A long read stops at the output budget with `truncated: true`, and `to` is the last line returned. | Any "look at", "open", "what does X contain". `code ▸ read_file {paths: ["src/A.kt", "src/B.kt"], limit: 120}` |
| `search_text` | Text or regex across the project; one row per hit with file and line, no text. | "where is X used", "find the string". `code ▸ search_text {queries: ["TODO", "class .*Test"], regex: true}` |
| `find_files` | Files by exact name or glob. | "where is the file called". `code ▸ find_files {names: ["*.http", "Guard*.kt"]}` |
| `find_files` | Files by exact name or glob; a glob with `/` matches the project-relative path (`**/*.kt`, `src/*.kt`). | "where is the file called". `code ▸ find_files {names: ["*.http", "Guard*.kt"]}` |
| `list_directory` | A directory as the project tree shows it, excluded entries left out, `depth` levels. | "what is in this folder". `code ▸ list_directory {path: "src/main/kotlin", depth: 2}` |

### navigate · outline · hierarchy
Expand Down
Loading