Step 1991: Wire AST sidecar lifecycle hooks - openFile (DispatchPart2.h): auto-calls loadSidecarAST() after parse, returns annotationsRestored + staleAnnotations - saveBuffer (DispatchPart3.h): auto-calls saveSidecarAST() + saveSemannoSidecar() after write, returns annotationsSaved + sidecarPath + semannoPath Step 1992: Fix 9 schema stubs in Sprint 142/143/144 tool registrations - All 9 text/AST sync and merge tools now have proper inputSchema with required id and documented optional parameters (language, text, mode) Step 1993: Fix ingest_legacy_to_ir → generate_cpp_from_ir chain - ingest_legacy_to_ir now emits ir field (SemanticCoreIR format) alongside existing graph/api_intent/assumption/readiness fields - RecoveryNode → IRNode mapping: intent→kind, confidence in metadata - Neighbor lists become inferred edges; moduleId = "legacy:" + slug - generate_cpp_from_ir now accepts result["ir"] directly 15/15 tests passing across all three steps. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
147 lines
5.2 KiB
C++
147 lines
5.2 KiB
C++
// Step 1991: AST sidecar lifecycle hooks
|
|
// Verifies that saveBuffer auto-persists annotated AST sidecars, and that
|
|
// openFile auto-restores those annotations into the newly-parsed live AST.
|
|
|
|
#include "HeadlessEditorState.h"
|
|
#include "MCPServer.h"
|
|
#include "SidecarPersistence.h"
|
|
|
|
#include <cassert>
|
|
#include <filesystem>
|
|
#include <fstream>
|
|
#include <iostream>
|
|
#include <string>
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
static json rpc(HeadlessEditorState& state, const std::string& method,
|
|
json params = json::object()) {
|
|
json req = {{"jsonrpc", "2.0"}, {"id", 1},
|
|
{"method", method}, {"params", std::move(params)}};
|
|
return state.processAgentRequest(req, "test-session");
|
|
}
|
|
|
|
static std::string makeTempWorkspace(const std::string& name) {
|
|
fs::path tmp = fs::temp_directory_path() / name;
|
|
fs::create_directories(tmp / "src");
|
|
std::ofstream f(tmp / "src" / "Foo.h");
|
|
f << "namespace foo {\nstruct Foo {\n int x = 0;\n};\n}\n";
|
|
return tmp.string();
|
|
}
|
|
|
|
int main() {
|
|
int passed = 0;
|
|
|
|
// Test 1: saveBuffer creates .ast.json sidecar
|
|
{
|
|
std::string ws = makeTempWorkspace("step1991_t1");
|
|
std::string filePath = ws + "/src/Foo.h";
|
|
|
|
HeadlessEditorState state;
|
|
state.workspaceRoot = ws;
|
|
state.defaultLanguage = "cpp";
|
|
|
|
rpc(state, "openFile", {{"path", filePath}, {"language", "cpp"}});
|
|
auto saveResp = rpc(state, "saveBuffer", {{"path", filePath}});
|
|
|
|
std::string sc = sidecarPath(ws, filePath);
|
|
assert(fs::exists(sc));
|
|
assert(saveResp["result"].contains("annotationsSaved"));
|
|
assert(saveResp["result"].contains("sidecarPath"));
|
|
std::cout << "Test 1 PASSED: saveBuffer creates .ast.json sidecar\n";
|
|
++passed;
|
|
}
|
|
|
|
// Test 2: saveBuffer response includes semannoPath
|
|
{
|
|
std::string ws = makeTempWorkspace("step1991_t2");
|
|
std::string filePath = ws + "/src/Foo.h";
|
|
|
|
HeadlessEditorState state;
|
|
state.workspaceRoot = ws;
|
|
state.defaultLanguage = "cpp";
|
|
|
|
rpc(state, "openFile", {{"path", filePath}, {"language", "cpp"}});
|
|
auto saveResp = rpc(state, "saveBuffer", {{"path", filePath}});
|
|
|
|
assert(saveResp["result"].contains("semannoPath"));
|
|
assert(saveResp["result"]["success"] == true);
|
|
std::cout << "Test 2 PASSED: saveBuffer response includes semannoPath\n";
|
|
++passed;
|
|
}
|
|
|
|
// Test 3: openFile after save returns annotationsRestored in response
|
|
{
|
|
std::string ws = makeTempWorkspace("step1991_t3");
|
|
std::string filePath = ws + "/src/Foo.h";
|
|
|
|
HeadlessEditorState state;
|
|
state.workspaceRoot = ws;
|
|
state.defaultLanguage = "cpp";
|
|
|
|
rpc(state, "openFile", {{"path", filePath}, {"language", "cpp"}});
|
|
rpc(state, "saveBuffer", {{"path", filePath}});
|
|
rpc(state, "closeFile", {{"path", filePath}});
|
|
|
|
auto reopenResp = rpc(state, "openFile",
|
|
{{"path", filePath}, {"language", "cpp"}});
|
|
assert(!reopenResp.contains("error"));
|
|
assert(reopenResp["result"].contains("annotationsRestored"));
|
|
assert(reopenResp["result"].contains("staleAnnotations"));
|
|
std::cout << "Test 3 PASSED: openFile response includes annotationsRestored\n";
|
|
++passed;
|
|
}
|
|
|
|
// Test 4: stale annotations (file content replaced) are skipped without error
|
|
{
|
|
std::string ws = makeTempWorkspace("step1991_t4");
|
|
std::string altPath = ws + "/src/Alt.h";
|
|
{
|
|
std::ofstream f(altPath);
|
|
f << "namespace alt {\nstruct Alt {\n bool flag = false;\n};\n}\n";
|
|
}
|
|
|
|
HeadlessEditorState state;
|
|
state.workspaceRoot = ws;
|
|
state.defaultLanguage = "cpp";
|
|
|
|
rpc(state, "openFile", {{"path", altPath}});
|
|
rpc(state, "saveBuffer", {{"path", altPath}});
|
|
rpc(state, "closeFile", {{"path", altPath}});
|
|
|
|
// Replace file content — existing sidecar nodes become stale
|
|
{ std::ofstream f(altPath); f << "// empty\n"; }
|
|
|
|
auto reopenResp = rpc(state, "openFile", {{"path", altPath}});
|
|
assert(!reopenResp.contains("error"));
|
|
assert(reopenResp["result"].contains("annotationsRestored"));
|
|
std::cout << "Test 4 PASSED: stale annotations skipped without error\n";
|
|
++passed;
|
|
}
|
|
|
|
// Test 5: openFile with no sidecar returns annotationsRestored=0 silently
|
|
{
|
|
std::string ws = makeTempWorkspace("step1991_t5");
|
|
std::string freshPath = ws + "/src/Fresh.h";
|
|
{ std::ofstream f(freshPath); f << "struct Fresh { int v = 0; };\n"; }
|
|
|
|
// Ensure no sidecar exists
|
|
std::string sc = sidecarPath(ws, freshPath);
|
|
if (fs::exists(sc)) fs::remove(sc);
|
|
|
|
HeadlessEditorState state;
|
|
state.workspaceRoot = ws;
|
|
state.defaultLanguage = "cpp";
|
|
|
|
auto openResp = rpc(state, "openFile", {{"path", freshPath}});
|
|
assert(!openResp.contains("error"));
|
|
assert(openResp["result"].contains("annotationsRestored"));
|
|
assert(openResp["result"]["annotationsRestored"] == 0);
|
|
std::cout << "Test 5 PASSED: openFile with no sidecar returns annotationsRestored=0\n";
|
|
++passed;
|
|
}
|
|
|
|
std::cout << "\n" << passed << "/5 passed\n";
|
|
return passed == 5 ? 0 : 1;
|
|
}
|