singularity-forge/sf-orchestrator/workflows/step-by-step.md
ace-pm 6b0ac484ba refactor: update log prefixes and string values from gsd- to sf- namespace
Updates channel prefixes, log messages, comments, and configuration values
across daemon, mcp-server, and related packages to complete the rebrand from
gsd to sf-run naming.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 15:37:12 +02:00

3.6 KiB

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

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

  # Check if milestone complete
  CURRENT_PHASE=$(sf headless query | jq -r '.state.phase')
  if [ "$CURRENT_PHASE" = "complete" ]; then
    TOTAL_COST=$(sf headless query | jq -r '.cost.total')
    echo "Milestone complete. Total cost: \$$TOTAL_COST"
    break
  fi

  # Budget check
  TOTAL_COST=$(sf headless query | jq -r '.cost.total')
  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

  # Progress report
  PROGRESS=$(sf headless query | jq -r '"\(.state.progress.tasks.done)/\(.state.progress.tasks.total) tasks"')
  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:

# 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

  PHASE=$(sf headless query | jq -r '.state.phase')
  COST=$(sf headless query | jq -r '.cost.total')

  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:

# 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

sf headless skip
sf headless --output-format json next 2>/dev/null

Undo last completed unit

sf headless undo --force
sf headless --output-format json next 2>/dev/null

Force a specific phase

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