Complete step475 architect review interface with tests
This commit is contained in:
@@ -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)
|
||||
|
||||
231
editor/src/ArchitectReviewInterface.h
Normal file
231
editor/src/ArchitectReviewInterface.h
Normal file
@@ -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 <algorithm>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct ModuleReviewSummary {
|
||||
std::string moduleName;
|
||||
std::string language;
|
||||
int functionCount = 0;
|
||||
int dependencyCount = 0;
|
||||
std::vector<std::string> keyAnnotations;
|
||||
};
|
||||
|
||||
struct ArchitectReviewSnapshot {
|
||||
std::vector<ModuleReviewSummary> modules;
|
||||
std::vector<ModuleEdge> dependencyGraph;
|
||||
std::vector<std::string> stackRationales;
|
||||
};
|
||||
|
||||
struct ArchitectReviewState {
|
||||
SkeletonProjectSpec skeleton;
|
||||
TechStackDecision stack;
|
||||
ModuleGraph moduleGraph;
|
||||
bool approved = false;
|
||||
std::vector<std::string> 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<int>(m.functions.size());
|
||||
s.dependencyCount = static_cast<int>(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<size_t>(pIdx)];
|
||||
auto& s = st.skeleton.modules[static_cast<size_t>(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<std::string>& 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<std::string> collectKeyAnnotations(const SkeletonModuleSpec& m) {
|
||||
std::set<std::string> 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<std::string>(keys.begin(), keys.end());
|
||||
}
|
||||
|
||||
static int indexOf(const std::vector<SkeletonModuleSpec>& mods, const std::string& name) {
|
||||
for (size_t i = 0; i < mods.size(); ++i) if (mods[i].moduleName == name) return static_cast<int>(i);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static void dedupe(std::vector<std::string>& values) {
|
||||
std::set<std::string> 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<std::string> seen;
|
||||
std::vector<ModuleEdge> 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<SkeletonAnnotation>& 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});
|
||||
}
|
||||
};
|
||||
171
editor/tests/step475_test.cpp
Normal file
171
editor/tests/step475_test.cpp
Normal file
@@ -0,0 +1,171 @@
|
||||
// Step 475: Architect Review Interface Tests (12 tests)
|
||||
|
||||
#include "ArchitectReviewInterface.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 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;
|
||||
}
|
||||
39
progress.md
39
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)
|
||||
|
||||
Reference in New Issue
Block a user