Files
whetstone_DSL/tools/mcp/ollama_step_executor.sh

231 lines
6.7 KiB
Bash
Executable File

#!/usr/bin/env bash
# Whetstone Ollama Step Executor
# Uses qwen2.5-coder:14b to implement a single sprint step.
#
# Usage: ./ollama_step_executor.sh <step_number>
# Example: ./ollama_step_executor.sh 859
#
# The script:
# 1. Reads the sprint plan to get the step description
# 2. Reads the relevant source header (if it exists)
# 3. Constructs a minimal, self-contained prompt
# 4. Calls ollama to generate the test file
# 5. Appends to CMakeLists.txt
# 6. Builds and verifies
# 7. Exits 0 on pass, 1 on fail
set -euo pipefail
STEP="${1:-}"
if [[ -z "$STEP" ]]; then
echo "Usage: $0 <step_number>"
exit 1
fi
WHETSTONE_DIR="/home/bill/Documents/CLionProjects/whetstone_DSL"
TESTS_DIR="$WHETSTONE_DIR/editor/tests"
SRC_DIR="$WHETSTONE_DIR/editor/src"
BUILD_DIR="$WHETSTONE_DIR/editor/build-native"
MODEL="qwen2.5-coder:14b"
OLLAMA_URL="http://localhost:11434/api/generate"
# Compute sprint number from step
SPRINT=$(( (STEP - 829) / 10 + 60 ))
echo "=== Step $STEP (Sprint $SPRINT) ==="
# Check if already done
if [[ -f "$TESTS_DIR/step${STEP}_test.cpp" ]]; then
echo " Already exists: step${STEP}_test.cpp"
if [[ -f "$BUILD_DIR/step${STEP}_test" ]]; then
result=$("$BUILD_DIR/step${STEP}_test" 2>&1 | tail -1)
echo " Status: $result"
exit 0
fi
fi
# Get step description from sprint plan
SPRINT_PLAN="$WHETSTONE_DIR/sprint${SPRINT}_plan.md"
STEP_DESC=""
if [[ -f "$SPRINT_PLAN" ]]; then
STEP_DESC=$(grep "^### Step ${STEP}:" "$SPRINT_PLAN" | sed 's/^### //')
fi
if [[ -z "$STEP_DESC" ]]; then
STEP_DESC="Step ${STEP}: implementation"
fi
echo " Description: $STEP_DESC"
# Find relevant header(s) for this step
HEADER_CONTENT=""
HEADER_PATHS=()
# Search graduation/ for step comment
for hdir in graduation runtime drift corpus tenant improvement web_api data_pipeline embedded; do
for h in "$SRC_DIR/$hdir/"*.h 2>/dev/null; do
[[ -f "$h" ]] || continue
if grep -q "Step ${STEP}:" "$h" 2>/dev/null; then
HEADER_PATHS+=("$h")
fi
done
done
# Also check top-level src
for h in "$SRC_DIR/"*.h 2>/dev/null; do
[[ -f "$h" ]] || continue
if grep -q "Step ${STEP}:" "$h" 2>/dev/null; then
HEADER_PATHS+=("$h")
fi
done
# Also check governance/
for h in "$SRC_DIR/governance/"*.h 2>/dev/null; do
[[ -f "$h" ]] || continue
if grep -q "Step ${STEP}:" "$h" 2>/dev/null; then
HEADER_PATHS+=("$h")
fi
done
for hp in "${HEADER_PATHS[@]}"; do
rel=$(echo "$hp" | sed "s|$SRC_DIR/||")
HEADER_CONTENT+="// === $rel ===\n"
HEADER_CONTENT+="$(cat "$hp")\n\n"
done
# Check if this is an MCP tool step (contains whetstone_ in description)
IS_MCP=false
if echo "$STEP_DESC" | grep -q "whetstone_\|MCP tool"; then
IS_MCP=true
fi
# Get test count from description
TEST_COUNT=$(echo "$STEP_DESC" | grep -oP '\(\K[0-9]+(?= tests\))' || echo "8")
# Determine include path
INCLUDE_PATH=""
if [[ "$IS_MCP" == "true" ]]; then
INCLUDE_PATH="MCPServer.h"
elif [[ ${#HEADER_PATHS[@]} -gt 0 ]]; then
INCLUDE_PATH=$(echo "${HEADER_PATHS[0]}" | sed "s|$SRC_DIR/||")
fi
# Build the prompt
PROMPT="You are writing a C++ unit test file for the Whetstone DSL project.
TASK: Write the complete test file for: ${STEP_DESC}
$(if [[ -n "$HEADER_CONTENT" ]]; then
echo "SOURCE HEADER TO TEST:
$(echo -e "$HEADER_CONTENT")
"
fi)
RULES:
1. File must start with: // Step ${STEP}: <description> (${TEST_COUNT} tests)
2. Include: \"${INCLUDE_PATH}\"
$(if [[ "$IS_MCP" == "true" ]]; then
echo "3. MCPServer WRAPS tool results — you MUST parse content[0][text] first:
auto r = mcp.handleRequest(...);
auto text = r[\"result\"][\"content\"][0][\"text\"].get<std::string>();
auto res = nlohmann::json::parse(text);
// then use res[\"success\"] etc.
// tools/list is NOT wrapped: r[\"result\"][\"tools\"] directly"
fi)
3. Use this exact test boilerplate:
static int p=0,f=0;
#define T(n) { std::cout << \" \" << #n << \"... \"; }
#define P() { std::cout << \"PASS\\n\"; ++p; }
#define F(m) { std::cout << \"FAIL: \" << m << \"\\n\"; ++f; }
#define C(c,m) if (!(c)) { F(m); return; }
4. Write void t1()...void t${TEST_COUNT}() functions
5. main() must print \"Step ${STEP}: <title>\" then call t1()...t${TEST_COUNT}()
6. main() must end with: std::cout << \"\\nResults: \" << p << \"/\" << (p+f) << \" passed\\n\"; return f?1:0;
7. All tests must PASS — do not write tests that fail
8. Keep it simple: test the obvious API surface from the header
OUTPUT: Only the complete C++ file contents, no markdown, no explanation."
echo " Calling ollama ($MODEL)..."
# Call ollama
RESPONSE=$(curl -s -X POST "$OLLAMA_URL" \
-H "Content-Type: application/json" \
-d "$(python3 -c "
import json, sys
prompt = sys.argv[1]
print(json.dumps({
'model': '$MODEL',
'prompt': prompt,
'stream': False,
'options': {
'temperature': 0.1,
'num_predict': 4096,
'top_p': 0.9
}
}))
" "$PROMPT")" 2>/dev/null)
# Extract the generated text
GENERATED=$(echo "$RESPONSE" | python3 -c "
import json, sys
d = json.load(sys.stdin)
print(d.get('response', ''))
" 2>/dev/null)
if [[ -z "$GENERATED" ]]; then
echo " ERROR: ollama returned empty response"
echo " Response: $RESPONSE" | head -3
exit 1
fi
# Strip markdown code fences if present
GENERATED=$(echo "$GENERATED" | sed '/^```/d')
# Write the test file
echo "$GENERATED" > "$TESTS_DIR/step${STEP}_test.cpp"
echo " Written: step${STEP}_test.cpp ($(wc -l < "$TESTS_DIR/step${STEP}_test.cpp") lines)"
# Add to CMakeLists.txt if not already there
if ! grep -q "step${STEP}_test" "$WHETSTONE_DIR/editor/CMakeLists.txt"; then
cat >> "$WHETSTONE_DIR/editor/CMakeLists.txt" << EOF
add_executable(step${STEP}_test tests/step${STEP}_test.cpp)
target_include_directories(step${STEP}_test PRIVATE src)
target_link_libraries(step${STEP}_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)
EOF
echo " Added to CMakeLists.txt"
fi
# Build
echo " Building..."
if ! cmake --build "$BUILD_DIR" --target "step${STEP}_test" --parallel -j4 2>/tmp/build_${STEP}.log; then
echo " BUILD FAILED:"
tail -20 /tmp/build_${STEP}.log
exit 1
fi
# Run
echo " Running..."
RESULT=$("$BUILD_DIR/step${STEP}_test" 2>&1)
LAST_LINE=$(echo "$RESULT" | tail -1)
echo " $LAST_LINE"
if echo "$LAST_LINE" | grep -q "passed$"; then
PASSED=$(echo "$LAST_LINE" | grep -oP '\d+/\d+' | head -1)
TOTAL=$(echo "$PASSED" | cut -d/ -f2)
ACTUAL=$(echo "$PASSED" | cut -d/ -f1)
if [[ "$ACTUAL" == "$TOTAL" ]]; then
echo " PASS ✓"
exit 0
fi
fi
echo " FAIL — full output:"
echo "$RESULT"
exit 1