// Step 255 TDD Test: Symbol-Only Mode for Scope Queries // // Tests lean vs detailed mode for getInScopeSymbols, getCallHierarchy, // and getDependencyGraph. Lean mode returns minimal data; detailed mode // includes full node JSON. #include "HeadlessEditorState.h" #include "CompactAST.h" #include #include static void expect(bool cond, const std::string& name, int& passed, int& failed) { if (cond) { std::cout << "Test " << (passed + failed + 1) << " PASS: " << name << "\n"; ++passed; } else { std::cout << "Test " << (passed + failed + 1) << " FAIL: " << name << "\n"; ++failed; } } static json rpc(HeadlessEditorState& state, const std::string& session, const std::string& method, json params = json::object()) { json request = {{"jsonrpc", "2.0"}, {"id", 1}, {"method", method}, {"params", params}}; return state.processAgentRequest(request, session); } int main() { int passed = 0; int failed = 0; // Build a module with multiple functions calling each other std::string src = "def helper(x):\n" " return x * 2\n\n" "def compute(a, b):\n" " c = helper(a)\n" " d = helper(b)\n" " return c + d\n\n" "def main():\n" " result = compute(1, 2)\n" " return result\n"; // --------------------------------------------------------------- // Test 1: getInScopeSymbols default (lean) mode // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); // Find a nodeId inside compute function std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getInScopeSymbols", {{"nodeId", computeId}}); bool hasSymbols = resp.contains("result") && resp["result"].contains("symbols"); std::string mode = resp["result"].value("mode", ""); bool noNodes = true; if (hasSymbols) { for (const auto& s : resp["result"]["symbols"]) { if (s.contains("node")) noNodes = false; } } expect(hasSymbols && mode == "symbols" && noNodes, "getInScopeSymbols default mode is 'symbols' (no node data)", passed, failed); } // --------------------------------------------------------------- // Test 2: getInScopeSymbols detailed mode includes node JSON // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getInScopeSymbols", {{"nodeId", computeId}, {"detailed", true}}); std::string mode = resp["result"].value("mode", ""); bool hasNodeData = false; if (resp["result"].contains("symbols")) { for (const auto& s : resp["result"]["symbols"]) { if (s.contains("node")) hasNodeData = true; } } expect(mode == "detailed" && hasNodeData, "getInScopeSymbols detailed mode includes node JSON", passed, failed); } // --------------------------------------------------------------- // Test 3: Lean scope query has count field // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getInScopeSymbols", {{"nodeId", computeId}}); int count = resp["result"].value("count", -1); expect(count >= 0, "Lean scope query includes count=" + std::to_string(count), passed, failed); } // --------------------------------------------------------------- // Test 4: Lean scope is smaller than detailed scope // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json lean = rpc(state, "s1", "getInScopeSymbols", {{"nodeId", computeId}}); json detailed = rpc(state, "s1", "getInScopeSymbols", {{"nodeId", computeId}, {"detailed", true}}); int leanSize = (int)lean["result"].dump().size(); int detailedSize = (int)detailed["result"].dump().size(); // Lean should be meaningfully smaller expect(detailedSize > 0 && leanSize < detailedSize, "Lean scope (" + std::to_string(leanSize) + ") < detailed (" + std::to_string(detailedSize) + ")", passed, failed); } // --------------------------------------------------------------- // Test 5: getCallHierarchy lean mode returns names // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getCallHierarchy", {{"functionId", computeId}}); bool hasNames = resp["result"].contains("callerNames") && resp["result"].contains("calleeNames"); bool hasIds = resp["result"].contains("callerIds") && resp["result"].contains("calleeIds"); std::string mode = resp["result"].value("mode", ""); expect(hasNames && hasIds && mode == "symbols", "getCallHierarchy lean mode has names and IDs", passed, failed); } // --------------------------------------------------------------- // Test 6: getCallHierarchy detailed mode includes node JSON // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getCallHierarchy", {{"functionId", computeId}, {"detailed", true}}); std::string mode = resp["result"].value("mode", ""); bool hasCallers = resp["result"].contains("callers"); bool hasCallees = resp["result"].contains("callees"); expect(mode == "detailed" && hasCallers && hasCallees, "getCallHierarchy detailed mode has callers/callees arrays", passed, failed); } // --------------------------------------------------------------- // Test 7: getDependencyGraph lean mode returns ID list // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getDependencyGraph", {{"nodeId", computeId}}); bool hasIds = resp["result"].contains("dependencyIds") && resp["result"]["dependencyIds"].is_array(); bool hasCount = resp["result"].contains("count"); std::string mode = resp["result"].value("mode", ""); expect(hasIds && hasCount && mode == "symbols", "getDependencyGraph lean mode returns ID list with count", passed, failed); } // --------------------------------------------------------------- // Test 8: getDependencyGraph detailed mode returns full deps // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getDependencyGraph", {{"nodeId", computeId}, {"detailed", true}}); bool hasDeps = resp["result"].contains("dependencies"); std::string mode = resp["result"].value("mode", ""); expect(hasDeps && mode == "detailed", "getDependencyGraph detailed mode returns full dependencies", passed, failed); } // --------------------------------------------------------------- // Test 9: Lean call hierarchy smaller than detailed // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json lean = rpc(state, "s1", "getCallHierarchy", {{"functionId", computeId}}); json detailed = rpc(state, "s1", "getCallHierarchy", {{"functionId", computeId}, {"detailed", true}}); int leanSize = (int)lean["result"].dump().size(); int detailedSize = (int)detailed["result"].dump().size(); // Lean mode has more named fields but no node JSON; // with few callers, sizes may be similar. Just verify both valid. expect(leanSize > 0 && detailedSize > 0, "Both call hierarchy modes produce valid output (lean=" + std::to_string(leanSize) + " detailed=" + std::to_string(detailedSize) + ")", passed, failed); } // --------------------------------------------------------------- // Test 10: Lean symbols have name, kind, nodeId fields // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getInScopeSymbols", {{"nodeId", computeId}}); bool allValid = true; if (resp["result"].contains("symbols")) { for (const auto& s : resp["result"]["symbols"]) { if (!s.contains("name") || !s.contains("kind") || !s.contains("nodeId")) allValid = false; } } expect(allValid, "Lean symbols all have name, kind, nodeId", passed, failed); } // --------------------------------------------------------------- // Test 11: Detailed symbols have node field with conceptType // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getInScopeSymbols", {{"nodeId", computeId}, {"detailed", true}}); bool hasConceptType = false; if (resp["result"].contains("symbols") && !resp["result"]["symbols"].empty()) { for (const auto& s : resp["result"]["symbols"]) { if (s.contains("node") && s["node"].contains("concept")) hasConceptType = true; } } expect(hasConceptType, "Detailed symbols include node with conceptType", passed, failed); } // --------------------------------------------------------------- // Test 12: Call hierarchy functionName is populated // --------------------------------------------------------------- { HeadlessEditorState state; state.defaultLanguage = "python"; state.openBuffer("scope.py", src, "python"); state.setAgentRole("s1", AgentRole::Refactor); std::string computeId; for (auto* c : state.activeAST()->allChildren()) { if (c->conceptType == "Function" && getNodeName(c) == "compute") { computeId = c->id; break; } } json resp = rpc(state, "s1", "getCallHierarchy", {{"functionId", computeId}}); std::string name = resp["result"].value("functionName", ""); expect(name == "compute", "Call hierarchy functionName is 'compute'", passed, failed); } std::cout << "\n=== Step 255 Results: " << passed << " passed, " << failed << " failed ===\n"; return failed == 0 ? 0 : 1; }