2026-03-08 10:53:30 -06:00
// Step 615: whetstone_generate_taskitems MCP Tool (13 tests)
2026-02-17 20:43:37 -07:00
# include "MCPServer.h"
# include <iostream>
# 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 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 std : : string clearSpec ( ) {
return
" ## Goals \n "
" - Ship intake parser \n "
" \n "
" ## Constraints \n "
" - Keep modules header only \n "
" \n "
" ## Dependencies \n "
" - nlohmann json \n "
" \n "
" ## Acceptance Criteria \n "
" - tests pass \n " ;
}
static std : : string riskySpec ( ) {
return
" ## Goals \n "
" - maybe improve intake and maybe improve review \n "
" \n "
" ## Constraints \n "
" - Do not use dynamic allocation in hot path \n "
" - Use dynamic allocation in hot path for warmup \n "
" \n "
" ## Dependencies \n "
" - maybe add cache lib \n "
" \n "
" ## Acceptance Criteria \n "
" - maybe latency under 50ms \n " ;
}
static json intakeOutput ( MCPServer & mcp , const std : : string & markdown ) {
return callTool ( mcp , " whetstone_architect_intake " , { { " markdown " , markdown } } ) ;
}
static json taskitemArgsFromIntake ( const json & intake ) {
return {
{ " normalizedRequirements " , intake [ " normalizedRequirements " ] } ,
{ " conflicts " , intake [ " conflicts " ] }
} ;
}
void test_tool_registered_accessor ( ) {
TEST ( tool_registered_accessor ) ;
MCPServer mcp ;
bool found = false ;
for ( const auto & tool : mcp . getTools ( ) ) {
if ( tool . name = = " whetstone_generate_taskitems " ) found = true ;
}
CHECK ( found , " tool should be registered " ) ;
PASS ( ) ;
}
void test_tool_schema_requires_normalized_requirements ( ) {
TEST ( tool_schema_requires_normalized_requirements ) ;
MCPServer mcp ;
bool required = false ;
for ( const auto & tool : mcp . getTools ( ) ) {
if ( tool . name ! = " whetstone_generate_taskitems " ) continue ;
for ( const auto & req : tool . inputSchema [ " required " ] ) {
if ( req = = " normalizedRequirements " ) required = true ;
}
}
CHECK ( required , " normalizedRequirements should be required " ) ;
PASS ( ) ;
}
void test_success_for_intake_output ( ) {
TEST ( success_for_intake_output ) ;
MCPServer mcp ;
json intake = intakeOutput ( mcp , clearSpec ( ) ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " , taskitemArgsFromIntake ( intake ) ) ;
CHECK ( out . value ( " success " , false ) , " success should be true " ) ;
PASS ( ) ;
}
void test_returns_non_empty_tasks ( ) {
TEST ( returns_non_empty_tasks ) ;
MCPServer mcp ;
json intake = intakeOutput ( mcp , clearSpec ( ) ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " , taskitemArgsFromIntake ( intake ) ) ;
CHECK ( out [ " tasks " ] . is_array ( ) & & ! out [ " tasks " ] . empty ( ) , " tasks should be non-empty array " ) ;
PASS ( ) ;
}
void test_task_contains_annotation_fields ( ) {
TEST ( task_contains_annotation_fields ) ;
MCPServer mcp ;
json intake = intakeOutput ( mcp , clearSpec ( ) ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " , taskitemArgsFromIntake ( intake ) ) ;
const auto & task = out [ " tasks " ] [ 0 ] ;
CHECK ( task . contains ( " confidence " ) , " confidence missing " ) ;
CHECK ( task . contains ( " ambiguityCount " ) , " ambiguityCount missing " ) ;
CHECK ( task . contains ( " escalate " ) , " escalate missing " ) ;
CHECK ( task . contains ( " reasons " ) , " reasons missing " ) ;
PASS ( ) ;
}
void test_conflict_count_roundtrips_from_input ( ) {
TEST ( conflict_count_roundtrips_from_input ) ;
MCPServer mcp ;
json intake = intakeOutput ( mcp , riskySpec ( ) ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " , taskitemArgsFromIntake ( intake ) ) ;
CHECK ( out . value ( " conflictCount " , 0 ) > = 1 , " expected conflict count >= 1 " ) ;
PASS ( ) ;
}
void test_risky_input_triggers_escalation ( ) {
TEST ( risky_input_triggers_escalation ) ;
MCPServer mcp ;
json intake = intakeOutput ( mcp , riskySpec ( ) ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " , taskitemArgsFromIntake ( intake ) ) ;
CHECK ( out . value ( " escalateCount " , 0 ) > = 1 , " expected escalations " ) ;
PASS ( ) ;
}
void test_clear_input_has_non_escalated_task ( ) {
TEST ( clear_input_has_non_escalated_task ) ;
MCPServer mcp ;
json intake = intakeOutput ( mcp , clearSpec ( ) ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " , taskitemArgsFromIntake ( intake ) ) ;
bool foundNonEscalated = false ;
for ( const auto & task : out [ " tasks " ] ) {
if ( ! task . value ( " escalate " , true ) ) foundNonEscalated = true ;
}
CHECK ( foundNonEscalated , " expected at least one non-escalated task " ) ;
PASS ( ) ;
}
void test_missing_normalized_requirements_errors ( ) {
TEST ( missing_normalized_requirements_errors ) ;
MCPServer mcp ;
json out = callTool ( mcp , " whetstone_generate_taskitems " , json : : object ( ) ) ;
CHECK ( ! out . value ( " success " , true ) , " success should be false " ) ;
CHECK ( out . value ( " error " , " " ) = = " normalized_requirements_missing " , " wrong error " ) ;
PASS ( ) ;
}
void test_normalized_requirements_must_be_array ( ) {
TEST ( normalized_requirements_must_be_array ) ;
MCPServer mcp ;
json out = callTool ( mcp , " whetstone_generate_taskitems " ,
{ { " normalizedRequirements " , " bad " } } ) ;
CHECK ( ! out . value ( " success " , true ) , " success should be false " ) ;
CHECK ( out . value ( " error " , " " ) = = " normalized_requirements_not_array " , " wrong error " ) ;
PASS ( ) ;
}
void test_rejects_empty_normalized_requirements ( ) {
TEST ( rejects_empty_normalized_requirements ) ;
MCPServer mcp ;
json out = callTool ( mcp , " whetstone_generate_taskitems " ,
{ { " normalizedRequirements " , json : : array ( ) } } ) ;
CHECK ( ! out . value ( " success " , true ) , " success should be false " ) ;
CHECK ( out . value ( " error " , " " ) = = " normalized_requirements_empty " , " wrong error " ) ;
PASS ( ) ;
}
void test_rejects_invalid_requirement_kind ( ) {
TEST ( rejects_invalid_requirement_kind ) ;
MCPServer mcp ;
json bad = json : : array ( { {
{ " requirementId " , " goal-1 " } ,
{ " kind " , " invalid-kind " } ,
{ " normalizedText " , " ship it " } ,
{ " anchor " , " goals " } ,
{ " sourceLine " , 2 } ,
{ " ambiguous " , false }
} } ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " ,
{ { " normalizedRequirements " , bad } } ) ;
CHECK ( ! out . value ( " success " , true ) , " success should be false " ) ;
CHECK ( out . value ( " error " , " " ) = = " requirement_kind_invalid " , " wrong error " ) ;
PASS ( ) ;
}
2026-03-08 10:53:30 -06:00
void test_cross_project_repo_signal_grounds_target_files ( ) {
TEST ( cross_project_repo_signal_grounds_target_files ) ;
MCPServer mcp ;
json reqs = json : : array ( { {
{ " requirementId " , " goal-constcad " } ,
{ " kind " , " goal " } ,
{ " normalizedText " , " ConstCAD ecosystem integration across cad_orchestrator, hivemind, gleaner, and constcad " } ,
{ " anchor " , " goals " } ,
{ " sourceLine " , 1 } ,
{ " ambiguous " , false }
} } ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " ,
{ { " normalizedRequirements " , reqs } , { " strictExecutionContract " , true } } ) ;
CHECK ( out . value ( " success " , false ) , " success should be true " ) ;
const auto & files = out [ " tasks " ] [ 0 ] [ " executionContract " ] [ " targetFiles " ] ;
bool hasExternal = false ;
for ( const auto & f : files ) {
if ( ! f . is_string ( ) ) continue ;
std : : string v = f . get < std : : string > ( ) ;
if ( v . rfind ( " cad_orchestrator/ " , 0 ) = = 0 | |
v . rfind ( " hivemind/ " , 0 ) = = 0 | |
v . rfind ( " gleaner/ " , 0 ) = = 0 | |
v . rfind ( " constcad/ " , 0 ) = = 0 ) {
hasExternal = true ;
}
CHECK ( v . rfind ( " editor/src/mcp/ " , 0 ) ! = 0 , " should not fallback to editor/src/mcp internal defaults " ) ;
}
CHECK ( hasExternal , " expected at least one cross-project target file " ) ;
PASS ( ) ;
}
2026-03-21 02:17:17 -06:00
void test_project_requirement_generates_specific_task_title_and_files ( ) {
TEST ( project_requirement_generates_specific_task_title_and_files ) ;
MCPServer mcp ;
json reqs = json : : array ( { {
{ " requirementId " , " goal-guidance-bridge " } ,
{ " kind " , " goal " } ,
{ " normalizedText " , " Implement GuidanceBridge formulas in WHIMP/software/cpp/src/guidance/GuidanceBridge.cpp and WHIMP/software/cpp/src/guidance/GuidanceBridge.h " } ,
{ " anchor " , " goals " } ,
{ " sourceLine " , 1 } ,
{ " ambiguous " , false }
} } ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " ,
{ { " normalizedRequirements " , reqs } , { " strictExecutionContract " , true } } ) ;
CHECK ( out . value ( " success " , false ) , " success should be true " ) ;
CHECK ( out [ " tasks " ] . is_array ( ) & & ! out [ " tasks " ] . empty ( ) , " tasks should be non-empty " ) ;
const auto & task = out [ " tasks " ] [ 0 ] ;
std : : string title = task . value ( " title " , " " ) ;
std : : string intent = task . value ( " intent " , " " ) ;
CHECK ( title . find ( " GuidanceBridge " ) ! = std : : string : : npos , " title should mention GuidanceBridge " ) ;
CHECK ( title . find ( " Intake Foundation " ) = = std : : string : : npos , " title should not fallback to generic milestone wording " ) ;
CHECK ( intent . find ( " GuidanceBridge " ) ! = std : : string : : npos , " intent should preserve requirement text " ) ;
const auto & files = task [ " executionContract " ] [ " targetFiles " ] ;
bool hasCpp = false ;
bool hasHeader = false ;
for ( const auto & f : files ) {
if ( ! f . is_string ( ) ) continue ;
std : : string v = f . get < std : : string > ( ) ;
if ( v = = " WHIMP/software/cpp/src/guidance/GuidanceBridge.cpp " ) hasCpp = true ;
if ( v = = " WHIMP/software/cpp/src/guidance/GuidanceBridge.h " ) hasHeader = true ;
}
CHECK ( hasCpp , " expected cpp target file " ) ;
CHECK ( hasHeader , " expected header target file " ) ;
PASS ( ) ;
}
void test_single_project_product_name_does_not_trigger_cross_repo_fallback ( ) {
TEST ( single_project_product_name_does_not_trigger_cross_repo_fallback ) ;
MCPServer mcp ;
json reqs = json : : array ( { {
{ " requirementId " , " goal-hivemind-shell-onboarding " } ,
{ " kind " , " goal " } ,
{ " normalizedText " , " Add onboarding plan for installing hivemind_shell in docs/onboarding/hivemind_shell_ubuntu.md " } ,
{ " anchor " , " requirements " } ,
{ " sourceLine " , 1 } ,
{ " ambiguous " , false }
} } ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " ,
{ { " normalizedRequirements " , reqs } , { " strictExecutionContract " , true } } ) ;
CHECK ( out . value ( " success " , false ) , " success should be true " ) ;
CHECK ( out [ " tasks " ] . is_array ( ) & & ! out [ " tasks " ] . empty ( ) , " tasks should be non-empty " ) ;
const auto & files = out [ " tasks " ] [ 0 ] [ " executionContract " ] [ " targetFiles " ] ;
bool hasLocalDoc = false ;
for ( const auto & f : files ) {
if ( ! f . is_string ( ) ) continue ;
std : : string v = f . get < std : : string > ( ) ;
if ( v = = " docs/onboarding/hivemind_shell_ubuntu.md " ) hasLocalDoc = true ;
CHECK ( v ! = " hivemind/README.md " , " single-project product name should not trigger cross-repo fallback " ) ;
}
CHECK ( hasLocalDoc , " expected local onboarding doc target " ) ;
PASS ( ) ;
}
void test_strict_contract_infers_test_files_and_verification_type_for_code_work ( ) {
TEST ( strict_contract_infers_test_files_and_verification_type_for_code_work ) ;
MCPServer mcp ;
json reqs = json : : array ( { {
{ " requirementId " , " goal-guidance-bridge-tests " } ,
{ " kind " , " goal " } ,
{ " normalizedText " , " Implement GuidanceBridge formulas in WHIMP/software/cpp/src/guidance/GuidanceBridge.cpp and WHIMP/software/cpp/src/guidance/GuidanceBridge.h " } ,
{ " anchor " , " goals " } ,
{ " sourceLine " , 1 } ,
{ " ambiguous " , false }
} } ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " ,
{ { " normalizedRequirements " , reqs } , { " strictExecutionContract " , true } } ) ;
CHECK ( out . value ( " success " , false ) , " success should be true " ) ;
CHECK ( out [ " tasks " ] . is_array ( ) & & ! out [ " tasks " ] . empty ( ) , " tasks should be non-empty " ) ;
const auto & contract = out [ " tasks " ] [ 0 ] [ " executionContract " ] ;
CHECK ( contract . value ( " verificationType " , " " ) = = " unit " , " expected unit verification type " ) ;
const auto & tests = contract [ " testFiles " ] ;
CHECK ( tests . is_array ( ) & & ! tests . empty ( ) , " expected inferred test files " ) ;
bool hasGuidanceBridgeTest = false ;
for ( const auto & testFile : tests ) {
if ( ! testFile . is_string ( ) ) continue ;
if ( testFile . get < std : : string > ( ) = = " WHIMP/software/cpp/tests/guidance/GuidanceBridge_test.cpp " ) {
hasGuidanceBridgeTest = true ;
}
}
CHECK ( hasGuidanceBridgeTest , " expected inferred GuidanceBridge C++ test path " ) ;
PASS ( ) ;
}
void test_rust_repo_contract_drops_wrong_language_commands_and_uses_real_test_file ( ) {
TEST ( rust_repo_contract_drops_wrong_language_commands_and_uses_real_test_file ) ;
MCPServer mcp ;
json reqs = json : : array ( {
{
{ " requirementId " , " goal-rust-1 " } ,
{ " kind " , " goal " } ,
{ " normalizedText " , " Implement control-plane recomposition in crates/whimpwm/src/render.rs and crates/whimpwm/src/presentation.rs " } ,
{ " sourceText " , " Implement control-plane recomposition in `crates/whimpwm/src/render.rs` and `crates/whimpwm/src/presentation.rs` " } ,
{ " anchor " , " goals " } ,
{ " sourceLine " , 1 } ,
{ " ambiguous " , false }
} ,
{
{ " requirementId " , " constraint-rust-1 " } ,
{ " kind " , " constraint " } ,
{ " normalizedText " , " Do not generate or verify go tasks for this sprint " } ,
{ " sourceText " , " Do not generate or verify Go tasks for this sprint. " } ,
{ " anchor " , " constraints " } ,
{ " sourceLine " , 2 } ,
{ " ambiguous " , false }
}
} ) ;
json out = callTool ( mcp , " whetstone_generate_taskitems " ,
{ { " normalizedRequirements " , reqs } ,
{ " strictExecutionContract " , true } ,
{ " workspace " , " /home/bill/Documents/whimpwm " } } ) ;
CHECK ( out . value ( " success " , false ) , " success should be true " ) ;
CHECK ( out [ " tasks " ] . is_array ( ) & & ! out [ " tasks " ] . empty ( ) , " tasks should be non-empty " ) ;
const auto & contract = out [ " tasks " ] [ 0 ] [ " executionContract " ] ;
bool sawGo = false ;
bool sawCargo = false ;
for ( const auto & commandJson : contract [ " acceptanceCommands " ] ) {
if ( ! commandJson . is_string ( ) ) continue ;
const std : : string command = commandJson . get < std : : string > ( ) ;
if ( command . find ( " go test " ) ! = std : : string : : npos ) sawGo = true ;
if ( command . find ( " cargo " ) ! = std : : string : : npos ) sawCargo = true ;
}
CHECK ( ! sawGo , " go test should be removed for Rust repo " ) ;
CHECK ( sawCargo , " cargo command should be present for Rust repo " ) ;
bool sawMainTest = false ;
for ( const auto & testFileJson : contract [ " testFiles " ] ) {
if ( ! testFileJson . is_string ( ) ) continue ;
if ( testFileJson . get < std : : string > ( ) = = " crates/whimpwm/tests/main_test.rs " ) sawMainTest = true ;
}
CHECK ( sawMainTest , " expected real whimpwm Rust test file " ) ;
PASS ( ) ;
}
2026-02-17 20:43:37 -07:00
int main ( ) {
std : : cout < < " Step 615: whetstone_generate_taskitems MCP Tool \n " ;
test_tool_registered_accessor ( ) ; // 1
test_tool_schema_requires_normalized_requirements ( ) ; // 2
test_success_for_intake_output ( ) ; // 3
test_returns_non_empty_tasks ( ) ; // 4
test_task_contains_annotation_fields ( ) ; // 5
test_conflict_count_roundtrips_from_input ( ) ; // 6
test_risky_input_triggers_escalation ( ) ; // 7
test_clear_input_has_non_escalated_task ( ) ; // 8
test_missing_normalized_requirements_errors ( ) ; // 9
test_normalized_requirements_must_be_array ( ) ; // 10
test_rejects_empty_normalized_requirements ( ) ; // 11
test_rejects_invalid_requirement_kind ( ) ; // 12
2026-03-08 10:53:30 -06:00
test_cross_project_repo_signal_grounds_target_files ( ) ; // 13
2026-03-21 02:17:17 -06:00
test_project_requirement_generates_specific_task_title_and_files ( ) ; // 14
test_single_project_product_name_does_not_trigger_cross_repo_fallback ( ) ; // 15
test_strict_contract_infers_test_files_and_verification_type_for_code_work ( ) ; // 16
test_rust_repo_contract_drops_wrong_language_commands_and_uses_real_test_file ( ) ; // 17
2026-02-17 20:43:37 -07:00
std : : cout < < " \n Results: " < < passed < < " / " < < ( passed + failed ) < < " passed \n " ;
return failed = = 0 ? 0 : 1 ;
}