diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index b615546..c099f63 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4513,4 +4513,13 @@ target_link_libraries(step625_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step626_test tests/step626_test.cpp) +target_include_directories(step626_test PRIVATE src) +target_link_libraries(step626_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/AgentChatPanelModel.h b/editor/src/AgentChatPanelModel.h index 87ba09c..ea5a0a5 100644 --- a/editor/src/AgentChatPanelModel.h +++ b/editor/src/AgentChatPanelModel.h @@ -2,6 +2,7 @@ // Step 624: Agent Chat Panel model #include "AgentToolCallVisualization.h" +#include "AgentMutationPreview.h" #include #include @@ -22,6 +23,7 @@ struct AgentChatMessage { struct AgentChatState { std::vector messages; std::vector toolCalls; + std::vector mutationPreviews; std::string draftInput; bool autoScroll = true; bool open = false; @@ -69,6 +71,22 @@ public: state->autoScroll = true; } + static void addMutationPreview(AgentChatState* state, + const std::string& previewId, + const std::string& mutationJson, + const std::string& beforeCode, + const std::string& afterCode, + const std::string& timestamp) { + if (!state) return; + AgentMutationPreview preview = AgentMutationPreviewBuilder::build( + previewId, mutationJson, beforeCode, afterCode); + state->mutationPreviews.push_back(preview); + std::string label = std::string("[PREVIEW] ") + previewId + + (preview.hasChanges ? " (changes)" : " (no changes)"); + state->messages.push_back({AgentChatRole::Tool, label, timestamp}); + state->autoScroll = true; + } + static const char* roleLabel(AgentChatRole role) { if (role == AgentChatRole::User) return "user"; if (role == AgentChatRole::Assistant) return "assistant"; diff --git a/editor/src/AgentMutationPreview.h b/editor/src/AgentMutationPreview.h new file mode 100644 index 0000000..6a6d4ae --- /dev/null +++ b/editor/src/AgentMutationPreview.h @@ -0,0 +1,41 @@ +#pragma once +// Step 626: AST mutation preview model + +#include "DiffUtils.h" + +#include +#include + +struct AgentMutationPreview { + std::string previewId; + std::string mutationJson; + std::string beforeCode; + std::string afterCode; + LineDiff diff; + bool hasChanges = false; +}; + +class AgentMutationPreviewBuilder { +public: + static AgentMutationPreview build(const std::string& previewId, + const std::string& mutationJson, + const std::string& beforeCode, + const std::string& afterCode) { + AgentMutationPreview out; + out.previewId = previewId; + out.mutationJson = mutationJson; + out.beforeCode = beforeCode; + out.afterCode = afterCode; + out.diff = buildLineDiff(beforeCode, afterCode); + out.hasChanges = !out.diff.beforeLines.empty() || !out.diff.afterLines.empty(); + return out; + } + + static std::vector beforeLines(const AgentMutationPreview& preview) { + return splitLines(preview.beforeCode); + } + + static std::vector afterLines(const AgentMutationPreview& preview) { + return splitLines(preview.afterCode); + } +}; diff --git a/editor/src/panels/AgentChatPanel.h b/editor/src/panels/AgentChatPanel.h index 9ba1eb9..17e2991 100644 --- a/editor/src/panels/AgentChatPanel.h +++ b/editor/src/panels/AgentChatPanel.h @@ -47,6 +47,33 @@ static void renderAgentChatPanel(EditorState& state) { } } + if (!chat.mutationPreviews.empty()) { + ImGui::Separator(); + ImGui::TextUnformatted("Mutation Previews"); + for (std::size_t i = 0; i < chat.mutationPreviews.size(); ++i) { + const auto& preview = chat.mutationPreviews[i]; + std::string header = preview.previewId + (preview.hasChanges ? " (changes)" : " (no changes)"); + if (ImGui::CollapsingHeader((header + "##preview_" + std::to_string(i)).c_str())) { + ImGui::TextUnformatted("Mutation JSON"); + ImGui::BeginChild(("##preview_json_" + std::to_string(i)).c_str(), ImVec2(-1, 60), true); + ImGui::TextUnformatted(preview.mutationJson.c_str()); + ImGui::EndChild(); + + ImGui::Columns(2, ("##preview_cols_" + std::to_string(i)).c_str(), true); + ImGui::TextUnformatted("Before"); + ImGui::BeginChild(("##preview_before_" + std::to_string(i)).c_str(), ImVec2(0, 100), true); + ImGui::TextUnformatted(preview.beforeCode.c_str()); + ImGui::EndChild(); + ImGui::NextColumn(); + ImGui::TextUnformatted("After"); + ImGui::BeginChild(("##preview_after_" + std::to_string(i)).c_str(), ImVec2(0, 100), true); + ImGui::TextUnformatted(preview.afterCode.c_str()); + ImGui::EndChild(); + ImGui::Columns(1); + } + } + } + ImGuiInputTextFlags flags = ImGuiInputTextFlags_AllowTabInput; InputTextMultilineStr("##AgentChatInput", &chat.draftInput, ImVec2(-90, 80), flags); ImGui::SameLine(); diff --git a/editor/tests/step626_test.cpp b/editor/tests/step626_test.cpp new file mode 100644 index 0000000..11aead0 --- /dev/null +++ b/editor/tests/step626_test.cpp @@ -0,0 +1,135 @@ +// Step 626: AST mutation preview (12 tests) + +#include "AgentChatPanelModel.h" +#include "AgentMutationPreview.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 {} + +static std::string beforeCode() { + return "def add(a, b):\n return a + b\n"; +} + +static std::string afterCode() { + return "def add_numbers(a, b):\n return a + b\n"; +} + +void test_build_sets_preview_id() { + TEST(build_sets_preview_id); + auto preview = AgentMutationPreviewBuilder::build("p1", "{}", beforeCode(), afterCode()); + CHECK(preview.previewId == "p1", "previewId mismatch"); + PASS(); +} + +void test_build_carries_mutation_json() { + TEST(build_carries_mutation_json); + auto preview = AgentMutationPreviewBuilder::build("p1", "{\"type\":\"setProperty\"}", beforeCode(), afterCode()); + CHECK(preview.mutationJson.find("setProperty") != std::string::npos, "mutation json mismatch"); + PASS(); +} + +void test_build_sets_before_and_after_code() { + TEST(build_sets_before_and_after_code); + auto preview = AgentMutationPreviewBuilder::build("p1", "{}", beforeCode(), afterCode()); + CHECK(preview.beforeCode == beforeCode(), "before code mismatch"); + CHECK(preview.afterCode == afterCode(), "after code mismatch"); + PASS(); +} + +void test_build_detects_changed_lines() { + TEST(build_detects_changed_lines); + auto preview = AgentMutationPreviewBuilder::build("p1", "{}", beforeCode(), afterCode()); + CHECK(!preview.diff.beforeLines.empty(), "before diff should not be empty"); + CHECK(!preview.diff.afterLines.empty(), "after diff should not be empty"); + PASS(); +} + +void test_build_marks_has_changes_true_when_diff_exists() { + TEST(build_marks_has_changes_true_when_diff_exists); + auto preview = AgentMutationPreviewBuilder::build("p1", "{}", beforeCode(), afterCode()); + CHECK(preview.hasChanges, "hasChanges should be true"); + PASS(); +} + +void test_build_marks_has_changes_false_when_equal() { + TEST(build_marks_has_changes_false_when_equal); + auto preview = AgentMutationPreviewBuilder::build("p1", "{}", beforeCode(), beforeCode()); + CHECK(!preview.hasChanges, "hasChanges should be false"); + PASS(); +} + +void test_before_lines_split_preserves_count() { + TEST(before_lines_split_preserves_count); + auto preview = AgentMutationPreviewBuilder::build("p1", "{}", beforeCode(), afterCode()); + auto lines = AgentMutationPreviewBuilder::beforeLines(preview); + CHECK(lines.size() >= 2, "expected at least 2 lines"); + PASS(); +} + +void test_after_lines_split_preserves_count() { + TEST(after_lines_split_preserves_count); + auto preview = AgentMutationPreviewBuilder::build("p1", "{}", beforeCode(), afterCode()); + auto lines = AgentMutationPreviewBuilder::afterLines(preview); + CHECK(lines.size() >= 2, "expected at least 2 lines"); + PASS(); +} + +void test_chat_add_mutation_preview_appends_record() { + TEST(chat_add_mutation_preview_appends_record); + AgentChatState state; + AgentChatPanelModel::addMutationPreview(&state, "preview-1", "{\"type\":\"setProperty\"}", + beforeCode(), afterCode(), "11:10"); + CHECK(state.mutationPreviews.size() == 1, "preview count mismatch"); + CHECK(state.mutationPreviews[0].previewId == "preview-1", "preview id mismatch"); + PASS(); +} + +void test_chat_add_mutation_preview_appends_message() { + TEST(chat_add_mutation_preview_appends_message); + AgentChatState state; + AgentChatPanelModel::addMutationPreview(&state, "preview-1", "{}", beforeCode(), afterCode(), "11:11"); + CHECK(state.messages.size() == 1, "message count mismatch"); + CHECK(state.messages[0].content.find("[PREVIEW] preview-1") != std::string::npos, "preview message missing"); + PASS(); +} + +void test_chat_preview_message_marks_change_state() { + TEST(chat_preview_message_marks_change_state); + AgentChatState state; + AgentChatPanelModel::addMutationPreview(&state, "preview-1", "{}", beforeCode(), afterCode(), "11:12"); + CHECK(state.messages[0].content.find("(changes)") != std::string::npos, "expected changes marker"); + PASS(); +} + +void test_chat_preview_message_marks_no_change_state() { + TEST(chat_preview_message_marks_no_change_state); + AgentChatState state; + AgentChatPanelModel::addMutationPreview(&state, "preview-2", "{}", beforeCode(), beforeCode(), "11:13"); + CHECK(state.messages[0].content.find("(no changes)") != std::string::npos, "expected no-changes marker"); + PASS(); +} + +int main() { + std::cout << "Step 626: AST mutation preview\n"; + + test_build_sets_preview_id(); // 1 + test_build_carries_mutation_json(); // 2 + test_build_sets_before_and_after_code(); // 3 + test_build_detects_changed_lines(); // 4 + test_build_marks_has_changes_true_when_diff_exists(); // 5 + test_build_marks_has_changes_false_when_equal(); // 6 + test_before_lines_split_preserves_count(); // 7 + test_after_lines_split_preserves_count(); // 8 + test_chat_add_mutation_preview_appends_record(); // 9 + test_chat_add_mutation_preview_appends_message(); // 10 + test_chat_preview_message_marks_change_state(); // 11 + test_chat_preview_message_marks_no_change_state(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index cfedb81..3554e0c 100644 --- a/progress.md +++ b/progress.md @@ -12493,3 +12493,37 @@ JSON payload capture. - `editor/src/panels/AgentChatPanel.h` (`58` <= `600`) - `editor/tests/step625_test.cpp` within test-file size guidance (`155` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 626: AST mutation preview +**Status:** PASS (12/12 tests) + +Adds mutation preview records with before/after code snapshots and line-level +diff metadata so pending agent edits can be inspected before application. + +**Files added:** +- `editor/src/AgentMutationPreview.h` - preview model: + - before/after code capture + - line-diff generation via `DiffUtils` + - change-detection flagging and split-line helpers +- `editor/tests/step626_test.cpp` - 12 tests covering: + - preview creation and payload retention + - diff/change detection behavior + - chat integration for preview record + message insertion + +**Files modified:** +- `editor/src/AgentChatPanelModel.h` - added mutation preview storage + append helper +- `editor/src/panels/AgentChatPanel.h` - added collapsible side-by-side preview rendering +- `editor/CMakeLists.txt` - `step626_test` target + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step626_test step625_test` - PASS +- `./editor/build-native/step626_test` - PASS (12/12) +- `./editor/build-native/step625_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/AgentMutationPreview.h` (`41` <= `600`) +- `editor/src/AgentChatPanelModel.h` (`109` <= `600`) +- `editor/src/panels/AgentChatPanel.h` (`85` <= `600`) +- `editor/tests/step626_test.cpp` within test-file size guidance (`135` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`