Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
5118667
fix(engine): WA-CLANGD-009 starts clangd without modules support for …
Sunrisepeak Oct 1, 2026
78e292c
docs(plan): 0.0.9 decisions, multi-angle check, tasks and their depen…
Sunrisepeak Oct 1, 2026
d84e22c
fix(settings): S-1..S-4 mcppls.engine.name and mcppls.buildDiscovery.…
Sunrisepeak Oct 1, 2026
9114c3f
fix(server): P-1 a server whose client process is gone exits, and a s…
Sunrisepeak Oct 1, 2026
487eb4a
Merge branch 'worktree-agent-ab936669089ca8160' into release/0.0.9
Sunrisepeak Oct 1, 2026
c468c28
fix(engine): P-3 a clangd restart stops the old clangd off the event …
Sunrisepeak Oct 1, 2026
84e9534
fix(payload): R-2 a payload is dirty only when a tracked file differs…
Sunrisepeak Oct 1, 2026
66aaa14
feat(vscode): D-4 downloads can be allowed for a workspace and are th…
Sunrisepeak Oct 1, 2026
bc417cb
docs: R-1 drafts of the LLVM issues for UP-22 and UP-23
Sunrisepeak Oct 1, 2026
f95d4b0
docs: S3 onlineRun (S3-4-26 to S3-4-28) and the 0.0.9 changelog so far
Sunrisepeak Oct 1, 2026
91820fe
feat(report): M-1 splits the time of a request an engine answered int…
Sunrisepeak Oct 1, 2026
801a631
test(conformance): M-2, M-3 canaries for WA-CLANGD-009 and 010 over L…
Sunrisepeak Oct 1, 2026
ce5182b
Merge branch 'worktree-agent-ad0a7cade20db93cf' into release/0.0.9
Sunrisepeak Oct 1, 2026
6a87423
docs: changelog for M-1 to M-3
Sunrisepeak Oct 1, 2026
95c195c
fix(xmake): pass only the xmake.conf keys xmake f accepts, say why a …
Sunrisepeak Oct 1, 2026
87268cd
Merge branch 'worktree-agent-ab234c53318af89f3' into release/0.0.9
Sunrisepeak Oct 1, 2026
efd3a53
feat(status): D-5 onlineRun says how a fetch the person asked for end…
Sunrisepeak Oct 1, 2026
df861ea
fix: review of 0.0.9 -- nothing reaches clangd while the old one is l…
Sunrisepeak Oct 1, 2026
1727e18
test(vscode): Windows e2e cleanup and the workspace hash retry a file…
Sunrisepeak Oct 1, 2026
ae7addd
test(conformance): tidy-const-views waits for clangd's diagnostics be…
Sunrisepeak Oct 1, 2026
57095f9
fix: a cache clear lets go of the stopped clangd so a later restart s…
Sunrisepeak Oct 1, 2026
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
468 changes: 468 additions & 0 deletions .agents/docs/2026-10-01-0.0.9-plan.md

Large diffs are not rendered by default.

78 changes: 78 additions & 0 deletions .agents/docs/2026-10-01-0.0.9-upstream-drafts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# LLVM issue drafts for UP-22 and UP-23 (plan 0.0.9, R-1)

These are the two upstream defects behind WA-CLANGD-009 and WA-CLANGD-010, written for
llvm/llvm-project. Neither is filed yet. Once one is filed, its link goes into issue #24 (UP-22, UP-23)
and into the `upstream` field of the workaround in `src/engine/clangd/workarounds.cpp`.

---

## UP-22 · [clang-tidy] misc-const-correctness suggests `const` for a view that has no const `begin()`

Labels: `clang-tidy`, `false-positive`

`misc-const-correctness` (clang-tidy 23.1.0, as bundled with clangd 23.1.0) reports that a variable
holding a `std::views::filter` result "can be declared 'const'". Declared `const`, the code no longer
compiles: `filter_view` (also `drop_while_view`, `chunk_by_view` and `split_view`) caches its
`begin()`, so it has no `begin() const`.

```cpp
// clang-tidy -checks='-*,misc-const-correctness' t.cpp -- -std=c++23
#include <algorithm>
#include <array>
#include <functional>
#include <ranges>
struct H { int f; unsigned long s; };
bool keep(const H& h) { return h.f == 1; }
unsigned long a1(const std::array<H, 4>& in) {
auto v = in | std::views::filter(keep); // warning: variable 'v' ... can be declared 'const'
return std::ranges::fold_left(v | std::views::transform(&H::s), 0UL, std::plus());
}
```

With `const auto v`, GCC 16 says `no match for 'operator|' (operand types are 'const
std::ranges::filter_view<...>' ...)`. Clang rejects it too.

Observed with:

| Version | Standard library | Result |
|---|---|---|
| 23.1.0 | libstdc++ 16 | warns |
| 23.1.0 | libc++ 23 | warns |
| 22.1.8 | — | does not warn on a view returned by an adaptor; does warn on `std::ranges::filter_view v { r, p };` written out, which is the same false positive |

The same holds when `v` is iterated with a range-for, or passed to `std::ranges::distance(v | std::views::take(2))`.

Expected: no diagnostic when the variable's type has no const `begin()` and is used as a range.

---

## UP-23 · [clangd] with `--experimental-modules-support`, every code completion rescans the file's module dependencies

Labels: `clangd`, `clang:modules`, `performance`

With `--experimental-modules-support`, each `textDocument/completion` in a file that imports no
module and is in a project with no modules is about 3 times slower when the file includes a heavy
header (vulkan-hpp's `vulkan.hpp` and `vulkan_raii.hpp`). The verbose log shows one more driver
invocation per completion: 25 against 13 for the same session. That is a module dependency scan,
which preprocesses the whole translation unit again on every request.

**Measurement.** clangd 23.1.0, Linux x86-64, libstdc++ 16, compile_commands.json with
`-std=c++23 -I<Vulkan-Headers v1.4.351>`. Twelve completions after `phy_device.`, `vk::`,
`memory_properties.` and `std::ranges::`, each on a settled preamble.

| | median | worst |
|---|---|---|
| without the flag | 82 ms | 95 ms |
| with `--experimental-modules-support` | 254 ms | 869 ms |
| the same in a header that is not in the database | 81 ms / 257 ms | — |

`--use-dirty-headers`, `--header-insertion=never` and `-j=8` change nothing.

Expected: the result of the dependency scan is kept with the preamble, or reused while the file's
`import` lines are unchanged, so completion costs the same with and without the flag in a file with
no imports.

**Reproducing.** A single `.cpp` with `#include <vulkan/vulkan_raii.hpp>` and a function body, a
compile_commands.json entry for it, and an LSP client that sends didOpen, waits for diagnostics, then
sends completions. The scripts we used are `bench.py` in the issue #37 review
(`.agents/docs/reviews/2026-10-01-issue-37-review.md` §B).
30 changes: 19 additions & 11 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -277,26 +277,26 @@ jobs:
- platform: linux-x64
part: 1 of 2
os: ubuntu-24.04
fixtures: mcpp-split mcpp-partition-definition mcpp-all-cppm verify-changes mingw mcpp-split-gcc mcpp-watch multi-root mcpp-llvm mcpp-watch@polling mcpp-emit mcpp-rules-generated s1-two-sets compdb-lto-msvc failure-at-base@vscode generated-module-negotiated typing-import typing-import-spin workaround-canaries typing-autosave module-edit-autosave partial-scan-standins compdb-rejected-command compdb-mixed-standards mcpp-emit-partial mcpp-emit-partial-download compdb-midwrite xmake-basic xmake-late-config xmake-user-mode xmake-no-standard
fixtures: mcpp-split mcpp-partition-definition mcpp-all-cppm verify-changes mingw mcpp-split-gcc mcpp-watch multi-root mcpp-llvm mcpp-watch@polling mcpp-emit mcpp-rules-generated s1-two-sets compdb-lto-msvc failure-at-base@vscode generated-module-negotiated typing-import typing-import-spin workaround-canaries typing-autosave module-edit-autosave partial-scan-standins compdb-rejected-command compdb-mixed-standards mcpp-emit-partial mcpp-emit-partial-download compdb-midwrite xmake-basic xmake-late-config xmake-user-mode xmake-no-standard xmake-needs-download
- platform: linux-x64
part: 2 of 2
os: ubuntu-24.04
extras: true
fixtures: inferred inferred-bom build-discovery-off engine-none module-faults untrusted mcpp-gcc mcpp-gcc@plain mcpp-emit-package-std mcpp-emit-broken mcpp-emit-unavailable mcpp-emit-hang mcpp-emit-needs-download mcpp-emit-provisioned mcpp-emit-watch mcpp-emit-watch@polling mcpp-emit-edits cmake-clang cmake-clang-bdb cmake-fetchcontent-offline watch-polling payload-corrupt clangd-cannot-load generated-module generated-module@vscode generated-module-old-mcpp generated-module-old-mcpp@neovim module-faults@zed failure-at-base failure-at-base@neovim completion-keywords diagnostic-bundle clangd-crash-context mcpp-emit-wait provisional-no-prime inferred-cxx26 reset-cache include-cleaner-modules
fixtures: inferred inferred-bom build-discovery-off engine-none module-faults untrusted mcpp-gcc mcpp-gcc@plain mcpp-emit-package-std mcpp-emit-broken mcpp-emit-unavailable mcpp-emit-hang mcpp-emit-needs-download mcpp-emit-provisioned mcpp-emit-watch mcpp-emit-watch@polling mcpp-emit-edits cmake-clang cmake-clang-bdb cmake-fetchcontent-offline watch-polling payload-corrupt clangd-cannot-load generated-module generated-module@vscode generated-module-old-mcpp generated-module-old-mcpp@neovim module-faults@zed failure-at-base failure-at-base@neovim completion-keywords diagnostic-bundle clangd-crash-context mcpp-emit-wait provisional-no-prime inferred-cxx26 reset-cache include-cleaner-modules no-modules tidy-const-views
# generated-module{,-old-mcpp,-negotiated}'s mcpp-mock.json bakes in a POSIX driver path
# (${env:HOME}/.mcpp/registry/..., no {exe}); it resolves the same way here as on Linux, so
# these run on macOS but are left off win32-x64 below rather than fixed unverified.
- platform: darwin-arm64
os: macos-14
extras: true
fixtures: inferred inferred-bom build-discovery-off engine-none module-faults untrusted mcpp-llvm mcpp-split mcpp-partition-definition mcpp-all-cppm verify-changes mcpp-watch multi-root failure-at-base clangd-cannot-load failure-at-base@zed generated-module generated-module-old-mcpp generated-module-negotiated typing-import typing-import-spin workaround-canaries completion-keywords diagnostic-bundle typing-autosave module-edit-autosave partial-scan-standins clangd-crash-context mcpp-emit-wait provisional-no-prime inferred-cxx26 reset-cache include-cleaner-modules
fixtures: inferred inferred-bom build-discovery-off engine-none module-faults untrusted mcpp-llvm mcpp-split mcpp-partition-definition mcpp-all-cppm verify-changes mcpp-watch multi-root failure-at-base clangd-cannot-load failure-at-base@zed generated-module generated-module-old-mcpp generated-module-negotiated typing-import typing-import-spin workaround-canaries completion-keywords diagnostic-bundle typing-autosave module-edit-autosave partial-scan-standins clangd-crash-context mcpp-emit-wait provisional-no-prime inferred-cxx26 reset-cache include-cleaner-modules no-modules tidy-const-views
# No mcpp on the arm64 runner (its tools are the cross-built ones), so the fixtures that
# need no build tool and no compiler of their own: the semantic kit, clangd and the server
# on aarch64, with module faults, a corrupt payload and polling included.
- platform: linux-arm64
os: ubuntu-24.04-arm
cross-tools: true
fixtures: inferred inferred-bom build-discovery-off engine-none module-faults untrusted payload-corrupt clangd-cannot-load failure-at-base watch-polling typing-import typing-import-spin workaround-canaries completion-keywords diagnostic-bundle typing-autosave module-edit-autosave partial-scan-standins clangd-crash-context inferred-cxx26 reset-cache include-cleaner-modules
fixtures: inferred inferred-bom build-discovery-off engine-none module-faults untrusted payload-corrupt clangd-cannot-load failure-at-base watch-polling typing-import typing-import-spin workaround-canaries completion-keywords diagnostic-bundle typing-autosave module-edit-autosave partial-scan-standins clangd-crash-context inferred-cxx26 reset-cache include-cleaner-modules no-modules tidy-const-views
- platform: win32-x64
part: 1 of 2
os: windows-2022
Expand All @@ -305,7 +305,7 @@ jobs:
part: 2 of 2
os: windows-2022
extras: true
fixtures: inferred inferred-bom build-discovery-off engine-none module-faults mcpp-emit-hang inferred-msvc untrusted mingw cmake-msvc cmake-msvc-bdb cmake-clangxx-msvc cmake-clang-cl mcpp-msvc mcpp-watch failure-at-base clangd-cannot-load completion-keywords diagnostic-bundle mcpp-emit-wait inferred-cxx26 reset-cache include-cleaner-modules
fixtures: inferred inferred-bom build-discovery-off engine-none module-faults mcpp-emit-hang inferred-msvc untrusted mingw cmake-msvc cmake-msvc-bdb cmake-clangxx-msvc cmake-clang-cl mcpp-msvc mcpp-watch failure-at-base clangd-cannot-load completion-keywords diagnostic-bundle mcpp-emit-wait inferred-cxx26 reset-cache include-cleaner-modules no-modules tidy-const-views
defaults:
run:
shell: bash
Expand Down Expand Up @@ -739,7 +739,9 @@ jobs:
# (cold start, warm start on the same cache, typing and saves on that cache, faults on a cache of their
# own); a stage that fails does not skip the ones after it, so a run says everything that is over budget,
# and every stage's measure file is uploaded whether or not it passed, for the trend. The `long` stage
# (ten idle minutes, a git checkout twenty commits back) is nightly's.
# (ten idle minutes, a git checkout twenty commits back) is nightly's. ux-heavy-headers (0.0.9 plan M-2)
# needs no checkout: it generates a project of heavy headers and holds completion through the server to
# what clangd alone does on it, in its one stage.
ux:
name: ux (${{ matrix.fixture }})
needs: payload
Expand All @@ -748,14 +750,15 @@ jobs:
strategy:
fail-fast: false
matrix:
fixture: [ux-mcpp, ux-xlings]
fixture: [ux-mcpp, ux-xlings, ux-heavy-headers]
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v7
- uses: ./.github/actions/setup-mcpp
- name: GCC 16, which the mcpp and xlings repositories build with
if: matrix.fixture != 'ux-heavy-headers'
run: mcpp toolchain install gcc 16.1.0
# The mcpp repository's .xlings.json asks for the mcpp it is built with, and xlings runs that one
# inside it, refusing to when it is not installed (nightly's self-hosting job does the same).
Expand Down Expand Up @@ -792,10 +795,15 @@ jobs:
fi
echo "::endgroup::"
}
run_stage cold cache-a
run_stage warm cache-a --expect-warm
run_stage edits cache-a --expect-warm
run_stage faults cache-b
if [ "${{ matrix.fixture }}" = ux-heavy-headers ]; then
# 0.0.9 plan M-2: one run, of a generated project: completion through the server against clangd alone.
run_stage edits cache-a
else
run_stage cold cache-a
run_stage warm cache-a --expect-warm
run_stage edits cache-a --expect-warm
run_stage faults cache-b
fi
{
echo "### ${{ matrix.fixture }}"
echo
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ mcpp.lock
# build.mcpp would generate it at, so each is self-contained; `target/` above would otherwise hide
# it. Only those fixtures: any other fixture's target/ is build output like everywhere else.
!conformance/fixtures/generated-module*/target/

# Local review reports (issue analyses, not shipped).
.agents/docs/reviews/
91 changes: 91 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,97 @@ release's notes are that section.
Versions are three-part semantic versions, `MAJOR.MINOR.PATCH`, and every editor plugin carries the
product version unchanged.

## [0.0.9] — 2026-10-02

Completion on a project without modules as fast as plain clangd's, and the rest of issue #37. clangd's
modules support made every completion in a header-heavy project about three times slower (vulkan-hpp:
259 ms at the median through mcppls, now 97 ms); a project that uses no modules now gets clangd without
it. A view that cannot be const is no longer said to be, settings no longer show `undefined`, an xmake
project no longer starts with a configuration that must fail, a download that is needed says what it is
and what else helps, and a fetch you ask for reports how it ended. A second editor window, or a server
left behind by a reload, no longer makes the next one start cold. The analysis and the plan are
`.agents/docs/2026-10-01-0.0.9-plan.md`.

### Completion and diagnostics

- **Completion on a project without modules is as fast as plain clangd's again** (issue #37). clangd's
modules support scans a file's module dependencies again for every completion; on vulkan-hpp that took
completion from 82 ms to 254 ms at the median, 869 ms at worst, in sources and headers alike. A project
whose plan has no module unit, no module import, no `import std` and no stand-in now gets clangd without
`--experimental-modules-support`; the verdict is kept for the next session, and the first plan that uses
modules restarts clangd with it (workaround `WA-CLANGD-009`, UP-23 in issue #24). Through mcppls, the
same completions went from 259 ms to 97 ms at the median.
- **No "can be declared 'const'" for a view that cannot be const** (issue #37). clang-tidy 23.1's
`misc-const-correctness` says so of a variable holding a filter, drop_while, chunk_by or split view, or
a view built on one, although none of them has a const `begin()`; that diagnostic is dropped and the
check's others are kept (workaround `WA-CLANGD-010`, UP-22 in issue #24).

### Starting

- **A second instance plans with the owner's model at once** (issue #37). A server that finds the
workspace served by another instance works in a private directory; it now reads the owner's cached
project model, read only, instead of planning from scanned sources and restarting clangd when the
build tool answered (3 to 4.4 s of slow first requests on vulkan-rt).
- **A server whose editor is gone leaves.** The client's process id from `initialize` is watched (Linux
and macOS, when the process is visible at the start); once it is gone the server exits as if its input
had closed, and the next server owns the workspace instead of starting cold beside a leftover one.
- **clangd waits for a build tool that is known to answer soon.** With no cached model, clangd started
on the model scanned from sources after 2.5 s and was restarted when the build tool answered, building
its preambles twice. How long each build tool took is now kept per workspace, and clangd waits up to
1.2 times that (at most 5.5 s more); mcppls's own engine answers meanwhile, as before.
- **A clangd restart no longer holds the event loop.** The old clangd is stopped on its own thread and
the new one starts once it is gone; a clangd building a preamble takes 2.4 to 3.9 s to exit, and every
request waited for it (446 ms and 1803 ms in the reporter's logs, up to 2.5 s).

### Settings

- **The settings screen no longer shows `undefined`, and `engine.workers` takes effect.**
`mcppls.engine` and `mcppls.buildDiscovery` were plain values that were also the parents of other
settings, and VS Code drops a child's default when its parent is a plain value:
`mcppls.engine.workers` showed `undefined` and "Value must match regex", and the build discovery
checkbox showed unchecked. They are now `mcppls.engine.name` and `mcppls.buildDiscovery.mode`. The
old names still apply in every editor, Neovim and Zed `init_options` included; VS Code offers once to
move your values to the new names and changes nothing until you click. `mcppls.engine.workers` now
reaches the server, accepts `auto` or 1 to 99 (empty means `auto`), and a change restarts it. A `null`
in `initializationOptions` means not set, and in `didChangeConfiguration` returns the setting to its
default; the bundle no longer reports "compiler as the wrong kind of value" for an unset compiler.

### Build tools

- **xmake no longer starts with a configuration that must fail.** mcppls asks `xmake f --help` which
options it takes and passes on only the `xmake.conf` keys on that list. The keys xmake wrote there
itself (`proxy`, `dotnet`, `dotnet_sdkver`) were passed back, `xmake f` refused them, and every start
ran it twice and showed a wrong "run `xmake f -c`" notice.
- **A needed download says what it is and what else works.** The message says these are packages xmake
would fetch or build for the project's requirements (build tools included, such as `libtool` or
`libpthread-stubs` for a library built from source), that the run stayed offline, and that installing
them with the system package manager works too. A run with the network allowed whose install fails
reports `producer-install-failed` with xmake's error lines and its install log, instead of "stayed
offline" and an offer to download the same thing again. Missing packages found by `xmake project` are
reported the same way.
- **A download you asked for says how it ended, and can be allowed for a workspace.** After "Download
and Continue", the status carries the outcome (S3 `onlineRun`), and VS Code tells it once: fetched, or
failed with what failed and buttons for the log and the terminal. "Always Download in This Workspace"
fetches every download the build description needs without asking, while the build tool still runs
offline otherwise; "C++ Modules: Ask Before Downloading in This Workspace" takes it back. Nothing is
written into the project.

### Diagnostic bundle and tests

- **A slow request shows whose time it was.** The report's `requests.<method>` splits the requests an
engine answered into `engineP50Ms`/`engineP95Ms` (the engine's own time) and
`overheadP50Ms`/`overheadP95Ms` (what mcppls added).
- The conformance suite talks to clangd directly for two canaries: WA-CLANGD-009 (completion with
`--experimental-modules-support` more than 1.8 times slower on a heavy header) and WA-CLANGD-010 (the
filter view's const advice). New fixtures `no-modules` and `tidy-const-views`, and a ux scenario,
`ux-heavy-headers`, that holds completion through mcppls to 1.3 times clangd's own plus 30 ms on a
project without modules (it fails with WA-CLANGD-009 turned off: 530 ms against 56 ms).

### Packaging

- **A released payload is no longer marked `dirty`.** Its build record counted files the build itself
leaves in the checkout; only a tracked file that differs from the commit counts now.

## [0.0.8] — 2026-10-01

Completion that shows up. In VS Code 1.125 and later with Copilot (built into VS Code) or another
Expand Down
Loading
Loading