2026-03-08 10:53:30 -06:00
|
|
|
// Step 682: whetstone_validate_taskitem MCP tool (9 tests)
|
2026-02-18 18:11:31 -07:00
|
|
|
|
|
|
|
|
#include "MCPServer.h"
|
|
|
|
|
|
|
|
|
|
#include <filesystem>
|
|
|
|
|
#include <fstream>
|
|
|
|
|
#include <iostream>
|
|
|
|
|
#include <nlohmann/json.hpp>
|
|
|
|
|
#include <string>
|
|
|
|
|
|
|
|
|
|
using json = nlohmann::json;
|
|
|
|
|
namespace fs = std::filesystem;
|
|
|
|
|
|
|
|
|
|
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; }
|
|
|
|
|
|
|
|
|
|
static json callTool(MCPServer& mcp, const std::string& name, const json& args) {
|
|
|
|
|
json req = {
|
|
|
|
|
{"jsonrpc", "2.0"},
|
|
|
|
|
{"id", 1},
|
|
|
|
|
{"method", "tools/call"},
|
|
|
|
|
{"params", {{"name", name}, {"arguments", args}}}
|
|
|
|
|
};
|
|
|
|
|
json resp = mcp.handleRequest(req);
|
|
|
|
|
std::string text = resp["result"]["content"][0].value("text", "{}");
|
|
|
|
|
return json::parse(text);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static json loadToolsJson() {
|
|
|
|
|
std::ifstream in("tools/claude/tools.json");
|
|
|
|
|
if (!in.good()) in.open("../tools/claude/tools.json");
|
|
|
|
|
if (!in.good()) in.open("/home/bill/Documents/CLionProjects/whetstone_DSL/tools/claude/tools.json");
|
|
|
|
|
if (!in.good()) return json::object();
|
|
|
|
|
json d;
|
|
|
|
|
in >> d;
|
|
|
|
|
return d;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static std::string makeWorkspace() {
|
|
|
|
|
fs::path root = fs::temp_directory_path() / "whetstone_step682_workspace";
|
|
|
|
|
std::error_code ec;
|
|
|
|
|
fs::remove_all(root, ec);
|
|
|
|
|
fs::create_directories(root / "editor/src", ec);
|
|
|
|
|
std::ofstream(root / "editor/src/file.h") << "int x();\n";
|
|
|
|
|
return root.string();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static json validTaskitem() {
|
|
|
|
|
return {
|
|
|
|
|
{"task_id", "T1"},
|
|
|
|
|
{"title", "Do thing"},
|
|
|
|
|
{"prerequisite_ops", json::array({"read editor/src/file.h"})},
|
|
|
|
|
{"reasons", json::array({"r1", "r2", "r3"})},
|
|
|
|
|
{"confidence", 90},
|
|
|
|
|
{"dependency_task_ids", json::array()}
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t1() {
|
|
|
|
|
TEST(tool_registered);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
json req = {{"jsonrpc", "2.0"}, {"id", 2}, {"method", "tools/list"}};
|
|
|
|
|
auto resp = mcp.handleRequest(req);
|
|
|
|
|
bool found = false;
|
|
|
|
|
for (const auto& t : resp["result"]["tools"]) {
|
|
|
|
|
if (t.value("name", "") == "whetstone_validate_taskitem") { found = true; break; }
|
|
|
|
|
}
|
|
|
|
|
CHECK(found, "tool missing");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t2() {
|
|
|
|
|
TEST(empty_taskitems_returns_zero_report);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem", {{"taskitems", json::array()}});
|
|
|
|
|
CHECK(out.value("success", false), "expected success");
|
|
|
|
|
CHECK(out["report"].value("total_taskitems", -1) == 0, "expected total 0");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t3() {
|
|
|
|
|
TEST(single_valid_taskitem_returns_score);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
auto workspace = makeWorkspace();
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem",
|
|
|
|
|
{{"taskitems", json::array({validTaskitem()})}, {"workspace", workspace}});
|
|
|
|
|
CHECK(out.value("success", false), "expected success");
|
|
|
|
|
CHECK(out["report"]["results"].size() == 1, "expected one result");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t4() {
|
|
|
|
|
TEST(missing_task_id_returns_error);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem",
|
|
|
|
|
{{"taskitems", json::array({json::object()})}});
|
|
|
|
|
CHECK(!out.value("success", true), "expected failure");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t5() {
|
|
|
|
|
TEST(prerequisite_ops_resolved_against_workspace);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
auto workspace = makeWorkspace();
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem",
|
|
|
|
|
{{"taskitems", json::array({validTaskitem()})}, {"workspace", workspace}});
|
|
|
|
|
CHECK(out.value("success", false), "expected success");
|
|
|
|
|
int score = out["report"]["results"][0].value("score", 0);
|
|
|
|
|
CHECK(score >= 80, "expected resolved-op high score");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t6() {
|
|
|
|
|
TEST(results_length_matches_input_length);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
auto workspace = makeWorkspace();
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem",
|
|
|
|
|
{{"taskitems", json::array({validTaskitem(), validTaskitem()})},
|
|
|
|
|
{"workspace", workspace}});
|
|
|
|
|
CHECK(out["report"]["results"].size() == 2, "expected two results");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t7() {
|
|
|
|
|
TEST(category_counts_sum_to_total);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
auto workspace = makeWorkspace();
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem",
|
|
|
|
|
{{"taskitems", json::array({validTaskitem()})}, {"workspace", workspace}});
|
|
|
|
|
const auto& r = out["report"];
|
|
|
|
|
int total = r.value("total_taskitems", 0);
|
|
|
|
|
int sum = r.value("self_contained_count", 0) + r.value("warning_count", 0) + r.value("failing_count", 0);
|
|
|
|
|
CHECK(total == sum, "count sum mismatch");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t8() {
|
|
|
|
|
TEST(tool_count_updated_to_88);
|
|
|
|
|
auto d = loadToolsJson();
|
|
|
|
|
CHECK(d.contains("tools"), "tools missing");
|
2026-03-08 10:53:30 -06:00
|
|
|
CHECK(d["tools"].size() >= 88, "expected at least 88 tools");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t9() {
|
|
|
|
|
TEST(cross_project_target_mismatch_classified);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
json item = validTaskitem();
|
|
|
|
|
item["execution_contract"] = {
|
|
|
|
|
{"targetFiles", json::array({
|
|
|
|
|
"editor/src/mcp/RegisterArchitectIntakeTools.h",
|
|
|
|
|
"tools/mcp/run_sprint_taskitem_pipeline.sh"
|
|
|
|
|
})}
|
|
|
|
|
};
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem", {
|
|
|
|
|
{"taskitems", json::array({item})},
|
|
|
|
|
{"expected_project_roots", json::array({"cad_orchestrator", "hivemind", "gleaner", "constcad"})}
|
|
|
|
|
});
|
|
|
|
|
CHECK(out.value("success", false), "expected success");
|
|
|
|
|
CHECK(out["report"]["results"].size() == 1, "expected one result");
|
|
|
|
|
CHECK(out["report"]["results"][0].value("gap_class", "") == "cross_project_targeting_gap",
|
|
|
|
|
"expected cross_project_targeting_gap");
|
2026-02-18 18:11:31 -07:00
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-21 02:17:17 -06:00
|
|
|
void t10() {
|
|
|
|
|
TEST(strict_contract_flags_missing_verification_metadata);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
auto workspace = makeWorkspace();
|
|
|
|
|
json item = validTaskitem();
|
|
|
|
|
item["execution_contract"] = {
|
|
|
|
|
{"stepIds", json::array({"1939"})},
|
|
|
|
|
{"targetFiles", json::array({"python/hivemind_installer/models.py"})},
|
|
|
|
|
{"requiredTools", json::array({"whetstone_generate_taskitems"})},
|
|
|
|
|
{"acceptanceCommands", json::array({"pytest"})},
|
|
|
|
|
{"executionSpecificityScore", 75}
|
|
|
|
|
};
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem", {
|
|
|
|
|
{"taskitems", json::array({item})},
|
|
|
|
|
{"workspace", workspace},
|
|
|
|
|
{"strict_execution_contract", true}
|
|
|
|
|
});
|
|
|
|
|
CHECK(out.value("success", false), "expected success");
|
|
|
|
|
CHECK(out["report"]["results"].size() == 1, "expected one result");
|
|
|
|
|
const auto& result = out["report"]["results"][0];
|
|
|
|
|
CHECK(result.value("gap_class", "") == "under_constrained_taskitem",
|
|
|
|
|
"expected under_constrained_taskitem");
|
|
|
|
|
bool sawVerificationIssue = false;
|
|
|
|
|
for (const auto& issue : result["issues"]) {
|
|
|
|
|
if (!issue.is_string()) continue;
|
|
|
|
|
const std::string text = issue.get<std::string>();
|
|
|
|
|
if (text.find("verificationType") != std::string::npos ||
|
|
|
|
|
text.find("testFiles") != std::string::npos) {
|
|
|
|
|
sawVerificationIssue = true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
CHECK(sawVerificationIssue, "expected verification metadata issue");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void t11() {
|
|
|
|
|
TEST(malformed_paths_and_wrong_language_commands_are_under_constrained);
|
|
|
|
|
MCPServer mcp;
|
|
|
|
|
json item = validTaskitem();
|
|
|
|
|
item["execution_contract"] = {
|
|
|
|
|
{"targetFiles", json::array({
|
|
|
|
|
"crates/whimpwm/src/render.rs",
|
|
|
|
|
"crates/whimpwm/tests/`.",
|
|
|
|
|
"crates/whimpwm`."
|
|
|
|
|
})},
|
|
|
|
|
{"acceptanceCommands", json::array({
|
|
|
|
|
"cargo test",
|
|
|
|
|
"go test ./..."
|
|
|
|
|
})},
|
|
|
|
|
{"verificationType", "unit"},
|
|
|
|
|
{"testFiles", json::array({"crates/whimpwm/tests/main_test.rs"})},
|
|
|
|
|
{"executionSpecificityScore", 90}
|
|
|
|
|
};
|
|
|
|
|
auto out = callTool(mcp, "whetstone_validate_taskitem", {
|
|
|
|
|
{"taskitems", json::array({item})},
|
|
|
|
|
{"strict_execution_contract", true}
|
|
|
|
|
});
|
|
|
|
|
CHECK(out.value("success", false), "expected success");
|
|
|
|
|
CHECK(out["report"]["results"].size() == 1, "expected one result");
|
|
|
|
|
const auto& result = out["report"]["results"][0];
|
|
|
|
|
CHECK(result.value("gap_class", "") == "under_constrained_taskitem",
|
|
|
|
|
"expected under_constrained_taskitem");
|
|
|
|
|
bool sawMalformed = false;
|
|
|
|
|
bool sawIncompatible = false;
|
|
|
|
|
for (const auto& issue : result["issues"]) {
|
|
|
|
|
if (!issue.is_string()) continue;
|
|
|
|
|
const std::string text = issue.get<std::string>();
|
|
|
|
|
if (text.find("malformed_target_path") != std::string::npos) sawMalformed = true;
|
|
|
|
|
if (text.find("incompatible_acceptance_commands") != std::string::npos) sawIncompatible = true;
|
|
|
|
|
}
|
|
|
|
|
CHECK(sawMalformed, "expected malformed target path issue");
|
|
|
|
|
CHECK(sawIncompatible, "expected incompatible acceptance commands issue");
|
|
|
|
|
PASS();
|
|
|
|
|
}
|
|
|
|
|
|
2026-02-18 18:11:31 -07:00
|
|
|
int main() {
|
|
|
|
|
std::cout << "Step 682: whetstone_validate_taskitem MCP tool\n";
|
2026-03-21 02:17:17 -06:00
|
|
|
t1(); t2(); t3(); t4(); t5(); t6(); t7(); t8(); t9(); t10(); t11();
|
2026-02-18 18:11:31 -07:00
|
|
|
std::cout << "\nResults: " << passed << "/" << (passed + failed) << " passed\n";
|
|
|
|
|
return failed == 0 ? 0 : 1;
|
|
|
|
|
}
|