diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index 44b910c..5924395 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -3604,4 +3604,13 @@ target_link_libraries(step524_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step525_test tests/step525_test.cpp) +target_include_directories(step525_test PRIVATE src) +target_link_libraries(step525_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/LegalOperationGraph.h b/editor/src/LegalOperationGraph.h new file mode 100644 index 0000000..47dd52e --- /dev/null +++ b/editor/src/LegalOperationGraph.h @@ -0,0 +1,79 @@ +#pragma once +// Step 525: Legal Operation Graph + +#include +#include +#include +#include + +struct LegalOpNode { + std::string language; + std::string nodeKind; + std::set operations; +}; + +struct LegalOpQueryResult { + bool supported = false; + std::vector operations; +}; + +class LegalOperationGraph { +public: + LegalOperationGraph() { initDefaults(); } + + void registerRule(const std::string& language, + const std::string& nodeKind, + const std::vector& ops) { + std::set dedup(ops.begin(), ops.end()); + rules_[key(language, nodeKind)] = LegalOpNode{language, nodeKind, dedup}; + } + + LegalOpQueryResult allowedOps(const std::string& language, + const std::string& nodeKind) const { + auto it = rules_.find(key(language, nodeKind)); + if (it == rules_.end()) return {}; + LegalOpQueryResult r; + r.supported = true; + r.operations.assign(it->second.operations.begin(), it->second.operations.end()); + return r; + } + + bool opAllowed(const std::string& language, + const std::string& nodeKind, + const std::string& op) const { + auto q = allowedOps(language, nodeKind); + if (!q.supported) return false; + for (const auto& candidate : q.operations) { + if (candidate == op) return true; + } + return false; + } + +private: + std::map rules_; + + static std::string key(const std::string& language, + const std::string& nodeKind) { + return language + "::" + nodeKind; + } + + void initDefaults() { + // C/C++ + registerRule("cpp", "Function", {"rename", "update", "extract", "inline"}); + registerRule("cpp", "Variable", {"rename", "update", "delete"}); + registerRule("cpp", "Include", {"insert", "delete", "reorder"}); + registerRule("c", "Function", {"rename", "update", "extract"}); + + // Python/TypeScript + registerRule("python", "Function", {"rename", "update", "extract"}); + registerRule("python", "Class", {"rename", "update", "extract"}); + registerRule("typescript", "Function", {"rename", "update", "extract", "inline"}); + registerRule("typescript", "Import", {"insert", "delete", "reorder"}); + + // Rust/Go + registerRule("rust", "Function", {"rename", "update", "extract"}); + registerRule("rust", "Struct", {"rename", "update"}); + registerRule("go", "Function", {"rename", "update", "extract"}); + registerRule("go", "Import", {"insert", "delete", "reorder"}); + } +}; diff --git a/editor/tests/step525_test.cpp b/editor/tests/step525_test.cpp new file mode 100644 index 0000000..6c6e034 --- /dev/null +++ b/editor/tests/step525_test.cpp @@ -0,0 +1,131 @@ +// Step 525: Legal Operation Graph (12 tests) + +#include "LegalOperationGraph.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; } else {} + +void test_cpp_function_has_expected_ops() { + TEST(cpp_function_has_expected_ops); + LegalOperationGraph g; + auto q = g.allowedOps("cpp", "Function"); + CHECK(q.supported, "cpp function should be supported"); + CHECK(g.opAllowed("cpp", "Function", "rename"), "rename should be allowed"); + CHECK(g.opAllowed("cpp", "Function", "inline"), "inline should be allowed"); + PASS(); +} + +void test_cpp_variable_disallows_extract() { + TEST(cpp_variable_disallows_extract); + LegalOperationGraph g; + CHECK(!g.opAllowed("cpp", "Variable", "extract"), "extract should be disallowed"); + PASS(); +} + +void test_python_class_supports_rename_update_extract() { + TEST(python_class_supports_rename_update_extract); + LegalOperationGraph g; + CHECK(g.opAllowed("python", "Class", "rename"), "rename missing"); + CHECK(g.opAllowed("python", "Class", "update"), "update missing"); + CHECK(g.opAllowed("python", "Class", "extract"), "extract missing"); + PASS(); +} + +void test_typescript_import_supports_reorder() { + TEST(typescript_import_supports_reorder); + LegalOperationGraph g; + CHECK(g.opAllowed("typescript", "Import", "reorder"), "reorder should be allowed"); + PASS(); +} + +void test_rust_struct_disallows_delete() { + TEST(rust_struct_disallows_delete); + LegalOperationGraph g; + CHECK(!g.opAllowed("rust", "Struct", "delete"), "delete should be disallowed"); + PASS(); +} + +void test_unknown_language_is_unsupported() { + TEST(unknown_language_is_unsupported); + LegalOperationGraph g; + auto q = g.allowedOps("haskell", "Function"); + CHECK(!q.supported, "unknown language should be unsupported"); + PASS(); +} + +void test_unknown_node_kind_is_unsupported() { + TEST(unknown_node_kind_is_unsupported); + LegalOperationGraph g; + auto q = g.allowedOps("cpp", "LambdaMagic"); + CHECK(!q.supported, "unknown node kind should be unsupported"); + PASS(); +} + +void test_register_rule_adds_new_language_mapping() { + TEST(register_rule_adds_new_language_mapping); + LegalOperationGraph g; + g.registerRule("java", "Method", {"rename", "update"}); + CHECK(g.opAllowed("java", "Method", "rename"), "registered op missing"); + PASS(); +} + +void test_register_rule_overrides_existing_mapping() { + TEST(register_rule_overrides_existing_mapping); + LegalOperationGraph g; + CHECK(g.opAllowed("cpp", "Function", "inline"), "baseline inline missing"); + g.registerRule("cpp", "Function", {"rename"}); + CHECK(g.opAllowed("cpp", "Function", "rename"), "rename should remain"); + CHECK(!g.opAllowed("cpp", "Function", "inline"), "inline should be removed by override"); + PASS(); +} + +void test_rule_deduplicates_ops() { + TEST(rule_deduplicates_ops); + LegalOperationGraph g; + g.registerRule("go", "Function", {"rename", "rename", "update"}); + auto q = g.allowedOps("go", "Function"); + CHECK(q.supported, "go function should be supported"); + CHECK((int)q.operations.size() == 2, "ops should be deduplicated"); + PASS(); +} + +void test_c_function_supports_extract() { + TEST(c_function_supports_extract); + LegalOperationGraph g; + CHECK(g.opAllowed("c", "Function", "extract"), "extract should be allowed for c function"); + PASS(); +} + +void test_go_import_supports_insert_delete_reorder() { + TEST(go_import_supports_insert_delete_reorder); + LegalOperationGraph g; + CHECK(g.opAllowed("go", "Import", "insert"), "insert missing"); + CHECK(g.opAllowed("go", "Import", "delete"), "delete missing"); + CHECK(g.opAllowed("go", "Import", "reorder"), "reorder missing"); + PASS(); +} + +int main() { + std::cout << "Step 525: Legal Operation Graph\n"; + + test_cpp_function_has_expected_ops(); // 1 + test_cpp_variable_disallows_extract(); // 2 + test_python_class_supports_rename_update_extract(); // 3 + test_typescript_import_supports_reorder(); // 4 + test_rust_struct_disallows_delete(); // 5 + test_unknown_language_is_unsupported(); // 6 + test_unknown_node_kind_is_unsupported(); // 7 + test_register_rule_adds_new_language_mapping(); // 8 + test_register_rule_overrides_existing_mapping(); // 9 + test_rule_deduplicates_ops(); // 10 + test_c_function_supports_extract(); // 11 + test_go_import_supports_insert_delete_reorder(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index e2babdf..4f99cfe 100644 --- a/progress.md +++ b/progress.md @@ -8591,3 +8591,36 @@ expected diagnostics deltas, rejecting under-specified taskitems. - `editor/src/TypedTaskitemContractSchema.h` within header-size limit (`114` <= `600`) - `editor/tests/step524_test.cpp` within test-file size guidance (`148` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 525: Legal Operation Graph +**Status:** PASS (12/12 tests) + +Implements a legal-operation graph for language/node-kind pairs so constrained +editor tasks can validate whether requested refactors are structurally allowed +before execution. + +**Files added:** +- `editor/src/LegalOperationGraph.h` - operation-eligibility graph module: + - default per-language/per-node operation rules + - rule registration and override support + - deduplication of operation sets + - query helpers for supported nodes and allowed operations +- `editor/tests/step525_test.cpp` - 12 tests covering: + - default rule coverage across cpp/c/python/typescript/rust/go + - unsupported language/node behavior + - rule registration and override semantics + - operation deduplication behavior + +**Files modified:** +- `editor/CMakeLists.txt` - `step525_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step525_test step524_test` - PASS +- `./editor/build-native/step525_test` - PASS (12/12) +- `./editor/build-native/step524_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/LegalOperationGraph.h` within header-size limit (`79` <= `600`) +- `editor/tests/step525_test.cpp` within test-file size guidance (`131` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`