From f28ba7c059416f4b72e518d8b235d84a1ead37ad Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 16 Feb 2026 21:05:03 -0700 Subject: [PATCH] Complete Step 479: dependency build awareness --- editor/CMakeLists.txt | 9 ++ editor/src/ArchitectBuildAwareness.h | 208 +++++++++++++++++++++++++++ editor/tests/step479_test.cpp | 186 ++++++++++++++++++++++++ progress.md | 35 +++++ 4 files changed, 438 insertions(+) create mode 100644 editor/src/ArchitectBuildAwareness.h create mode 100644 editor/tests/step479_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 3a144ea..60bf477 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3190,4 +3190,13 @@ target_link_libraries(step478_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step479_test tests/step479_test.cpp) +target_include_directories(step479_test PRIVATE src) +target_link_libraries(step479_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/ArchitectBuildAwareness.h b/editor/src/ArchitectBuildAwareness.h new file mode 100644 index 0000000..baad3bc --- /dev/null +++ b/editor/src/ArchitectBuildAwareness.h @@ -0,0 +1,208 @@ +#pragma once + +// Step 479: Dependency + Build System Awareness +// Adds build/dependency annotations from scaffold module dependencies. + +#include "ArchitectSkeletonGenerator.h" + +#include +#include +#include +#include +#include +#include + +struct BuildDependencyAnnotation { + std::string moduleName; + std::string language; + std::vector moduleDependencies; + std::vector importHints; + std::vector packageDependencies; + std::string manifestSnippet; + int migrationOrder = 0; // SQL-only: 1-based order + std::vector notes; +}; + +struct BuildAwarenessReport { + std::vector modules; + std::vector globalNotes; + + bool hasModule(const std::string& moduleName) const { + for (const auto& m : modules) if (m.moduleName == moduleName) return true; + return false; + } + + BuildDependencyAnnotation getModule(const std::string& moduleName) const { + for (const auto& m : modules) if (m.moduleName == moduleName) return m; + return {}; + } +}; + +class ArchitectBuildAwareness { +public: + static BuildAwarenessReport annotate(const SkeletonProjectSpec& skeleton) { + BuildAwarenessReport out; + const auto sqlOrder = computeSqlMigrationOrder(skeleton); + + for (const auto& m : skeleton.modules) { + BuildDependencyAnnotation ann; + ann.moduleName = m.moduleName; + ann.language = lower(m.language); + ann.moduleDependencies = m.dependencies; + + if (ann.language == "python") { + annotatePython(m, ann); + } else if (ann.language == "rust") { + annotateRust(m, ann); + } else if (ann.language == "typescript" || ann.language == "javascript") { + annotateNode(m, ann); + } else if (ann.language == "cpp" || ann.language == "c") { + annotateCpp(m, ann); + } else if (ann.language == "sql") { + annotateSql(m, ann, sqlOrder); + } else { + annotateGeneric(m, ann); + } + + out.modules.push_back(std::move(ann)); + } + + out.globalNotes.push_back("Build awareness report generated from module dependencies"); + out.globalNotes.push_back("Snippets are annotation-level hints, not executable build configs"); + 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 void annotatePython(const SkeletonModuleSpec& m, BuildDependencyAnnotation& ann) { + for (const auto& dep : m.dependencies) { + ann.importHints.push_back("from " + dep + " import *"); + } + ann.packageDependencies.push_back("typing-extensions"); + if (m.moduleName.find("api") != std::string::npos) ann.packageDependencies.push_back("fastapi"); + if (m.moduleName.find("data") != std::string::npos) ann.packageDependencies.push_back("sqlalchemy"); + + ann.manifestSnippet = "dependencies = ["; + for (size_t i = 0; i < ann.packageDependencies.size(); ++i) { + if (i) ann.manifestSnippet += ", "; + ann.manifestSnippet += "\"" + ann.packageDependencies[i] + "\""; + } + ann.manifestSnippet += "]"; + ann.notes.push_back("Python imports mapped from module graph"); + } + + static void annotateRust(const SkeletonModuleSpec& m, BuildDependencyAnnotation& ann) { + ann.packageDependencies.push_back("serde = \"1\""); + for (const auto& dep : m.dependencies) { + ann.packageDependencies.push_back(dep + " = { path = \"../" + dep + "\" }"); + ann.importHints.push_back("use crate::" + dep + "::*;"); + } + + ann.manifestSnippet = "[dependencies]\n"; + for (const auto& p : ann.packageDependencies) ann.manifestSnippet += p + "\n"; + if (m.dependencies.empty()) ann.manifestSnippet += "# no local module dependencies\n"; + ann.notes.push_back("Cargo dependency hints generated from module dependencies"); + } + + static void annotateNode(const SkeletonModuleSpec& m, BuildDependencyAnnotation& ann) { + ann.packageDependencies.push_back("typescript"); + if (m.moduleName.find("api") != std::string::npos) ann.packageDependencies.push_back("express"); + + for (const auto& dep : m.dependencies) { + ann.importHints.push_back("import * as " + dep + " from \"../" + dep + "/" + dep + "\";"); + } + + ann.manifestSnippet = "\"dependencies\": {"; + for (size_t i = 0; i < ann.packageDependencies.size(); ++i) { + if (i) ann.manifestSnippet += ", "; + ann.manifestSnippet += "\"" + ann.packageDependencies[i] + "\": \"*\""; + } + ann.manifestSnippet += "}"; + ann.notes.push_back("Node package and import hints generated"); + } + + static void annotateCpp(const SkeletonModuleSpec& m, BuildDependencyAnnotation& ann) { + for (const auto& dep : m.dependencies) { + ann.importHints.push_back("#include \"" + dep + ".h\""); + } + ann.packageDependencies.push_back("cmake-target:" + m.moduleName); + + std::string snippet = "add_library(" + m.moduleName + " INTERFACE)\n"; + if (!m.dependencies.empty()) { + snippet += "target_link_libraries(" + m.moduleName + " INTERFACE"; + for (const auto& dep : m.dependencies) snippet += " " + dep; + snippet += ")\n"; + } + ann.manifestSnippet = snippet; + ann.notes.push_back("CMake target/link hints generated from dependencies"); + } + + static void annotateSql(const SkeletonModuleSpec& m, BuildDependencyAnnotation& ann, + const std::map& sqlOrder) { + auto it = sqlOrder.find(m.moduleName); + ann.migrationOrder = (it == sqlOrder.end()) ? 0 : it->second; + ann.manifestSnippet = "-- migration_order: " + std::to_string(ann.migrationOrder); + for (const auto& dep : m.dependencies) { + ann.importHints.push_back("-- depends_on: " + dep); + } + ann.notes.push_back("SQL migration ordering derived from dependency graph"); + } + + static void annotateGeneric(const SkeletonModuleSpec& m, BuildDependencyAnnotation& ann) { + ann.manifestSnippet = "# build-hints unavailable for language: " + lower(m.language); + if (m.dependencies.empty()) ann.notes.push_back("No dependencies recorded"); + else ann.notes.push_back("Dependency links preserved for manual review"); + } + + static std::map computeSqlMigrationOrder(const SkeletonProjectSpec& sk) { + std::set sql; + for (const auto& m : sk.modules) { + if (lower(m.language) == "sql") sql.insert(m.moduleName); + } + + std::vector ordered; + std::set temp, perm; + bool hasCycle = false; + + std::function visit = [&](const std::string& node) { + if (perm.count(node) || hasCycle) return; + if (temp.count(node)) { + hasCycle = true; + return; + } + temp.insert(node); + + for (const auto& m : sk.modules) { + if (m.moduleName != node) continue; + for (const auto& dep : m.dependencies) { + if (sql.count(dep)) visit(dep); + } + break; + } + + temp.erase(node); + perm.insert(node); + ordered.push_back(node); + }; + + for (const auto& n : sql) { + if (!perm.count(n)) visit(n); + } + + // Cycle fallback: deterministic lexical order. + if (hasCycle) { + ordered.assign(sql.begin(), sql.end()); + std::sort(ordered.begin(), ordered.end()); + } + + std::map out; + for (size_t i = 0; i < ordered.size(); ++i) out[ordered[i]] = (int)i + 1; + return out; + } +}; diff --git a/editor/tests/step479_test.cpp b/editor/tests/step479_test.cpp new file mode 100644 index 0000000..6fc57ff --- /dev/null +++ b/editor/tests/step479_test.cpp @@ -0,0 +1,186 @@ +// Step 479: Dependency + Build System Awareness Tests (12 tests) + +#include "ArchitectBuildAwareness.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; +} + +void test_python_dependency_annotations_include_imports_and_pip_requirements() { + TEST(python_dependency_annotations_include_imports_and_pip_requirements); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python", {"data"})); + auto report = ArchitectBuildAwareness::annotate(sk); + auto api = report.getModule("api"); + CHECK(!api.importHints.empty(), "expected import hints"); + CHECK(api.importHints[0].find("from data import *") != std::string::npos, "missing data import"); + CHECK(api.manifestSnippet.find("fastapi") != std::string::npos, "missing fastapi hint"); + PASS(); +} + +void test_rust_dependency_annotations_include_cargo_format() { + TEST(rust_dependency_annotations_include_cargo_format); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("core", "rust", {"util"})); + sk.modules.push_back(mk("util", "rust")); + auto report = ArchitectBuildAwareness::annotate(sk); + auto core = report.getModule("core"); + CHECK(core.manifestSnippet.find("[dependencies]") != std::string::npos, "missing dependencies section"); + CHECK(core.manifestSnippet.find("util = { path = \"../util\" }") != std::string::npos, + "missing local crate path dep"); + PASS(); +} + +void test_node_dependency_annotations_include_package_json_style() { + TEST(node_dependency_annotations_include_package_json_style); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "typescript", {"auth"})); + auto report = ArchitectBuildAwareness::annotate(sk); + auto api = report.getModule("api"); + CHECK(api.manifestSnippet.find("\"dependencies\"") != std::string::npos, "missing package deps object"); + CHECK(api.manifestSnippet.find("express") != std::string::npos, "missing express hint"); + PASS(); +} + +void test_cpp_dependency_annotations_include_cmake_target_hints() { + TEST(cpp_dependency_annotations_include_cmake_target_hints); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("engine", "cpp", {"math"})); + sk.modules.push_back(mk("math", "cpp")); + auto report = ArchitectBuildAwareness::annotate(sk); + auto engine = report.getModule("engine"); + CHECK(engine.importHints[0].find("#include \"math.h\"") != std::string::npos, "missing include hint"); + CHECK(engine.manifestSnippet.find("target_link_libraries(engine INTERFACE math)") != std::string::npos, + "missing cmake link line"); + PASS(); +} + +void test_sql_dependency_annotations_include_migration_ordering() { + TEST(sql_dependency_annotations_include_migration_ordering); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("schema", "sql")); + sk.modules.push_back(mk("seed", "sql", {"schema"})); + auto report = ArchitectBuildAwareness::annotate(sk); + auto schema = report.getModule("schema"); + auto seed = report.getModule("seed"); + CHECK(schema.migrationOrder > 0, "schema missing migration order"); + CHECK(seed.migrationOrder > schema.migrationOrder, "seed should come after schema"); + PASS(); +} + +void test_module_dependencies_are_preserved_in_report() { + TEST(module_dependencies_are_preserved_in_report); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python", {"auth", "data"})); + auto report = ArchitectBuildAwareness::annotate(sk); + auto api = report.getModule("api"); + CHECK(api.moduleDependencies.size() == 2, "dependency count mismatch"); + CHECK(api.moduleDependencies[0] == "auth", "missing auth dep"); + CHECK(api.moduleDependencies[1] == "data", "missing data dep"); + PASS(); +} + +void test_module_without_dependencies_still_has_manifest_snippet() { + TEST(module_without_dependencies_still_has_manifest_snippet); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("core", "rust")); + auto report = ArchitectBuildAwareness::annotate(sk); + auto core = report.getModule("core"); + CHECK(core.manifestSnippet.find("[dependencies]") != std::string::npos, "missing manifest header"); + PASS(); +} + +void test_mixed_language_report_contains_all_modules() { + TEST(mixed_language_report_contains_all_modules); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python")); + sk.modules.push_back(mk("core", "rust")); + sk.modules.push_back(mk("ui", "typescript")); + sk.modules.push_back(mk("engine", "cpp")); + sk.modules.push_back(mk("schema", "sql")); + auto report = ArchitectBuildAwareness::annotate(sk); + CHECK(report.modules.size() == 5, "expected five modules"); + PASS(); +} + +void test_node_dependencies_emit_import_hints_for_each_module_dep() { + TEST(node_dependencies_emit_import_hints_for_each_module_dep); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("ui", "typescript", {"api", "state"})); + auto report = ArchitectBuildAwareness::annotate(sk); + auto ui = report.getModule("ui"); + CHECK(ui.importHints.size() == 2, "import hint count mismatch"); + CHECK(ui.importHints[0].find("../api/api") != std::string::npos, "missing api import path"); + PASS(); +} + +void test_unknown_language_falls_back_to_generic_hint() { + TEST(unknown_language_falls_back_to_generic_hint); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("docs", "markdown")); + auto report = ArchitectBuildAwareness::annotate(sk); + auto docs = report.getModule("docs"); + CHECK(docs.manifestSnippet.find("build-hints unavailable") != std::string::npos, + "missing generic fallback hint"); + PASS(); +} + +void test_sql_cycle_fallback_is_deterministic() { + TEST(sql_cycle_fallback_is_deterministic); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("a", "sql", {"b"})); + sk.modules.push_back(mk("b", "sql", {"a"})); + auto report = ArchitectBuildAwareness::annotate(sk); + auto a = report.getModule("a"); + auto b = report.getModule("b"); + CHECK(a.migrationOrder > 0 && b.migrationOrder > 0, "expected migration orders"); + CHECK(a.migrationOrder != b.migrationOrder, "orders should remain unique"); + PASS(); +} + +void test_global_notes_explain_annotation_scope() { + TEST(global_notes_explain_annotation_scope); + SkeletonProjectSpec sk; + sk.modules.push_back(mk("api", "python")); + auto report = ArchitectBuildAwareness::annotate(sk); + CHECK(report.globalNotes.size() >= 2, "missing report notes"); + CHECK(report.globalNotes[1].find("annotation-level") != std::string::npos, + "missing scope disclaimer"); + PASS(); +} + +int main() { + std::cout << "Step 479: Dependency + Build System Awareness Tests\n"; + + test_python_dependency_annotations_include_imports_and_pip_requirements(); // 1 + test_rust_dependency_annotations_include_cargo_format(); // 2 + test_node_dependency_annotations_include_package_json_style(); // 3 + test_cpp_dependency_annotations_include_cmake_target_hints(); // 4 + test_sql_dependency_annotations_include_migration_ordering(); // 5 + test_module_dependencies_are_preserved_in_report(); // 6 + test_module_without_dependencies_still_has_manifest_snippet(); // 7 + test_mixed_language_report_contains_all_modules(); // 8 + test_node_dependencies_emit_import_hints_for_each_module_dep(); // 9 + test_unknown_language_falls_back_to_generic_hint(); // 10 + test_sql_cycle_fallback_is_deterministic(); // 11 + test_global_notes_explain_annotation_scope(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) + << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index f67712f..27738e1 100644 --- a/progress.md +++ b/progress.md @@ -6741,3 +6741,38 @@ files, with operation plans compatible with `fileCreate`/`fileWrite`. - `editor/src/ArchitectScaffoldGenerator.h` within header-size limit (`358` <= `600`) - `editor/tests/step478_test.cpp` within test-file size guidance (`224` lines) - Header-only architecture preserved; naming conventions remain `PascalCase` types and `camelCase` methods + +### Step 479: Dependency + Build System Awareness +**Status:** PASS (12/12 tests) + +Adds annotation-level dependency/build awareness derived from module graph +dependencies so scaffolded modules carry structured hints for packaging, +imports/includes, and SQL migration order. + +**Files added:** +- `editor/src/ArchitectBuildAwareness.h` — dependency/build annotation engine: + - `BuildDependencyAnnotation`, `BuildAwarenessReport` + - `annotate(...)` for per-module build/dependency hints by language: + - Python import hints + pip-style dependency snippet + - Rust `Cargo.toml` `[dependencies]` snippet + local crate path links + - Node/TypeScript package.json-style dependency snippet + import hints + - C/C++ include hints + CMake target/link snippet + - SQL migration ordering annotations with dependency-derived order + - deterministic SQL cycle fallback behavior for stable ordering +- `editor/tests/step479_test.cpp` — 12 tests covering: + - language-specific manifest/import annotation generation + - dependency preservation and mixed-language report coverage + - SQL migration ordering and cycle fallback + - no-dependency and unknown-language edge cases + - global note/disclaimer presence for annotation scope +- `editor/CMakeLists.txt` — `step479_test` target + +**Verification run:** +- `cmake --build editor/build-native --target step479_test step478_test` — PASS +- `./editor/build-native/step479_test` — PASS (12/12) +- `./editor/build-native/step478_test` — PASS (12/12) regression coverage + +**Architecture gate check:** +- `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)