Skip to content

feat(server): improve configuration file handling - #423

Merged
16bit-ykiko merged 11 commits into
mainfrom
improve/config-handling
Apr 19, 2026
Merged

16bit-ykiko merged 11 commits into
mainfrom
improve/config-handling

Conversation

@16bit-ykiko

@16bit-ykiko 16bit-ykiko commented Apr 12, 2026 •

Copy link
Copy Markdown
Member

Summary

  • [[rules]]: TOML array-of-tables config for per-file compilation flag rules with glob pattern matching (append/remove). Patterns are pre-compiled at config load time. Rules whose patterns all fail to compile are dropped entirely (no silent no-op entries), and rules now apply uniformly to every compilation — including the header-context fallback path used when editing a header without its own CDB entry.
  • CDB auto-scan: Default search scans workspace root + all immediate subdirectories for compile_commands.json, replacing the hardcoded directory list.
  • LSP initializationOptions: Clients can pass config as JSON via the LSP initialize request; priority is initializationOptions > clice.toml > defaults.
  • XDG cache paths: Default cache/index/logging paths prefer $XDG_CACHE_HOME/clice/<workspace-hash>/; fall back to $HOME/.cache/clice/<hash>/, then <workspace>/.clice/.
  • ${workspace} substitution: supported in cache_dir, index_dir, logging_dir, and every compile_commands_paths entry. No-op when workspace_root is empty.
  • Partial config support: All TOML/JSON fields are optional via kota::meta::defaulted<T>, so minimal config files work correctly.
  • Detailed diagnostics: malformed clice.toml now logs line, column and parser description (via toml++ direct parse); a malformed workspace config surfaces a clear fallback warning instead of silently reverting to defaults.

Test plan

  • 28 unit tests for config (full suite 545 unit tests pass, Debug)
  • 119 integration tests pass
  • 2 smoke tests pass

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • XDG-based, workspace-scoped project cache (PCH/PCM and header caches moved under project cache) with workspace fallback
    • Initialization options JSON can override config (takes precedence over file/defaults)
    • Per-file pattern rules to append/remove compile flags; expanded discovery of compilation databases (multiple paths)
  • Refactor

    • Configuration fields reorganized under a project scope; runtime behavior now respects project-scoped values
  • Tests

    • New unit and integration tests for config parsing, rule matching, and persistent cache behavior

@coderabbitai

coderabbitai Bot commented Apr 12, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Nest workspace config under project, add glob-pattern rules and JSON-based LSP config loading, apply rule-derived append/remove flags into compilation-database lookups, move cache/index/pch/pcm paths to project-scoped cache_dir, and update compile_commands discovery to handle multiple paths and directories.

Changes

Cohort / File(s) Summary
Configuration API & Defaults
src/server/config.h, src/server/config.cpp
Introduce project (ProjectConfig), ConfigRule/CompiledRule, switch compile_commands_path → compile_commands_paths, add match_rules() and load_from_json(), precompile glob patterns, use XDG for project.cache_dir with workspace fallback, and change several APIs to llvm::StringRef.
Master server & init handling
src/server/master_server.cpp, src/server/master_server.h
Capture LSP initializationOptions JSON (init_options_json), prioritize JSON config on Initialized, centralize reads via cfg = workspace.config.project, iterate cfg.compile_commands_paths (file or dir) with workspace-scan fallback, and use project-scoped values for cache/index/worker setup.
Compiler / Compile-args & CDB lookups
src/server/compiler.cpp
Apply workspace.config.match_rules(path, ...) to build CommandOptions (append/remove) for file and header lookups and pass them into workspace.cdb.lookup(...); update module PCM lazy-resolver to use project cache paths for PCM storage.
Workspace & Cache Paths
src/server/workspace.cpp, src/server/master_server.cpp, src/server/compiler.cpp
Move all cache/index/pch/pcm and header-preamble output paths to workspace.config.project.cache_dir / project.index_dir; gate cache operations on project.cache_dir.
Indexer & Scheduling
src/server/indexer.cpp
Gate scheduling and idle-timer on project.enable_indexing / project.idle_timeout_ms, and persist/load indexes from project.index_dir.
Glob pattern const-correctness
src/support/glob_pattern.h, src/support/glob_pattern.cpp
Mark GlobPattern::match(...) as const so compiled rules can be matched on const instances.
Tests
tests/unit/server/config_tests.cpp, tests/integration/compilation/test_persistent_cache.py
Add unit tests for TOML/JSON parsing, rule compilation/matching, defaults and XDG-derived cache paths; add helper to pin workspace cache and update persistent-cache integration tests to assert PCH/PCM and cache.json under pinned workspace cache.

Sequence Diagram

sequenceDiagram
    participant Client
    participant MasterServer
    participant Config as ConfigLoader
    participant Workspace
    participant Compiler

    Client->>MasterServer: initialize(with initializationOptions JSON)
    MasterServer->>MasterServer: store init_options_json
    Client->>MasterServer: Initialized

    MasterServer->>Config: load_from_json(init_options_json)
    alt parsed OK
        Config-->>MasterServer: CliceConfig (project + compiled_rules)
    else fallback
        MasterServer->>Config: load_from_workspace(workspace_root)
        Config-->>MasterServer: CliceConfig (from TOML/defaults)
    end

    MasterServer->>Workspace: initialize workspace with config (cache/index paths)
    MasterServer->>MasterServer: clear init_options_json

    Client->>MasterServer: request compile args (file)
    MasterServer->>Compiler: fill_compile_args(path)
    Compiler->>Config: match_rules(path)
    Config-->>Compiler: append/remove flags
    Compiler->>Workspace: cdb.lookup(path, CommandOptions(with rules))
    Workspace-->>Compiler: compile args
    Compiler-->>MasterServer: resolved compile args
    MasterServer-->>Client: respond with compile args
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Poem

🐇 I dug a tunnel through configs, neat and sound,
Rules like clover scattered all around.
Cache boxes nested under the project tree,
JSON carrots dropped in by LSP to see.
Hop—compile flags match; my joy is unbound! 🎋

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 22.73% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(server): improve configuration file handling' directly aligns with the main objectives: adding rules-based compilation flags, XDG cache support, JSON initialization options, and overall configuration system improvements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch improve/config-handling

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/server/compiler.cpp (1)

48-53: ⚠️ Potential issue | 🟠 Major

Apply match_rules() in the lazy module dependency resolver too.

fill_compile_args now applies rule-based flags, but at Line 49-53 the compile-graph resolver still does workspace.cdb.lookup without append/remove. That can produce inconsistent module dependency scanning vs actual compilation.

Suggested fix
     auto resolve = [this](std::uint32_t path_id) -> llvm::SmallVector<std::uint32_t> {
         auto file_path = workspace.path_pool.resolve(path_id);
-        auto results =
-            workspace.cdb.lookup(file_path, {.query_toolchain = true, .suppress_logging = true});
+        std::vector<std::string> rule_append, rule_remove;
+        workspace.config.match_rules(file_path, rule_append, rule_remove);
+        auto results = workspace.cdb.lookup(file_path,
+                                            {.query_toolchain = true,
+                                             .suppress_logging = true,
+                                             .remove = rule_remove,
+                                             .append = rule_append});
         if(results.empty())
             return {};
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/compiler.cpp` around lines 48 - 53, The lazy module dependency
resolver (the resolve lambda) calls workspace.cdb.lookup without applying
rule-based flag adjustments, causing inconsistent scanning versus
fill_compile_args; modify resolve to call match_rules() (or otherwise apply
append/remove from the rule matcher) to the compile args returned by
workspace.cdb.lookup so the same rule-driven flags are appended/removed as in
fill_compile_args; locate the resolve lambda and ensure it uses the same
match_rules()/append/remove logic (or a helper used by fill_compile_args) before
returning the resolved compile args/results.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server/config.cpp`:
- Around line 77-82: apply_defaults currently only performs variable
substitution via substitute_workspace for entries in p.compile_commands_paths
but doesn't anchor relative paths to the workspace root, so relative entries
like "build" resolve against the process CWD; after calling substitute_workspace
for each path in p.compile_commands_paths, check if the path is not absolute
(use std::filesystem::path::is_absolute) and if so replace it with
workspace_root / path (and normalize, e.g., lexically_normal) so
compile_commands_paths entries are anchored to workspace_root; update the loop
in apply_defaults that iterates p.compile_commands_paths to perform this
relative->absolute anchoring after substitution.

In `@src/server/config.h`:
- Around line 27-75: The new CliceConfig nests project settings but
deserialization still expects old flat keys, so add a migration shim in the
loading paths: update CliceConfig::load and CliceConfig::load_from_json to
detect legacy top-level keys (e.g., cache_dir, index_dir, logging_dir,
enable_indexing, idle_timeout_ms, clang_tidy, max_active_file,
stateful_worker_count, stateless_worker_count, worker_memory_limit,
compile_commands_paths) and move them into a ProjectConfig instance (or
translate them into a temporary JSON/TOML map) before populating
CliceConfig::project; ensure apply_defaults and match_rules continue to operate
on the migrated project and preserve backwards compatibility for
initializationOptions and clice.toml users.

In `@src/server/master_server.cpp`:
- Around line 101-121: The current auto-scan uses directory_iterator to pick the
first compile_commands.json and is non-deterministic; modify the scan inside the
directory iteration (where try_candidate, cdb_path, and workspace_root are used)
to collect all matching candidate paths into a vector, sort that vector (e.g.,
lexicographically), then choose a stable winner (e.g., the first element) or
log/warn if multiple matches exist before assigning cdb_path; ensure you still
short-circuit if a match was already found by the initial
try_candidate(workspace_root) check.

---

Outside diff comments:
In `@src/server/compiler.cpp`:
- Around line 48-53: The lazy module dependency resolver (the resolve lambda)
calls workspace.cdb.lookup without applying rule-based flag adjustments, causing
inconsistent scanning versus fill_compile_args; modify resolve to call
match_rules() (or otherwise apply append/remove from the rule matcher) to the
compile args returned by workspace.cdb.lookup so the same rule-driven flags are
appended/removed as in fill_compile_args; locate the resolve lambda and ensure
it uses the same match_rules()/append/remove logic (or a helper used by
fill_compile_args) before returning the resolved compile args/results.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d276d414-56a8-44bc-814e-826a6b37ab73

📥 Commits

Reviewing files that changed from the base of the PR and between d42d9d5 and 5f8b358.

📒 Files selected for processing (11)
  • src/server/compiler.cpp
  • src/server/config.cpp
  • src/server/config.h
  • src/server/indexer.cpp
  • src/server/master_server.cpp
  • src/server/master_server.h
  • src/server/workspace.cpp
  • src/support/glob_pattern.cpp
  • src/support/glob_pattern.h
  • tests/integration/compilation/test_persistent_cache.py
  • tests/unit/server/config_tests.cpp

Comment thread src/server/config.cpp Outdated
Comment thread src/server/config.h Outdated
Comment thread src/server/master_server.cpp
@16bit-ykiko
16bit-ykiko force-pushed the improve/config-handling branch from 5f8b358 to 5891b35 Compare April 18, 2026 08:51

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
src/server/config.h (1)

52-59: ⚠️ Potential issue | 🟠 Major

Preserve legacy flat config keys or migrate them explicitly.

Line 55 and Line 57 make project/rules the only deserialized config surface, while src/server/config.cpp still parses TOML/JSON directly into CliceConfig. Existing configs using top-level cache_dir, enable_indexing, worker counts, or compile_commands_paths can be silently ignored after upgrade unless a migration/alias layer is added.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/config.h` around lines 52 - 59, CliceConfig's deserializer
currently only exposes the nested fields project and rules which will drop
legacy top-level keys (e.g., cache_dir, enable_indexing, worker counts,
compile_commands_paths) parsed in src/server/config.cpp; update the
parsing/deserialization so it accepts legacy flat keys and maps them into the
new nested structure (or perform an explicit migration/aliasing pass) before
populating CliceConfig.project and CliceConfig.rules, leaving
annotation<std::vector<CompiledRule>, serde_schema::skip> compiled_rules
behavior unchanged; specifically modify the code that constructs CliceConfig in
src/server/config.cpp to detect legacy keys and copy them into the corresponding
ProjectConfig fields (and translate any worker/compile_commands settings to
their new locations) so existing configs are preserved.
src/server/master_server.cpp (1)

103-123: ⚠️ Potential issue | 🟠 Major

Make CDB auto-scan deterministic when multiple subdirectories match.

Lines 116-120 still pick the first compile_commands.json returned by directory_iterator, whose order is filesystem-dependent. Workspaces with build-debug/ and build-release/ can load different CDBs across machines or runs; collect matches, sort them, and warn when more than one candidate exists.

Proposed fix
-        if(!try_candidate(workspace_root)) {
+        if(!try_candidate(workspace_root)) {
+            std::vector<std::string> candidates;
             std::error_code ec;
             for(llvm::sys::fs::directory_iterator it(workspace_root, ec), end; it != end && !ec;
                 it.increment(ec)) {
                 if(it->type() == llvm::sys::fs::file_type::directory_file) {
-                    if(try_candidate(it->path()))
-                        break;
+                    auto candidate = path::join(it->path(), "compile_commands.json");
+                    if(llvm::sys::fs::exists(candidate))
+                        candidates.push_back(std::move(candidate));
                 }
             }
+            std::ranges::sort(candidates);
+            if(candidates.size() > 1)
+                LOG_WARN("Multiple compile_commands.json files found; using {}", candidates.front());
+            if(!candidates.empty())
+                cdb_path = std::move(candidates.front());
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/master_server.cpp` around lines 103 - 123, The current auto-scan
uses directory_iterator order to set cdb_path via the try_candidate lambda,
which is non-deterministic; change the logic to collect all matching
compile_commands.json paths (use try_candidate or inline check) into a
vector<string> (e.g., candidates), sort the vector lexicographically, then if
candidates is non-empty set cdb_path = candidates.front(); if candidates.size()
> 1 emit a warning listing the extra candidates (so users know which CDB was
chosen). Update the block that currently iterates workspace_root and uses
try_candidate to instead populate, sort, choose, and warn (referencing cdb_path,
try_candidate, workspace_root, and the directory_iterator loop to locate where
to change).
src/server/config.cpp (1)

80-85: ⚠️ Potential issue | 🟠 Major

Anchor relative project paths to workspace_root after substitution.

Line 84 still leaves configured compile_commands_paths relative to the server process CWD; the same applies to user-provided cache/index/logging paths on Lines 81-83. A config value like compile_commands_paths = ["build"] or cache_dir = ".clice" should resolve under the workspace, not wherever the server was launched.

Proposed fix
-    // Variable substitution on string fields.
-    substitute_workspace(p.cache_dir, workspace_root);
-    substitute_workspace(p.index_dir, workspace_root);
-    substitute_workspace(p.logging_dir, workspace_root);
-    for(auto& path: p.compile_commands_paths)
-        substitute_workspace(path, workspace_root);
+    // Variable substitution on string fields.
+    auto normalize_project_path = [&](std::string& value) {
+        substitute_workspace(value, workspace_root);
+        if(!value.empty() && !workspace_root.empty() && !llvm::sys::path::is_absolute(value))
+            value = path::join(workspace_root, value);
+    };
+
+    normalize_project_path(p.cache_dir);
+    normalize_project_path(p.index_dir);
+    normalize_project_path(p.logging_dir);
+    for(auto& path: p.compile_commands_paths)
+        normalize_project_path(path);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/config.cpp` around lines 80 - 85, The substituted paths
(p.cache_dir, p.index_dir, p.logging_dir and each entry in
p.compile_commands_paths) must be anchored to workspace_root when they are
relative; after calling substitute_workspace(...) detect if the resulting string
is a relative path and, if so, prepend/resolve it against workspace_root (e.g.
using std::filesystem::path(workspace_root) / path and assign back to the same
variable). Update the loop over p.compile_commands_paths and the three directory
assignments to normalize/absolute them relative to workspace_root when necessary
so config values like "build" or ".clice" resolve under the workspace rather
than the server CWD.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/server/master_server.cpp`:
- Around line 185-190: The current logic treats any present
initialization_options (even empty "{}") as a valid override because
kota::codec::json::to_json on init.initialization_options yields a non-empty
string and is stored in init_options_json, causing load_from_json /
from_json<CliceConfig> / apply_defaults to shadow file config; change the
handling so that when init.initialization_options exists but serializes to an
empty object (e.g., "{}") or contains no relevant CliceConfig fields you treat
it as absent: after calling
kota::codec::json::to_json(*init.initialization_options) check the parsed JSON
object for emptiness or absence of expected keys before assigning to
init_options_json (or instead perform a merge where explicit fields from the
parsed JSON overlay the result of load_from_workspace rather than replacing it);
update the flow around init.initialization_options, init_options_json,
load_from_json, load_from_workspace, and apply_defaults accordingly and add a
regression test where workspace has clice.toml and client sends {} to ensure
file config wins.

---

Duplicate comments:
In `@src/server/config.cpp`:
- Around line 80-85: The substituted paths (p.cache_dir, p.index_dir,
p.logging_dir and each entry in p.compile_commands_paths) must be anchored to
workspace_root when they are relative; after calling substitute_workspace(...)
detect if the resulting string is a relative path and, if so, prepend/resolve it
against workspace_root (e.g. using std::filesystem::path(workspace_root) / path
and assign back to the same variable). Update the loop over
p.compile_commands_paths and the three directory assignments to
normalize/absolute them relative to workspace_root when necessary so config
values like "build" or ".clice" resolve under the workspace rather than the
server CWD.

In `@src/server/config.h`:
- Around line 52-59: CliceConfig's deserializer currently only exposes the
nested fields project and rules which will drop legacy top-level keys (e.g.,
cache_dir, enable_indexing, worker counts, compile_commands_paths) parsed in
src/server/config.cpp; update the parsing/deserialization so it accepts legacy
flat keys and maps them into the new nested structure (or perform an explicit
migration/aliasing pass) before populating CliceConfig.project and
CliceConfig.rules, leaving annotation<std::vector<CompiledRule>,
serde_schema::skip> compiled_rules behavior unchanged; specifically modify the
code that constructs CliceConfig in src/server/config.cpp to detect legacy keys
and copy them into the corresponding ProjectConfig fields (and translate any
worker/compile_commands settings to their new locations) so existing configs are
preserved.

In `@src/server/master_server.cpp`:
- Around line 103-123: The current auto-scan uses directory_iterator order to
set cdb_path via the try_candidate lambda, which is non-deterministic; change
the logic to collect all matching compile_commands.json paths (use try_candidate
or inline check) into a vector<string> (e.g., candidates), sort the vector
lexicographically, then if candidates is non-empty set cdb_path =
candidates.front(); if candidates.size() > 1 emit a warning listing the extra
candidates (so users know which CDB was chosen). Update the block that currently
iterates workspace_root and uses try_candidate to instead populate, sort,
choose, and warn (referencing cdb_path, try_candidate, workspace_root, and the
directory_iterator loop to locate where to change).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 1e979306-131c-4655-b22f-c4c6d5897bf9

📥 Commits

Reviewing files that changed from the base of the PR and between 5f8b358 and 5891b35.

📒 Files selected for processing (11)
  • src/server/compiler.cpp
  • src/server/config.cpp
  • src/server/config.h
  • src/server/indexer.cpp
  • src/server/master_server.cpp
  • src/server/master_server.h
  • src/server/workspace.cpp
  • src/support/glob_pattern.cpp
  • src/support/glob_pattern.h
  • tests/integration/compilation/test_persistent_cache.py
  • tests/unit/server/config_tests.cpp
✅ Files skipped from review due to trivial changes (4)
  • src/server/workspace.cpp
  • src/support/glob_pattern.h
  • src/server/indexer.cpp
  • src/server/compiler.cpp
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/server/master_server.h
  • tests/integration/compilation/test_persistent_cache.py
  • src/support/glob_pattern.cpp

Comment thread src/server/master_server.cpp
16bit-ykiko and others added 2 commits April 19, 2026 01:17
Restructure CliceConfig into nested ProjectConfig with per-file
compilation rules, XDG cache paths, initializationOptions support,
and auto-scanning CDB discovery.

- Add [[rules]] support with pre-compiled glob pattern matching
- Default cache/index/log paths to $XDG_CACHE_HOME/clice/<hash>/
- Accept config via LSP initializationOptions JSON
- Auto-scan workspace root + immediate subdirs for compile_commands.json
- Use eventide defaulted<T> for optional TOML/JSON fields
- Make GlobPattern::match() const

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The PR's check-format CI job was failing because of include ordering and
line-wrapping. Rerun the project formatter and commit the result.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@16bit-ykiko
16bit-ykiko force-pushed the improve/config-handling branch from 5891b35 to 0b1dec1 Compare April 18, 2026 17:17

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
src/server/master_server.cpp (2)

103-123: ⚠️ Potential issue | 🟠 Major

Make CDB auto-scan deterministic before choosing a match.

directory_iterator order is filesystem-dependent, so workspaces with multiple immediate build dirs can load different compile_commands.json files across machines/runs. Collect matching candidates, sort them, then pick a stable winner and optionally warn on ambiguity.

Suggested direction
-        if(!try_candidate(workspace_root)) {
+        if(!try_candidate(workspace_root)) {
+            std::vector<std::string> candidates;
             std::error_code ec;
             for(llvm::sys::fs::directory_iterator it(workspace_root, ec), end; it != end && !ec;
                 it.increment(ec)) {
                 if(it->type() == llvm::sys::fs::file_type::directory_file) {
-                    if(try_candidate(it->path()))
-                        break;
+                    auto candidate = path::join(it->path(), "compile_commands.json");
+                    if(llvm::sys::fs::exists(candidate))
+                        candidates.push_back(std::move(candidate));
                 }
             }
+            std::ranges::sort(candidates);
+            if(!candidates.empty()) {
+                if(candidates.size() > 1)
+                    LOG_WARN("Multiple compile_commands.json files found; using {}", candidates.front());
+                cdb_path = std::move(candidates.front());
+            }
         }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/master_server.cpp` around lines 103 - 123, Auto-scan currently
picks the first compile_commands.json found via directory_iterator which is
non-deterministic; update the logic around try_candidate, cdb_path and the
directory iteration over workspace_root to collect all matching candidates into
a vector, sort them (e.g., lexicographically by path), then select a single
deterministic winner to assign to cdb_path and emit a warning if more than one
candidate was found to indicate ambiguity; ensure try_candidate is used only to
test paths and that the sorted selection replaces the existing early-exit
behavior so results are reproducible across machines/runs.

185-190: ⚠️ Potential issue | 🟠 Major

Do not let empty initializationOptions shadow clice.toml.

An empty JSON object is still a non-empty string, so {} will take the initializationOptions path, load defaults, and skip file-based config. Treat empty/unrelated initialization options as absent, or merge explicit JSON fields over the workspace config.

Suggested direction
         if(init.initialization_options.has_value()) {
             auto json =
                 kota::codec::json::to_json<kota::ipc::lsp_config>(*init.initialization_options);
-            if(json)
+            if(json && !is_empty_or_unrelated_config_json(*json))
                 init_options_json = std::move(*json);
         }

is_empty_or_unrelated_config_json should reject at least {} / null, and ideally only accept objects containing supported config keys such as project or rules.

Also applies to: 273-280

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/master_server.cpp` around lines 185 - 190, The current logic
accepts any non-null JSON (e.g. `{}`) from init.initialization_options and
causes init_options_json to override file-based clice.toml; update the handling
so empty or unrelated JSON is treated as absent: add/modify a helper (e.g.
is_empty_or_unrelated_config_json) that examines the result of
kota::codec::json::to_json<kota::ipc::lsp_config>(*init.initialization_options)
and returns false for `{}`, `null`, or objects lacking supported keys (like
"project" or "rules"); only move/assign to init_options_json when that helper
returns true. Apply the same check in both places where
init.initialization_options is parsed (the current block around
init_options_json and the similar block at lines 273-280) so file-based config
isn't shadowed by empty/unrelated JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/server/master_server.cpp`:
- Around line 103-123: Auto-scan currently picks the first compile_commands.json
found via directory_iterator which is non-deterministic; update the logic around
try_candidate, cdb_path and the directory iteration over workspace_root to
collect all matching candidates into a vector, sort them (e.g.,
lexicographically by path), then select a single deterministic winner to assign
to cdb_path and emit a warning if more than one candidate was found to indicate
ambiguity; ensure try_candidate is used only to test paths and that the sorted
selection replaces the existing early-exit behavior so results are reproducible
across machines/runs.
- Around line 185-190: The current logic accepts any non-null JSON (e.g. `{}`)
from init.initialization_options and causes init_options_json to override
file-based clice.toml; update the handling so empty or unrelated JSON is treated
as absent: add/modify a helper (e.g. is_empty_or_unrelated_config_json) that
examines the result of
kota::codec::json::to_json<kota::ipc::lsp_config>(*init.initialization_options)
and returns false for `{}`, `null`, or objects lacking supported keys (like
"project" or "rules"); only move/assign to init_options_json when that helper
returns true. Apply the same check in both places where
init.initialization_options is parsed (the current block around
init_options_json and the similar block at lines 273-280) so file-based config
isn't shadowed by empty/unrelated JSON.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: d76eaa53-adfe-4e38-b103-c6da6c2a22fa

📥 Commits

Reviewing files that changed from the base of the PR and between 5891b35 and 0b1dec1.

📒 Files selected for processing (11)
  • src/server/compiler.cpp
  • src/server/config.cpp
  • src/server/config.h
  • src/server/indexer.cpp
  • src/server/master_server.cpp
  • src/server/master_server.h
  • src/server/workspace.cpp
  • src/support/glob_pattern.cpp
  • src/support/glob_pattern.h
  • tests/integration/compilation/test_persistent_cache.py
  • tests/unit/server/config_tests.cpp
✅ Files skipped from review due to trivial changes (2)
  • src/support/glob_pattern.h
  • src/server/workspace.cpp
🚧 Files skipped from review as they are similar to previous changes (6)
  • src/server/master_server.h
  • src/server/indexer.cpp
  • src/support/glob_pattern.cpp
  • src/server/compiler.cpp
  • tests/integration/compilation/test_persistent_cache.py
  • src/server/config.cpp

- Drop rules whose glob patterns all fail to compile, instead of
  silently adding an empty CompiledRule whose append/remove flags
  would never be applied to any file.
- Include parser error message in the load_from_json failure log,
  matching load()'s behavior.
- Apply match_rules() in init_compile_graph's dependency resolver
  for consistency with fill_compile_args().
- Add unit tests covering load_from_json (success + malformed JSON),
  malformed/missing TOML, ${workspace} substitution, XDG cache
  resolution, invalid glob pattern handling, and defaults-after-JSON
  priority.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/server/compiler.cpp (1)

217-218: ⚠️ Potential issue | 🟡 Minor

match_rules not applied to the host CDB lookup in header-context path.

fill_compile_args and the init_compile_graph resolver both feed rule-derived append/remove into cdb.lookup, but this branch builds the command for a header's host (whose flags become the actual compile command after substitution at Line 228–237) without them. Files compiled via a header context will therefore miss configured append/remove flags that would apply to the host source.

Proposed diff
     auto host_path = workspace.path_pool.resolve(ctx_ptr->host_path_id);
-    auto host_results = workspace.cdb.lookup(host_path, {.query_toolchain = true});
+    std::vector<std::string> rule_append, rule_remove;
+    workspace.config.match_rules(host_path, rule_append, rule_remove);
+    auto host_results = workspace.cdb.lookup(host_path,
+                                             {.query_toolchain = true,
+                                              .remove = rule_remove,
+                                              .append = rule_append});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/compiler.cpp` around lines 217 - 218, The host CDB lookup for
header-context paths (resolving host_path from ctx_ptr->host_path_id then
calling workspace.cdb.lookup) is missing rule-derived modifications; change the
lookup to include the same match_rules/append/remove options used by
fill_compile_args and init_compile_graph so the host lookup receives
ctx_ptr->match_rules (or equivalent append/remove fields) in its lookup
parameters (e.g., pass {.query_toolchain = true, .match_rules =
ctx_ptr->match_rules} or the struct that carries append/remove) so
header-context compilations get the rule-derived flags applied.
♻️ Duplicate comments (1)
src/server/config.cpp (1)

84-85: ⚠️ Potential issue | 🟠 Major

Relative compile_commands_paths still resolved against CWD, not workspace root.

substitute_workspace only expands the ${workspace} placeholder; a plain relative entry like "build" is passed through unchanged and later resolved against the server process CWD in src/server/master_server.cpp. Configured CDB discovery in common setups (where users write compile_commands_paths = ["build"]) will fail to locate the file.

Proposed patch
-    for(auto& path: p.compile_commands_paths)
-        substitute_workspace(path, workspace_root);
+    for(auto& cdb_path: p.compile_commands_paths) {
+        substitute_workspace(cdb_path, workspace_root);
+        if(!workspace_root.empty() && !llvm::sys::path::is_absolute(cdb_path))
+            cdb_path = path::join(workspace_root, cdb_path);
+    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/config.cpp` around lines 84 - 85, substitute_workspace only
expands the ${workspace} token but leaves plain relative entries like "build"
unchanged; update the loop that processes p.compile_commands_paths to, after
calling substitute_workspace(path, workspace_root), detect if the resulting path
is not absolute and then resolve it by joining it with workspace_root (so
relative entries become workspace-root-relative instead of CWD-relative). Refer
to p.compile_commands_paths, substitute_workspace(path, workspace_root), and
workspace_root when implementing this fix.
🧹 Nitpick comments (1)
src/server/config.cpp (1)

89-109: Include GlobPattern::create error detail in the log.

GlobPattern::create returns a descriptive std::string in std::unexpected (multiple /, brace expansion failures, sub-pattern errors), but the warning only prints the raw pattern string, leaving users to guess why the pattern was rejected.

Proposed diff
-            auto pat = GlobPattern::create(pattern_str);
-            if(!pat) {
-                LOG_WARN("Invalid glob pattern in rule: {}", pattern_str);
-                continue;
-            }
+            auto pat = GlobPattern::create(pattern_str);
+            if(!pat) {
+                LOG_WARN("Invalid glob pattern in rule: '{}' ({})", pattern_str, pat.error());
+                continue;
+            }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/config.cpp` around lines 89 - 109, The warning for invalid glob
patterns should include the error message returned by GlobPattern::create
instead of only the raw pattern; update the branch where pat is falsy to extract
the descriptive error (from the returned unexpected/error payload of
GlobPattern::create) and pass it into LOG_WARN along with pattern_str so users
see why compilation failed (keep the existing check of
compiled.patterns.empty(), and adjust the invalid-pattern log in the loop that
handles pat failures inside the rule-processing loop).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/unit/server/config_tests.cpp`:
- Around line 204-215: The test TEST_CASE(XdgCacheDir) mutates the process
environment with ::setenv/::unsetenv which clobbers any existing XDG_CACHE_HOME
and is non-portable/racy; change it to save the prior value (via getenv), set
the test value, run CliceConfig::apply_defaults, then restore the original value
(reinstating or clearing XDG_CACHE_HOME as it was) or, better, use a small RAII
helper (e.g. ScopedEnv) to set and automatically restore the variable around the
test; update references in the test (TempDir, ::setenv/::unsetenv, CliceConfig
config) to use this restore-safe approach so the environment is not permanently
changed and works on MSVC/parallel runs.

---

Outside diff comments:
In `@src/server/compiler.cpp`:
- Around line 217-218: The host CDB lookup for header-context paths (resolving
host_path from ctx_ptr->host_path_id then calling workspace.cdb.lookup) is
missing rule-derived modifications; change the lookup to include the same
match_rules/append/remove options used by fill_compile_args and
init_compile_graph so the host lookup receives ctx_ptr->match_rules (or
equivalent append/remove fields) in its lookup parameters (e.g., pass
{.query_toolchain = true, .match_rules = ctx_ptr->match_rules} or the struct
that carries append/remove) so header-context compilations get the rule-derived
flags applied.

---

Duplicate comments:
In `@src/server/config.cpp`:
- Around line 84-85: substitute_workspace only expands the ${workspace} token
but leaves plain relative entries like "build" unchanged; update the loop that
processes p.compile_commands_paths to, after calling substitute_workspace(path,
workspace_root), detect if the resulting path is not absolute and then resolve
it by joining it with workspace_root (so relative entries become
workspace-root-relative instead of CWD-relative). Refer to
p.compile_commands_paths, substitute_workspace(path, workspace_root), and
workspace_root when implementing this fix.

---

Nitpick comments:
In `@src/server/config.cpp`:
- Around line 89-109: The warning for invalid glob patterns should include the
error message returned by GlobPattern::create instead of only the raw pattern;
update the branch where pat is falsy to extract the descriptive error (from the
returned unexpected/error payload of GlobPattern::create) and pass it into
LOG_WARN along with pattern_str so users see why compilation failed (keep the
existing check of compiled.patterns.empty(), and adjust the invalid-pattern log
in the loop that handles pat failures inside the rule-processing loop).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 8c517bfd-2e6b-43ab-ac7b-5cf76e957849

📥 Commits

Reviewing files that changed from the base of the PR and between 0b1dec1 and deba73f.

📒 Files selected for processing (3)
  • src/server/compiler.cpp
  • src/server/config.cpp
  • tests/unit/server/config_tests.cpp

Comment thread tests/unit/server/config_tests.cpp
16bit-ykiko and others added 2 commits April 19, 2026 02:35
Correctness:
- Apply match_rules() in fill_header_context_args so that user-defined
  [[rules]] apply uniformly to header files served via header-context
  fallback, not just via the normal CDB lookup path.
- substitute_workspace() is now a no-op when workspace_root is empty,
  avoiding bogus paths like "/cache" from "${workspace}/cache".
- load() now parses TOML with toml++ directly to capture line, column
  and description on failure (kotatsu's wrapper discarded this). The
  warning is promoted to LOG_ERROR and a follow-up warning is emitted
  when load_from_workspace() falls back because the file is malformed.

Style:
- Drop unused <thread> include.
- Migrate const std::string& path/workspace_root parameters to
  llvm::StringRef per project convention.
- Remove using-declarations from config.h to avoid leaking kota::meta
  names into every TU that includes it.
- Rename shadowing "path" loop variable.
- Replace noisy std::string() comparisons with .empty() in tests.

Tests (+8, total 545):
- XdgHashUnique: different workspaces get different hashed cache dirs.
- HomeFallback: $HOME/.cache/clice/<hash> used when XDG is unset.
- WorkspaceCacheFallback: ${workspace}/.clice used when both unset.
- WorkspaceSubstEmpty: empty workspace_root leaves placeholder intact.
- WorkspaceSubstRepeated: multiple ${workspace} in one string.
- CompilePathsList: substitution applied per compile_commands_paths entry.
- TomlErrorLocated / WorkspaceMalformedFallback: malformed clice.toml
  returns nullopt from load() and falls back to defaults from
  load_from_workspace().

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Revert the toml++ direct-parse workaround; kotatsu's serde_error already
carries source_location (populated for schema errors) and formats it via
to_string(), so bypassing the wrapper was unnecessary.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (4)
tests/unit/server/config_tests.cpp (1)

315-321: WorkspaceSubstRepeated is pinning a surprising behavior — consider asserting post-normalization instead.

Expected value "/root/a//root/b" contains a // that comes purely from naive string replacement (user-written /${workspace} + workspace_root = "/root"). Any future PR that normalizes output (e.g., collapsing duplicate separators, which llvm::sys::path helpers sometimes do) would break this test even though it would be an improvement. If the point is "every occurrence is substituted" rather than "no normalization happens", consider asserting both halves independently or counting occurrences, so the test doesn't pin a debatable artifact.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/server/config_tests.cpp` around lines 315 - 321, The test
WorkspaceSubstRepeated pins an implementation artifact by expecting
"/root/a//root/b" after CliceConfig::apply_defaults substitutes
project.cache_dir; instead update the test to assert the semantic intent (that
every "${workspace}" occurrence is substituted) rather than exact raw string
with duplicate separators—for example, verify that project.cache_dir contains
two occurrences of "/root" in the expected positions or assert the two path
segments independently after normalization; locate the TEST_CASE
WorkspaceSubstRepeated and CliceConfig.project.cache_dir and change the
EXPECT_EQ to an assertion that checks occurrence count or compares normalized
segments so future path-normalization changes won't break the test.
src/server/config.cpp (2)

46-55: XDG cache key is workspace_root as-received — not normalized.

xxh3_64bits(workspace_root) is computed on the raw string passed by the caller, so logically-equivalent roots (/home/user/proj, /home/user/proj/, /home/user/./proj, a path with a different case on case-insensitive FS, a symlinked path) map to distinct cache dirs. That means the same project opened two slightly different ways on the same machine will cold-start the index twice and consume extra disk. Consider canonicalizing via llvm::sys::fs::real_path/remove_dots (with a safe fallback when the path doesn't yet exist) before hashing. Low-severity but worth doing while the layout is fresh.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/config.cpp` around lines 46 - 55, The cache key is computed from
the raw workspace_root string (xxh3_64bits(workspace_root)), causing equivalent
paths to map to different directories; normalize the path before hashing by
resolving dots/symlinks and canonicalizing where possible (use
llvm::sys::fs::real_path or remove_dots) and fall back to a safe normalization
when the path does not exist (e.g., remove_dots or weakly canonicalize and
normalize case on case-insensitive platforms) so that workspace_root is
normalized prior to calling xxh3_64bits; update the code around workspace_root,
xxh3_64bits, and the path::join creation so the hash is computed from the
normalized_path and keep existing create_directories logic unchanged.

188-195: Log string split is a formatter trap.

Adjacent string-literal concatenation between a trailing-space literal and the format string works, but it means the format string is assembled at compile time across two literals — easy to silently lose a {}-count when someone edits only one half. Since this already logs worker_memory_limit that isn't part of what ends up happening (e.g., a user-overridden memory_limit), consider just writing a single contiguous format string.

Nit
-    LOG_INFO(
-        "No clice.toml found, using default configuration " "(stateful={}, stateless={}, memory_limit={}MB)",
-        config.project.stateful_worker_count.value,
-        config.project.stateless_worker_count.value,
-        config.project.worker_memory_limit.value / (1024 * 1024));
+    LOG_INFO("No clice.toml found, using default configuration "
+             "(stateful={}, stateless={}, memory_limit={}MB)",
+             config.project.stateful_worker_count.value,
+             config.project.stateless_worker_count.value,
+             config.project.worker_memory_limit.value / (1024 * 1024));
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/config.cpp` around lines 188 - 195, The log message in the
fallback branch uses adjacent string-literal concatenation which risks
mismatched format placeholders; update the LOG_INFO call in this block (the call
that follows CliceConfig config; config.apply_defaults(workspace_root);) to use
a single contiguous format string that includes all three placeholders in the
correct order for config.project.stateful_worker_count.value,
config.project.stateless_worker_count.value, and
config.project.worker_memory_limit.value / (1024 * 1024), ensuring spacing and
braces are correct so the formatter and arguments cannot get out of sync.
src/server/config.h (1)

33-39: Intentional split between defaulted<> and std::optional<> is fine, just worth calling out.

enable_indexing/idle_timeout_ms use std::optional specifically so apply_defaults can distinguish "unset" from an explicit false/0 (see ApplyDefaultsPreserveSet). The rest use defaulted<> because their zero/empty sentinel is also a valid "unset" signal. Consider a brief comment on these two lines so future contributors don't "normalize" them to defaulted<> and silently break user-overrides of false.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server/config.h` around lines 33 - 39, Add a brief clarifying comment
above the two members so future maintainers know why enable_indexing and
idle_timeout_ms use std::optional instead of kota::meta::defaulted: explain that
ApplyDefaultsPreserveSet needs to distinguish "unset" from explicit values
(false/0) so enable_indexing and idle_timeout_ms must be optional, whereas
stateful_worker_count/stateless_worker_count/worker_memory_limit use defaulted<>
because their zero/empty sentinel can represent "unset". Reference the member
names enable_indexing, idle_timeout_ms and the function/behavior
ApplyDefaultsPreserveSet in the comment.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/server/config.cpp`:
- Around line 46-55: The cache key is computed from the raw workspace_root
string (xxh3_64bits(workspace_root)), causing equivalent paths to map to
different directories; normalize the path before hashing by resolving
dots/symlinks and canonicalizing where possible (use llvm::sys::fs::real_path or
remove_dots) and fall back to a safe normalization when the path does not exist
(e.g., remove_dots or weakly canonicalize and normalize case on case-insensitive
platforms) so that workspace_root is normalized prior to calling xxh3_64bits;
update the code around workspace_root, xxh3_64bits, and the path::join creation
so the hash is computed from the normalized_path and keep existing
create_directories logic unchanged.
- Around line 188-195: The log message in the fallback branch uses adjacent
string-literal concatenation which risks mismatched format placeholders; update
the LOG_INFO call in this block (the call that follows CliceConfig config;
config.apply_defaults(workspace_root);) to use a single contiguous format string
that includes all three placeholders in the correct order for
config.project.stateful_worker_count.value,
config.project.stateless_worker_count.value, and
config.project.worker_memory_limit.value / (1024 * 1024), ensuring spacing and
braces are correct so the formatter and arguments cannot get out of sync.

In `@src/server/config.h`:
- Around line 33-39: Add a brief clarifying comment above the two members so
future maintainers know why enable_indexing and idle_timeout_ms use
std::optional instead of kota::meta::defaulted: explain that
ApplyDefaultsPreserveSet needs to distinguish "unset" from explicit values
(false/0) so enable_indexing and idle_timeout_ms must be optional, whereas
stateful_worker_count/stateless_worker_count/worker_memory_limit use defaulted<>
because their zero/empty sentinel can represent "unset". Reference the member
names enable_indexing, idle_timeout_ms and the function/behavior
ApplyDefaultsPreserveSet in the comment.

In `@tests/unit/server/config_tests.cpp`:
- Around line 315-321: The test WorkspaceSubstRepeated pins an implementation
artifact by expecting "/root/a//root/b" after CliceConfig::apply_defaults
substitutes project.cache_dir; instead update the test to assert the semantic
intent (that every "${workspace}" occurrence is substituted) rather than exact
raw string with duplicate separators—for example, verify that project.cache_dir
contains two occurrences of "/root" in the expected positions or assert the two
path segments independently after normalization; locate the TEST_CASE
WorkspaceSubstRepeated and CliceConfig.project.cache_dir and change the
EXPECT_EQ to an assertion that checks occurrence count or compares normalized
segments so future path-normalization changes won't break the test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 62255d15-b86a-4f56-8133-0f089ed975c6

📥 Commits

Reviewing files that changed from the base of the PR and between deba73f and 006d008.

📒 Files selected for processing (4)
  • src/server/compiler.cpp
  • src/server/config.cpp
  • src/server/config.h
  • tests/unit/server/config_tests.cpp
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/server/compiler.cpp

16bit-ykiko and others added 6 commits April 19, 2026 04:46
- scan_dependency_graph now applies [[rules]] via an optional RuleMatcher
  callback so -I/-isystem/-std modifications are visible to include
  resolution — previously only fill_compile_args honored rules, leaving
  rule-affected files with unresolved includes in the dependency graph.

- initializationOptions is now layered on top of the workspace config
  instead of replacing it: load clice.toml first, overlay JSON fields
  that are present, then re-apply defaults. Partial overrides no longer
  silently drop unrelated toml settings like rules and cache paths.

- match_rules processes rules in declaration order and cancels earlier
  appends when a later rule removes the same flag. Lookup applies
  removals only against base CDB flags, so without this fold a later
  rule could not override what an earlier matching rule appended.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
POSIX setenv/unsetenv aren't part of the Windows CRT, so the XDG fallback
tests failed to compile with MSVC. Wrap the calls in small helpers that
dispatch to _putenv_s on Windows (passing an empty value removes the
variable).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
path::join produces native separators on Windows, so cache_dir
assertions comparing against literal forward-slash paths failed
under Windows CI. Normalize with path::convert_to_slash before
comparing, matching the pattern already used in command_tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…Options rules

Covers five scenarios: baseline (no rules), TOML-declared rules, rules sent
via LSP initializationOptions, initializationOptions replacing TOML rules,
and patterns that don't match. Each test's main.cpp references a macro
that is only defined when the rule is applied, so rule handling is
observable through compiler diagnostics.

Adds an @pytest.mark.init_options marker so tests can pass initialization
options through the client fixture.
@16bit-ykiko
16bit-ykiko merged commit 17e6801 into main Apr 19, 2026
20 checks passed
@16bit-ykiko
16bit-ykiko deleted the improve/config-handling branch April 19, 2026 16:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant