Skip to content
Draft
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
141 changes: 141 additions & 0 deletions .agents/docs/2026-11-05-clion-backend-location-and-stable-runtime.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
# CLion backend location and stable runtime validation

Status: fixes and the initial stable runtime suite pass canonical CI; expanded debugger CI pending.

## Observed failure

The published mcpp-clion 0.2.0 package contains `backend/mcpp-ide.okb` and its runners. Its
`pluginDirectory()` nevertheless derives the installation directory from the class loader's
`CodeSource.location`. A full local CLion 2026.1.5.2 run, build CL-261.27258.141, verified that
the actual `McppSession` class has a null location. The original package consequently fails
to find the bundled backend in an isolated environment.

A separate user log records downloading 0.2.0, dynamically loading the plugin, and starting a
backend with source `xlings`. It records neither the external backend's version nor the
unknown-method response. The log establishes fallback selection, not the selected backend's
protocol. No user log excerpts, account names, project names or local paths are included here.

An independent wire experiment with the real released 0.1.0 backend reproduces error -32601
for `mcpp-ide/i18n/bundle`; that backend reports protocol 1. The 0.2.0 bundled backend reports
protocol 2 and answers the request. This is a sufficient reproduction of the notification,
not proof of the user's external backend version. The shorter reported `mcpp-ide/bundle`
spelling has not been observed on the wire; the current client sends `mcpp-ide/i18n/bundle`.

## Responsibilities and implementation

The CLion adapter owns installation lookup and protocol acceptance. It obtains its directory
from the IDE plugin descriptor and retains the existing backend preference: explicit setting,
bundle, xlings, PATH. The initializer rejects incompatible or missing protocol values before
sending any custom requests. No legacy method aliases are introduced.

Startup errors previously reached an action's notification handler without a corresponding
log entry. Startup failures now write a warning with backend source, negotiated protocol,
and, for RPC failures, wire method and error code. Process launch errors and a missing backend
also produce a warning. The existing startup line continues to identify the selected command
locally; private diagnostics are not published as CI artifacts.

The mcppls language service is a separate connection. Its successful initialization in the
user log does not validate the mcpp-ide session, and this lookup failure does not require a
clangd rebuild or a runtime dependency release.

## Controlled local result

The original input is the Marketplace 0.2.0 ZIP, SHA-256
`7e3fec81186d897e4185413ffbf3b69b08f9185f0da78842f4826f2408d274ba`.
Only the plugin JAR was changed for a diagnostic comparison. The backend bundle and every
runner remained byte-identical. With the descriptor lookup and handshake diagnostics, the
same CLion build completes protocol 2 startup using backend 0.2.0, loads translations and
commands, opens the mcpp tool window, and creates all three views.

This patched ZIP is an experiment, not a published release or the canonical Gradle package.
The normal CI build must still compile and package the complete plugin and run that same
candidate ZIP on both stable versions.

## Runtime coverage and limits

`clion-stable.yml` pins CLion 2026.1.5.2 and 2026.2.4.1. Each test starts the actual IDE,
installs the same packaged plugin ZIP with mcppls 0.0.19, opens a synthetic mcpp project,
checks the actual IDE version and build, completes the session, and verifies the visible
tool window and three views. The standalone test build does not compile the product plugin
against each tested SDK. A manual release check can reuse the published ZIP without rebuilding
the backend. Only a report of public versions and assertions is uploaded.

Plugin Verifier remains a separate static compatibility check. These runtime checks cover
Linux startup and the functional scenarios below on the two stable IDE families; native
Windows/macOS GUI behavior, dynamic uninstall/reinstall and remaining interactions require
their own evidence.
The null CodeSource reproduction uses a cold IDE start and does not depend on dynamic installation.

## Functional and user-experience coverage

The denominator is the current command registry and the adapter's additional UI surfaces.
`spec/commands.json` declares 59 commands, of which 55 include JetBrains (five are deprecated).
The runtime table can offer fewer entries according to the language service's advertised
capabilities. Every offered command and each forwarded action must resolve to a registered
IDE action; hidden or deprecated commands are not counted as executed functionality.

The local 2026.1.5.2 and 2026.2.4.1 experiments pass the same 29 checks. They invoke the actual
Build, Run and Test actions, wait for fresh process handlers, require the expected exit code,
and check a file written by the program. A compilation failure must appear in the console;
restoring the source must restore a successful build. Test failure and recovery must reach the
native test tree. The project path contains Unicode and spaces. Compilation uses GCC 16.1.0;
CI installs the same compiler. These are diagnostic adapter packages with unchanged backend
bytes; the canonical package must still pass CI.

Two additional adapter defects were reproduced and corrected. The completion contributor did
not implement `DumbAware`, so an index-independent backend feature was suppressed during IDE
analysis. Marking that contributor allows a real completion popup and exact replacement of
`[dep` by `[dependencies]` while indexing is active. Opening Cache through its command also
left the tree empty until manual refresh. Content selection now focuses and refreshes the view,
including command selection and tab selection, and the runtime check requires real inventory
rows without an error. The same selection path handles existing graph tabs and refresh requests.
Both versions also pass physical mouse selection of the Libraries and Cache tabs.

The native debugger adapter constructed `CidrLocalDebugProcess` without starting it. The
published adapter consequently opened a debug view without loading the target. Starting the
process inside the session starter follows the native launch lifecycle. Both stable versions
now execute the actual Debug action, finish with exit code zero through a native
`CidrDebugProcess` process handler, and expose the Debug tool window. Both versions then stop
at a real source breakpoint, step to the next source line, resume to normal completion and
check program output. A separate debug run stops at the breakpoint and must terminate before
the program writes its output. Variable inspection and runner-backed debugging remain pending.

The first canonical CI run passed the plugin build, static verification and other ecosystem
checks. Both stable runtime jobs stopped during xlings installation when the installer tried
to read `/dev/tty`; neither reached IDE startup. The reusable workflow now sets
`XLINGS_NON_INTERACTIVE=1` itself, because the caller's workflow environment is not inherited.
Canonical run 42005596492 at commit `c5aed8e` subsequently passed all 25 jobs, including
the actual packaged plugin on both stable IDE builds. The stable runtime reports contain
23 successful checks on 2026.1 and 24 on 2026.2, including completion while indexing on the
latter. The added breakpoint, step, resume and stop cases have passed locally on both IDEs;
the expanded canonical suite still requires its own successful run.

Verifier 1.411 confirms both stable builds are compatible with zero internal API usages after
using the public plugin-aware class loader descriptor. The original deprecated/experimental API
warnings remain. Tests resolve the test-runner classes through the product plugin's class loader:
the classes are in the platform on 2026.1 and a separate optional plugin on 2026.2. Local GUI
tests serialize use of the Driver's ports; CI matrix entries use independent runners.

| Area | Required scenario | Current local evidence |
|---|---|---|
| Backend/session | Bundle lookup, protocol mismatch, missing backend, restart and recovery | Original lookup failure and corrected startup reproduced; protocol data validation passes; recovery pending |
| Command integration | Registry, context menu, shortcuts, forwarded mcppls commands | All offered actions resolve; individual forwarded operations and interactive discoverability pending |
| Build | Success, compilation failure, incremental build, cancellation, console source links | Success, compiler error text, failure and recovery pass; cancellation and links pending |
| Run | Executable selection, arguments, working directory, PTY, stop | Successful IDE action and program-produced file; other interactions pending |
| Test | Discovery, success, failure, filter, test tree and rerun | Native tree success, failure and corrected rerun pass; filter pending |
| Debug | Native breakpoint, variables, step, continue, stop; runner-backed program boundary | Native debug launch, breakpoint, step over, resume, normal completion and stop pass on both stable versions; variables and runner-backed program pending |
| Project | Data rows, targets, result/artifact links, refresh | Three views instantiate; populated rows and links pending |
| Libraries | Search, detail, add dependency, index update, error feedback | Pending functional interaction |
| Cache | Inventory, plan, confirmation/cancel, isolated apply, refused untrusted actions | Opening the view loads real inventory without manual refresh; plan/apply/refusal pending; no global user cache deletion |
| Settings | Registry form, persist/reload, invalid value, locale | Pending interactive validation |
| Graphs | Package/module graph render, source navigation, empty/error view, no-JCEF fallback | Pending actual JCEF rendering |
| Editor | Manifest/buildscript completion, hover, diagnostics, navigation, snippets; C++ module handoff | Manifest popup and exact insertion during indexing pass; other interactions pending |
| Project/toolchain setup | New project, target/profile choice, install/default toolchain | Compiler installation is test setup; plugin flows pending |
| Diagnostics | Self-check, report export/redaction, logs, degraded operation | Startup logging implemented; export and recovery interactions pending |

Availability requires an observed operation and its expected effect. Completeness additionally
requires the specified negative and recovery paths. User experience requires checking visible
feedback, discoverability, absence of stalled progress, selection persistence and actionable
errors. Static API compatibility and registered actions alone do not satisfy these criteria.
The two stable IDE versions run the same candidate; platform-specific GUI behavior remains
separately bounded. Findings and fixes continue in the same review PR.
8 changes: 8 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -471,6 +471,14 @@ jobs:
name: mcpp-clion-zip
path: mide-plugins/clion/build/distributions/*.zip

# Both stable families load the exact same packaged ZIP. These are actual
# IDE startup/session checks, in addition to the static Plugin Verifier.
clion-stable:
needs: clion
uses: ./.github/workflows/clion-stable.yml
with:
plugin_artifact: mcpp-clion-zip

# The Neovim plugin (mide-plugins/nvim) in a headless Neovim, against the backend built here: over
# the mock mcpp on the oldest and the newest Neovim it supports, then over the real mcpp, which
# builds the test's project. Neovim comes from xlings (`nvim` in xim-pkgindex), each version
Expand Down
98 changes: 98 additions & 0 deletions .github/workflows/clion-stable.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: CLion stable runtime

on:
workflow_call:
inputs:
plugin_artifact:
type: string
required: true
workflow_dispatch:
inputs:
release_tag:
description: Published plugin release to install without rebuilding the backend
type: string
default: v0.2.0

permissions:
contents: read

env:
XLINGS_NON_INTERACTIVE: '1'

jobs:
runtime:
name: packaged plugin on CLion ${{ matrix.version }}
runs-on: ubuntu-24.04
timeout-minutes: 35
strategy:
fail-fast: false
matrix:
version: ['2026.1.5.2', '2026.2.4.1']
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v5
- uses: ./.github/actions/setup-mcpp
- name: Gradle and a virtual display
run: |
xlings install gradle -y -g
sudo apt-get update -qq
sudo apt-get install -y xvfb
- name: The compiler for actual build, run and test actions
run: |
mcpp toolchain install gcc 16.1.0
mcpp toolchain default gcc@16.1.0
- uses: actions/cache@v5
with:
path: ~/.gradle/caches/modules-2
key: clion-stable-runtime-${{ hashFiles('mide-plugins/clion/integration-tests/*.gradle.kts') }}
- name: The candidate ZIP built once by CI
if: inputs.plugin_artifact != ''
uses: actions/download-artifact@v5
with:
name: ${{ inputs.plugin_artifact }}
path: stable-plugin
- name: The published ZIP for an isolated release check
if: inputs.plugin_artifact == ''
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.release_tag || 'v0.2.0' }}
run: |
set -euo pipefail
[[ "$RELEASE_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]
mkdir -p stable-plugin
gh release download "$RELEASE_TAG" --repo "$GITHUB_REPOSITORY" \
--pattern 'mcpp-clion-*.zip' --dir stable-plugin
- name: The pinned language-service plugin
env:
GH_TOKEN: ${{ github.token }}
run: |
set -euo pipefail
version="$(sed -n 's/^mcpplsPluginVersion *= *//p' mide-plugins/clion/gradle.properties)"
gh release download "v$version" --repo Sunrisepeak/mcpp-language-server \
--pattern "mcppls-clion-$version-linux-x64.zip" --dir stable-plugin
- name: Start the real IDE and complete the plugin startup chain
env:
IDE_VERSION: ${{ matrix.version }}
run: |
set -euo pipefail
plugin="$(find stable-plugin -maxdepth 1 -name 'mcpp-clion-*.zip' -print)"
language="$(find stable-plugin -maxdepth 1 -name 'mcppls-clion-*.zip' -print)"
test -n "$plugin" && test -n "$language"
xvfb-run -a gradle --no-daemon --max-workers=4 \
-p mide-plugins/clion/integration-tests test \
-PpluginZip="$GITHUB_WORKSPACE/$plugin" \
-PmcpplsZip="$GITHUB_WORKSPACE/$language" \
-PideVersion="$IDE_VERSION" \
-Poutput="$RUNNER_TEMP/clion-stable-result"
# The report contains public versions and assertions only. Private user
# logs and arbitrary IDE process output are not uploaded as artifacts.
- uses: actions/upload-artifact@v5
if: always()
with:
name: clion-stable-${{ matrix.version }}
path: |
${{ runner.temp }}/clion-stable-result/result.json
${{ runner.temp }}/clion-stable-result/checks.json
if-no-files-found: warn
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,4 @@ webui/dist/
compile_commands.json
dist-upload/
.xlings/
out/ide-tests
9 changes: 9 additions & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ bundle and the target set's tree (`mcpp-ide-<version>-openkal-bundle-all.tar.gz`
one pack on Linux, takes okrun from that tree, opens both editor packages and the tree on every
system and starts what they carry for it (`.github/scripts/check-package.sh`).

The CLion adapter obtains its installation directory from the IDE's plugin descriptor. Its
class loader's `CodeSource` URL is not required. The session accepts protocol 2 before sending
`mcpp-ide/i18n/bundle` and `mcpp-ide/commands/list`. Backend selection and startup failures are
recorded in `idea.log`; RPC startup failures include the method and error code. The stable
CLion runtime checks install one packaged plugin ZIP in both stable IDE families and exercise
the complete startup chain in each IDE process. Functional checks cover build, run, test,
native debug launch, error recovery, manifest completion and view selection. The debugger
adapter starts the native Cidr process from the session starter before returning it.

## Generated declarations

`spec/` is the source of everything the editors declare statically. `mcpp-ide-dev gen vscode` writes
Expand Down
24 changes: 24 additions & 0 deletions mide-plugins/clion/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,27 @@ docs/architecture.md, Distribution. Without them the plugin finds the backend th
`MCPP_IDE_MOCK_MCPP` name the programs `mcpp build` produced, and the views' models and the
settings form headless. `npm run test:webui` in `../vscode` runs the graph page in Chromium over the
backend.

The packaged ZIP is also run in the real stable CLion 2026.1.5.2 and 2026.2.4.1 IDEs by
`clion-stable.yml`. Both versions load the same ZIP and the pinned mcppls plugin. The test opens
an mcpp project, completes protocol 2 initialization, loads runtime translations and commands,
and verifies the visible mcpp tool window and cache inventory. It executes build, run and test
actions in a project whose path contains Unicode and spaces, launches the native debugger,
checks its visible view, source breakpoint, step over, resume and stop, checks the native test tree,
compiler-error output, build/test failure and recovery, and exact manifest completion insertion
during project analysis. It supplements Plugin Verifier's static compatibility checks.

An individual packaged plugin can be checked without rebuilding the backend:

```bash
xvfb-run -a gradle --no-daemon -p integration-tests test \
-PpluginZip=/absolute/path/mcpp-clion-0.2.0.zip \
-PmcpplsZip=/absolute/path/mcppls-clion-0.0.19-linux-x64.zip \
-PideVersion=2026.1.5.2
```

The CLion session writes backend selection and startup failures to `idea.log`, under
`io.github.mcppcommunity.mcppclion.McppSession`. A protocol mismatch stops initialization before
custom requests. For an explicitly configured older backend, update it or clear
`mcpp.ide.backendPath` to select the bundled backend. Error notifications can also appear in
the IDE's notification area.
3 changes: 3 additions & 0 deletions mide-plugins/clion/integration-tests/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.gradle/
.kotlin/
build/
39 changes: 39 additions & 0 deletions mide-plugins/clion/integration-tests/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
plugins {
kotlin("jvm") version "2.4.0"
}

repositories {
mavenCentral()
maven("https://www.jetbrains.com/intellij-repository/releases")
maven("https://packages.jetbrains.team/maven/p/ij/intellij-dependencies")
maven("https://download.jetbrains.com/teamcity-repository")
maven("https://cache-redirector.jetbrains.com/packages.jetbrains.team/maven/p/grazi/grazie-platform-public")
}

dependencies {
testImplementation("com.jetbrains.intellij.tools:ide-starter-squashed:262.10968.63")
testImplementation("com.jetbrains.intellij.tools:ide-starter-junit5:262.10968.63")
testImplementation("com.jetbrains.intellij.tools:ide-starter-driver:262.10968.63")
testImplementation("com.jetbrains.intellij.driver:driver-client:262.10968.63")
testImplementation("com.jetbrains.intellij.driver:driver-sdk:262.10968.63")
testImplementation("com.jetbrains.intellij.driver:driver-model:262.10968.63")
testImplementation("org.junit.jupiter:junit-jupiter:5.12.2")
testImplementation("org.kodein.di:kodein-di-jvm:7.20.2")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.10.1")
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
}

kotlin { jvmToolchain(25) }

// This build has no dependency on the plugin's main output. Both IDE versions
// load the same packaged ZIP; compiling the plugin against each tested SDK
// would hide binary compatibility defects in the actual distributed plugin.
tasks.test {
useJUnitPlatform()
maxHeapSize = "2g"
for (key in listOf("pluginZip", "mcpplsZip", "ideVersion", "ideHome", "output")) {
providers.gradleProperty(key).orNull?.let { systemProperty("mcpp.clion.$key", it) }
}
providers.gradleProperty("testHome").orNull?.let { systemProperty("user.home", it) }
testLogging { events("passed", "failed", "skipped") }
}
1 change: 1 addition & 0 deletions mide-plugins/clion/integration-tests/settings.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
rootProject.name = "mcpp-clion-integration-tests"
Loading
Loading