Complete Step 480: multi-language orchestration
This commit is contained in:
@@ -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)
|
||||
|
||||
175
editor/src/ArchitectMultiLanguageOrchestrator.h
Normal file
175
editor/src/ArchitectMultiLanguageOrchestrator.h
Normal file
@@ -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 <algorithm>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::string> dependencies;
|
||||
int buildOrder = 0;
|
||||
bool reviewRequired = false;
|
||||
std::vector<std::string> annotations;
|
||||
std::vector<std::string> buildHints;
|
||||
};
|
||||
|
||||
struct MultiLanguageWorkflowPlan {
|
||||
std::vector<OrchestrationTask> tasks;
|
||||
std::vector<CrossLanguageLink> links;
|
||||
std::vector<std::string> 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<std::string, SkeletonModuleSpec> byName;
|
||||
for (const auto& m : skeleton.modules) byName[m.moduleName] = m;
|
||||
|
||||
std::set<std::string> 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<char>(std::tolower(c)); });
|
||||
return out;
|
||||
}
|
||||
|
||||
static std::vector<std::string> topoOrder(const SkeletonProjectSpec& skeleton,
|
||||
std::vector<std::string>& notes) {
|
||||
std::map<std::string, int> indeg;
|
||||
std::map<std::string, std::vector<std::string>> adj;
|
||||
std::set<std::string> 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<std::string> ready;
|
||||
for (const auto& n : nodes) {
|
||||
if (indeg[n] == 0) ready.push_back(n);
|
||||
}
|
||||
std::sort(ready.begin(), ready.end());
|
||||
|
||||
std::vector<std::string> 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<std::string> 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;
|
||||
}
|
||||
};
|
||||
203
editor/tests/step480_test.cpp
Normal file
203
editor/tests/step480_test.cpp
Normal file
@@ -0,0 +1,203 @@
|
||||
// Step 480: Multi-Language Project Orchestration Tests (12 tests)
|
||||
|
||||
#include "ArchitectMultiLanguageOrchestrator.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
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<std::string> deps = {}) {
|
||||
SkeletonModuleSpec m;
|
||||
m.moduleName = name;
|
||||
m.language = lang;
|
||||
m.dependencies.assign(deps.begin(), deps.end());
|
||||
return m;
|
||||
}
|
||||
|
||||
static std::map<std::string, int> buildOrderMap(const MultiLanguageWorkflowPlan& p) {
|
||||
std::map<std::string, int> 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;
|
||||
}
|
||||
40
progress.md
40
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`
|
||||
|
||||
Reference in New Issue
Block a user