Files
whetstone_DSL/editor/src/ArchitectModuleDecomposer.h

213 lines
7.8 KiB
C++

#pragma once
// Step 472: Module Decomposition Engine
// Produces a module graph from structured requirements with strategy-specific
// decomposition behavior (layered/monolith/microservice).
#include "ArchitectProblemParser.h"
#include <algorithm>
#include <map>
#include <set>
#include <string>
#include <vector>
enum class DecompositionStrategy {
Layered,
Monolith,
Microservice
};
struct ModuleNode {
std::string name;
std::vector<std::string> responsibilities;
int complexity = 1; // 1-10 heuristic
bool crossCutting = false;
};
struct ModuleEdge {
std::string from;
std::string to;
std::string reason;
};
struct ModuleGraph {
std::vector<ModuleNode> nodes;
std::vector<ModuleEdge> edges;
DecompositionStrategy strategy = DecompositionStrategy::Layered;
bool hasModule(const std::string& name) const {
for (const auto& n : nodes) if (n.name == name) return true;
return false;
}
int dependencyCountFor(const std::string& moduleName) const {
int count = 0;
for (const auto& e : edges)
if (e.from == moduleName || e.to == moduleName) ++count;
return count;
}
};
class ArchitectModuleDecomposer {
public:
static ModuleGraph decompose(const StructuredRequirements& reqs,
DecompositionStrategy strategy = DecompositionStrategy::Layered) {
ModuleGraph g;
g.strategy = strategy;
// Core modules inferred from functional/integration requirements
maybeAddAuth(reqs, g);
maybeAddApi(reqs, g);
maybeAddUi(reqs, g);
maybeAddData(reqs, g);
maybeAddSearch(reqs, g);
maybeAddReporting(reqs, g);
maybeAddNotifications(reqs, g);
maybeAddIntegration(reqs, g);
if (strategy == DecompositionStrategy::Monolith) {
addModule(g, "core", {"application orchestration", "module coordination"}, 5);
}
if (g.nodes.empty()) {
g.nodes.push_back({"core", {"core application behavior"}, 3, false});
}
addCrossCutting(reqs, g);
addEdges(strategy, g);
normalizeComplexity(g);
return g;
}
private:
static void addModule(ModuleGraph& g,
const std::string& name,
const std::vector<std::string>& responsibilities,
int complexity,
bool crossCutting = false) {
if (g.hasModule(name)) return;
g.nodes.push_back({name, responsibilities, complexity, crossCutting});
}
static bool hasReqKeyword(const std::vector<ExtractedItem>& items, const std::string& key) {
auto lk = toLower(key);
for (const auto& it : items) {
if (toLower(it.text).find(lk) != std::string::npos) return true;
}
return false;
}
static bool hasAny(const StructuredRequirements& r,
const std::vector<std::string>& keys) {
for (const auto& k : keys) {
if (hasReqKeyword(r.functionalRequirements, k)) return true;
if (hasReqKeyword(r.integrationRequirements, k)) return true;
if (hasReqKeyword(r.platformConstraints, k)) return true;
}
return false;
}
static void maybeAddAuth(const StructuredRequirements& r, ModuleGraph& g) {
if (hasAny(r, {"auth", "login", "signup", "oauth", "jwt"}))
addModule(g, "auth", {"identity", "session", "authorization"}, 6);
}
static void maybeAddApi(const StructuredRequirements& r, ModuleGraph& g) {
if (hasAny(r, {"api", "rest", "endpoint", "crud"}))
addModule(g, "api", {"http routing", "request validation", "response mapping"}, 6);
}
static void maybeAddUi(const StructuredRequirements& r, ModuleGraph& g) {
if (hasAny(r, {"web", "mobile", "frontend", "dashboard", "ios", "android"}))
addModule(g, "ui", {"presentation", "interaction", "client state"}, 5);
}
static void maybeAddData(const StructuredRequirements& r, ModuleGraph& g) {
if (hasAny(r, {"postgres", "postgresql", "mysql", "sqlite", "redis", "data"}))
addModule(g, "data", {"persistence", "queries", "transactions"}, 7);
}
static void maybeAddSearch(const StructuredRequirements& r, ModuleGraph& g) {
if (hasAny(r, {"search"}))
addModule(g, "search", {"indexing", "query parsing"}, 5);
}
static void maybeAddReporting(const StructuredRequirements& r, ModuleGraph& g) {
if (hasAny(r, {"reporting"}))
addModule(g, "reporting", {"aggregation", "export", "analytics"}, 5);
}
static void maybeAddNotifications(const StructuredRequirements& r, ModuleGraph& g) {
if (hasAny(r, {"notification"}))
addModule(g, "notifications", {"event fanout", "delivery"}, 4);
}
static void maybeAddIntegration(const StructuredRequirements& r, ModuleGraph& g) {
if (hasAny(r, {"stripe", "salesforce", "ldap", "kafka", "s3"}))
addModule(g, "integrations", {"external api clients", "adapters"}, 6);
}
static void addCrossCutting(const StructuredRequirements& r, ModuleGraph& g) {
bool secure = hasReqKeyword(r.nonFunctionalRequirements, "secure") ||
hasReqKeyword(r.nonFunctionalRequirements, "security");
bool scalable = hasReqKeyword(r.nonFunctionalRequirements, "scalable") ||
hasReqKeyword(r.nonFunctionalRequirements, "performance");
addModule(g, "logging", {"structured logs", "trace context"}, 2, true);
addModule(g, "config", {"env config", "feature flags"}, 2, true);
addModule(g, "error_handling", {"error taxonomy", "mapping"}, 2, true);
if (secure) addModule(g, "security", {"policy", "hardening"}, 4, true);
if (scalable) addModule(g, "observability", {"metrics", "alerts"}, 3, true);
}
static void addEdges(DecompositionStrategy strategy, ModuleGraph& g) {
auto edge = [&g](const std::string& a, const std::string& b, const std::string& reason) {
if (!g.hasModule(a) || !g.hasModule(b)) return;
g.edges.push_back({a, b, reason});
};
if (strategy == DecompositionStrategy::Layered) {
edge("ui", "api", "ui consumes api");
edge("api", "auth", "endpoint auth checks");
edge("api", "data", "persistence access");
edge("api", "search", "search endpoints");
edge("api", "reporting", "report generation");
} else if (strategy == DecompositionStrategy::Microservice) {
edge("api", "auth", "auth service call");
edge("api", "data", "data service call");
edge("api", "integrations", "integration gateway");
edge("notifications", "integrations", "delivery integrations");
} else { // Monolith
edge("core", "data", "monolith persistence");
edge("core", "auth", "monolith auth");
edge("core", "ui", "monolith ui");
}
// Cross-cutting edges
std::vector<std::string> cross;
for (const auto& n : g.nodes) if (n.crossCutting) cross.push_back(n.name);
for (const auto& n : g.nodes) {
if (n.crossCutting) continue;
for (const auto& c : cross) edge(n.name, c, "cross-cutting concern");
}
}
static void normalizeComplexity(ModuleGraph& g) {
std::map<std::string, int> incoming;
for (const auto& e : g.edges) incoming[e.to]++;
for (auto& n : g.nodes) {
int c = n.complexity + incoming[n.name];
if (c < 1) c = 1;
if (c > 10) c = 10;
n.complexity = c;
}
}
static std::string toLower(std::string s) {
std::transform(s.begin(), s.end(), s.begin(),
[](unsigned char c){ return static_cast<char>(std::tolower(c)); });
return s;
}
};