Add step 629 chat context auto-injection
This commit is contained in:
@@ -4540,4 +4540,13 @@ target_link_libraries(step628_test PRIVATE
|
|||||||
tree_sitter_javascript tree_sitter_typescript
|
tree_sitter_javascript tree_sitter_typescript
|
||||||
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
tree_sitter_java tree_sitter_rust tree_sitter_go)
|
||||||
|
|
||||||
|
add_executable(step629_test tests/step629_test.cpp)
|
||||||
|
target_include_directories(step629_test PRIVATE src)
|
||||||
|
target_link_libraries(step629_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)
|
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||||
|
|||||||
54
editor/src/AgentChatContextInjector.h
Normal file
54
editor/src/AgentChatContextInjector.h
Normal file
@@ -0,0 +1,54 @@
|
|||||||
|
#pragma once
|
||||||
|
// Step 629: Project context auto-injection
|
||||||
|
|
||||||
|
#include "AgentChatPanelModel.h"
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
struct AgentChatRuntimeSnapshot {
|
||||||
|
std::string activeFile = "(no-active-buffer)";
|
||||||
|
std::string language = "python";
|
||||||
|
int astNodeCount = 0;
|
||||||
|
int openAnnotationCount = 0;
|
||||||
|
std::string workspacePath = ".";
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AgentChatContextBlock {
|
||||||
|
std::string activeFile;
|
||||||
|
std::string language;
|
||||||
|
int astNodeCount = 0;
|
||||||
|
int openAnnotationCount = 0;
|
||||||
|
std::string workspacePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
class AgentChatContextInjector {
|
||||||
|
public:
|
||||||
|
static AgentChatContextBlock fromSnapshot(const AgentChatRuntimeSnapshot& snapshot) {
|
||||||
|
AgentChatContextBlock block;
|
||||||
|
block.activeFile = snapshot.activeFile;
|
||||||
|
block.language = snapshot.language;
|
||||||
|
block.workspacePath = snapshot.workspacePath;
|
||||||
|
block.astNodeCount = snapshot.astNodeCount;
|
||||||
|
block.openAnnotationCount = snapshot.openAnnotationCount;
|
||||||
|
return block;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::string toPromptText(const AgentChatContextBlock& block) {
|
||||||
|
return "Context:\n"
|
||||||
|
"- activeFile: " + block.activeFile + "\n"
|
||||||
|
"- language: " + block.language + "\n"
|
||||||
|
"- astNodeCount: " + std::to_string(block.astNodeCount) + "\n"
|
||||||
|
"- openAnnotationCount: " + std::to_string(block.openAnnotationCount) + "\n"
|
||||||
|
"- workspacePath: " + block.workspacePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void inject(AgentChatState* chat,
|
||||||
|
const AgentChatRuntimeSnapshot& snapshot,
|
||||||
|
const std::string& timestamp) {
|
||||||
|
if (!chat) return;
|
||||||
|
AgentChatContextBlock block = fromSnapshot(snapshot);
|
||||||
|
chat->systemContext = toPromptText(block);
|
||||||
|
chat->messages.push_back({AgentChatRole::Assistant, chat->systemContext, timestamp});
|
||||||
|
chat->autoScroll = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -26,6 +26,7 @@ struct AgentChatState {
|
|||||||
std::vector<AgentToolCallView> toolCalls;
|
std::vector<AgentToolCallView> toolCalls;
|
||||||
std::vector<AgentMutationPreview> mutationPreviews;
|
std::vector<AgentMutationPreview> mutationPreviews;
|
||||||
std::vector<MutationApprovalRecord> mutationApprovals;
|
std::vector<MutationApprovalRecord> mutationApprovals;
|
||||||
|
std::string systemContext;
|
||||||
std::string draftInput;
|
std::string draftInput;
|
||||||
bool autoScroll = true;
|
bool autoScroll = true;
|
||||||
bool open = false;
|
bool open = false;
|
||||||
|
|||||||
@@ -1,9 +1,17 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "../AgentChatPanelModel.h"
|
#include "../AgentChatPanelModel.h"
|
||||||
|
#include "../AgentChatContextInjector.h"
|
||||||
#include "../EditorState.h"
|
#include "../EditorState.h"
|
||||||
#include "../EditorUtils.h"
|
#include "../EditorUtils.h"
|
||||||
|
|
||||||
|
static int countChatContextNodes(const ASTNode* node) {
|
||||||
|
if (!node) return 0;
|
||||||
|
int total = 1;
|
||||||
|
for (const auto* child : node->allChildren()) total += countChatContextNodes(child);
|
||||||
|
return total;
|
||||||
|
}
|
||||||
|
|
||||||
static void renderAgentChatPanel(EditorState& state) {
|
static void renderAgentChatPanel(EditorState& state) {
|
||||||
if (!state.ui.showAgentChatPanel) return;
|
if (!state.ui.showAgentChatPanel) return;
|
||||||
if (!ImGui::Begin("Agent Chat", &state.ui.showAgentChatPanel)) {
|
if (!ImGui::Begin("Agent Chat", &state.ui.showAgentChatPanel)) {
|
||||||
@@ -12,6 +20,18 @@ static void renderAgentChatPanel(EditorState& state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
AgentChatState& chat = state.agent.chat;
|
AgentChatState& chat = state.agent.chat;
|
||||||
|
if (chat.systemContext.empty()) {
|
||||||
|
AgentChatRuntimeSnapshot snapshot;
|
||||||
|
if (state.activeBuffer) {
|
||||||
|
snapshot.activeFile = state.activeBuffer->path;
|
||||||
|
snapshot.language = state.activeBuffer->language;
|
||||||
|
}
|
||||||
|
snapshot.workspacePath = state.workspaceRoot.empty() ? "." : state.workspaceRoot;
|
||||||
|
Module* ast = state.activeAST();
|
||||||
|
snapshot.astNodeCount = countChatContextNodes(ast);
|
||||||
|
snapshot.openAnnotationCount = countAnnotationNodes(ast);
|
||||||
|
AgentChatContextInjector::inject(&chat, snapshot, "context");
|
||||||
|
}
|
||||||
ImGui::BeginChild("##AgentChatHistory", ImVec2(0, -120), true);
|
ImGui::BeginChild("##AgentChatHistory", ImVec2(0, -120), true);
|
||||||
for (const auto& msg : chat.messages) {
|
for (const auto& msg : chat.messages) {
|
||||||
ImGui::Text("[%s] %s", msg.timestamp.c_str(), AgentChatPanelModel::roleLabel(msg.role));
|
ImGui::Text("[%s] %s", msg.timestamp.c_str(), AgentChatPanelModel::roleLabel(msg.role));
|
||||||
|
|||||||
146
editor/tests/step629_test.cpp
Normal file
146
editor/tests/step629_test.cpp
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
// Step 629: Project context auto-injection (12 tests)
|
||||||
|
|
||||||
|
#include "AgentChatContextInjector.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 {}
|
||||||
|
|
||||||
|
void test_from_editor_without_active_buffer_uses_defaults() {
|
||||||
|
TEST(from_editor_without_active_buffer_uses_defaults);
|
||||||
|
AgentChatRuntimeSnapshot snapshot;
|
||||||
|
snapshot.workspacePath = "/tmp/ws";
|
||||||
|
auto block = AgentChatContextInjector::fromSnapshot(snapshot);
|
||||||
|
CHECK(block.activeFile == "(no-active-buffer)", "active file mismatch");
|
||||||
|
CHECK(block.language == "python", "language mismatch");
|
||||||
|
CHECK(block.workspacePath == "/tmp/ws", "workspace mismatch");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_from_editor_uses_active_buffer_path_and_language() {
|
||||||
|
TEST(from_editor_uses_active_buffer_path_and_language);
|
||||||
|
AgentChatRuntimeSnapshot snapshot;
|
||||||
|
snapshot.activeFile = "src/main.cpp";
|
||||||
|
snapshot.language = "cpp";
|
||||||
|
auto block = AgentChatContextInjector::fromSnapshot(snapshot);
|
||||||
|
CHECK(block.activeFile == "src/main.cpp", "active file mismatch");
|
||||||
|
CHECK(block.language == "cpp", "language mismatch");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_from_editor_uses_dot_workspace_when_empty() {
|
||||||
|
TEST(from_editor_uses_dot_workspace_when_empty);
|
||||||
|
AgentChatRuntimeSnapshot snapshot;
|
||||||
|
auto block = AgentChatContextInjector::fromSnapshot(snapshot);
|
||||||
|
CHECK(block.workspacePath == ".", "workspace fallback mismatch");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_from_editor_counts_zero_without_ast() {
|
||||||
|
TEST(from_editor_counts_zero_without_ast);
|
||||||
|
AgentChatRuntimeSnapshot snapshot;
|
||||||
|
auto block = AgentChatContextInjector::fromSnapshot(snapshot);
|
||||||
|
CHECK(block.astNodeCount == 0, "ast count should be zero");
|
||||||
|
CHECK(block.openAnnotationCount == 0, "annotation count should be zero");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_prompt_text_contains_active_file_line() {
|
||||||
|
TEST(prompt_text_contains_active_file_line);
|
||||||
|
AgentChatContextBlock block;
|
||||||
|
block.activeFile = "file.cpp";
|
||||||
|
block.language = "cpp";
|
||||||
|
block.workspacePath = "/tmp/ws";
|
||||||
|
std::string text = AgentChatContextInjector::toPromptText(block);
|
||||||
|
CHECK(text.find("activeFile: file.cpp") != std::string::npos, "activeFile line missing");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_prompt_text_contains_language_line() {
|
||||||
|
TEST(prompt_text_contains_language_line);
|
||||||
|
AgentChatContextBlock block;
|
||||||
|
block.language = "rust";
|
||||||
|
std::string text = AgentChatContextInjector::toPromptText(block);
|
||||||
|
CHECK(text.find("language: rust") != std::string::npos, "language line missing");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_prompt_text_contains_ast_count_line() {
|
||||||
|
TEST(prompt_text_contains_ast_count_line);
|
||||||
|
AgentChatContextBlock block;
|
||||||
|
block.astNodeCount = 17;
|
||||||
|
std::string text = AgentChatContextInjector::toPromptText(block);
|
||||||
|
CHECK(text.find("astNodeCount: 17") != std::string::npos, "ast count line missing");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_prompt_text_contains_annotation_count_line() {
|
||||||
|
TEST(prompt_text_contains_annotation_count_line);
|
||||||
|
AgentChatContextBlock block;
|
||||||
|
block.openAnnotationCount = 3;
|
||||||
|
std::string text = AgentChatContextInjector::toPromptText(block);
|
||||||
|
CHECK(text.find("openAnnotationCount: 3") != std::string::npos, "annotation count line missing");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_prompt_text_contains_workspace_line() {
|
||||||
|
TEST(prompt_text_contains_workspace_line);
|
||||||
|
AgentChatContextBlock block;
|
||||||
|
block.workspacePath = "/home/user/proj";
|
||||||
|
std::string text = AgentChatContextInjector::toPromptText(block);
|
||||||
|
CHECK(text.find("workspacePath: /home/user/proj") != std::string::npos, "workspace line missing");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_inject_sets_system_context() {
|
||||||
|
TEST(inject_sets_system_context);
|
||||||
|
AgentChatRuntimeSnapshot snapshot;
|
||||||
|
AgentChatState chat;
|
||||||
|
AgentChatContextInjector::inject(&chat, snapshot, "12:30");
|
||||||
|
CHECK(!chat.systemContext.empty(), "systemContext should be set");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_inject_appends_assistant_message() {
|
||||||
|
TEST(inject_appends_assistant_message);
|
||||||
|
AgentChatRuntimeSnapshot snapshot;
|
||||||
|
AgentChatState chat;
|
||||||
|
AgentChatContextInjector::inject(&chat, snapshot, "12:31");
|
||||||
|
CHECK(chat.messages.size() == 1, "one message expected");
|
||||||
|
CHECK(chat.messages[0].role == AgentChatRole::Assistant, "role should be assistant");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
void test_inject_enables_auto_scroll() {
|
||||||
|
TEST(inject_enables_auto_scroll);
|
||||||
|
AgentChatRuntimeSnapshot snapshot;
|
||||||
|
AgentChatState chat;
|
||||||
|
chat.autoScroll = false;
|
||||||
|
AgentChatContextInjector::inject(&chat, snapshot, "12:32");
|
||||||
|
CHECK(chat.autoScroll, "autoScroll should be enabled");
|
||||||
|
PASS();
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
std::cout << "Step 629: Project context auto-injection\n";
|
||||||
|
|
||||||
|
test_from_editor_without_active_buffer_uses_defaults(); // 1
|
||||||
|
test_from_editor_uses_active_buffer_path_and_language();// 2
|
||||||
|
test_from_editor_uses_dot_workspace_when_empty(); // 3
|
||||||
|
test_from_editor_counts_zero_without_ast(); // 4
|
||||||
|
test_prompt_text_contains_active_file_line(); // 5
|
||||||
|
test_prompt_text_contains_language_line(); // 6
|
||||||
|
test_prompt_text_contains_ast_count_line(); // 7
|
||||||
|
test_prompt_text_contains_annotation_count_line(); // 8
|
||||||
|
test_prompt_text_contains_workspace_line(); // 9
|
||||||
|
test_inject_sets_system_context(); // 10
|
||||||
|
test_inject_appends_assistant_message(); // 11
|
||||||
|
test_inject_enables_auto_scroll(); // 12
|
||||||
|
|
||||||
|
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
||||||
|
return failed == 0 ? 0 : 1;
|
||||||
|
}
|
||||||
34
progress.md
34
progress.md
@@ -12595,3 +12595,37 @@ accept decision, and final code state update.
|
|||||||
- `editor/src/Phase37aIntegration.h` (`51` <= `600`)
|
- `editor/src/Phase37aIntegration.h` (`51` <= `600`)
|
||||||
- `editor/tests/step628_test.cpp` within test-file size guidance (`93` lines)
|
- `editor/tests/step628_test.cpp` within test-file size guidance (`93` lines)
|
||||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||||
|
|
||||||
|
### Step 629: Project context auto-injection
|
||||||
|
**Status:** PASS (12/12 tests)
|
||||||
|
|
||||||
|
Adds automatic chat-context injection at chat start with:
|
||||||
|
active file, language, AST node count, open annotation count, and workspace path.
|
||||||
|
|
||||||
|
**Files added:**
|
||||||
|
- `editor/src/AgentChatContextInjector.h` - context block model + prompt injection:
|
||||||
|
- runtime snapshot-to-context conversion
|
||||||
|
- prompt-text composition for system context
|
||||||
|
- chat-state context injection helper
|
||||||
|
- `editor/tests/step629_test.cpp` - 12 tests covering:
|
||||||
|
- snapshot/context mapping defaults and field propagation
|
||||||
|
- prompt text composition content
|
||||||
|
- injection behavior for system context + assistant message + auto-scroll
|
||||||
|
|
||||||
|
**Files modified:**
|
||||||
|
- `editor/src/AgentChatPanelModel.h` - added `systemContext` storage field
|
||||||
|
- `editor/src/panels/AgentChatPanel.h` - auto-injects context snapshot on chat start
|
||||||
|
- `editor/CMakeLists.txt` - `step629_test` target
|
||||||
|
|
||||||
|
**Verification run:**
|
||||||
|
- `cmake -S editor -B editor/build-native` - PASS
|
||||||
|
- `cmake --build editor/build-native --target step629_test step628_test` - PASS
|
||||||
|
- `./editor/build-native/step629_test` - PASS (12/12)
|
||||||
|
- `./editor/build-native/step628_test` - PASS (8/8) regression coverage
|
||||||
|
|
||||||
|
**Architecture gate check:**
|
||||||
|
- `editor/src/AgentChatContextInjector.h` (`54` <= `600`)
|
||||||
|
- `editor/src/AgentChatPanelModel.h` (`112` <= `600`)
|
||||||
|
- `editor/src/panels/AgentChatPanel.h` (`126` <= `600`)
|
||||||
|
- `editor/tests/step629_test.cpp` within test-file size guidance (`146` lines)
|
||||||
|
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
||||||
|
|||||||
Reference in New Issue
Block a user