diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 0383b5b..00d62ff 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4630,4 +4630,13 @@ target_link_libraries(step638_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step639_test tests/step639_test.cpp) +target_include_directories(step639_test PRIVATE src) +target_link_libraries(step639_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/ProjectSkeletonGenerator.h b/editor/src/ProjectSkeletonGenerator.h new file mode 100644 index 0000000..d590c5c --- /dev/null +++ b/editor/src/ProjectSkeletonGenerator.h @@ -0,0 +1,67 @@ +#pragma once +// Step 639: whetstone_generate_project MCP tool model + +#include + +#include +#include + +using json = nlohmann::json; + +struct GeneratedProjectSkeleton { + bool success = false; + std::string name; + std::string cmakeLists; + std::string mainCpp; + std::vector moduleHeaders; + std::vector dependencies; + std::vector errors; +}; + +class ProjectSkeletonGenerator { +public: + static std::string toolName() { return "whetstone_generate_project"; } + + static GeneratedProjectSkeleton generate(const std::string& name, + const std::string& description, + const std::vector& dependencies) { + GeneratedProjectSkeleton out; + if (name.empty()) { + out.errors.push_back("name_required"); + return out; + } + + out.success = true; + out.name = name; + out.dependencies = dependencies; + out.moduleHeaders = { + "include/AppCore.h", + "include/TaskRouter.h" + }; + + out.cmakeLists = "cmake_minimum_required(VERSION 3.24)\n" + "project(" + name + " LANGUAGES CXX)\n" + "set(CMAKE_CXX_STANDARD 20)\n" + "add_executable(" + name + " src/main.cpp)\n"; + for (const auto& dep : dependencies) { + out.cmakeLists += "find_package(" + dep + " CONFIG REQUIRED)\n"; + } + out.cmakeLists += "target_include_directories(" + name + " PRIVATE include)\n"; + + out.mainCpp = "#include \n" + "int main(){ std::cout << \"" + description + "\\n\"; return 0; }\n"; + return out; + } + + static json asToolResponse(const GeneratedProjectSkeleton& out) { + return { + {"success", out.success}, + {"name", out.name}, + {"cmakeLists", out.cmakeLists}, + {"mainCpp", out.mainCpp}, + {"moduleHeaders", out.moduleHeaders}, + {"dependencies", out.dependencies}, + {"errors", out.errors} + }; + } +}; diff --git a/editor/tests/step639_test.cpp b/editor/tests/step639_test.cpp new file mode 100644 index 0000000..1b19153 --- /dev/null +++ b/editor/tests/step639_test.cpp @@ -0,0 +1,121 @@ +// Step 639: whetstone_generate_project MCP tool (12 tests) + +#include "ProjectSkeletonGenerator.h" + +#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; } + +void test_tool_name_matches_spec() { + TEST(tool_name_matches_spec); + CHECK(ProjectSkeletonGenerator::toolName() == "whetstone_generate_project", "tool name mismatch"); + PASS(); +} + +void test_generate_rejects_empty_name() { + TEST(generate_rejects_empty_name); + auto out = ProjectSkeletonGenerator::generate("", "desc", {}); + CHECK(!out.success, "empty name should fail"); + CHECK(!out.errors.empty(), "error expected"); + PASS(); +} + +void test_generate_sets_project_name() { + TEST(generate_sets_project_name); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {}); + CHECK(out.success, "generation should succeed"); + CHECK(out.name == "drone", "name mismatch"); + PASS(); +} + +void test_generate_emits_cmake_project_line() { + TEST(generate_emits_cmake_project_line); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {}); + CHECK(out.cmakeLists.find("project(drone") != std::string::npos, "project line missing"); + PASS(); +} + +void test_generate_emits_executable_target() { + TEST(generate_emits_executable_target); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {}); + CHECK(out.cmakeLists.find("add_executable(drone") != std::string::npos, "target line missing"); + PASS(); +} + +void test_generate_emits_find_package_for_dependencies() { + TEST(generate_emits_find_package_for_dependencies); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {"nlohmann_json"}); + CHECK(out.cmakeLists.find("find_package(nlohmann_json CONFIG REQUIRED)") != std::string::npos, + "dependency line missing"); + PASS(); +} + +void test_generate_emits_main_cpp_with_description() { + TEST(generate_emits_main_cpp_with_description); + auto out = ProjectSkeletonGenerator::generate("drone", "HiveMind bootstrap", {}); + CHECK(out.mainCpp.find("HiveMind bootstrap") != std::string::npos, "description missing in main"); + PASS(); +} + +void test_generate_includes_module_headers() { + TEST(generate_includes_module_headers); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {}); + CHECK(out.moduleHeaders.size() == 2, "module header count mismatch"); + PASS(); +} + +void test_as_tool_response_includes_success_flag() { + TEST(as_tool_response_includes_success_flag); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {}); + auto response = ProjectSkeletonGenerator::asToolResponse(out); + CHECK(response.value("success", false), "success flag missing"); + PASS(); +} + +void test_as_tool_response_includes_cmake_text() { + TEST(as_tool_response_includes_cmake_text); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {}); + auto response = ProjectSkeletonGenerator::asToolResponse(out); + CHECK(response.value("cmakeLists", "").find("cmake_minimum_required") != std::string::npos, + "cmake text missing"); + PASS(); +} + +void test_as_tool_response_includes_dependencies() { + TEST(as_tool_response_includes_dependencies); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {"sqlite3"}); + auto response = ProjectSkeletonGenerator::asToolResponse(out); + CHECK(response["dependencies"].size() == 1, "dependency count mismatch"); + PASS(); +} + +void test_generate_handles_empty_dependencies_edge_case() { + TEST(generate_handles_empty_dependencies_edge_case); + auto out = ProjectSkeletonGenerator::generate("drone", "desc", {}); + CHECK(out.dependencies.empty(), "dependencies should be empty"); + PASS(); +} + +int main() { + std::cout << "Step 639: whetstone_generate_project MCP tool\n"; + + test_tool_name_matches_spec(); + test_generate_rejects_empty_name(); + test_generate_sets_project_name(); + test_generate_emits_cmake_project_line(); + test_generate_emits_executable_target(); + test_generate_emits_find_package_for_dependencies(); + test_generate_emits_main_cpp_with_description(); + test_generate_includes_module_headers(); + test_as_tool_response_includes_success_flag(); + test_as_tool_response_includes_cmake_text(); + test_as_tool_response_includes_dependencies(); + test_generate_handles_empty_dependencies_edge_case(); + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index e4c7cf2..aba4d2d 100644 --- a/progress.md +++ b/progress.md @@ -12982,3 +12982,34 @@ and a shared-header main entry path. - `editor/src/Phase38aIntegration.h` (`61` <= `600`) - `editor/tests/step638_test.cpp` within test-file size guidance (`83` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 639: `whetstone_generate_project` MCP tool +**Status:** PASS (12/12 tests) + +Adds a project skeleton generator for one-call C++ project bootstrap from +description metadata, including `CMakeLists.txt`, `main.cpp`, module-header +stubs, dependency wiring, and JSON tool-response shaping. + +**Files added:** +- `editor/src/ProjectSkeletonGenerator.h` - project-generation module: + - generator entrypoint for project name/description/dependencies + - CMake and main-entry text emission + - MCP-style JSON response shaping helper +- `editor/tests/step639_test.cpp` - 12 tests covering: + - tool identity and invalid-input handling + - project/cmake/main generation behavior + - dependency propagation and response payload shape + +**Files modified:** +- `editor/CMakeLists.txt` - `step639_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step639_test step638_test` - PASS +- `./editor/build-native/step639_test` - PASS (12/12) +- `./editor/build-native/step638_test` - PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/ProjectSkeletonGenerator.h` (`67` <= `600`) +- `editor/tests/step639_test.cpp` within test-file size guidance (`121` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`