From f03e73dbf3ac11e4e2d801c08d0d6964ad76845d Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 17 Feb 2026 20:47:58 -0700 Subject: [PATCH] Add step 617 initialize instructions field --- editor/CMakeLists.txt | 9 ++ editor/src/MCPServer.h | 33 ++++++- editor/tests/step617_test.cpp | 165 ++++++++++++++++++++++++++++++++++ progress.md | 29 ++++++ 4 files changed, 235 insertions(+), 1 deletion(-) create mode 100644 editor/tests/step617_test.cpp diff --git a/editor/CMakeLists.txt b/editor/CMakeLists.txt index a771ac9..a7a13f2 100644 --- a/editor/CMakeLists.txt +++ b/editor/CMakeLists.txt @@ -4432,4 +4432,13 @@ target_link_libraries(step616_test PRIVATE tree_sitter_javascript tree_sitter_typescript tree_sitter_java tree_sitter_rust tree_sitter_go) +add_executable(step617_test tests/step617_test.cpp) +target_include_directories(step617_test PRIVATE src) +target_link_libraries(step617_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/MCPServer.h b/editor/src/MCPServer.h index 65aa807..a6b5727 100644 --- a/editor/src/MCPServer.h +++ b/editor/src/MCPServer.h @@ -15,6 +15,9 @@ #include #include #include +#include +#include +#include #include #include "MarkdownSpecParser.h" @@ -145,7 +148,8 @@ private: {"serverInfo", { {"name", "whetstone-mcp"}, {"version", "0.1.0"} - }} + }}, + {"instructions", loadInitializeInstructions()} }; return response; } @@ -342,6 +346,33 @@ private: return ""; } + static std::string readFileText(const std::string& path) { + std::ifstream input(path); + if (!input.is_open()) return ""; + std::ostringstream buffer; + buffer << input.rdbuf(); + return buffer.str(); + } + + static std::string loadInitializeInstructions() { + const char* envPath = std::getenv("WHETSTONE_SYSTEM_PROMPT_PATH"); + if (envPath != nullptr) { + std::string envText = readFileText(envPath); + if (!envText.empty()) return envText; + } + + const std::vector candidates = { + "tools/claude/system_prompt.txt", + "../tools/claude/system_prompt.txt", + "../../tools/claude/system_prompt.txt" + }; + for (const auto& path : candidates) { + std::string text = readFileText(path); + if (!text.empty()) return text; + } + return "System prompt unavailable: tools/claude/system_prompt.txt not found."; + } + static bool isIgnoredOnboardPath(const std::string& path) { std::string lower = toLowerCopy(path); auto hasSegment = [&](const std::string& segment) { diff --git a/editor/tests/step617_test.cpp b/editor/tests/step617_test.cpp new file mode 100644 index 0000000..036f70f --- /dev/null +++ b/editor/tests/step617_test.cpp @@ -0,0 +1,165 @@ +// Step 617: MCP initialize instructions field (12 tests) + +#include "MCPServer.h" + +#include +#include +#include +#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 json initialize(MCPServer& mcp) { + json req = { + {"jsonrpc", "2.0"}, + {"id", 1}, + {"method", "initialize"}, + {"params", {{"protocolVersion", "2024-11-05"}}} + }; + return mcp.handleRequest(req); +} + +static std::string readPrompt() { + std::ifstream in("tools/claude/system_prompt.txt"); + if (!in.is_open()) return ""; + std::ostringstream ss; + ss << in.rdbuf(); + return ss.str(); +} + +void test_initialize_returns_result_object() { + TEST(initialize_returns_result_object); + MCPServer mcp; + json resp = initialize(mcp); + CHECK(resp.contains("result"), "missing result"); + PASS(); +} + +void test_initialize_protocol_version_unchanged() { + TEST(initialize_protocol_version_unchanged); + MCPServer mcp; + json resp = initialize(mcp); + CHECK(resp["result"].value("protocolVersion", "") == "2024-11-05", "wrong protocol version"); + PASS(); +} + +void test_initialize_includes_instructions_field() { + TEST(initialize_includes_instructions_field); + MCPServer mcp; + json resp = initialize(mcp); + CHECK(resp["result"].contains("instructions"), "instructions field missing"); + CHECK(resp["result"]["instructions"].is_string(), "instructions should be string"); + PASS(); +} + +void test_instructions_not_empty() { + TEST(instructions_not_empty); + MCPServer mcp; + json resp = initialize(mcp); + std::string instructions = resp["result"].value("instructions", ""); + CHECK(!instructions.empty(), "instructions should be non-empty"); + PASS(); +} + +void test_instructions_include_available_tools_header() { + TEST(instructions_include_available_tools_header); + MCPServer mcp; + json resp = initialize(mcp); + std::string instructions = resp["result"].value("instructions", ""); + CHECK(instructions.find("Available tools:") != std::string::npos, "missing tools header"); + PASS(); +} + +void test_instructions_include_core_tool_name() { + TEST(instructions_include_core_tool_name); + MCPServer mcp; + json resp = initialize(mcp); + std::string instructions = resp["result"].value("instructions", ""); + CHECK(instructions.find("whetstone_get_ast") != std::string::npos, "missing whetstone_get_ast"); + PASS(); +} + +void test_instructions_include_guidance_line() { + TEST(instructions_include_guidance_line); + MCPServer mcp; + json resp = initialize(mcp); + std::string instructions = resp["result"].value("instructions", ""); + CHECK(instructions.find("You must follow user instructions carefully") != std::string::npos, + "missing guidance line"); + PASS(); +} + +void test_instructions_match_system_prompt_file_when_available() { + TEST(instructions_match_system_prompt_file_when_available); + MCPServer mcp; + json resp = initialize(mcp); + std::string expected = readPrompt(); + std::string instructions = resp["result"].value("instructions", ""); + CHECK(!expected.empty(), "expected prompt file should be readable"); + CHECK(instructions == expected, "initialize instructions should match prompt file"); + PASS(); +} + +void test_server_info_still_present() { + TEST(server_info_still_present); + MCPServer mcp; + json resp = initialize(mcp); + CHECK(resp["result"].contains("serverInfo"), "missing serverInfo"); + CHECK(resp["result"]["serverInfo"].value("name", "") == "whetstone-mcp", "wrong server name"); + PASS(); +} + +void test_tools_list_still_available_after_initialize() { + TEST(tools_list_still_available_after_initialize); + MCPServer mcp; + (void)initialize(mcp); + json req = {{"jsonrpc", "2.0"}, {"id", 2}, {"method", "tools/list"}}; + json resp = mcp.handleRequest(req); + CHECK(resp.contains("result"), "missing result"); + CHECK(resp["result"].contains("tools"), "missing tools"); + CHECK(!resp["result"]["tools"].empty(), "tools should not be empty"); + PASS(); +} + +void test_notifications_initialized_still_no_response() { + TEST(notifications_initialized_still_no_response); + MCPServer mcp; + json req = {{"jsonrpc", "2.0"}, {"method", "notifications/initialized"}}; + json resp = mcp.handleRequest(req); + CHECK(resp.is_null(), "notification should return null response"); + PASS(); +} + +void test_instructions_do_not_use_fallback_message() { + TEST(instructions_do_not_use_fallback_message); + MCPServer mcp; + json resp = initialize(mcp); + std::string instructions = resp["result"].value("instructions", ""); + CHECK(instructions.find("System prompt unavailable") == std::string::npos, + "fallback message should not appear"); + PASS(); +} + +int main() { + std::cout << "Step 617: MCP initialize instructions field\n"; + + test_initialize_returns_result_object(); // 1 + test_initialize_protocol_version_unchanged(); // 2 + test_initialize_includes_instructions_field(); // 3 + test_instructions_not_empty(); // 4 + test_instructions_include_available_tools_header(); // 5 + test_instructions_include_core_tool_name(); // 6 + test_instructions_include_guidance_line(); // 7 + test_instructions_match_system_prompt_file_when_available();// 8 + test_server_info_still_present(); // 9 + test_tools_list_still_available_after_initialize(); // 10 + test_notifications_initialized_still_no_response(); // 11 + test_instructions_do_not_use_fallback_message(); // 12 + + std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n"; + return failed == 0 ? 0 : 1; +} diff --git a/progress.md b/progress.md index 044303f..87f94ab 100644 --- a/progress.md +++ b/progress.md @@ -12157,3 +12157,32 @@ ready-count/escalation summaries, and bound acceptance-check queue JSON. - `editor/src/MCPServer.h` within header-size limit (`520` <= `600`) - `editor/tests/step616_test.cpp` within test-file size guidance (`237` lines) - Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md` + +### Step 617: MCP initialize `instructions` field +**Status:** PASS (12/12 tests) + +Adds an `instructions` string field to MCP `initialize` responses, populated from +`tools/claude/system_prompt.txt` (with fallback path probing and env override). + +**Files modified:** +- `editor/src/MCPServer.h` - initialize payload now includes `instructions`; added + file-loading helpers for prompt text lookup. +- `editor/CMakeLists.txt` - `step617_test` target + +**Files added:** +- `editor/tests/step617_test.cpp` - 12 tests covering: + - initialize response shape and protocol continuity + - instructions field presence and expected content + - parity with `tools/claude/system_prompt.txt` + - compatibility checks for tools list and notifications behavior + +**Verification run:** +- `cmake -S editor -B editor/build-native` - PASS +- `cmake --build editor/build-native --target step617_test step616_test` - PASS +- `./editor/build-native/step617_test` - PASS (12/12) +- `./editor/build-native/step616_test` - PASS (12/12) regression coverage + +**Architecture gate check:** +- `editor/src/MCPServer.h` within header-size limit (`551` <= `600`) +- `editor/tests/step617_test.cpp` within test-file size guidance (`165` lines) +- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`