Complete step472 module decomposition engine with tests

This commit is contained in:
Bill
2026-02-16 20:43:02 -07:00
parent c4274e1200
commit 828a4ed377
4 changed files with 413 additions and 0 deletions

View File

@@ -3127,4 +3127,13 @@ target_link_libraries(step471_test PRIVATE
tree_sitter_javascript tree_sitter_typescript tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go) tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step472_test tests/step472_test.cpp)
target_include_directories(step472_test PRIVATE src)
target_link_libraries(step472_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies) # Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

View File

@@ -0,0 +1,212 @@
#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;
}
};

View File

@@ -0,0 +1,156 @@
// Step 472: Module Decomposition Engine Tests (12 tests)
#include "ArchitectModuleDecomposer.h"
#include <iostream>
static int passed = 0, failed = 0;
#define TEST(name) { std::cout << " " << #name << "... "; }
#define PASS() { std::cout << "PASS\n"; ++passed; }
#define FAIL(msg) { std::cout << "FAIL: " << msg << "\n"; ++failed; }
#define CHECK(cond, msg) if (!(cond)) { FAIL(msg); return; } else {}
static StructuredRequirements reqFromText(const std::string& s) {
return ArchitectProblemParser::parse(s);
}
void test_decomposes_auth_api_data_modules() {
TEST(decomposes_auth_api_data_modules);
auto req = reqFromText("Build a REST API with auth and PostgreSQL.");
auto g = ArchitectModuleDecomposer::decompose(req);
CHECK(g.hasModule("auth"), "missing auth module");
CHECK(g.hasModule("api"), "missing api module");
CHECK(g.hasModule("data"), "missing data module");
PASS();
}
void test_decomposes_ui_module_from_web_mobile_constraints() {
TEST(decomposes_ui_module_from_web_mobile_constraints);
auto req = reqFromText("Create a web and mobile dashboard.");
auto g = ArchitectModuleDecomposer::decompose(req);
CHECK(g.hasModule("ui"), "missing ui module");
PASS();
}
void test_decomposes_search_reporting_modules() {
TEST(decomposes_search_reporting_modules);
auto req = reqFromText("App requires search and reporting features.");
auto g = ArchitectModuleDecomposer::decompose(req);
CHECK(g.hasModule("search"), "missing search module");
CHECK(g.hasModule("reporting"), "missing reporting module");
PASS();
}
void test_adds_integration_module_for_external_systems() {
TEST(adds_integration_module_for_external_systems);
auto req = reqFromText("Integrate Stripe and Salesforce APIs.");
auto g = ArchitectModuleDecomposer::decompose(req);
CHECK(g.hasModule("integrations"), "missing integrations module");
PASS();
}
void test_cross_cutting_modules_are_present() {
TEST(cross_cutting_modules_are_present);
auto req = reqFromText("Build a REST API.");
auto g = ArchitectModuleDecomposer::decompose(req);
CHECK(g.hasModule("logging"), "missing logging");
CHECK(g.hasModule("config"), "missing config");
CHECK(g.hasModule("error_handling"), "missing error_handling");
PASS();
}
void test_security_nonfunctional_adds_security_module() {
TEST(security_nonfunctional_adds_security_module);
auto req = reqFromText("System must be secure and reliable.");
auto g = ArchitectModuleDecomposer::decompose(req);
CHECK(g.hasModule("security"), "missing security module");
PASS();
}
void test_layered_strategy_generates_expected_dependencies() {
TEST(layered_strategy_generates_expected_dependencies);
auto req = reqFromText("Web REST API with auth and PostgreSQL.");
auto g = ArchitectModuleDecomposer::decompose(req, DecompositionStrategy::Layered);
bool sawUiApi = false;
bool sawApiData = false;
for (const auto& e : g.edges) {
if (e.from == "ui" && e.to == "api") sawUiApi = true;
if (e.from == "api" && e.to == "data") sawApiData = true;
}
CHECK(sawUiApi, "missing ui->api edge");
CHECK(sawApiData, "missing api->data edge");
PASS();
}
void test_microservice_strategy_changes_edge_shape() {
TEST(microservice_strategy_changes_edge_shape);
auto req = reqFromText("REST API with auth and stripe integration.");
auto g = ArchitectModuleDecomposer::decompose(req, DecompositionStrategy::Microservice);
bool sawApiIntegrations = false;
for (const auto& e : g.edges) {
if (e.from == "api" && e.to == "integrations") sawApiIntegrations = true;
}
CHECK(sawApiIntegrations, "missing api->integrations edge in microservice strategy");
PASS();
}
void test_monolith_strategy_uses_core_hub() {
TEST(monolith_strategy_uses_core_hub);
auto req = reqFromText("simple app with auth and web ui");
auto g = ArchitectModuleDecomposer::decompose(req, DecompositionStrategy::Monolith);
bool sawCoreDep = false;
for (const auto& e : g.edges) {
if (e.from == "core") sawCoreDep = true;
}
CHECK(g.hasModule("core"), "missing core module");
CHECK(sawCoreDep, "expected core dependencies in monolith strategy");
PASS();
}
void test_complexity_scores_are_clamped() {
TEST(complexity_scores_are_clamped);
auto req = reqFromText("web mobile api auth search reporting notification postgres stripe secure scalable");
auto g = ArchitectModuleDecomposer::decompose(req);
for (const auto& n : g.nodes) {
CHECK(n.complexity >= 1 && n.complexity <= 10, "complexity out of range");
}
PASS();
}
void test_dependency_count_helper_reports_nonzero() {
TEST(dependency_count_helper_reports_nonzero);
auto req = reqFromText("REST API with auth and PostgreSQL.");
auto g = ArchitectModuleDecomposer::decompose(req);
CHECK(g.dependencyCountFor("api") > 0, "expected api dependencies");
PASS();
}
void test_fallback_core_module_when_sparse_requirements() {
TEST(fallback_core_module_when_sparse_requirements);
StructuredRequirements req;
req.functionalRequirements = {{"core application behavior", 0.55f}};
auto g = ArchitectModuleDecomposer::decompose(req, DecompositionStrategy::Monolith);
CHECK(g.hasModule("core"), "missing fallback core module");
PASS();
}
int main() {
std::cout << "Step 472: Module Decomposition Engine Tests\n";
test_decomposes_auth_api_data_modules(); // 1
test_decomposes_ui_module_from_web_mobile_constraints(); // 2
test_decomposes_search_reporting_modules(); // 3
test_adds_integration_module_for_external_systems(); // 4
test_cross_cutting_modules_are_present(); // 5
test_security_nonfunctional_adds_security_module(); // 6
test_layered_strategy_generates_expected_dependencies(); // 7
test_microservice_strategy_changes_edge_shape(); // 8
test_monolith_strategy_uses_core_hub(); // 9
test_complexity_scores_are_clamped(); // 10
test_dependency_count_helper_reports_nonzero(); // 11
test_fallback_core_module_when_sparse_requirements(); // 12
std::cout << "\nResults: " << passed << "/" << (passed + failed)
<< " passed\n";
return failed == 0 ? 0 : 1;
}

View File

@@ -6469,3 +6469,39 @@ natural-language problem descriptions with per-item confidence scores.
**Architecture gate check:** **Architecture gate check:**
- `editor/src/ArchitectProblemParser.h` within header-size limit (`114` <= `600`) - `editor/src/ArchitectProblemParser.h` within header-size limit (`114` <= `600`)
- `editor/tests/step471_test.cpp` within test-file size guidance (`157` lines) - `editor/tests/step471_test.cpp` within test-file size guidance (`157` lines)
### Step 472: Module Decomposition Engine
**Status:** PASS (12/12 tests)
Implements strategy-aware module decomposition from structured requirements,
with module graph nodes/edges, cross-cutting concerns, and complexity scoring.
**Files added:**
- `editor/src/ArchitectModuleDecomposer.h` — module decomposition engine:
- decomposition strategies:
- `Layered`
- `Monolith`
- `Microservice`
- module graph model (`ModuleNode`, `ModuleEdge`, `ModuleGraph`)
- inferred modules from requirements (auth/api/ui/data/search/reporting/notifications/integrations)
- cross-cutting module insertion (logging/config/error/security/observability)
- strategy-specific dependency edge shaping
- normalized complexity scoring with dependency influence
- helper APIs (`hasModule`, dependency counting)
- `editor/tests/step472_test.cpp` — 12 tests covering:
- module inference from requirement categories
- strategy-specific edge behavior
- cross-cutting concern insertion
- complexity clamping
- monolith core-hub behavior
- sparse-input fallback behavior
- `editor/CMakeLists.txt``step472_test` target
**Verification run:**
- `cmake --build editor/build-native --target step472_test` — PASS
- `./editor/build-native/step472_test` — PASS (12/12)
- `./editor/build-native/step471_test` — PASS (12/12) regression coverage
**Architecture gate check:**
- `editor/src/ArchitectModuleDecomposer.h` within header-size limit (`212` <= `600`)
- `editor/tests/step472_test.cpp` within test-file size guidance (`156` lines)