Add step 626 AST mutation preview model
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Step 624: Agent Chat Panel model
|
||||
|
||||
#include "AgentToolCallVisualization.h"
|
||||
#include "AgentMutationPreview.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <string>
|
||||
@@ -22,6 +23,7 @@ struct AgentChatMessage {
|
||||
struct AgentChatState {
|
||||
std::vector<AgentChatMessage> messages;
|
||||
std::vector<AgentToolCallView> toolCalls;
|
||||
std::vector<AgentMutationPreview> 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";
|
||||
|
||||
41
editor/src/AgentMutationPreview.h
Normal file
41
editor/src/AgentMutationPreview.h
Normal file
@@ -0,0 +1,41 @@
|
||||
#pragma once
|
||||
// Step 626: AST mutation preview model
|
||||
|
||||
#include "DiffUtils.h"
|
||||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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<std::string> beforeLines(const AgentMutationPreview& preview) {
|
||||
return splitLines(preview.beforeCode);
|
||||
}
|
||||
|
||||
static std::vector<std::string> afterLines(const AgentMutationPreview& preview) {
|
||||
return splitLines(preview.afterCode);
|
||||
}
|
||||
};
|
||||
@@ -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();
|
||||
|
||||
135
editor/tests/step626_test.cpp
Normal file
135
editor/tests/step626_test.cpp
Normal file
@@ -0,0 +1,135 @@
|
||||
// Step 626: AST mutation preview (12 tests)
|
||||
|
||||
#include "AgentChatPanelModel.h"
|
||||
#include "AgentMutationPreview.h"
|
||||
|
||||
#include <iostream>
|
||||
|
||||
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;
|
||||
}
|
||||
34
progress.md
34
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`
|
||||
|
||||
Reference in New Issue
Block a user