Files
kappa/src/resolve/plan.cpp
T
huntedbytheirs ee9f280346 feat: dependency resolver (Phase 1) + formatter + doctor + assertion evaluation
Resolver (Phase 1):
- resolve::resolve() takes SystemConfig + Registry → BuildPlan
- Three-layer feature/config merge (uses config::resolve_package)
- Feature-gated dependencies: deps with feature=X skipped if feature disabled
- Topological sort via Kahn's algorithm (BFS on in-degree)
- Cycle detection, missing package warnings
- CLI: kappa resolve <config> [<package>]

Formatter:
- format_package() + format_config() → canonical output
- Consistent 4-space indent, canonical declaration order

Doctor:
- check_package() + check_config() → warnings for common issues
- Missing fields, empty configs, root shell, feature warnings

Assertion evaluation:
- evaluate_assertions() resolves dotted field paths
- Supports == and != operators for config validation
- kappa validate now runs assertion checks

Review fixes:
- plan.steps.empty() exit code corrected to 0
- Removed unused <unordered_set> include
- Eliminated duplicate merge logic (uses config::resolve_package)
2026-07-30 00:36:38 -04:00

82 lines
2.3 KiB
C++

#include "kappa/resolve/plan.hpp"
#include "kappa/config/merge.hpp"
#include <algorithm>
#include <queue>
namespace kappa::resolve {
BuildPlan resolve(const dsl::SystemConfig& cfg, const Registry& registry) {
BuildPlan plan;
std::unordered_map<std::string, std::size_t> name_to_idx;
std::vector<BuildStep> nodes;
for (auto& pref : cfg.packages) {
auto rit = registry.find(pref.name);
if (rit == registry.end()) {
plan.missing.push_back(pref.name);
continue;
}
auto& pkg = rit->second;
auto resolved = config::resolve_package(pkg, cfg.system, pref);
BuildStep step;
step.name = pkg.name;
step.package = pkg;
step.features = resolved.features;
step.config = resolved.config;
for (auto& dep : pkg.depends) {
if (!dep.feature.empty()) {
auto fit = step.features.find(dep.feature);
if (fit == step.features.end() || !fit->second.enabled) {
continue; // feature-gated and disabled
}
}
step.dependencies.push_back({dep.name, dep.version});
}
name_to_idx[step.name] = nodes.size();
nodes.push_back(std::move(step));
}
std::vector<int> in_degree(nodes.size(), 0);
std::vector<std::vector<std::size_t>> adj(nodes.size());
for (std::size_t i = 0; i < nodes.size(); ++i) {
for (auto& dep : nodes[i].dependencies) {
auto it = name_to_idx.find(dep.name);
if (it != name_to_idx.end()) {
adj[it->second].push_back(i);
in_degree[i]++;
}
}
}
std::queue<std::size_t> q;
for (std::size_t i = 0; i < nodes.size(); ++i) {
if (in_degree[i] == 0) { q.push(i); }
}
std::vector<bool> visited(nodes.size(), false);
while (!q.empty()) {
auto u = q.front(); q.pop();
visited[u] = true;
plan.steps.push_back(nodes[u]);
for (auto v : adj[u]) {
if (--in_degree[v] == 0) { q.push(v); }
}
}
for (std::size_t i = 0; i < nodes.size(); ++i) {
if (!visited[i]) {
plan.cycles.push_back(nodes[i].name);
}
}
return plan;
}
} // namespace kappa::resolve