Complete step473 technology stack selector with tests
This commit is contained in:
@@ -3136,4 +3136,13 @@ target_link_libraries(step472_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(step473_test tests/step473_test.cpp)
|
||||||
|
target_include_directories(step473_test PRIVATE src)
|
||||||
|
target_link_libraries(step473_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)
|
||||||
|
|||||||
156
editor/src/ArchitectTechStackSelector.h
Normal file
156
editor/src/ArchitectTechStackSelector.h
Normal file
@@ -0,0 +1,156 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
// Step 473: Technology Stack Selector
|
||||||
|
// Chooses per-module language/framework/database suggestions from requirements
|
||||||
|
// and module graph, with optional preference overrides and rationale.
|
||||||
|
|
||||||
|
#include "ArchitectModuleDecomposer.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <map>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
struct TechChoice {
|
||||||
|
std::string moduleName;
|
||||||
|
std::string language;
|
||||||
|
std::string framework;
|
||||||
|
std::string database;
|
||||||
|
std::string rationale;
|
||||||
|
float confidence = 0.0f;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TechStackDecision {
|
||||||
|
std::vector<TechChoice> choices;
|
||||||
|
std::map<std::string, std::string> preferencesApplied;
|
||||||
|
|
||||||
|
bool hasChoiceFor(const std::string& moduleName) const {
|
||||||
|
for (const auto& c : choices) if (c.moduleName == moduleName) return true;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
TechChoice getChoice(const std::string& moduleName) const {
|
||||||
|
for (const auto& c : choices) if (c.moduleName == moduleName) return c;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
class ArchitectTechStackSelector {
|
||||||
|
public:
|
||||||
|
static TechStackDecision select(const StructuredRequirements& reqs,
|
||||||
|
const ModuleGraph& graph,
|
||||||
|
const std::map<std::string, std::string>& preferences = {}) {
|
||||||
|
TechStackDecision out;
|
||||||
|
out.preferencesApplied = preferences;
|
||||||
|
|
||||||
|
std::string preferredBackend = pref(preferences, "backend_language");
|
||||||
|
std::string preferredDb = pref(preferences, "database");
|
||||||
|
std::string preferredFrontend = pref(preferences, "frontend_language");
|
||||||
|
|
||||||
|
for (const auto& mod : graph.nodes) {
|
||||||
|
if (mod.crossCutting) continue;
|
||||||
|
TechChoice c;
|
||||||
|
c.moduleName = mod.name;
|
||||||
|
|
||||||
|
if (mod.name == "ui") {
|
||||||
|
c.language = preferredFrontend.empty() ? "typescript" : preferredFrontend;
|
||||||
|
c.framework = pickFrontendFramework(reqs);
|
||||||
|
c.database = "";
|
||||||
|
c.rationale = "UI module mapped to frontend language/framework";
|
||||||
|
c.confidence = 0.92f;
|
||||||
|
} else if (mod.name == "data") {
|
||||||
|
c.language = preferredBackend.empty() ? pickBackendLanguage(reqs, mod) : preferredBackend;
|
||||||
|
c.framework = "";
|
||||||
|
c.database = preferredDb.empty() ? pickDatabase(reqs) : preferredDb;
|
||||||
|
c.rationale = "Data module mapped to persistence stack";
|
||||||
|
c.confidence = 0.90f;
|
||||||
|
} else if (mod.name == "api" || mod.name == "auth" || mod.name == "search" ||
|
||||||
|
mod.name == "reporting" || mod.name == "notifications" || mod.name == "core") {
|
||||||
|
c.language = preferredBackend.empty() ? pickBackendLanguage(reqs, mod) : preferredBackend;
|
||||||
|
c.framework = pickBackendFramework(c.language);
|
||||||
|
c.database = (mod.name == "api" || mod.name == "auth" || mod.name == "core")
|
||||||
|
? (preferredDb.empty() ? pickDatabase(reqs) : preferredDb)
|
||||||
|
: "";
|
||||||
|
c.rationale = "Service/backend module mapped to backend stack";
|
||||||
|
c.confidence = 0.88f;
|
||||||
|
} else if (mod.name == "integrations") {
|
||||||
|
c.language = preferredBackend.empty() ? "python" : preferredBackend;
|
||||||
|
c.framework = "http-client";
|
||||||
|
c.database = "";
|
||||||
|
c.rationale = "Integration module favors high-velocity adapter language";
|
||||||
|
c.confidence = 0.85f;
|
||||||
|
} else {
|
||||||
|
c.language = preferredBackend.empty() ? "python" : preferredBackend;
|
||||||
|
c.framework = "";
|
||||||
|
c.database = "";
|
||||||
|
c.rationale = "Fallback language selection";
|
||||||
|
c.confidence = 0.75f;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.choices.push_back(std::move(c));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure we always return at least one choice for minimal graphs.
|
||||||
|
if (out.choices.empty()) {
|
||||||
|
out.choices.push_back({"core", "python", "", pickDatabase(reqs),
|
||||||
|
"Fallback stack decision for empty graph", 0.70f});
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static std::string pref(const std::map<std::string, std::string>& p, const std::string& key) {
|
||||||
|
auto it = p.find(key);
|
||||||
|
return it == p.end() ? "" : it->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool hasKeyword(const std::vector<ExtractedItem>& items, const std::string& keyword) {
|
||||||
|
auto k = toLower(keyword);
|
||||||
|
for (const auto& it : items) {
|
||||||
|
if (toLower(it.text).find(k) != std::string::npos) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string pickBackendLanguage(const StructuredRequirements& reqs, const ModuleNode& mod) {
|
||||||
|
bool perf = hasKeyword(reqs.nonFunctionalRequirements, "performance") ||
|
||||||
|
hasKeyword(reqs.nonFunctionalRequirements, "latency") ||
|
||||||
|
hasKeyword(reqs.nonFunctionalRequirements, "throughput");
|
||||||
|
bool secure = hasKeyword(reqs.nonFunctionalRequirements, "secure") ||
|
||||||
|
hasKeyword(reqs.nonFunctionalRequirements, "security");
|
||||||
|
if (mod.name == "search" && perf) return "rust";
|
||||||
|
if ((mod.name == "api" || mod.name == "auth" || mod.name == "core") && (perf || secure))
|
||||||
|
return "rust";
|
||||||
|
if (hasKeyword(reqs.platformConstraints, "embedded")) return "c";
|
||||||
|
return "python";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string pickBackendFramework(const std::string& language) {
|
||||||
|
if (language == "rust") return "axum";
|
||||||
|
if (language == "python") return "fastapi";
|
||||||
|
if (language == "go") return "gin";
|
||||||
|
if (language == "java") return "spring";
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string pickFrontendFramework(const StructuredRequirements& reqs) {
|
||||||
|
(void)reqs;
|
||||||
|
return "react";
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string pickDatabase(const StructuredRequirements& reqs) {
|
||||||
|
if (hasKeyword(reqs.integrationRequirements, "postgresql") ||
|
||||||
|
hasKeyword(reqs.integrationRequirements, "postgres")) return "postgresql";
|
||||||
|
if (hasKeyword(reqs.integrationRequirements, "mysql")) return "mysql";
|
||||||
|
if (hasKeyword(reqs.integrationRequirements, "sqlite")) return "sqlite";
|
||||||
|
if (hasKeyword(reqs.integrationRequirements, "redis")) return "redis";
|
||||||
|
return "postgresql";
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
168
editor/tests/step473_test.cpp
Normal file
168
editor/tests/step473_test.cpp
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
// Step 473: Technology Stack Selector Tests (12 tests)
|
||||||
|
|
||||||
|
#include "ArchitectTechStackSelector.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 reqFrom(const std::string& s) {
|
||||||
|
return ArchitectProblemParser::parse(s);
|
||||||
|
}
|
||||||
|
|
||||||
|
static ModuleGraph graphFrom(const std::string& s, DecompositionStrategy strategy = DecompositionStrategy::Layered) {
|
||||||
|
auto req = reqFrom(s);
|
||||||
|
return ArchitectModuleDecomposer::decompose(req, strategy);
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_selects_frontend_typescript_for_ui_module() {
|
||||||
|
TEST(selects_frontend_typescript_for_ui_module);
|
||||||
|
auto req = reqFrom("Build a web dashboard.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
CHECK(d.hasChoiceFor("ui"), "missing ui choice");
|
||||||
|
auto c = d.getChoice("ui");
|
||||||
|
CHECK(c.language == "typescript", "expected typescript for ui");
|
||||||
|
CHECK(c.framework == "react", "expected react framework");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_selects_rust_for_perf_critical_api() {
|
||||||
|
TEST(selects_rust_for_perf_critical_api);
|
||||||
|
auto req = reqFrom("Need a secure low-latency REST API.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
auto c = d.getChoice("api");
|
||||||
|
CHECK(c.language == "rust", "expected rust for perf/secure api");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_selects_python_backend_when_no_perf_constraints() {
|
||||||
|
TEST(selects_python_backend_when_no_perf_constraints);
|
||||||
|
auto req = reqFrom("Build a REST API for internal admin tooling.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
auto c = d.getChoice("api");
|
||||||
|
CHECK(c.language == "python", "expected python default backend");
|
||||||
|
CHECK(c.framework == "fastapi", "expected fastapi framework");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_selects_postgresql_when_requirement_mentions_postgres() {
|
||||||
|
TEST(selects_postgresql_when_requirement_mentions_postgres);
|
||||||
|
auto req = reqFrom("API with PostgreSQL storage.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
auto c = d.getChoice("data");
|
||||||
|
CHECK(c.database == "postgresql", "expected postgresql database");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_selects_mysql_when_requirement_mentions_mysql() {
|
||||||
|
TEST(selects_mysql_when_requirement_mentions_mysql);
|
||||||
|
auto req = reqFrom("Service with MySQL backend.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
auto c = d.getChoice("data");
|
||||||
|
CHECK(c.database == "mysql", "expected mysql database");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_applies_backend_language_preference_override() {
|
||||||
|
TEST(applies_backend_language_preference_override);
|
||||||
|
auto req = reqFrom("Need a secure low-latency API.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g, {{"backend_language", "go"}});
|
||||||
|
auto c = d.getChoice("api");
|
||||||
|
CHECK(c.language == "go", "expected backend preference override");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_applies_database_preference_override() {
|
||||||
|
TEST(applies_database_preference_override);
|
||||||
|
auto req = reqFrom("API with postgres.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g, {{"database", "sqlite"}});
|
||||||
|
auto c = d.getChoice("data");
|
||||||
|
CHECK(c.database == "sqlite", "expected database preference override");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_applies_frontend_language_preference_override() {
|
||||||
|
TEST(applies_frontend_language_preference_override);
|
||||||
|
auto req = reqFrom("Build a web dashboard.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g, {{"frontend_language", "elm"}});
|
||||||
|
auto c = d.getChoice("ui");
|
||||||
|
CHECK(c.language == "elm", "expected frontend preference override");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_assigns_integrations_module_to_adapter_stack() {
|
||||||
|
TEST(assigns_integrations_module_to_adapter_stack);
|
||||||
|
auto req = reqFrom("Integrate with Stripe and Salesforce APIs.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
auto c = d.getChoice("integrations");
|
||||||
|
CHECK(c.language == "python", "expected python for integration adapters");
|
||||||
|
CHECK(c.framework == "http-client", "expected adapter framework");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_confidence_scores_in_reasonable_range() {
|
||||||
|
TEST(confidence_scores_in_reasonable_range);
|
||||||
|
auto req = reqFrom("Web dashboard with API and postgres.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
for (const auto& c : d.choices) {
|
||||||
|
CHECK(c.confidence >= 0.0f && c.confidence <= 1.0f, "confidence out of range");
|
||||||
|
}
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_every_non_crosscutting_module_has_choice() {
|
||||||
|
TEST(every_non_crosscutting_module_has_choice);
|
||||||
|
auto req = reqFrom("Web API with auth, search, and PostgreSQL.");
|
||||||
|
auto g = ArchitectModuleDecomposer::decompose(req);
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
for (const auto& n : g.nodes) {
|
||||||
|
if (n.crossCutting) continue;
|
||||||
|
CHECK(d.hasChoiceFor(n.name), "missing tech choice for module");
|
||||||
|
}
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_fallback_choice_for_empty_graph() {
|
||||||
|
TEST(fallback_choice_for_empty_graph);
|
||||||
|
StructuredRequirements req;
|
||||||
|
ModuleGraph g;
|
||||||
|
auto d = ArchitectTechStackSelector::select(req, g);
|
||||||
|
CHECK(!d.choices.empty(), "expected fallback choice");
|
||||||
|
CHECK(d.choices[0].moduleName == "core", "expected core fallback choice");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Step 473: Technology Stack Selector Tests\n";
|
||||||
|
|
||||||
|
test_selects_frontend_typescript_for_ui_module(); // 1
|
||||||
|
test_selects_rust_for_perf_critical_api(); // 2
|
||||||
|
test_selects_python_backend_when_no_perf_constraints();// 3
|
||||||
|
test_selects_postgresql_when_requirement_mentions_postgres(); // 4
|
||||||
|
test_selects_mysql_when_requirement_mentions_mysql(); // 5
|
||||||
|
test_applies_backend_language_preference_override(); // 6
|
||||||
|
test_applies_database_preference_override(); // 7
|
||||||
|
test_applies_frontend_language_preference_override(); // 8
|
||||||
|
test_assigns_integrations_module_to_adapter_stack(); // 9
|
||||||
|
test_confidence_scores_in_reasonable_range(); // 10
|
||||||
|
test_every_non_crosscutting_module_has_choice(); // 11
|
||||||
|
test_fallback_choice_for_empty_graph(); // 12
|
||||||
|
|
||||||
|
std::cout << "\nResults: " << passed << "/" << (passed + failed)
|
||||||
|
<< " passed\n";
|
||||||
|
return failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
33
progress.md
33
progress.md
@@ -6505,3 +6505,36 @@ with module graph nodes/edges, cross-cutting concerns, and complexity scoring.
|
|||||||
**Architecture gate check:**
|
**Architecture gate check:**
|
||||||
- `editor/src/ArchitectModuleDecomposer.h` within header-size limit (`212` <= `600`)
|
- `editor/src/ArchitectModuleDecomposer.h` within header-size limit (`212` <= `600`)
|
||||||
- `editor/tests/step472_test.cpp` within test-file size guidance (`156` lines)
|
- `editor/tests/step472_test.cpp` within test-file size guidance (`156` lines)
|
||||||
|
|
||||||
|
### Step 473: Technology Stack Selector
|
||||||
|
**Status:** PASS (12/12 tests)
|
||||||
|
|
||||||
|
Implements per-module technology stack selection from requirements + module
|
||||||
|
graph, including language/framework/database choices, rationale, confidence,
|
||||||
|
and preference overrides.
|
||||||
|
|
||||||
|
**Files added:**
|
||||||
|
- `editor/src/ArchitectTechStackSelector.h` — stack decision engine:
|
||||||
|
- output model: `TechChoice`, `TechStackDecision`
|
||||||
|
- strategy-informed module stack selection for UI/API/auth/data/integrations/etc.
|
||||||
|
- backend/frontend/database preference overrides
|
||||||
|
- language/framework/database heuristics with rationale/confidence
|
||||||
|
- fallback behavior for sparse graphs
|
||||||
|
- `editor/tests/step473_test.cpp` — 12 tests covering:
|
||||||
|
- UI/frontend stack selection
|
||||||
|
- performance/security-driven backend language selection
|
||||||
|
- database selection from integration requirements
|
||||||
|
- preference override behavior (backend/db/frontend)
|
||||||
|
- integration module adapter stack behavior
|
||||||
|
- confidence range checks
|
||||||
|
- per-module coverage and fallback decision behavior
|
||||||
|
- `editor/CMakeLists.txt` — `step473_test` target
|
||||||
|
|
||||||
|
**Verification run:**
|
||||||
|
- `cmake --build editor/build-native --target step473_test` — PASS
|
||||||
|
- `./editor/build-native/step473_test` — PASS (12/12)
|
||||||
|
- `./editor/build-native/step472_test` — PASS (12/12) regression coverage
|
||||||
|
|
||||||
|
**Architecture gate check:**
|
||||||
|
- `editor/src/ArchitectTechStackSelector.h` within header-size limit (`156` <= `600`)
|
||||||
|
- `editor/tests/step473_test.cpp` within test-file size guidance (`168` lines)
|
||||||
|
|||||||
Reference in New Issue
Block a user