Complete Step 479: dependency build awareness
This commit is contained in:
@@ -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)
|
||||
|
||||
208
editor/src/ArchitectBuildAwareness.h
Normal file
208
editor/src/ArchitectBuildAwareness.h
Normal file
@@ -0,0 +1,208 @@
|
||||
#pragma once
|
||||
|
||||
// Step 479: Dependency + Build System Awareness
|
||||
// Adds build/dependency annotations from scaffold module dependencies.
|
||||
|
||||
#include "ArchitectSkeletonGenerator.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
struct BuildDependencyAnnotation {
|
||||
std::string moduleName;
|
||||
std::string language;
|
||||
std::vector<std::string> moduleDependencies;
|
||||
std::vector<std::string> importHints;
|
||||
std::vector<std::string> packageDependencies;
|
||||
std::string manifestSnippet;
|
||||
int migrationOrder = 0; // SQL-only: 1-based order
|
||||
std::vector<std::string> notes;
|
||||
};
|
||||
|
||||
struct BuildAwarenessReport {
|
||||
std::vector<BuildDependencyAnnotation> modules;
|
||||
std::vector<std::string> 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<char>(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<std::string, int>& 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<std::string, int> computeSqlMigrationOrder(const SkeletonProjectSpec& sk) {
|
||||
std::set<std::string> sql;
|
||||
for (const auto& m : sk.modules) {
|
||||
if (lower(m.language) == "sql") sql.insert(m.moduleName);
|
||||
}
|
||||
|
||||
std::vector<std::string> ordered;
|
||||
std::set<std::string> temp, perm;
|
||||
bool hasCycle = false;
|
||||
|
||||
std::function<void(const std::string&)> 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<std::string, int> out;
|
||||
for (size_t i = 0; i < ordered.size(); ++i) out[ordered[i]] = (int)i + 1;
|
||||
return out;
|
||||
}
|
||||
};
|
||||
186
editor/tests/step479_test.cpp
Normal file
186
editor/tests/step479_test.cpp
Normal file
@@ -0,0 +1,186 @@
|
||||
// Step 479: Dependency + Build System Awareness Tests (12 tests)
|
||||
|
||||
#include "ArchitectBuildAwareness.h"
|
||||
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
35
progress.md
35
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)
|
||||
|
||||
Reference in New Issue
Block a user