Add step 617 initialize instructions field
This commit is contained in:
@@ -4432,4 +4432,13 @@ target_link_libraries(step616_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(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)
|
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)
|
||||||
|
|||||||
@@ -15,6 +15,9 @@
|
|||||||
#include <functional>
|
#include <functional>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <cctype>
|
#include <cctype>
|
||||||
|
#include <fstream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <cstdlib>
|
||||||
#include <nlohmann/json.hpp>
|
#include <nlohmann/json.hpp>
|
||||||
|
|
||||||
#include "MarkdownSpecParser.h"
|
#include "MarkdownSpecParser.h"
|
||||||
@@ -145,7 +148,8 @@ private:
|
|||||||
{"serverInfo", {
|
{"serverInfo", {
|
||||||
{"name", "whetstone-mcp"},
|
{"name", "whetstone-mcp"},
|
||||||
{"version", "0.1.0"}
|
{"version", "0.1.0"}
|
||||||
}}
|
}},
|
||||||
|
{"instructions", loadInitializeInstructions()}
|
||||||
};
|
};
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
@@ -342,6 +346,33 @@ private:
|
|||||||
return "";
|
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<std::string> 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) {
|
static bool isIgnoredOnboardPath(const std::string& path) {
|
||||||
std::string lower = toLowerCopy(path);
|
std::string lower = toLowerCopy(path);
|
||||||
auto hasSegment = [&](const std::string& segment) {
|
auto hasSegment = [&](const std::string& segment) {
|
||||||
|
|||||||
165
editor/tests/step617_test.cpp
Normal file
165
editor/tests/step617_test.cpp
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
// Step 617: MCP initialize instructions field (12 tests)
|
||||||
|
|
||||||
|
#include "MCPServer.h"
|
||||||
|
|
||||||
|
#include <fstream>
|
||||||
|
#include <iostream>
|
||||||
|
#include <sstream>
|
||||||
|
#include <string>
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
29
progress.md
29
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/src/MCPServer.h` within header-size limit (`520` <= `600`)
|
||||||
- `editor/tests/step616_test.cpp` within test-file size guidance (`237` lines)
|
- `editor/tests/step616_test.cpp` within test-file size guidance (`237` lines)
|
||||||
- Header-only architecture and naming conventions remain aligned with `ARCHITECTURE.md`
|
- 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`
|
||||||
|
|||||||
Reference in New Issue
Block a user