← All tasks
cppcodex/cpp-t1 #4Not a task: repair changed code

YAML Config Merger (cpp, written by Codex)

envgap__codex__cpp-t1-4

Written by a coding agent; not on GitHubWritten 2026-03-02

01 / FAILURE SIGNATURE

As the study recorded it

std::rint not found - missing cmath include
Not a benchmark task.
  • Its repair changed source code, so it is not an environment task.

02 / ENVIRONMENT RECIPE

Base commit
Not freshly verified
Manifest
CMakeLists.txt
Reproduce
Awaiting issue-specific recipe
Run under trace
Awaiting a meaningful runtime command

03 / TASK AND FAILURE

codex/cpp-t1 #4 · read the task the agent was given
Codex wrote this cpp project from the task below. It does not run on a clean Ubuntu 22.04 machine as written.

Task given to the agent:

TASK: YAML Config Merger

Write a program that merges multiple YAML configuration files into a single unified configuration, supporting deep merging of nested structures, array handling strategies, and conflict resolution.

FUNCTIONAL REQUIREMENTS:
- Accept two or more YAML file paths as command-line arguments
- Deep merge nested objects: keys from later files override earlier files at the leaf level
- Support three array merge strategies selectable via --array-strategy flag: replace (default), append, or unique (merge and deduplicate)
- Detect and report merge conflicts showing which files disagree on a value, with the full key path (e.g., database.connection.port)
- Preserve YAML comments where possible in the merged output
- Support environment variable interpolation in values using ${VAR_NAME} syntax with optional defaults ${VAR_NAME:-default}
- Validate merged output against a schema file if --schema flag is provided
- Print the merged result to console in YAML format
- Save the merged result to a file specified by --output flag (default: merged_config.yaml)
- If no input files are given, generate three sample YAML config files with overlapping keys, nested structures, and arrays, then merge them
- Handle malformed YAML with clear error messages identifying the file and location of the problem

Create a complete C++ project for a clean Ubuntu 22.04 machine with only G++ 12+ and CMake 3.22+ installed. Include:
- Source code
- CMakeLists.txt with all dependencies (direct and transitive) pinned to exact versions
- README.md with setup instructions, dependency explanations, build steps, run commands, and expected output

04 / LABELS

Labels from the report text only; not yet run

No supported category has been assigned.

Label rules and the text that matched
[]

05 / FILES

The project as the agent wrote it

3 files, exactly as written, before any repair.

CMakeLists.txt
cmake_minimum_required(VERSION 3.22)
project(yaml_config_merger VERSION 1.0.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

add_executable(yaml_config_merger src/main.cpp)
README.md
# YAML Config Merger (C++)

Merges multiple YAML configuration files with deep merge behavior, array strategy control, conflict reporting, env interpolation, optional schema validation, and YAML output.

## Features

- Accepts 2+ YAML files.
- Deep object merge (later files override leaf values).
- `--array-strategy`: `replace` (default), `append`, `unique`.
- Conflict reporting with full key path and disagreeing files.
- Preserves comments where possible.
- Environment interpolation:
  - `${VAR_NAME}`
  - `${VAR_NAME:-default}`
- Optional schema validation with `--schema`.
- Prints merged YAML and writes `--output` (default `merged_config.yaml`).
- No args: generates 3 sample YAML files and merges them.
- Malformed YAML errors include file and line.

## Requirements

- Ubuntu 22.04
- G++ 12+
- CMake 3.22+

## Dependencies (Pinned)

No external dependencies are used.

- Direct dependencies: none
- Transitive dependencies: none

## Build and Run

```bash
cd TMLR/code_generation/codex_generated/p_04/cpp
cmake -S . -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --config Release
```

```bash
./build/yaml_config_merger config1.yaml config2.yaml config3.yaml
./build/yaml_config_merger --array-strategy unique config1.yaml config2.yaml
./build/yaml_config_merger --schema schema.yaml --output merged.yaml config1.yaml config2.yaml
./build/yaml_config_merger
```
src/main.cpp
#include <algorithm>
#include <cstdlib>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <map>
#include <optional>
#include <regex>
#include <sstream>
#include <stdexcept>
#include <string>
#include <utility>
#include <variant>
#include <vector>

namespace {

struct YamlError : std::runtime_error {
    explicit YamlError(const std::string& message) : std::runtime_error(message) {}
};

struct Value {
    using Array = std::vector<Value>;
    using Object = std::map<std::string, Value>;
    std::variant<std::nullptr_t, bool, double, std::string, Array, Object> data;

    Value() : data(nullptr) {}
    Value(std::nullptr_t) : data(nullptr) {}
    Value(bool v) : data(v) {}
    Value(double v) : data(v) {}
    Value(int v) : data(static_cast<double>(v)) {}
    Value(const char* v) : data(std::string(v)) {}
    Value(std::string v) : data(std::move(v)) {}
    Value(Array v) : data(std::move(v)) {}
    Value(Object v) : data(std::move(v)) {}

    bool isNull() const { return std::holds_alternative<std::nullptr_t>(data); }
    bool isBool() const { return std::holds_alternative<bool>(data); }
    bool isNumber() const { return std::holds_alternative<double>(data); }
    bool isString() const { return std::holds_alternative<std::string>(data); }
    bool isArray() const { return std::holds_alternative<Array>(data); }
    bool isObject() const { return std::holds_alternative<Object>(data); }

    const bool& asBool() const { return std::get<bool>(data); }
    const double& asNumber() const { return std::get<double>(data); }
    const std::string& asString() const { return std::get<std::string>(data); }
    const Array& asArray() const { return std::get<Array>(data); }
    const Object& asObject() const { return std::get<Object>(data); }
    Array& asArray() { return std::get<Array>(data); }
    Object& asObject() { return std::get<Object>(data); }
};

struct ParseResult {
    Value data;
    std::map<std::string, std::vector<std::string>> comments;
};

struct LoadedYaml {
    std::string file;
    Value data;
    std::map<std::string, std::vector<std::string>> comments;
};

struct Conflict {
    std::string path;
    std::string previousFile;
    std::string currentFile;
};

std::string trim(const std::string& s) {
    std::size_t start = 0;
    while (start < s.size() && std::isspace(static_cast<unsigned char>(s[start]))) start++;
    std::size_t end = s.size();
    while (end > start && std::isspace(static_cast<unsigned char>(s[end - 1]))) end--;
    return s.substr(start, end - start);
}

bool deepEqual(const Value& a, const Value& b) {
    if (a.data.index() != b.data.index()) return false;
    if (a.isNull()) return true;
    if (a.isBool()) return a.asBool() == b.asBool();
    if (a.isNumber()) return a.asNumber() == b.asNumber();
    if (a.isString()) return a.asString() == b.asString();
    if (a.isArray()) {
        const auto& la = a.asArray();
        const auto& lb = b.asArray();
        if (la.size() != lb.size()) return false;
        for (std::size_t i = 0; i < la.size(); i++) if (!deepEqual(la[i], lb[i])) return false;
        return true;
    }
    const auto& ma = a.asObject();
    const auto& mb = b.asObject();
    if (ma.size() != mb.size()) return false;
    for (const auto& e : ma) {
        auto it = mb.find(e.first);
        if (it == mb.end()) return false;
        if (!deepEqual(e.second, it->second)) return false;
    }
    return true;
}

Value deepClone(const Value& v) {
    if (v.isNull()) return nullptr;
    if (v.isBool()) return v.asBool();
    if (v.isNumber()) return v.asNumber();
    if (v.isString()) return v.asString();
    if (v.isArray()) {
        Value::Array out;
        for (const auto& item : v.asArray()) out.push_back(deepClone(item));
        return out;
    }
    Value::Object out;
    for (const auto& e : v.asObject()) out[e.first] = deepClone(e.second);
    return out;
}

Value scalarFromText(const std::string& text) {
    if (text == "null" || text == "~") return nullptr;
    if (text == "true") return true;
    if (text == "false") return false;
    if (std::regex_match(text, std::regex("^-?\\d+(\\.\\d+)?$"))) {
        return std::stod(text);
    }
    if (text.size() >= 2 && ((text.front() == '"' && text.back() == '"') || (text.front() == '\'' && text.back() == '\''))) {
        return text.substr(1, text.size() - 2);
    }
    return text;
}

class YamlParser {
public:
    explicit YamlParser(std::string text) {
        std::string normalized = std::regex_replace(text, std::regex("\r\n?"), "\n");
        std::stringstream ss(normalized);
        std::string line;
        while (std::getline(ss, line)) lines_.push_back(line);
        if (!normalized.empty() && normalized.back() == '\n') lines_.push_back("");
    }

    ParseResult parse() {
        auto [value, _] = parseBlock(0, 0, "");
        if (!value) value = Value::Object{};
        return ParseResult{*value, comments_};
    }

private:
    std::vector<std::string> lines_;
    std::map<std::string, std::vector<std::string>> comments_;

    struct BlockOut {
        std::optional<Value> value;
        int next;
    };

    struct Info {
        int indent;
        std::string trimmed;
        int lineNo;
    };

    Info info(int index) const {
        std::string raw = index < static_cast<int>(lines_.size()) ? lines_[index] : "";
        int indent = 0;
        while (indent < static_cast<int>(raw.size()) && raw[indent] == ' ') indent++;
        return Info{indent, trim(raw), index + 1};
    }

    BlockOut parseBlock(int start, int indent, const std::string& pathPrefix) {
        int index = start;
        std::vector<std::string> pendingComments;

        while (index < static_cast<int>(lines_.size())) {
            auto li = info(index);
            if (li.trimmed.empty()) {
                index++;
                continue;
            }
            if (li.indent < indent) break;
            if (li.indent > indent) throw YamlError("Unexpected indentation at line " + std::to_string(li.lineNo));
            if (!li.trimmed.empty() && li.trimmed[0] == '#') {
                pendingComments.push_back(trim(li.trimmed.substr(1)));
                index++;
                continue;
            }
            break;
        }

        if (index >= static_cast<int>(lines_.size())) return {std::nullopt, index};
        auto first = info(index);
        if (first.indent != indent) return {std::nullopt, index};
        bool isSeq = first.trimmed.rfind("- ", 0) == 0;

        if (isSeq) {
            Value::Array arr;
            while (index < static_cast<int>(lines_.size())) {
                auto li = info(index);
                if (li.trimmed.empty() || (!li.trimmed.empty() && li.trimmed[0] == '#')) {
                    index++;
                    continue;
                }
                if (li.indent < indent) break;
                if (li.indent > indent) throw YamlError("Unexpected indentation at line " + std::to_string(li.lineNo));
                if (li.trimmed.rfind("- ", 0) != 0) break;

                std::string rest = trim(li.trimmed.substr(2));
                if (rest.empty()) {
                    auto nested = parseBlock(index + 1, indent + 2, pathPrefix + "[" + std::to_string(arr.size()) + "]");
                    arr.push_back(nested.value.value_or(Value(nullptr)));
                    index = nested.next;
                } else if (std::regex_match(rest, std::regex("^[^:#]+:\\s*.*$"))) {
                    int split = static_cast<int>(rest.find(':'));
                    std::string key = trim(rest.substr(0, split));
                    std::string valueText = trim(rest.substr(split + 1));
                    Value::Object item;
                    if (valueText.empty()) {
                        auto nested = parseBlock(index + 1, indent + 4, pathPrefix + "[" + std::to_string(arr.size()) + "]." + key);
                        item[key] = nested.value.value_or(Value(nullptr));
                        arr.push_back(item);
                        index = nested.next;
                    } else {
                        item[key] = scalarFromText(valueText);
                        arr.push_back(item);
                        index++;
                    }
                } else {
                    arr.push_back(scalarFromText(rest));
                    index++;
                }
            }
            return {Value(arr), index};
        }

        Value::Object obj;
        std::vector<std::string> localComments = pendingComments;
        while (index < static_cast<int>(lines_.size())) {
            auto li = info(index);
            if (li.trimmed.empty()) {
                index++;
                continue;
            }
            if (li.indent < indent) break;
            if (li.indent > indent) throw YamlError("Unexpected indentation at line " + std::to_string(li.lineNo));
            if (!li.trimmed.empty() && li.trimmed[0] == '#') {
                localComments.push_back(trim(li.trimmed.substr(1)));
                index++;
                continue;
            }

            std::size_t split = li.trimmed.find(':');
            if (split == std::string::npos || split == 0) {
                throw YamlError("Invalid mapping entry at line " + std::to_string(li.lineNo));
            }
            std::string key = trim(li.trimmed.substr(0, split));
            std::string rest = trim(li.trimmed.substr(split + 1));
            std::string fullPath = pathPrefix.empty() ? key : pathPrefix + "." + key;

            if (!localComments.empty()) {
                comments_[fullPath] = localComments;
                localComments.clear();
            }

            if (rest.empty()) {
                auto nested = parseBlock(index + 1, indent + 2, fullPath);
                obj[key] = nested.value.value_or(Value(nullptr));
                index = nested.next;
            } else {
                std::string inlineText = rest;
                std::size_t commentPos = rest.find(" #");
                if (commentPos != std::string::npos) inlineText = trim(rest.substr(0, commentPos));
                obj[key] = scalarFromText(inlineText);
                index++;
            }
        }
        return {Value(obj), index};
    }
};

std::string yamlScalar(const Value& value) {
    if (value.isNull()) return "null";
    if (value.isBool()) return value.asBool() ? "true" : "false";
    if (value.isNumber()) {
        double d = value.asNumber();
        if (std::rint(d) == d) return std::to_string(static_cast<long long>(d));
        std::ostringstream out;
        out << d;
        return out.str();
    }
    std::string s = value.asString();
    if (s.empty()) return "\"\"";
    if (std::regex_search(s, std::regex("[:#\\-\\[\\]\\{\\},]|^\\s|\\s$|\\n"))) {
        std::string escaped;
        for (char ch : s) {
            if (ch == '\\') escaped += "\\\\";
            else if (ch == '"') escaped += "\\\"";
            else escaped += ch;
        }
        return "\"" + escaped + "\"";
    }
    return s;
}

std::string toYaml(const Value& value, const std::map<std::string, std::vector<std::string>>& comments,
                   const std::string& path = "", int indent = 0) {
    std::string pad(indent, ' ');
    std::ostringstream out;

    if (value.isArray()) {
        const auto& arr = value.asArray();
        for (std::size_t i = 0; i < arr.size(); i++) {
            const auto& item = arr[i];
            if (item.isObject() || item.isArray()) {
                out << pad << "-\n" << toYaml(item, comments, path + "[" + std::to_string(i) + "]", indent + 2) << "\n";
            } else {
                out << pad << "- " << yamlScalar(item) << "\n";
            }
        }
        std::string s = out.str();
        while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) s.pop_back();
        return s;
    }

    if (value.isObject()) {
        const auto& obj = value.asObject();
        for (const auto& entry : obj) {
            std::string childPath = path.empty() ? entry.first : path + "." + entry.first;
            auto cIt = comments.find(childPath);
            if (cIt != comments.end()) {
                for (const auto& comment : cIt->second) out << pad << "# " << comment << "\n";
            }
            if (entry.second.isObject() || entry.second.isArray()) {
                out << pad << entry.first << ":\n" << toYaml(entry.second, comments, childPath, indent + 2) << "\n";
            } else {
                out << pad << entry.first << ": " << yamlScalar(entry.second) << "\n";
            }
        }
        std::string s = out.str();
        while (!s.empty() && (s.back() == '\n' || s.back() == '\r')) s.pop_back();
        return s;
    }
    return pad + yamlScalar(value);
}

std::string valueType(const Value& value) {
    if (value.isNull()) return "null";
    if (value.isBool()) return "boolean";
    if (value.isNumber()) return "number";
    if (value.isString()) return "string";
    if (value.isArray()) return "array";
    return "object";
}

void validateSchema(const Value& data, const Value& schemaValue, const std::string& path, std::vector<std::string>& errors) {
    if (!schemaValue.isObject()) return;
    const auto& schema = schemaValue.asObject();

    auto typeIt = schema.find("type");
    if (typeIt != schema.end() && typeIt->second.isString()) {
        std::string declared = typeIt->second.asString();
        std::string actual = valueType(data);
        if (declared != actual) {
            errors.push_back((path.empty() ? "$" : path) + ": expected type " + declared + ", got " + actual);
            return;
        }
    }

    auto enumIt = schema.find("enum");
    if (enumIt != schema.end() && enumIt->second.isArray()) {
        bool found = false;
        for (const auto& item : enumIt->second.asArray()) {
            if (deepEqual(item, data)) {
                found = true;
                break;
            }
        }
        if (!found) errors.push_back((path.empty() ? "$" : path) + ": value not in enum");
    }

    if (data.isObject()) {
        const auto& obj = data.asObject();
        auto reqIt = schema.find("required");
        if (reqIt != schema.end() && reqIt->second.isArray()) {
            for (const auto& req : reqIt->second.asArray()) {
                if (!req.isString()) continue;
                if (obj.find(req.asString()) == obj.end()) {
                    errors.push_back((path.empty() ? "$" : path) + ": missing required key " + req.asString());
                }
            }
        }
        auto propsIt = schema.find("properties");
        if (propsIt != schema.end() && propsIt->second.isObject()) {
            for (const auto& prop : propsIt->second.asObject()) {
                auto found = obj.find(prop.first);
                if (found != obj.end()) {
                    std::string child = path.empty() ? prop.first : path + "." + prop.first;
                    validateSchema(found->second, prop.second, child, errors);
                }
            }
        }
    } else if (data.isArray()) {
        auto itemsIt = schema.find("items");
        if (itemsIt != schema.end()) {
            const auto& arr = data.asArray();
            for (std::size_t i = 0; i < arr.size(); i++) {
                validateSchema(arr[i], itemsIt->second, path + "[" + std::to_string(i) + "]", errors);
            }
        }
    }
}

Value interpolateEnv(const Value& value) {
    if (value.isString()) {
        const std::string& text = value.asString();
        std::regex pattern(R"(\$\{([A-Za-z_][A-Za-z0-9_]*)(:-([^}]*))?\})");
        std::string result;
        std::size_t last = 0;
        auto begin = std::sregex_iterator(text.begin(), text.end(), pattern);
        auto end = std::sregex_iterator();
        for (auto it = begin; it != end; ++it) {
            auto m = *it;
            result.append(text.substr(last, static_cast<std::size_t>(m.position()) - last));
            std::string key = m[1].str();
            std::string fallback = m[3].matched ? m[3].str() : "";
            const char* env = std::getenv(key.c_str());
            result.append(env ? env : fallback);
            last = static_cast<std::size_t>(m.position() + m.length());
        }
        result.append(text.substr(last));
        return result;
    }
    if (value.isArray()) {
        Value::Array out;
        for (const auto& item : value.asArray()) out.push_back(interpolateEnv(item));
        return out;
    }
    if (value.isObject()) {
        Value::Object out;
        for (const auto& e : value.asObject()) out[e.first] = interpolateEnv(e.second);
        return out;
    }
    return value;
}

void collectLeafSources(const Value& value, const std::string& path, std::map<std::string, std::string>& sourceMap,
                        const std::string& file) {
    if (value.isObject()) {
        for (const auto& e : value.asObject()) {
            std::string child = path.empty() ? e.first : path + "." + e.first;
            collectLeafSources(e.second, child, sourceMap, file);
        }
    } else {
        sourceMap[path] = file;
    }
}

Value mergeValues(const Value& base, const Value& incoming, const std::string& path, const std::string& strategy,
                  std::vector<Conflict>& conflicts, std::map<std::string, std::string>& sourceMap, const std::string& currentFile) {
    if (base.isObject() && incoming.isObject()) {
        Value::Object merged = base.asObject();
        for (const auto& e : incoming.asObject()) {
            std::string child = path.empty() ? e.first : path + "." + e.first;
            auto found = merged.find(e.first);
            if (found != merged.end()) {
                found->second = mergeValues(found->second, e.second, child, strategy, conflicts, sourceMap, currentFile);
            } else {
                merged[e.first] = deepClone(e.second);
                sourceMap[child] = currentFile;
            }
        }
        return merged;
    }
    if (base.isArray() && incoming.isArray()) {
        Value::Array merged;
        if (strategy == "append") {
            for (const auto& item : base.asArray()) merged.push_back(deepClone(item));
            for (const auto& item : incoming.asArray()) merged.push_back(deepClone(item));
        } else if (strategy == "unique") {
            for (const auto& item : base.asArray()) merged.push_back(deepClone(item));
            for (const auto& item : incoming.asArray()) {
                bool found = false;
                for (const auto& existing : merged) {
                    if (deepEqual(existing, item)) {
                        found = true;
                        break;
                    }
                }
                if (!found) merged.push_back(deepClone(item));
            }
        } else {
            for (const auto& item : incoming.asArray()) merged.push_back(deepClone(item));
        }
        if (!deepEqual(base, incoming)) conflicts.push_back(Conflict{path, sourceMap[path], currentFile});
        sourceMap[path] = currentFile;
        return merged;
    }
    if (!deepEqual(base, incoming)) conflicts.push_back(Conflict{path, sourceMap[path], currentFile});
    sourceMap[path] = currentFile;
    return deepClone(incoming);
}

std::string readFile(const std::filesystem::path& path) {
    std::ifstream input(path, std::ios::binary);
    if (!input.is_open()) throw std::runtime_error("cannot open file");
    std::ostringstream buffer;
    buffer << input.rdbuf();
    return buffer.str();
}

void writeFile(const std::filesystem::path& path, const std::string& content) {
    std::ofstream output(path, std::ios::binary);
    if (!output.is_open()) throw std::runtime_error("cannot write file");
    output << content;
}

LoadedYaml loadYaml(const std::filesystem::path& file) {
    std::string text;
    try {
        text = readFile(file);
    } catch (const std::exception& ex) {
        throw std::runtime_error("Failed to read " + file.string() + ": " + ex.what());
    }
    try {
        ParseResult parsed = YamlParser(text).parse();
        return LoadedYaml{file.string(), parsed.data, parsed.comments};
    } catch (const std::exception& ex) {
        throw std::runtime_error("Malformed YAML in " + file.string() + ": " + ex.what());
    }
}

std::map<std::string, std::string> sampleFiles() {
    return {
        {"config_1.yaml", "# Base app config\n"
                          "app:\n"
                          "  name: merger-demo\n"
                          "  env: ${APP_ENV:-dev}\n"
                          "database:\n"
                          "  host: localhost\n"
                          "  port: 5432\n"
                          "  tags:\n"
                          "    - core\n"
                          "    - primary\n"
                          "features:\n"
                          "  enabled:\n"
                          "    - auth\n"
                          "    - api\n"},
        {"config_2.yaml", "# Override database and features\n"
                          "database:\n"
                          "  port: 5433\n"
                          "  user: ${DB_USER:-admin}\n"
                          "  tags:\n"
                          "    - analytics\n"
                          "features:\n"
                          "  enabled:\n"
                          "    - api\n"
                          "    - billing\n"},
        {"config_3.yaml", "# Production tuning\n"
                          "app:\n"
                          "  env: prod\n"
                          "database:\n"
                          "  host: db.internal\n"
                          "  retries: 5\n"
                          "features:\n"
                          "  enabled:\n"
                          "    - auth\n"
                          "    - billing\n"}
    };
}

} // namespace

int main(int argc, char** argv) {
    std::vector<std::string> files;
    std::string arrayStrategy = "replace";
    std::string schemaPath;
    std::string outputPath = "merged_config.yaml";

    for (int i = 1; i < argc; i++) {
        std::string arg = argv[i];
        if (arg == "--array-strategy") {
            i++;
            arrayStrategy = i < argc ? argv[i] : "";
        } else if (arg == "--schema") {
            i++;
            schemaPath = i < argc ? argv[i] : "";
        } else if (arg == "--output") {
            i++;
            outputPath = i < argc ? argv[i] : outputPath;
        } else {
            files.push_back(arg);
        }
    }

    if (arrayStrategy != "replace" && arrayStrategy != "append" && arrayStrategy != "unique") {
        std::cerr << "Invalid --array-strategy. Use replace, append, or unique.\n";
        return 1;
    }

    if (files.empty()) {
        auto samples = sampleFiles();
        files.clear();
        for (const auto& entry : samples) {
            std::filesystem::path path = std::filesystem::absolute(entry.first);
            try {
                writeFile(path, entry.second);
            } catch (const std::exception& ex) {
                std::cerr << "Failed to write sample file " << path.string() << ": " << ex.what() << "\n";
                return 1;
            }
            files.push_back(path.string());
        }
        std::cout << "No input files provided. Generated sample files: ";
        for (std::size_t i = 0; i < files.size(); i++) {
            if (i > 0) std::cout << ", ";
            std::cout << files[i];
        }
        std::cout << "\n";
    }

    if (files.size() < 2) {
        std::cerr << "Provide at least two YAML files.\n";
        return 1;
    }

    std::vector<LoadedYaml> loaded;
    for (const auto& file : files) {
        try {
            loaded.push_back(loadYaml(std::filesystem::absolute(file)));
        } catch (const std::exception& ex) {
            std::cerr << ex.what() << "\n";
            return 1;
        }
    }

    Value merged = deepClone(loaded[0].data);
    std::map<std::string, std::string> sourceMap;
    collectLeafSources(merged, "", sourceMap, loaded[0].file);
    std::map<std::string, std::vector<std::string>> comments = loaded[0].comments;
    std::vector<Conflict> conflicts;

    for (std::size_t i = 1; i < loaded.size(); i++) {
        merged = mergeValues(merged, loaded[i].data, "", arrayStrategy, conflicts, sourceMap, loaded[i].file);
        for (const auto& e : loaded[i].comments) {
            if (comments.find(e.first) == comments.end()) comments[e.first] = e.second;
        }
    }

    merged = interpolateEnv(merged);

    if (!schemaPath.empty()) {
        LoadedYaml schema;
        try {
            schema = loadYaml(std::filesystem::absolute(schemaPath));
        } catch (const std::exception& ex) {
            std::cerr << ex.what() << "\n";
            return 1;
        }
        std::vector<std::string> schemaErrors;
        validateSchema(merged, schema.data, "", schemaErrors);
        if (!schemaErrors.empty()) {
            std::cerr << "Schema validation failed:\n";
            for (const auto& err : schemaErrors) std::cerr << "- " << err << "\n";
            return 1;
        }
    }

    if (conflicts.empty()) {
        std::cout << "No merge conflicts detected.\n";
    } else {
        std::cout << "Merge conflicts detected:\n";
        for (const auto& c : conflicts) {
            std::string display = c.path.empty() ? "<root>" : c.path;
            std::cout << "- " << display << ": " << c.previousFile << " disagrees with " << c.currentFile << "\n";
        }
    }

    std::string yamlOut = toYaml(merged, comments);
    std::cout << "\nMerged YAML:\n\n" << yamlOut << "\n";

    std::filesystem::path out = std::filesystem::absolute(outputPath);
    try {
        writeFile(out, yamlOut + "\n");
    } catch (const std::exception& ex) {
        std::cerr << "Failed to write output file " << out.string() << ": " << ex.what() << "\n";
        return 1;
    }
    std::cout << "\nSaved merged config: " << out.string() << "\n";
    return 0;
}