From 7d41e455dc27ecec3f756b4c912a0be2cd6f37b8 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 21:06:52 -0700 Subject: [PATCH] Complete Step 480: multi-language orchestration --- editor/CMakeLists.txt | 9 + .../src/ArchitectMultiLanguageOrchestrator.h | 175 +++++++++++++++ editor/tests/step480_test.cpp | 203 ++++++++++++++++++ progress.md | 40 ++++ 4 files changed, 427 insertions(+) create mode 100644 editor/src/ArchitectMultiLanguageOrchestrator.h create mode 100644 editor/tests/step480_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 60bf477..9b7bf33 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3199,4 +3199,13 @@ target_link_libraries(step479_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step480_test tests/step480_test.cpp) +target_include_directories(step480_test PRIVATE src) +target_link_libraries(step480_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/ArchitectMultiLanguageOrchestrator.h b/editor/src/ArchitectMultiLanguageOrchestrator.h new file mode 100644 index 0000000..56fff41 --- /dev/null +++ b/editor/src/ArchitectMultiLanguageOrchestrator.h @@ -0,0 +1,175 @@ +#pragma once + +// Step 480: Multi-Language Project Orchestration +// Creates one project workflow with build-ordering and cross-language links. + +#include "ArchitectBuildAwareness.h" + +#include +#include +#include +#include +#include +#include + +struct CrossLanguageLink { + std::string fromModule; + std::string toModule; + std::string fromLanguage; + std::string toLanguage; + std::string linkAnnotation; // @Link(...) + std::string shimAnnotation; // @Shim(...) + bool ffiBoundary = false; + bool reviewRequired = false; +}; + +struct OrchestrationTask { + std::string taskId; + std::string moduleName; + std::string language; + std::vector dependencies; + int buildOrder = 0; + bool reviewRequired = false; + std::vector annotations; + std::vector buildHints; +}; + +struct MultiLanguageWorkflowPlan { + std::vector tasks; + std::vector links; + std::vector notes; + + bool hasTask(const std::string& moduleName) const { + for (const auto& t : tasks) if (t.moduleName == moduleName) return true; + return false; + } + + OrchestrationTask getTask(const std::string& moduleName) const { + for (const auto& t : tasks) if (t.moduleName == moduleName) return t; + return {}; + } +}; + +class ArchitectMultiLanguageOrchestrator { +public: + static MultiLanguageWorkflowPlan createWorkflow( + const SkeletonProjectSpec& skeleton, + const BuildAwarenessReport& awareness) { + MultiLanguageWorkflowPlan out; + const auto order = topoOrder(skeleton, out.notes); + + std::map byName; + for (const auto& m : skeleton.modules) byName[m.moduleName] = m; + + std::set reviewModules; + for (const auto& m : skeleton.modules) { + for (const auto& dep : m.dependencies) { + if (!byName.count(dep)) continue; + const auto& to = byName[dep]; + if (lower(m.language) == lower(to.language)) continue; + + CrossLanguageLink link; + link.fromModule = m.moduleName; + link.toModule = dep; + link.fromLanguage = lower(m.language); + link.toLanguage = lower(to.language); + link.linkAnnotation = "@Link(" + m.moduleName + "->" + dep + ")"; + link.shimAnnotation = "@Shim(" + link.fromLanguage + "_to_" + link.toLanguage + ")"; + link.ffiBoundary = true; + link.reviewRequired = true; + out.links.push_back(std::move(link)); + + reviewModules.insert(m.moduleName); + reviewModules.insert(dep); + } + } + + for (size_t i = 0; i < order.size(); ++i) { + const auto& name = order[i]; + if (!byName.count(name)) continue; + const auto& m = byName[name]; + + OrchestrationTask task; + task.taskId = "orchestrate-" + std::to_string(i + 1); + task.moduleName = name; + task.language = lower(m.language); + task.dependencies = m.dependencies; + task.buildOrder = (int)i + 1; + task.reviewRequired = reviewModules.count(name) > 0; + task.annotations.push_back("@Intent(orchestrate module " + name + ")"); + task.annotations.push_back("@BuildOrder(" + std::to_string(task.buildOrder) + ")"); + if (task.reviewRequired) task.annotations.push_back("@Review(ffi-boundary)"); + + auto hint = awareness.getModule(name); + if (!hint.manifestSnippet.empty()) task.buildHints.push_back(hint.manifestSnippet); + for (const auto& imp : hint.importHints) task.buildHints.push_back(imp); + + out.tasks.push_back(std::move(task)); + } + + out.notes.push_back("Workflow spans all modules as a single project plan"); + out.notes.push_back("Cross-language links emit @Link/@Shim and require human review"); + return out; + } + +private: + static std::string lower(const std::string& s) { + std::string out = s; + std::transform(out.begin(), out.end(), out.begin(), + [](unsigned char c) { return static_cast(std::tolower(c)); }); + return out; + } + + static std::vector topoOrder(const SkeletonProjectSpec& skeleton, + std::vector& notes) { + std::map indeg; + std::map> adj; + std::set nodes; + + for (const auto& m : skeleton.modules) { + nodes.insert(m.moduleName); + if (!indeg.count(m.moduleName)) indeg[m.moduleName] = 0; + } + + for (const auto& m : skeleton.modules) { + for (const auto& dep : m.dependencies) { + if (!nodes.count(dep)) continue; + adj[dep].push_back(m.moduleName); + indeg[m.moduleName]++; + } + } + + std::vector ready; + for (const auto& n : nodes) { + if (indeg[n] == 0) ready.push_back(n); + } + std::sort(ready.begin(), ready.end()); + + std::vector ordered; + while (!ready.empty()) { + const std::string current = ready.front(); + ready.erase(ready.begin()); + ordered.push_back(current); + for (const auto& next : adj[current]) { + indeg[next]--; + if (indeg[next] == 0) { + ready.push_back(next); + std::sort(ready.begin(), ready.end()); + } + } + } + + if (ordered.size() < nodes.size()) { + std::vector remainder; + for (const auto& n : nodes) { + if (std::find(ordered.begin(), ordered.end(), n) == ordered.end()) { + remainder.push_back(n); + } + } + std::sort(remainder.begin(), remainder.end()); + ordered.insert(ordered.end(), remainder.begin(), remainder.end()); + notes.push_back("Dependency cycle detected: fallback lexical ordering applied"); + } + return ordered; + } +}; diff --git a/editor/tests/step480_test.cpp b/editor/tests/step480_test.cpp new file mode 100644 index 0000000..52dcfd7 --- /dev/null +++ b/editor/tests/step480_test.cpp @@ -0,0 +1,203 @@ +// Step 480: Multi-Language Project Orchestration Tests (12 tests) + +#include "ArchitectMultiLanguageOrchestrator.h" + +#include +#include +#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 SkeletonModuleSpec mk(const std::string& name, const std::string& lang, + std::initializer_list deps = {}) { + SkeletonModuleSpec m; + m.moduleName = name; + m.language = lang; + m.dependencies.assign(deps.begin(), deps.end()); + return m; +} + +static std::map buildOrderMap(const MultiLanguageWorkflowPlan& p) { + std::map out; + for (const auto& t : p.tasks) out[t.moduleName] = t.buildOrder; + return out; +} + +void test_creates_tasks_for_all_modules_in_single_project_workflow() { + TEST(creates_tasks_for_all_modules_in_single_project_workflow); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python")); + sk.modules.push_back(mk("core", "rust")); + sk.modules.push_back(mk("db", "sql")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(plan.tasks.size() == 3, "task count mismatch"); + CHECK(plan.hasTask("api") && plan.hasTask("core") && plan.hasTask("db"), "missing module tasks"); + PASS(); +} + +void test_build_order_respects_dependencies() { + TEST(build_order_respects_dependencies); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("schema", "sql")); + sk.modules.push_back(mk("core", "rust", {"schema"})); + sk.modules.push_back(mk("api", "python", {"core"})); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + auto ord = buildOrderMap(plan); + CHECK(ord["schema"] < ord["core"], "schema should precede core"); + CHECK(ord["core"] < ord["api"], "core should precede api"); + PASS(); +} + +void test_cross_language_dependency_emits_link_annotation() { + TEST(cross_language_dependency_emits_link_annotation); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python", {"core"})); + sk.modules.push_back(mk("core", "rust")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(!plan.links.empty(), "expected cross-language link"); + CHECK(plan.links[0].linkAnnotation.find("@Link(api->core)") != std::string::npos, + "missing @Link annotation"); + PASS(); +} + +void test_cross_language_dependency_emits_shim_annotation() { + TEST(cross_language_dependency_emits_shim_annotation); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("ui", "typescript", {"api"})); + sk.modules.push_back(mk("api", "python")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(!plan.links.empty(), "expected cross-language link"); + CHECK(plan.links[0].shimAnnotation.find("@Shim(typescript_to_python)") != std::string::npos, + "missing @Shim annotation"); + PASS(); +} + +void test_same_language_dependency_does_not_emit_cross_language_link() { + TEST(same_language_dependency_does_not_emit_cross_language_link); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python", {"data"})); + sk.modules.push_back(mk("data", "python")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(plan.links.empty(), "expected no cross-language links"); + PASS(); +} + +void test_ffi_boundaries_are_marked_for_human_review() { + TEST(ffi_boundaries_are_marked_for_human_review); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python", {"core"})); + sk.modules.push_back(mk("core", "rust")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(plan.links[0].ffiBoundary, "ffi boundary should be true"); + CHECK(plan.links[0].reviewRequired, "review flag should be true"); + PASS(); +} + +void test_tasks_touching_cross_language_boundary_require_review() { + TEST(tasks_touching_cross_language_boundary_require_review); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python", {"core"})); + sk.modules.push_back(mk("core", "rust")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + auto api = plan.getTask("api"); + auto core = plan.getTask("core"); + CHECK(api.reviewRequired, "api should require review"); + CHECK(core.reviewRequired, "core should require review"); + PASS(); +} + +void test_cycle_fallback_still_produces_complete_ordering() { + TEST(cycle_fallback_still_produces_complete_ordering); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("a", "python", {"b"})); + sk.modules.push_back(mk("b", "rust", {"a"})); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(plan.tasks.size() == 2, "expected both tasks"); + CHECK(plan.tasks[0].buildOrder == 1, "first order mismatch"); + CHECK(plan.tasks[1].buildOrder == 2, "second order mismatch"); + PASS(); +} + +void test_link_direction_matches_dependency_direction() { + TEST(link_direction_matches_dependency_direction); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("ui", "typescript", {"api"})); + sk.modules.push_back(mk("api", "python", {"core"})); + sk.modules.push_back(mk("core", "rust")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(plan.links.size() == 2, "expected two links"); + CHECK(plan.links[0].fromModule == "ui", "first link should originate from ui"); + CHECK(plan.links[0].toModule == "api", "first link should target api"); + PASS(); +} + +void test_single_language_project_has_no_cross_language_reviews() { + TEST(single_language_project_has_no_cross_language_reviews); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python")); + sk.modules.push_back(mk("data", "python", {"api"})); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(plan.links.empty(), "expected zero cross-language links"); + CHECK(!plan.getTask("api").reviewRequired, "single-language task should not require review"); + PASS(); +} + +void test_task_carries_build_hints_from_awareness_report() { + TEST(task_carries_build_hints_from_awareness_report); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python", {"data"})); + sk.modules.push_back(mk("data", "python")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + auto api = plan.getTask("api"); + CHECK(!api.buildHints.empty(), "expected build hints"); + CHECK(api.buildHints[0].find("dependencies = [") != std::string::npos, "missing manifest hint"); + PASS(); +} + +void test_notes_describe_single_project_orchestration_contract() { + TEST(notes_describe_single_project_orchestration_contract); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python")); + auto awareness = ArchitectBuildAwareness::annotate(sk); + auto plan = ArchitectMultiLanguageOrchestrator::createWorkflow(sk, awareness); + CHECK(plan.notes.size() >= 2, "missing notes"); + CHECK(plan.notes[0].find("single project plan") != std::string::npos, + "missing single-project note"); + PASS(); +} + +int main() { + std::cout << "Step 480: Multi-Language Project Orchestration Tests\n"; + + test_creates_tasks_for_all_modules_in_single_project_workflow(); // 1 + test_build_order_respects_dependencies(); // 2 + test_cross_language_dependency_emits_link_annotation(); // 3 + test_cross_language_dependency_emits_shim_annotation(); // 4 + test_same_language_dependency_does_not_emit_cross_language_link(); // 5 + test_ffi_boundaries_are_marked_for_human_review(); // 6 + test_tasks_touching_cross_language_boundary_require_review(); // 7 + test_cycle_fallback_still_produces_complete_ordering(); // 8 + test_link_direction_matches_dependency_direction(); // 9 + test_single_language_project_has_no_cross_language_reviews(); // 10 + test_task_carries_build_hints_from_awareness_report(); // 11 + test_notes_describe_single_project_orchestration_contract(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 27738e1..194f79a 100644 --- a/progress.md +++ b/progress.md @@ -6776,3 +6776,43 @@ imports/includes, and SQL migration order. - `editor/src/ArchitectBuildAwareness.h` within header-size limit (`208` <= `600`) - `editor/tests/step479_test.cpp` within test-file size guidance (`186` lines) - Conventions aligned with `ARCHITECTURE.md` (`PascalCase` structs, `camelCase` methods, header-only) + +### Step 480: Multi-Language Project Orchestration +**Status:** PASS (12/12 tests) + +Introduces a single-project orchestration planner for multi-language module +graphs with deterministic build ordering and explicit cross-language +interface boundaries (`@Link`, `@Shim`) flagged for human review. + +**Files added:** +- `editor/src/ArchitectMultiLanguageOrchestrator.h` — orchestration planner: + - `CrossLanguageLink`, `OrchestrationTask`, `MultiLanguageWorkflowPlan` + - `createWorkflow(...)` producing: + - one task per module + - dependency-respecting build order + - cross-language link records with: + - `@Link(from->to)` + - `@Shim(source_to_target)` + - `ffiBoundary = true`, `reviewRequired = true` + - integration of build hints from `ArchitectBuildAwareness` + - cycle fallback to deterministic lexical ordering with explicit note +- `editor/tests/step480_test.cpp` — 12 tests covering: + - task generation for all modules in a single workflow + - dependency-order correctness + - cross-language link/shim emission + - FFI review gating behavior + - cycle fallback completeness + - link direction correctness + - single-language no-link behavior + - propagation of build-awareness hints into tasks +- `editor/CMakeLists.txt` — `step480_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step480_test step479_test` — PASS +- `./editor/build-native/step480_test` — PASS (12/12) +- `./editor/build-native/step479_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/ArchitectMultiLanguageOrchestrator.h` within header-size limit (`175` <= `600`) +- `editor/tests/step480_test.cpp` within test-file size guidance (`203` lines) +- Header-only and naming conventions remain aligned with `ARCHITECTURE.md`