From 07c629b88b890a48369239dc42bdb6536d52f118 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 20:56:12 -0700 Subject: [PATCH] Complete step475 architect review interface with tests --- editor/CMakeLists.txt | 9 + editor/src/ArchitectReviewInterface.h | 231 ++++++++++++++++++++++++++ editor/tests/step475_test.cpp | 171 +++++++++++++++++++ progress.md | 39 +++++ 4 files changed, 450 insertions(+) create mode 100644 editor/src/ArchitectReviewInterface.h create mode 100644 editor/tests/step475_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index d06344f..1305ef8 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3154,4 +3154,13 @@ target_link_libraries(step474_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step475_test tests/step475_test.cpp) +target_include_directories(step475_test PRIVATE src) +target_link_libraries(step475_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/ArchitectReviewInterface.h b/editor/src/ArchitectReviewInterface.h new file mode 100644 index 0000000..8535c85 --- /dev/null +++ b/editor/src/ArchitectReviewInterface.h @@ -0,0 +1,231 @@ +#pragma once + +// Step 475: Architect Review Interface +// Structured review state and mutation operations over proposed stack/skeleton. + +#include "ArchitectSkeletonGenerator.h" + +#include +#include +#include +#include +#include + +struct ModuleReviewSummary { + std::string moduleName; + std::string language; + int functionCount = 0; + int dependencyCount = 0; + std::vector keyAnnotations; +}; + +struct ArchitectReviewSnapshot { + std::vector modules; + std::vector dependencyGraph; + std::vector stackRationales; +}; + +struct ArchitectReviewState { + SkeletonProjectSpec skeleton; + TechStackDecision stack; + ModuleGraph moduleGraph; + bool approved = false; + std::vector changeLog; +}; + +class ArchitectReviewInterface { +public: + static ArchitectReviewState initialize(const SkeletonProjectSpec& skeleton, + const TechStackDecision& stack, + const ModuleGraph& graph) { + ArchitectReviewState st; + st.skeleton = skeleton; + st.stack = stack; + st.moduleGraph = graph; + st.approved = false; + return st; + } + + static ArchitectReviewSnapshot snapshot(const ArchitectReviewState& st) { + ArchitectReviewSnapshot snap; + snap.dependencyGraph = st.moduleGraph.edges; + + for (const auto& m : st.skeleton.modules) { + ModuleReviewSummary s; + s.moduleName = m.moduleName; + s.language = m.language; + s.functionCount = static_cast(m.functions.size()); + s.dependencyCount = static_cast(m.dependencies.size()); + s.keyAnnotations = collectKeyAnnotations(m); + snap.modules.push_back(std::move(s)); + } + + for (const auto& c : st.stack.choices) { + snap.stackRationales.push_back(c.moduleName + ": " + c.rationale); + } + return snap; + } + + static bool changeModuleLanguage(ArchitectReviewState& st, + const std::string& moduleName, + const std::string& newLanguage) { + bool changed = false; + for (auto& m : st.skeleton.modules) { + if (m.moduleName == moduleName) { + m.language = newLanguage; + changed = true; + } + } + for (auto& c : st.stack.choices) { + if (c.moduleName == moduleName) { + c.language = newLanguage; + changed = true; + } + } + if (changed) st.changeLog.push_back("language:" + moduleName + "->" + newLanguage); + return changed; + } + + static bool mergeModules(ArchitectReviewState& st, + const std::string& primary, + const std::string& secondary) { + auto pIdx = indexOf(st.skeleton.modules, primary); + auto sIdx = indexOf(st.skeleton.modules, secondary); + if (pIdx < 0 || sIdx < 0 || pIdx == sIdx) return false; + + auto& p = st.skeleton.modules[static_cast(pIdx)]; + auto& s = st.skeleton.modules[static_cast(sIdx)]; + + p.functions.insert(p.functions.end(), s.functions.begin(), s.functions.end()); + p.dependencies.insert(p.dependencies.end(), s.dependencies.begin(), s.dependencies.end()); + dedupe(p.dependencies); + + st.skeleton.modules.erase(st.skeleton.modules.begin() + sIdx); + removeStackChoice(st.stack, secondary); + remapGraphAfterMerge(st.moduleGraph, primary, secondary); + st.changeLog.push_back("merge:" + primary + "<-" + secondary); + return true; + } + + static bool addModule(ArchitectReviewState& st, + const std::string& moduleName, + const std::string& language, + const std::vector& responsibilities = {}) { + if (st.skeleton.hasModule(moduleName)) return false; + + SkeletonModuleSpec m; + m.moduleName = moduleName; + m.language = language; + m.functions.push_back(defaultFunctionFor(moduleName)); + st.skeleton.modules.push_back(m); + + st.stack.choices.push_back({moduleName, language, "", "", "Architect-added module", 0.70f}); + st.moduleGraph.nodes.push_back({moduleName, responsibilities, 4, false}); + st.changeLog.push_back("add_module:" + moduleName); + return true; + } + + static bool adjustRouting(ArchitectReviewState& st, + const std::string& moduleName, + const std::string& functionName, + const std::string& automatability, + const std::string& contextWidth, + const std::string& complexity) { + for (auto& m : st.skeleton.modules) { + if (m.moduleName != moduleName) continue; + for (auto& f : m.functions) { + if (f.name != functionName) continue; + setAnnotation(f.annotations, "@Automatability", automatability); + setAnnotation(f.annotations, "@ContextWidth", contextWidth); + setAnnotation(f.annotations, "@Complexity", complexity); + st.changeLog.push_back("routing:" + moduleName + "." + functionName); + return true; + } + } + return false; + } + + static void approve(ArchitectReviewState& st) { + st.approved = true; + st.changeLog.push_back("approved"); + } + +private: + static std::vector collectKeyAnnotations(const SkeletonModuleSpec& m) { + std::set keys; + for (const auto& f : m.functions) { + for (const auto& a : f.annotations) { + if (a.key == "@Intent" || a.key == "@Complexity" || a.key == "@Automatability" || + a.key == "@ContextWidth" || a.key == "@Contract") { + keys.insert(a.key); + } + } + } + return std::vector(keys.begin(), keys.end()); + } + + static int indexOf(const std::vector& mods, const std::string& name) { + for (size_t i = 0; i < mods.size(); ++i) if (mods[i].moduleName == name) return static_cast(i); + return -1; + } + + static void dedupe(std::vector& values) { + std::set uniq(values.begin(), values.end()); + values.assign(uniq.begin(), uniq.end()); + } + + static void removeStackChoice(TechStackDecision& stack, const std::string& moduleName) { + stack.choices.erase( + std::remove_if(stack.choices.begin(), stack.choices.end(), + [&](const TechChoice& c){ return c.moduleName == moduleName; }), + stack.choices.end()); + } + + static void remapGraphAfterMerge(ModuleGraph& g, + const std::string& primary, + const std::string& secondary) { + for (auto& e : g.edges) { + if (e.from == secondary) e.from = primary; + if (e.to == secondary) e.to = primary; + } + g.nodes.erase(std::remove_if(g.nodes.begin(), g.nodes.end(), + [&](const ModuleNode& n){ return n.name == secondary; }), + g.nodes.end()); + // Remove self-loop duplicates introduced by merge remapping. + std::set seen; + std::vector out; + for (const auto& e : g.edges) { + if (e.from == e.to) continue; + std::string k = e.from + "->" + e.to + ":" + e.reason; + if (!seen.insert(k).second) continue; + out.push_back(e); + } + g.edges.swap(out); + } + + static SkeletonFunctionSpec defaultFunctionFor(const std::string& moduleName) { + SkeletonFunctionSpec f; + f.name = "execute"; + f.signature = "void execute()"; + f.annotations = { + {"@Intent", moduleName + ":execute"}, + {"@Complexity", "medium"}, + {"@ContextWidth", "module"}, + {"@Automatability", "llm"}, + {"@Contract", "pre: valid input; post: deterministic output"} + }; + return f; + } + + static void setAnnotation(std::vector& anns, + const std::string& key, + const std::string& value) { + for (auto& a : anns) { + if (a.key == key) { + a.value = value; + return; + } + } + anns.push_back({key, value}); + } +}; diff --git a/editor/tests/step475_test.cpp b/editor/tests/step475_test.cpp new file mode 100644 index 0000000..1ca9c4d --- /dev/null +++ b/editor/tests/step475_test.cpp @@ -0,0 +1,171 @@ +// Step 475: Architect Review Interface Tests (12 tests) + +#include "ArchitectReviewInterface.h" + +#include + +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 ArchitectReviewState makeState(const std::string& prompt) { + auto req = ArchitectProblemParser::parse(prompt); + auto graph = ArchitectModuleDecomposer::decompose(req); + auto stack = ArchitectTechStackSelector::select(req, graph); + auto skel = ArchitectSkeletonGenerator::generate(req, graph, stack); + return ArchitectReviewInterface::initialize(skel, stack, graph); +} + +void test_snapshot_contains_module_summaries() { + TEST(snapshot_contains_module_summaries); + auto st = makeState("Build a web REST API with auth and PostgreSQL."); + auto snap = ArchitectReviewInterface::snapshot(st); + CHECK(!snap.modules.empty(), "expected module summaries"); + CHECK(!snap.dependencyGraph.empty(), "expected dependency graph"); + PASS(); +} + +void test_snapshot_includes_key_annotations() { + TEST(snapshot_includes_key_annotations); + auto st = makeState("Build a REST API with auth."); + auto snap = ArchitectReviewInterface::snapshot(st); + bool foundIntent = false; + for (const auto& m : snap.modules) { + for (const auto& a : m.keyAnnotations) if (a == "@Intent") foundIntent = true; + } + CHECK(foundIntent, "expected key annotation listing"); + PASS(); +} + +void test_change_language_updates_skeleton_and_stack() { + TEST(change_language_updates_skeleton_and_stack); + auto st = makeState("Build a web dashboard."); + bool ok = ArchitectReviewInterface::changeModuleLanguage(st, "ui", "elm"); + CHECK(ok, "language change should succeed"); + CHECK(st.skeleton.getModule("ui").language == "elm", "skeleton language not updated"); + CHECK(st.stack.getChoice("ui").language == "elm", "stack language not updated"); + PASS(); +} + +void test_change_language_fails_for_missing_module() { + TEST(change_language_fails_for_missing_module); + auto st = makeState("Build an API."); + bool ok = ArchitectReviewInterface::changeModuleLanguage(st, "missing", "go"); + CHECK(!ok, "language change should fail for missing module"); + PASS(); +} + +void test_merge_modules_moves_functions_and_removes_secondary() { + TEST(merge_modules_moves_functions_and_removes_secondary); + auto st = makeState("Build API with auth."); + CHECK(st.skeleton.hasModule("api"), "missing api"); + CHECK(st.skeleton.hasModule("auth"), "missing auth"); + auto before = st.skeleton.getModule("api").functions.size(); + bool ok = ArchitectReviewInterface::mergeModules(st, "api", "auth"); + CHECK(ok, "merge should succeed"); + CHECK(!st.skeleton.hasModule("auth"), "secondary module should be removed"); + auto after = st.skeleton.getModule("api").functions.size(); + CHECK(after >= before, "primary should have merged functions"); + PASS(); +} + +void test_merge_modules_fails_for_invalid_pair() { + TEST(merge_modules_fails_for_invalid_pair); + auto st = makeState("Build API with auth."); + bool ok = ArchitectReviewInterface::mergeModules(st, "api", "missing"); + CHECK(!ok, "merge should fail for missing secondary"); + PASS(); +} + +void test_add_module_inserts_into_skeleton_stack_graph() { + TEST(add_module_inserts_into_skeleton_stack_graph); + auto st = makeState("Build API."); + bool ok = ArchitectReviewInterface::addModule(st, "billing", "python", {"payment processing"}); + CHECK(ok, "add module should succeed"); + CHECK(st.skeleton.hasModule("billing"), "missing new skeleton module"); + CHECK(st.stack.hasChoiceFor("billing"), "missing new stack choice"); + bool graphHas = false; + for (const auto& n : st.moduleGraph.nodes) if (n.name == "billing") graphHas = true; + CHECK(graphHas, "missing new graph node"); + PASS(); +} + +void test_add_module_fails_when_already_present() { + TEST(add_module_fails_when_already_present); + auto st = makeState("Build API."); + bool ok = ArchitectReviewInterface::addModule(st, "api", "python"); + CHECK(!ok, "adding existing module should fail"); + PASS(); +} + +void test_adjust_routing_updates_annotations() { + TEST(adjust_routing_updates_annotations); + auto st = makeState("Build API with auth."); + auto api = st.skeleton.getModule("api"); + CHECK(!api.functions.empty(), "missing api functions"); + bool ok = ArchitectReviewInterface::adjustRouting( + st, "api", api.functions[0].name, "human", "project", "high"); + CHECK(ok, "routing adjustment should succeed"); + auto api2 = st.skeleton.getModule("api"); + std::string autoV, ctxV, compV; + for (const auto& a : api2.functions[0].annotations) { + if (a.key == "@Automatability") autoV = a.value; + if (a.key == "@ContextWidth") ctxV = a.value; + if (a.key == "@Complexity") compV = a.value; + } + CHECK(autoV == "human" && ctxV == "project" && compV == "high", + "routing annotations not updated"); + PASS(); +} + +void test_adjust_routing_fails_for_missing_function() { + TEST(adjust_routing_fails_for_missing_function); + auto st = makeState("Build API."); + bool ok = ArchitectReviewInterface::adjustRouting( + st, "api", "does_not_exist", "llm", "module", "low"); + CHECK(!ok, "routing adjustment should fail for missing function"); + PASS(); +} + +void test_approve_marks_state_approved() { + TEST(approve_marks_state_approved); + auto st = makeState("Build API."); + CHECK(!st.approved, "expected initial not-approved"); + ArchitectReviewInterface::approve(st); + CHECK(st.approved, "approve should set approved flag"); + PASS(); +} + +void test_change_log_records_modifications_and_approval() { + TEST(change_log_records_modifications_and_approval); + auto st = makeState("Build API with auth."); + ArchitectReviewInterface::changeModuleLanguage(st, "api", "go"); + ArchitectReviewInterface::addModule(st, "billing", "python"); + ArchitectReviewInterface::approve(st); + CHECK(st.changeLog.size() >= 3, "expected change log entries"); + CHECK(st.changeLog.back() == "approved", "expected approval in changelog"); + PASS(); +} + +int main() { + std::cout << "Step 475: Architect Review Interface Tests\n"; + + test_snapshot_contains_module_summaries(); // 1 + test_snapshot_includes_key_annotations(); // 2 + test_change_language_updates_skeleton_and_stack(); // 3 + test_change_language_fails_for_missing_module(); // 4 + test_merge_modules_moves_functions_and_removes_secondary(); // 5 + test_merge_modules_fails_for_invalid_pair(); // 6 + test_add_module_inserts_into_skeleton_stack_graph(); // 7 + test_add_module_fails_when_already_present(); // 8 + test_adjust_routing_updates_annotations(); // 9 + test_adjust_routing_fails_for_missing_function(); // 10 + test_approve_marks_state_approved(); // 11 + test_change_log_records_modifications_and_approval(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 600ab90..43b6a5c 100644 --- a/progress.md +++ b/progress.md @@ -6581,3 +6581,42 @@ annotations suitable for workflow creation. **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) + +### Step 475: Architect Review Interface +**Status:** PASS (12/12 tests) + +Implements a structured architect review state with snapshot/summary generation +and concrete modification actions (language change, merge, add, routing +adjustment, approval). + +**Files added:** +- `editor/src/ArchitectReviewInterface.h` — review interface model: + - review state/snapshot models: + - `ArchitectReviewState` + - `ArchitectReviewSnapshot` + - `ModuleReviewSummary` + - review actions: + - `changeModuleLanguage(...)` + - `mergeModules(...)` + - `addModule(...)` + - `adjustRouting(...)` + - `approve(...)` + - change-log tracking and graph remapping after merges + - key-annotation extraction for review summaries +- `editor/tests/step475_test.cpp` — 12 tests covering: + - summary snapshot content + - language change behavior + - module merge success/failure paths + - module add success/failure paths + - routing adjustment success/failure paths + - approval and change-log behavior +- `editor/CMakeLists.txt` — `step475_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step475_test` — PASS +- `./editor/build-native/step475_test` — PASS (12/12) +- `./editor/build-native/step474_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ArchitectReviewInterface.h` within header-size limit (`231` <= `600`) +- `editor/tests/step475_test.cpp` within test-file size guidance (`171` lines)