Complete step474 skeleton generation from requirements with tests
This commit is contained in:
@@ -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)
|
||||
|
||||
198
editor/src/ArchitectSkeletonGenerator.h
Normal file
198
editor/src/ArchitectSkeletonGenerator.h
Normal file
@@ -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 <algorithm>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<SkeletonAnnotation> annotations;
|
||||
};
|
||||
|
||||
struct SkeletonModuleSpec {
|
||||
std::string moduleName;
|
||||
std::string language;
|
||||
std::string framework;
|
||||
std::string database;
|
||||
std::vector<std::string> dependencies;
|
||||
std::vector<SkeletonFunctionSpec> functions;
|
||||
};
|
||||
|
||||
struct SkeletonProjectSpec {
|
||||
std::vector<SkeletonModuleSpec> 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<std::string> collectDependencies(const ModuleGraph& graph,
|
||||
const std::string& moduleName) {
|
||||
std::set<std::string> deps;
|
||||
for (const auto& e : graph.edges) {
|
||||
if (e.from == moduleName) deps.insert(e.to);
|
||||
}
|
||||
return std::vector<std::string>(deps.begin(), deps.end());
|
||||
}
|
||||
|
||||
static std::vector<SkeletonFunctionSpec> generateFunctions(const StructuredRequirements& reqs,
|
||||
const ModuleGraph& graph,
|
||||
const std::string& moduleName) {
|
||||
std::vector<SkeletonFunctionSpec> 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;
|
||||
}
|
||||
};
|
||||
187
editor/tests/step474_test.cpp
Normal file
187
editor/tests/step474_test.cpp
Normal file
@@ -0,0 +1,187 @@
|
||||
// Step 474: Skeleton Generation from Requirements Tests (12 tests)
|
||||
|
||||
#include "ArchitectSkeletonGenerator.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
43
progress.md
43
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)
|
||||
|
||||
Reference in New Issue
Block a user