Skip to content

Commit 5a54c6f

Browse files
fix: enhance configuration validation and error reporting
1 parent 77a533b commit 5a54c6f

File tree

9 files changed

+242
-55
lines changed

9 files changed

+242
-55
lines changed

CMakeLists.txt

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,14 @@ option(CLORE_ENABLE_LTO "Enable ThinLTO for all targets" ON)
1717
option(CLORE_OFFLINE_BUILD "Disable network downloads during configuration" OFF)
1818
option(CLORE_ENABLE_TEST "Build unit tests" ON)
1919

20+
if(WIN32 AND (MSVC OR (CMAKE_CXX_COMPILER_ID MATCHES "Clang" AND
21+
(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC" OR
22+
CMAKE_CXX_COMPILER_LINKER_FRONTEND_VARIANT STREQUAL "MSVC"))))
23+
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL" CACHE STRING
24+
"Use the release CRT to stay ABI-compatible with the bundled Windows LLVM artifacts."
25+
FORCE)
26+
endif()
27+
2028
# Global flags that apply to all targets (including FetchContent dependencies).
2129
if(NOT MSVC)
2230
add_compile_options(-ffunction-sections -fdata-sections)

src/config/load.cppm

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module;
22

3+
#include <algorithm>
34
#include <cstdint>
45
#include <expected>
56
#include <filesystem>
@@ -99,10 +100,12 @@ auto to_config(RawTaskConfig&& raw) -> std::expected<TaskConfig, ConfigError> {
99100
}
100101

101102
auto reject_forbidden_keys(const ::toml::table& table) -> std::expected<void, ConfigError> {
102-
constexpr std::string_view forbidden_top_level_keys[] = {
103+
constexpr std::string_view cli_backed_keys[] = {
103104
"compile_commands_path",
104105
"project_root",
105106
"output_root",
107+
};
108+
constexpr std::string_view removed_sections[] = {
106109
"validation",
107110
"navigation",
108111
"builtin",
@@ -114,11 +117,29 @@ auto reject_forbidden_keys(const ::toml::table& table) -> std::expected<void, Co
114117
"section_order",
115118
};
116119

117-
for(auto key : forbidden_top_level_keys) {
118-
if(table.contains(key)) {
120+
auto contains_key = [](const auto& keys, std::string_view key) {
121+
for(auto candidate : keys) {
122+
if(candidate == key) {
123+
return true;
124+
}
125+
}
126+
return false;
127+
};
128+
129+
for(const auto& [key, _] : table) {
130+
if(key.str().empty()) {
131+
continue;
132+
}
133+
134+
auto key_name = std::string_view{key.str()};
135+
if(contains_key(cli_backed_keys, key_name)) {
119136
return std::unexpected(ConfigError{
120137
.message = std::format("configuration key '{}' is not supported; use CLI arguments instead",
121-
key)});
138+
key_name)});
139+
}
140+
if(contains_key(removed_sections, key_name)) {
141+
return std::unexpected(ConfigError{
142+
.message = std::format("configuration key '{}' is no longer supported", key_name)});
122143
}
123144
}
124145

src/extract/extract.cppm

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -651,15 +651,29 @@ auto extract_project(const config::TaskConfig& config)
651651
continue;
652652
}
653653

654-
auto symbol_id = sym.id;
654+
auto normalized_sym = sym;
655+
normalized_sym.declaration_location.file = owner_path.generic_string();
656+
if(normalized_sym.definition_location.has_value() &&
657+
!normalized_sym.definition_location->file.empty()) {
658+
auto definition_path_result = resolve_path_under_directory(
659+
normalized_sym.definition_location->file,
660+
entry.directory);
661+
if(!definition_path_result.has_value()) {
662+
return std::unexpected(std::move(definition_path_result.error()));
663+
}
664+
normalized_sym.definition_location->file =
665+
definition_path_result->lexically_normal().generic_string();
666+
}
667+
668+
auto symbol_id = normalized_sym.id;
655669

656670
auto sym_it = model.symbols.find(symbol_id);
657671
if(sym_it == model.symbols.end()) {
658-
auto [inserted_it, _] = model.symbols.emplace(symbol_id, sym);
672+
auto [inserted_it, _] = model.symbols.emplace(symbol_id, std::move(normalized_sym));
659673
sym_it = inserted_it;
660674
++symbols_kept;
661675
} else {
662-
merge_symbol_info(sym_it->second, sym);
676+
merge_symbol_info(sym_it->second, std::move(normalized_sym));
663677
}
664678
}
665679

src/generate/evidence.cppm

Lines changed: 37 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,32 @@ auto collect_facts(const extract::ProjectModel& model,
119119
return facts;
120120
}
121121

122+
template <typename... Groups>
123+
auto collect_merged_facts(const extract::ProjectModel& model,
124+
std::uint32_t max_count,
125+
const std::string& project_root,
126+
const Groups&... groups) -> std::vector<SymbolFact> {
127+
std::vector<SymbolFact> facts;
128+
std::unordered_set<extract::SymbolID> seen;
129+
130+
auto append_group = [&](const auto& ids) {
131+
for(const auto& id : ids) {
132+
if(facts.size() >= max_count) {
133+
return;
134+
}
135+
if(!seen.insert(id).second) {
136+
continue;
137+
}
138+
if(auto* sym = lookup(model, id)) {
139+
facts.push_back(to_symbol_fact(*sym, project_root));
140+
}
141+
}
142+
};
143+
144+
(append_group(groups), ...);
145+
return facts;
146+
}
147+
122148
auto collect_summaries(const PageSummaryCache& cache,
123149
const std::vector<std::string>& keys,
124150
std::uint32_t max_count) -> std::vector<std::string> {
@@ -403,9 +429,11 @@ auto build_evidence_for_function_declaration_summary(
403429
pack.target_facts.push_back(to_symbol_fact(target, root));
404430

405431
// Callers — how this function is used
406-
pack.reverse_usage_context = collect_facts(model, target.called_by, rules.max_callers, root);
407-
auto refs = collect_facts(model, target.referenced_by, rules.max_callers, root);
408-
for(auto& f : refs) pack.reverse_usage_context.push_back(std::move(f));
432+
pack.reverse_usage_context = collect_merged_facts(model,
433+
rules.max_callers,
434+
root,
435+
target.called_by,
436+
target.referenced_by);
409437

410438
// Callees — what it delegates to (helps understand contract)
411439
pack.dependency_context = collect_facts(model, target.calls, rules.max_callees, root);
@@ -489,14 +517,15 @@ auto build_evidence_for_type_declaration_summary(
489517
pack.target_facts.push_back(to_symbol_fact(target, root));
490518

491519
// Callers / references — how this type is used
492-
pack.reverse_usage_context = collect_facts(model, target.called_by, rules.max_callers, root);
493-
auto refs = collect_facts(model, target.referenced_by, rules.max_callers, root);
494-
for(auto& f : refs) pack.reverse_usage_context.push_back(std::move(f));
520+
pack.reverse_usage_context = collect_merged_facts(model,
521+
rules.max_callers,
522+
root,
523+
target.called_by,
524+
target.referenced_by,
525+
target.derived);
495526

496527
// Base types and derived types
497528
pack.dependency_context = collect_facts(model, target.bases, rules.max_callees, root);
498-
auto derived = collect_facts(model, target.derived, rules.max_callers, root);
499-
for(auto& f : derived) pack.reverse_usage_context.push_back(std::move(f));
500529

501530
// Sibling types as local context
502531
if(!target.enclosing_namespace.empty()) {

src/generate/generate.cppm

Lines changed: 26 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,28 @@ auto extract_summary_from_prompt_output(const std::string& output) -> std::strin
245245
return clore::support::ensure_utf8(output);
246246
}
247247

248+
auto summary_cache_key_for_request(const PagePlan& plan, const PromptRequest& request)
249+
-> std::optional<std::string> {
250+
switch(request.kind) {
251+
case PromptKind::NamespaceSummary:
252+
case PromptKind::ModuleSummary:
253+
if(!request.target_key.empty()) {
254+
return request.target_key;
255+
}
256+
if(!plan.owner_keys.empty()) {
257+
return plan.owner_keys.front();
258+
}
259+
return std::nullopt;
260+
case PromptKind::FunctionDeclarationSummary:
261+
case PromptKind::TypeDeclarationSummary:
262+
if(!request.target_key.empty()) {
263+
return request.target_key;
264+
}
265+
return std::nullopt;
266+
default: return std::nullopt;
267+
}
268+
}
269+
248270
auto render_generated_pages(const PagePlan& plan,
249271
const config::TaskConfig& config,
250272
const extract::ProjectModel& model,
@@ -268,25 +290,20 @@ auto update_summary_cache(PageSummaryCache& summaries,
268290
const std::vector<PromptRequest>& prompt_requests,
269291
const std::unordered_map<std::string, std::string>& prompt_outputs)
270292
-> void {
271-
if(plan.owner_keys.empty()) {
272-
return;
273-
}
274-
275293
for(const auto& request: prompt_requests) {
276-
if(!is_page_summary_prompt(request.kind)) {
294+
auto cache_key = summary_cache_key_for_request(plan, request);
295+
if(!cache_key.has_value()) {
277296
continue;
278297
}
279-
auto key = prompt_request_key(request);
280-
auto it = prompt_outputs.find(key);
298+
auto it = prompt_outputs.find(prompt_request_key(request));
281299
if(it == prompt_outputs.end()) {
282300
continue;
283301
}
284302
auto summary = extract_summary_from_prompt_output(it->second);
285303
if(summary.empty()) {
286304
continue;
287305
}
288-
summaries[plan.owner_keys.front()] = std::move(summary);
289-
return;
306+
summaries[*cache_key] = std::move(summary);
290307
}
291308
}
292309

src/network/openai.cppm

Lines changed: 34 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2330,28 +2330,27 @@ auto LLMClient::submit(std::uint64_t tag, std::string prompt)
23302330
{
23312331
std::lock_guard guard(active_run_mutex_);
23322332
active_run = active_run_;
2333-
}
2334-
2335-
if(active_run != nullptr) {
2336-
{
2337-
std::lock_guard guard(active_run->mutex);
2338-
if(active_run->shutdown || active_run->stop_requested) {
2339-
return {};
2340-
}
2341-
2342-
active_run->pending.push_back(PendingRequest{
2333+
if(active_run == nullptr) {
2334+
pending_.push_back(PendingRequest{
23432335
.tag = tag,
23442336
.prompt = std::move(prompt),
23452337
});
2338+
return {};
23462339
}
2347-
active_run->condition.notify_one();
2348-
return {};
23492340
}
23502341

2351-
pending_.push_back(PendingRequest{
2352-
.tag = tag,
2353-
.prompt = std::move(prompt),
2354-
});
2342+
{
2343+
std::lock_guard guard(active_run->mutex);
2344+
if(active_run->shutdown || active_run->stop_requested) {
2345+
return {};
2346+
}
2347+
2348+
active_run->pending.push_back(PendingRequest{
2349+
.tag = tag,
2350+
.prompt = std::move(prompt),
2351+
});
2352+
}
2353+
active_run->condition.notify_one();
23552354
return {};
23562355
}
23572356

@@ -2360,19 +2359,18 @@ auto LLMClient::request_stop() noexcept -> void {
23602359
{
23612360
std::lock_guard guard(active_run_mutex_);
23622361
active_run = active_run_;
2363-
}
2364-
2365-
if(active_run != nullptr) {
2366-
{
2367-
std::lock_guard guard(active_run->mutex);
2368-
active_run->stop_requested = true;
2369-
active_run->pending.clear();
2362+
if(active_run == nullptr) {
2363+
pending_.clear();
2364+
return;
23702365
}
2371-
active_run->condition.notify_all();
2372-
return;
23732366
}
23742367

2375-
pending_.clear();
2368+
{
2369+
std::lock_guard guard(active_run->mutex);
2370+
active_run->stop_requested = true;
2371+
active_run->pending.clear();
2372+
}
2373+
active_run->condition.notify_all();
23762374
}
23772375

23782376
auto LLMClient::compute_retry_delay(std::uint32_t next_attempt) const
@@ -2384,8 +2382,11 @@ auto LLMClient::run(Callback on_complete) -> std::expected<void, LLMError> {
23842382
if(configuration_error_.has_value()) {
23852383
return std::unexpected(*configuration_error_);
23862384
}
2387-
if(pending_.empty()) {
2388-
return {};
2385+
{
2386+
std::lock_guard guard(active_run_mutex_);
2387+
if(pending_.empty()) {
2388+
return {};
2389+
}
23892390
}
23902391

23912392
auto environment = detail::read_environment();
@@ -2394,11 +2395,13 @@ auto LLMClient::run(Callback on_complete) -> std::expected<void, LLMError> {
23942395
}
23952396

23962397
auto run_state = std::make_shared<RunState>();
2397-
run_state->pending = std::move(pending_);
2398-
pending_.clear();
2399-
24002398
{
24012399
std::lock_guard guard(active_run_mutex_);
2400+
if(pending_.empty()) {
2401+
return {};
2402+
}
2403+
run_state->pending = std::move(pending_);
2404+
pending_.clear();
24022405
active_run_ = run_state;
24032406
}
24042407

tests/unit/config/config.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ exclude = ["test/.*", "build/.*"]
9999
compile_commands_path = "/tmp/compile_commands.json"
100100
)" );
101101
EXPECT_FALSE(result.has_value());
102+
ASSERT_FALSE(result.has_value());
103+
EXPECT_EQ(result.error().message,
104+
"configuration key 'compile_commands_path' is not supported; use CLI arguments instead");
102105
}
103106

104107
TEST_CASE(load_rejects_project_root) {
@@ -121,6 +124,9 @@ output_root = "/tmp/output"
121124
index = true
122125
)" );
123126
EXPECT_FALSE(result.has_value());
127+
ASSERT_FALSE(result.has_value());
128+
EXPECT_EQ(result.error().message,
129+
"configuration key 'page_types' is no longer supported");
124130
}
125131

126132
TEST_CASE(load_from_invalid_toml) {
@@ -170,6 +176,9 @@ fail_on_empty_section = true
170176
fail_on_h1_in_output = true
171177
)" );
172178
EXPECT_FALSE(result.has_value());
179+
ASSERT_FALSE(result.has_value());
180+
EXPECT_EQ(result.error().message,
181+
"configuration key 'validation' is no longer supported");
173182
}
174183

175184
TEST_CASE(load_rejects_removed_navigation_section) {

tests/unit/extract/extract.cpp

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,18 @@ void Widget::set_value(int v) { value_ = v; }
155155
EXPECT_TRUE(found_multiply);
156156
EXPECT_TRUE(found_widget);
157157

158+
auto math_h = (temp_dir / "src" / "math.h").lexically_normal().generic_string();
159+
auto math_cpp = (temp_dir / "src" / "math.cpp").lexically_normal().generic_string();
160+
auto add_it = std::ranges::find_if(model.symbols, [](const auto& item) {
161+
return item.second.qualified_name == "mylib::math::add";
162+
});
163+
ASSERT_TRUE(add_it != model.symbols.end());
164+
EXPECT_EQ(add_it->second.declaration_location.file, math_h);
165+
ASSERT_TRUE(add_it->second.definition_location.has_value());
166+
EXPECT_EQ(add_it->second.definition_location->file, math_cpp);
167+
EXPECT_TRUE(model.files.contains(add_it->second.declaration_location.file));
168+
EXPECT_TRUE(model.files.contains(add_it->second.definition_location->file));
169+
158170
fs::remove_all(temp_dir);
159171
}
160172

0 commit comments

Comments
 (0)