diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 83d4036..d06344f 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3145,4 +3145,13 @@ target_link_libraries(step473_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step474_test tests/step474_test.cpp) +target_include_directories(step474_test PRIVATE src) +target_link_libraries(step474_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) diff --git a/editor/src/ArchitectSkeletonGenerator.h b/editor/src/ArchitectSkeletonGenerator.h new file mode 100644 index 0000000..5ed44f6 --- /dev/null +++ b/editor/src/ArchitectSkeletonGenerator.h @@ -0,0 +1,198 @@ +#pragma once + +// Step 474: Skeleton Generation from Requirements +// Produces a multi-module annotated skeleton project specification from +// requirements, module graph, and selected technology stack. + +#include "ArchitectTechStackSelector.h" + +#include +#include +#include +#include + +struct SkeletonAnnotation { + std::string key; // e.g. @Intent + std::string value; // e.g. "user authentication" +}; + +struct SkeletonFunctionSpec { + std::string name; + std::string signature; + std::vector annotations; +}; + +struct SkeletonModuleSpec { + std::string moduleName; + std::string language; + std::string framework; + std::string database; + std::vector dependencies; + std::vector functions; +}; + +struct SkeletonProjectSpec { + std::vector modules; + + bool hasModule(const std::string& moduleName) const { + for (const auto& m : modules) if (m.moduleName == moduleName) return true; + return false; + } + + SkeletonModuleSpec getModule(const std::string& moduleName) const { + for (const auto& m : modules) if (m.moduleName == moduleName) return m; + return {}; + } +}; + +class ArchitectSkeletonGenerator { +public: + static SkeletonProjectSpec generate(const StructuredRequirements& reqs, + const ModuleGraph& graph, + const TechStackDecision& stack) { + SkeletonProjectSpec out; + for (const auto& choice : stack.choices) { + SkeletonModuleSpec mod; + mod.moduleName = choice.moduleName; + mod.language = choice.language; + mod.framework = choice.framework; + mod.database = choice.database; + mod.dependencies = collectDependencies(graph, choice.moduleName); + mod.functions = generateFunctions(reqs, graph, choice.moduleName); + out.modules.push_back(std::move(mod)); + } + + // Ensure all non-cross-cutting modules are present even if stack choice was sparse. + for (const auto& n : graph.nodes) { + if (n.crossCutting) continue; + if (!out.hasModule(n.name)) { + SkeletonModuleSpec mod; + mod.moduleName = n.name; + mod.language = "python"; + mod.dependencies = collectDependencies(graph, n.name); + mod.functions = generateFunctions(reqs, graph, n.name); + out.modules.push_back(std::move(mod)); + } + } + return out; + } + +private: + static std::vector collectDependencies(const ModuleGraph& graph, + const std::string& moduleName) { + std::set deps; + for (const auto& e : graph.edges) { + if (e.from == moduleName) deps.insert(e.to); + } + return std::vector(deps.begin(), deps.end()); + } + + static std::vector generateFunctions(const StructuredRequirements& reqs, + const ModuleGraph& graph, + const std::string& moduleName) { + std::vector out; + + // Seed function names per module role. + if (moduleName == "api") { + out.push_back(makeFn("handleRequest", "Response handleRequest(Request req)", reqs, graph, moduleName)); + out.push_back(makeFn("validateRequest", "bool validateRequest(Request req)", reqs, graph, moduleName)); + } else if (moduleName == "auth") { + out.push_back(makeFn("authenticateUser", "AuthResult authenticateUser(Credentials creds)", reqs, graph, moduleName)); + out.push_back(makeFn("authorizeAction", "bool authorizeAction(User user, Action action)", reqs, graph, moduleName)); + } else if (moduleName == "ui") { + out.push_back(makeFn("renderView", "View renderView(State state)", reqs, graph, moduleName)); + out.push_back(makeFn("handleInteraction", "State handleInteraction(Event event)", reqs, graph, moduleName)); + } else if (moduleName == "data") { + out.push_back(makeFn("saveEntity", "bool saveEntity(Entity entity)", reqs, graph, moduleName)); + out.push_back(makeFn("loadEntity", "Entity loadEntity(Id id)", reqs, graph, moduleName)); + } else if (moduleName == "search") { + out.push_back(makeFn("search", "Results search(Query query)", reqs, graph, moduleName)); + } else if (moduleName == "reporting") { + out.push_back(makeFn("generateReport", "Report generateReport(Filter filter)", reqs, graph, moduleName)); + } else if (moduleName == "notifications") { + out.push_back(makeFn("sendNotification", "bool sendNotification(Message msg)", reqs, graph, moduleName)); + } else if (moduleName == "integrations") { + out.push_back(makeFn("callExternalService", "ExternalResult callExternalService(Payload payload)", reqs, graph, moduleName)); + } else { + out.push_back(makeFn("execute", "void execute()", reqs, graph, moduleName)); + } + + return out; + } + + static SkeletonFunctionSpec makeFn(const std::string& name, + const std::string& sig, + const StructuredRequirements& reqs, + const ModuleGraph& graph, + const std::string& moduleName) { + SkeletonFunctionSpec fn; + fn.name = name; + fn.signature = sig; + + // Intent annotation from module + functional requirements. + fn.annotations.push_back({"@Intent", inferIntent(reqs, moduleName, name)}); + + // Routing annotations + fn.annotations.push_back({"@Complexity", inferComplexity(graph, moduleName)}); + fn.annotations.push_back({"@ContextWidth", inferContextWidth(moduleName)}); + fn.annotations.push_back({"@Automatability", inferAutomatability(moduleName, name)}); + + // Contract annotation from functional requirements + fn.annotations.push_back({"@Contract", inferContract(reqs, moduleName, name)}); + + return fn; + } + + static std::string inferIntent(const StructuredRequirements& reqs, + const std::string& moduleName, + const std::string& fnName) { + std::string seed = moduleName + ":" + fnName; + if (!reqs.functionalRequirements.empty()) { + seed += " " + reqs.functionalRequirements[0].text; + } + return seed; + } + + static std::string inferComplexity(const ModuleGraph& graph, const std::string& moduleName) { + for (const auto& n : graph.nodes) { + if (n.name == moduleName) { + if (n.complexity >= 8) return "high"; + if (n.complexity >= 4) return "medium"; + return "low"; + } + } + return "medium"; + } + + static std::string inferContextWidth(const std::string& moduleName) { + if (moduleName == "api" || moduleName == "core" || moduleName == "auth") return "project"; + if (moduleName == "integrations" || moduleName == "reporting") return "module"; + return "file"; + } + + static std::string inferAutomatability(const std::string& moduleName, + const std::string& fnName) { + (void)fnName; + if (moduleName == "security" || moduleName == "auth") return "human"; + if (moduleName == "api" || moduleName == "data") return "llm"; + return "deterministic"; + } + + static std::string inferContract(const StructuredRequirements& reqs, + const std::string& moduleName, + const std::string& fnName) { + std::string contract = "pre: valid input; post: deterministic output"; + if (moduleName == "auth") { + contract = "pre: credentials provided; post: auth decision emitted"; + } else if (moduleName == "data") { + contract = "pre: entity schema valid; post: persistence consistency"; + } else if (moduleName == "api" && fnName.find("validate") != std::string::npos) { + contract = "pre: request shape provided; post: validation status"; + } + if (!reqs.nonFunctionalRequirements.empty() && + reqs.nonFunctionalRequirements[0].text.find("secure") != std::string::npos) { + contract += "; sec: enforce security constraints"; + } + return contract; + } +}; diff --git a/editor/tests/step474_test.cpp b/editor/tests/step474_test.cpp new file mode 100644 index 0000000..88f1b61 --- /dev/null +++ b/editor/tests/step474_test.cpp @@ -0,0 +1,187 @@ +// Step 474: Skeleton Generation from Requirements Tests (12 tests) + +#include "ArchitectSkeletonGenerator.h" + +#include + +static int passed = 0, failed = 0; +#define TEST(name) { std::cout << " " << #name << "... " << std::flush; } +#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 SkeletonProjectSpec makeSkeleton(const std::string& prompt) { + auto req = reqFrom(prompt); + auto graph = ArchitectModuleDecomposer::decompose(req, DecompositionStrategy::Layered); + auto stack = ArchitectTechStackSelector::select(req, graph); + return ArchitectSkeletonGenerator::generate(req, graph, stack); +} + +void test_generates_modules_from_graph_and_stack() { + TEST(generates_modules_from_graph_and_stack); + auto sk = makeSkeleton("Build a web REST API with auth and PostgreSQL."); + CHECK(sk.hasModule("api"), "missing api module"); + CHECK(sk.hasModule("auth"), "missing auth module"); + CHECK(sk.hasModule("data"), "missing data module"); + PASS(); +} + +void test_module_languages_match_stack_choices() { + TEST(module_languages_match_stack_choices); + auto sk = makeSkeleton("Build a web REST API with auth and PostgreSQL."); + auto ui = sk.getModule("ui"); + auto api = sk.getModule("api"); + CHECK(ui.language == "typescript", "expected typescript ui"); + CHECK(!api.language.empty(), "expected backend language"); + PASS(); +} + +void test_generates_skeleton_functions_per_module() { + TEST(generates_skeleton_functions_per_module); + auto sk = makeSkeleton("Build a REST API with auth."); + auto api = sk.getModule("api"); + auto auth = sk.getModule("auth"); + CHECK(api.functions.size() >= 1, "expected api functions"); + CHECK(auth.functions.size() >= 1, "expected auth functions"); + PASS(); +} + +void test_adds_intent_annotations() { + TEST(adds_intent_annotations); + auto sk = makeSkeleton("Build a REST API with auth."); + CHECK(sk.hasModule("api"), "missing api module"); + auto api = sk.getModule("api"); + CHECK(!api.functions.empty(), "missing api functions"); + bool foundIntent = false; + for (const auto& a : api.functions[0].annotations) { + if (a.key == "@Intent" && !a.value.empty()) foundIntent = true; + } + CHECK(foundIntent, "missing @Intent annotation"); + PASS(); +} + +void test_adds_routing_annotations_complexity_context_automatability() { + TEST(adds_routing_annotations_complexity_context_automatability); + auto sk = makeSkeleton("Build a secure low-latency REST API with auth."); + CHECK(sk.hasModule("api"), "missing api module"); + auto api = sk.getModule("api"); + CHECK(!api.functions.empty(), "missing api functions"); + bool hasComplexity = false, hasContext = false, hasAutomatability = false; + for (const auto& a : api.functions[0].annotations) { + if (a.key == "@Complexity") hasComplexity = true; + if (a.key == "@ContextWidth") hasContext = true; + if (a.key == "@Automatability") hasAutomatability = true; + } + CHECK(hasComplexity && hasContext && hasAutomatability, "missing routing annotations"); + PASS(); +} + +void test_adds_contract_annotations() { + TEST(adds_contract_annotations); + auto sk = makeSkeleton("Build a secure REST API with auth."); + CHECK(sk.hasModule("auth"), "missing auth module"); + auto auth = sk.getModule("auth"); + CHECK(!auth.functions.empty(), "missing auth functions"); + bool hasContract = false; + for (const auto& a : auth.functions[0].annotations) { + if (a.key == "@Contract" && a.value.find("auth") != std::string::npos) hasContract = true; + } + CHECK(hasContract, "missing @Contract annotation"); + PASS(); +} + +void test_adds_dependency_annotations_between_modules() { + TEST(adds_dependency_annotations_between_modules); + auto sk = makeSkeleton("Build a web REST API with auth and PostgreSQL."); + auto api = sk.getModule("api"); + bool hasAuthDep = false; + for (const auto& d : api.dependencies) { + if (d == "auth") hasAuthDep = true; + } + CHECK(hasAuthDep, "expected api->auth dependency"); + PASS(); +} + +void test_generates_multifile_multi_module_spec() { + TEST(generates_multifile_multi_module_spec); + auto sk = makeSkeleton("Build a web dashboard API with auth, search, reporting, and PostgreSQL."); + CHECK(sk.modules.size() >= 4, "expected multi-module skeleton"); + PASS(); +} + +void test_fallback_module_generated_when_stack_sparse() { + TEST(fallback_module_generated_when_stack_sparse); + StructuredRequirements req; + ModuleGraph g; + g.nodes.push_back({"core", {"core behavior"}, 3, false}); + TechStackDecision d; + auto sk = ArchitectSkeletonGenerator::generate(req, g, d); + CHECK(sk.hasModule("core"), "expected fallback module generation"); + PASS(); +} + +void test_context_width_varies_by_module_role() { + TEST(context_width_varies_by_module_role); + auto sk = makeSkeleton("Build a REST API with auth and Stripe integration."); + CHECK(sk.hasModule("api"), "missing api module"); + CHECK(sk.hasModule("integrations"), "missing integrations module"); + auto api = sk.getModule("api"); + auto integ = sk.getModule("integrations"); + CHECK(!api.functions.empty(), "missing api functions"); + CHECK(!integ.functions.empty(), "missing integrations functions"); + std::string apiCtx, integCtx; + for (const auto& a : api.functions[0].annotations) if (a.key == "@ContextWidth") apiCtx = a.value; + for (const auto& a : integ.functions[0].annotations) if (a.key == "@ContextWidth") integCtx = a.value; + CHECK(apiCtx == "project", "expected project context for api"); + CHECK(integCtx == "module", "expected module context for integrations"); + PASS(); +} + +void test_automatability_human_for_auth_module() { + TEST(automatability_human_for_auth_module); + auto sk = makeSkeleton("Build a secure API with auth."); + CHECK(sk.hasModule("auth"), "missing auth module"); + auto auth = sk.getModule("auth"); + CHECK(!auth.functions.empty(), "missing auth functions"); + std::string autoValue; + for (const auto& a : auth.functions[0].annotations) if (a.key == "@Automatability") autoValue = a.value; + CHECK(autoValue == "human", "expected human automatability for auth"); + PASS(); +} + +void test_contract_enriched_for_secure_requirement() { + TEST(contract_enriched_for_secure_requirement); + auto sk = makeSkeleton("Build a secure API with auth and postgres."); + CHECK(sk.hasModule("api"), "missing api module"); + auto api = sk.getModule("api"); + CHECK(!api.functions.empty(), "missing api functions"); + std::string contract; + for (const auto& a : api.functions[0].annotations) if (a.key == "@Contract") contract = a.value; + CHECK(contract.find("sec:") != std::string::npos, "expected security contract enrichment"); + PASS(); +} + +int main() { + std::cout << "Step 474: Skeleton Generation from Requirements Tests\n"; + + test_generates_modules_from_graph_and_stack(); // 1 + test_module_languages_match_stack_choices(); // 2 + test_generates_skeleton_functions_per_module(); // 3 + test_adds_intent_annotations(); // 4 + test_adds_routing_annotations_complexity_context_automatability(); // 5 + test_adds_contract_annotations(); // 6 + test_adds_dependency_annotations_between_modules(); // 7 + test_generates_multifile_multi_module_spec(); // 8 + test_fallback_module_generated_when_stack_sparse(); // 9 + test_context_width_varies_by_module_role(); // 10 + test_automatability_human_for_auth_module(); // 11 + test_contract_enriched_for_secure_requirement(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 7987e5d..600ab90 100644 --- a/progress.md +++ b/progress.md @@ -6538,3 +6538,46 @@ and preference overrides. **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) + +### Step 474: Skeleton Generation from Requirements +**Status:** PASS (12/12 tests) + +Builds an annotated multi-module skeleton project spec from requirements, +module graph, and technology choices, including routing/contract/dependency +annotations suitable for workflow creation. + +**Files added:** +- `editor/src/ArchitectSkeletonGenerator.h` — skeleton generator: + - output model: + - `SkeletonAnnotation` + - `SkeletonFunctionSpec` + - `SkeletonModuleSpec` + - `SkeletonProjectSpec` + - module-level skeleton assembly from stack decisions + - function stub generation by module role (api/auth/ui/data/etc.) + - annotation generation: + - `@Intent` + - `@Complexity` + - `@ContextWidth` + - `@Automatability` + - `@Contract` + - dependency capture from module graph edges + - sparse/partial stack fallback behavior +- `editor/tests/step474_test.cpp` — 12 tests covering: + - module and language propagation from graph + stack + - function skeleton generation by module + - annotation presence and value behavior + - dependency propagation + - context/automatability routing semantics + - contract enrichment under security requirements + - sparse-input fallback behavior +- `editor/CMakeLists.txt` — `step474_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step474_test` — PASS +- `./editor/build-native/step474_test` — PASS (12/12) +- `./editor/build-native/step473_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ArchitectSkeletonGenerator.h` within header-size limit (`198` <= `600`) +- `editor/tests/step474_test.cpp` within test-file size guidance (`187` lines)