2026-04-15 15:37:12 +02:00
|
|
|
# Step-by-Step Execution
|
|
|
|
|
|
|
|
|
|
Run SF one unit at a time with decision points between steps. Use this when you need
|
|
|
|
|
control over execution — budget enforcement, progress reporting, conditional logic,
|
|
|
|
|
or the ability to steer mid-build.
|
|
|
|
|
|
|
|
|
|
## When to use this vs `auto`
|
|
|
|
|
|
|
|
|
|
| Approach | Use when |
|
|
|
|
|
|----------|----------|
|
|
|
|
|
| `auto` | You trust the build, just want the result |
|
|
|
|
|
| `next` loop | You need budget checks, progress updates, or intervention points |
|
|
|
|
|
|
|
|
|
|
## Core Loop
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
cd /path/to/project
|
|
|
|
|
MAX_BUDGET=20.00
|
|
|
|
|
TOTAL_COST=0
|
|
|
|
|
|
|
|
|
|
while true; do
|
|
|
|
|
# Run one unit
|
|
|
|
|
RESULT=$(sf headless --output-format json next 2>/dev/null)
|
|
|
|
|
EXIT=$?
|
|
|
|
|
|
|
|
|
|
# Parse result
|
|
|
|
|
STATUS=$(echo "$RESULT" | jq -r '.status')
|
|
|
|
|
STEP_COST=$(echo "$RESULT" | jq -r '.cost.total')
|
|
|
|
|
PHASE=$(echo "$RESULT" | jq -r '.phase // empty')
|
|
|
|
|
SESSION_ID=$(echo "$RESULT" | jq -r '.sessionId // empty')
|
|
|
|
|
|
|
|
|
|
# Handle exit codes
|
|
|
|
|
case $EXIT in
|
|
|
|
|
0) ;; # success — continue
|
|
|
|
|
1)
|
|
|
|
|
echo "Step failed: $STATUS"
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
10)
|
|
|
|
|
echo "Blocked — needs intervention"
|
|
|
|
|
sf headless query | jq '.state'
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
11)
|
|
|
|
|
echo "Cancelled"
|
|
|
|
|
break
|
|
|
|
|
;;
|
|
|
|
|
esac
|
|
|
|
|
|
2026-04-25 06:34:49 +02:00
|
|
|
# One query — extract phase, cost, and progress together
|
|
|
|
|
STATE=$(sf headless query)
|
|
|
|
|
CURRENT_PHASE=$(echo "$STATE" | jq -r '.state.phase')
|
|
|
|
|
TOTAL_COST=$(echo "$STATE" | jq -r '.cost.total')
|
|
|
|
|
PROGRESS=$(echo "$STATE" | jq -r '"\(.state.progress.tasks.done)/\(.state.progress.tasks.total) tasks"')
|
|
|
|
|
|
2026-04-15 15:37:12 +02:00
|
|
|
if [ "$CURRENT_PHASE" = "complete" ]; then
|
|
|
|
|
echo "Milestone complete. Total cost: \$$TOTAL_COST"
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Budget check
|
|
|
|
|
OVER=$(echo "$TOTAL_COST > $MAX_BUDGET" | bc -l)
|
|
|
|
|
if [ "$OVER" = "1" ]; then
|
|
|
|
|
echo "Budget limit (\$$MAX_BUDGET) exceeded at \$$TOTAL_COST"
|
|
|
|
|
sf headless stop
|
|
|
|
|
break
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "Step done ($STATUS). Phase: $CURRENT_PHASE, Progress: $PROGRESS, Cost: \$$TOTAL_COST"
|
|
|
|
|
done
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Step-by-Step with Spec Creation
|
|
|
|
|
|
|
|
|
|
Complete flow from idea to working code with full control:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# 1. Setup
|
|
|
|
|
PROJECT_DIR="/tmp/my-project"
|
|
|
|
|
mkdir -p "$PROJECT_DIR" && cd "$PROJECT_DIR" && git init 2>/dev/null
|
|
|
|
|
|
|
|
|
|
# 2. Write spec
|
|
|
|
|
cat > spec.md << 'SPEC'
|
|
|
|
|
[Your spec here]
|
|
|
|
|
SPEC
|
|
|
|
|
|
|
|
|
|
# 3. Create the milestone (planning only, no execution)
|
|
|
|
|
RESULT=$(sf headless --output-format json --context spec.md new-milestone 2>/dev/null)
|
|
|
|
|
EXIT=$?
|
|
|
|
|
|
|
|
|
|
if [ $EXIT -ne 0 ]; then
|
|
|
|
|
echo "Milestone creation failed"
|
|
|
|
|
echo "$RESULT" | jq .
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
echo "Milestone created. Starting execution..."
|
|
|
|
|
|
|
|
|
|
# 4. Execute step-by-step
|
|
|
|
|
STEP=0
|
|
|
|
|
while true; do
|
|
|
|
|
STEP=$((STEP + 1))
|
|
|
|
|
RESULT=$(sf headless --output-format json next 2>/dev/null)
|
|
|
|
|
EXIT=$?
|
|
|
|
|
|
|
|
|
|
[ $EXIT -ne 0 ] && break
|
|
|
|
|
|
2026-04-25 06:34:49 +02:00
|
|
|
STATE=$(sf headless query)
|
|
|
|
|
PHASE=$(echo "$STATE" | jq -r '.state.phase')
|
|
|
|
|
COST=$(echo "$STATE" | jq -r '.cost.total')
|
2026-04-15 15:37:12 +02:00
|
|
|
|
|
|
|
|
echo "Step $STEP complete. Phase: $PHASE, Cost: \$$COST"
|
|
|
|
|
|
|
|
|
|
[ "$PHASE" = "complete" ] && break
|
|
|
|
|
done
|
|
|
|
|
|
|
|
|
|
echo "Build finished in $STEP steps"
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
## Intervention Patterns
|
|
|
|
|
|
|
|
|
|
### Steer mid-execution
|
|
|
|
|
|
|
|
|
|
If you detect the build going in the wrong direction:
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
# Check what's happening
|
|
|
|
|
sf headless query | jq '{phase: .state.phase, task: .state.activeTask}'
|
|
|
|
|
|
|
|
|
|
# Redirect
|
|
|
|
|
sf headless steer "Use SQLite instead of PostgreSQL for storage"
|
|
|
|
|
|
|
|
|
|
# Continue
|
|
|
|
|
sf headless --output-format json next 2>/dev/null
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Skip a stuck unit
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
sf headless skip
|
|
|
|
|
sf headless --output-format json next 2>/dev/null
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Undo last completed unit
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
sf headless undo --force
|
|
|
|
|
sf headless --output-format json next 2>/dev/null
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
### Force a specific phase
|
|
|
|
|
|
|
|
|
|
```bash
|
|
|
|
|
sf headless dispatch replan # Re-plan the current slice
|
|
|
|
|
sf headless dispatch execute # Skip to execution
|
|
|
|
|
sf headless dispatch uat # Jump to user acceptance testing
|
|
|
|
|
```
|