Complete Step 479: dependency build awareness

This commit is contained in:
Bill
2026-02-16 21:05:03 -07:00
parent 99d81b7dad
commit f28ba7c059
4 changed files with 438 additions and 0 deletions

View File

@@ -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)

View 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;
}
};

View 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;
}

View File

@@ -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)