231 lines
6.7 KiB
Bash
Executable File
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
|