From 97acad04aac0b5dd6f863a0519e73d6e7c8bdf6f Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 21:47:58 -0700 Subject: [PATCH] Step 644: self-hosting project config --- editor/CMakeLists.txt | 9 ++ editor/src/SelfHostingProjectConfig.h | 53 ++++++++++ editor/tests/step644_test.cpp | 135 ++++++++++++++++++++++++++ progress.md | 31 ++++++ 4 files changed, 228 insertions(+) create mode 100644 editor/src/SelfHostingProjectConfig.h create mode 100644 editor/tests/step644_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 745dc71..01c7a23 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4675,4 +4675,13 @@ target_link_libraries(step643_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step644_test tests/step644_test.cpp) +target_include_directories(step644_test PRIVATE src) +target_link_libraries(step644_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/SelfHostingProjectConfig.h b/editor/src/SelfHostingProjectConfig.h new file mode 100644 index 0000000..98cfd45 --- /dev/null +++ b/editor/src/SelfHostingProjectConfig.h @@ -0,0 +1,53 @@ +#pragma once +// Step 644: .whetstone.json for self-hosting editor project + +#include + +#include +#include + +using json = nlohmann::json; + +struct SelfHostingProjectConfig { + std::string projectName; + std::string workspaceRoot; + std::vector sourceRoots; + std::vector testTargets; + bool selfHosting = false; +}; + +class SelfHostingProjectConfigModel { +public: + static json toJson(const SelfHostingProjectConfig& cfg) { + return { + {"projectName", cfg.projectName}, + {"workspaceRoot", cfg.workspaceRoot}, + {"sourceRoots", cfg.sourceRoots}, + {"testTargets", cfg.testTargets}, + {"selfHosting", cfg.selfHosting} + }; + } + + static bool fromJson(const json& j, SelfHostingProjectConfig* out, std::string* error) { + if (!out || !error) return false; + error->clear(); + if (!j.is_object()) { + *error = "config_invalid"; + return false; + } + out->projectName = j.value("projectName", ""); + out->workspaceRoot = j.value("workspaceRoot", ""); + out->sourceRoots = j.value("sourceRoots", std::vector{}); + out->testTargets = j.value("testTargets", std::vector{}); + out->selfHosting = j.value("selfHosting", false); + if (out->projectName.empty()) { + *error = "project_name_missing"; + return false; + } + return true; + } + + static bool canSelfHost(const SelfHostingProjectConfig& cfg) { + return cfg.selfHosting && !cfg.workspaceRoot.empty() && !cfg.sourceRoots.empty(); + } +}; diff --git a/editor/tests/step644_test.cpp b/editor/tests/step644_test.cpp new file mode 100644 index 0000000..a2f19a7 --- /dev/null +++ b/editor/tests/step644_test.cpp @@ -0,0 +1,135 @@ +// Step 644: .whetstone.json for self-hosting editor project (12 tests) + +#include "SelfHostingProjectConfig.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; } + +static SelfHostingProjectConfig sample() { + return { + "whetstone_DSL", + "/workspace/whetstone_DSL", + {"editor/src", "editor/tests"}, + {"step644_test", "step645_test"}, + true + }; +} + +void test_to_json_emits_project_name() { + TEST(to_json_emits_project_name); + auto j = SelfHostingProjectConfigModel::toJson(sample()); + CHECK(j.value("projectName", "") == "whetstone_DSL", "project name mismatch"); + PASS(); +} + +void test_to_json_emits_workspace_root() { + TEST(to_json_emits_workspace_root); + auto j = SelfHostingProjectConfigModel::toJson(sample()); + CHECK(j.value("workspaceRoot", "") == "/workspace/whetstone_DSL", "workspace mismatch"); + PASS(); +} + +void test_to_json_emits_source_roots() { + TEST(to_json_emits_source_roots); + auto j = SelfHostingProjectConfigModel::toJson(sample()); + CHECK(j["sourceRoots"].size() == 2, "source root count mismatch"); + PASS(); +} + +void test_to_json_emits_test_targets() { + TEST(to_json_emits_test_targets); + auto j = SelfHostingProjectConfigModel::toJson(sample()); + CHECK(j["testTargets"].size() == 2, "test target count mismatch"); + PASS(); +} + +void test_from_json_rejects_non_object() { + TEST(from_json_rejects_non_object); + SelfHostingProjectConfig out; + std::string err; + CHECK(!SelfHostingProjectConfigModel::fromJson(json::array(), &out, &err), "array should fail"); + PASS(); +} + +void test_from_json_rejects_missing_project_name() { + TEST(from_json_rejects_missing_project_name); + SelfHostingProjectConfig out; + std::string err; + CHECK(!SelfHostingProjectConfigModel::fromJson({{"workspaceRoot", "/ws"}}, &out, &err), "missing name should fail"); + PASS(); +} + +void test_from_json_parses_valid_config() { + TEST(from_json_parses_valid_config); + SelfHostingProjectConfig out; + std::string err; + CHECK(SelfHostingProjectConfigModel::fromJson(SelfHostingProjectConfigModel::toJson(sample()), &out, &err), + "valid config should parse"); + CHECK(out.projectName == "whetstone_DSL", "parsed name mismatch"); + PASS(); +} + +void test_from_json_parses_self_hosting_flag() { + TEST(from_json_parses_self_hosting_flag); + SelfHostingProjectConfig out; + std::string err; + CHECK(SelfHostingProjectConfigModel::fromJson(SelfHostingProjectConfigModel::toJson(sample()), &out, &err), + "valid config should parse"); + CHECK(out.selfHosting, "selfHosting flag mismatch"); + PASS(); +} + +void test_can_self_host_true_for_valid_self_hosting_config() { + TEST(can_self_host_true_for_valid_self_hosting_config); + CHECK(SelfHostingProjectConfigModel::canSelfHost(sample()), "should self-host"); + PASS(); +} + +void test_can_self_host_false_when_flag_disabled() { + TEST(can_self_host_false_when_flag_disabled); + auto cfg = sample(); + cfg.selfHosting = false; + CHECK(!SelfHostingProjectConfigModel::canSelfHost(cfg), "disabled flag should fail"); + PASS(); +} + +void test_can_self_host_false_when_workspace_missing() { + TEST(can_self_host_false_when_workspace_missing); + auto cfg = sample(); + cfg.workspaceRoot.clear(); + CHECK(!SelfHostingProjectConfigModel::canSelfHost(cfg), "missing workspace should fail"); + PASS(); +} + +void test_can_self_host_false_when_source_roots_empty() { + TEST(can_self_host_false_when_source_roots_empty); + auto cfg = sample(); + cfg.sourceRoots.clear(); + CHECK(!SelfHostingProjectConfigModel::canSelfHost(cfg), "missing sources should fail"); + PASS(); +} + +int main() { + std::cout << "Step 644: .whetstone.json self-hosting config\n"; + + test_to_json_emits_project_name(); + test_to_json_emits_workspace_root(); + test_to_json_emits_source_roots(); + test_to_json_emits_test_targets(); + test_from_json_rejects_non_object(); + test_from_json_rejects_missing_project_name(); + test_from_json_parses_valid_config(); + test_from_json_parses_self_hosting_flag(); + test_can_self_host_true_for_valid_self_hosting_config(); + test_can_self_host_false_when_flag_disabled(); + test_can_self_host_false_when_workspace_missing(); + test_can_self_host_false_when_source_roots_empty(); + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 1eb206e..9ce1f4f 100644 --- a/progress.md +++ b/progress.md @@ -13172,3 +13172,34 @@ with size/function constraints and naming conventions. - `editor/src/Sprint38IntegrationSummary.h` (`60` <= `600`) - Sprint 38 test files remain within test-file size guidance. - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 644: `.whetstone.json` for the editor project itself +**Status:** PASS (12/12 tests) + +Adds a self-hosting project-config model for Whetstone’s own workspace using +`.whetstone.json` semantics, including JSON round-trip helpers and +self-hosting eligibility checks. + +**Files added:** +- `editor/src/SelfHostingProjectConfig.h` - self-hosting config model: + - JSON encode/decode for project/workspace/source/test metadata + - validation with structured error signaling + - `canSelfHost(...)` readiness helper +- `editor/tests/step644_test.cpp` - 12 tests covering: + - JSON emission and parse behavior + - invalid-config edge rejection + - self-hosting readiness decision paths + +**Files modified:** +- `editor/CMakeLists.txt` - `step644_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step644_test step643_test` - PASS +- `./editor/build-native/step644_test` - PASS (12/12) +- `./editor/build-native/step643_test` - PASS (8/8) regression coverage + +**Architecture gate check:** +- `editor/src/SelfHostingProjectConfig.h` (`53` <= `600`) +- `editor/tests/step644_test.cpp` within test-file size guidance (`135` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`