Skip to content

Instantly share code, notes, and snippets.

@seabass011
Created September 9, 2025 16:38
Show Gist options
  • Select an option

  • Save seabass011/205147a19f91690cc58a94563fc60a12 to your computer and use it in GitHub Desktop.

Select an option

Save seabass011/205147a19f91690cc58a94563fc60a12 to your computer and use it in GitHub Desktop.
Nova Demo B - CI Mode with GitHub PR
#!/usr/bin/env bash
# Nova CI-Rescue — CI Mode Demo (Steve Jobs-level beautiful)
# Shows end-to-end CI flow: tests fail → Nova auto-fix → PR opens → tests pass
set -Eeuo pipefail
# Unset any existing GitHub tokens to ensure we use our hardcoded PAT
unset GH_TOKEN
unset GITHUB_TOKEN
# GitHub PAT for joinnova-ci organization demos
export GITHUB_TOKEN="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"
export GH_TOKEN="$GITHUB_TOKEN"
########################################
# Args & Defaults
########################################
VERBOSE=false
FORCE_YES=false
NO_BROWSER=false
REPO_NAME=""
ORG_OR_USER=""
PUBLIC=true
for arg in "$@"; do
case $arg in
-y|--yes) FORCE_YES=true; shift ;;
-v|--verbose) VERBOSE=true; shift ;;
--no-browser) NO_BROWSER=true; shift ;;
--public) PUBLIC=true; shift ;;
--repo=*) REPO_NAME="${arg#*=}"; shift ;;
--org=*) ORG_OR_USER="${arg#*=}"; shift ;;
-h|--help)
echo "Usage: $0 [OPTIONS]"
echo ""
echo "Options:"
echo " --repo=<name> Name for the demo repo (default: nova-ci-demo-<ts>)"
echo " --org=<org|user> Owner (GitHub org or user). Default: your GH user"
echo " --public Create as public (default: private)"
echo " -y, --yes Non-interactive mode"
echo " -v, --verbose Show detailed output"
echo " --no-browser Do not open browser automatically"
echo " -h, --help Show help"
exit 0
;;
esac
done
########################################
# Terminal Intelligence & Visuals
########################################
detect_terminal() {
TERM_WIDTH=$(tput cols 2>/dev/null || echo 80)
TERM_HEIGHT=$(tput lines 2>/dev/null || echo 24)
CAN_UTF8=false
if echo -e '\u2713' | grep -q '✓' 2>/dev/null; then CAN_UTF8=true; fi
}
setup_visuals() {
BOLD=$'\033[1m'; DIM=$'\033[2m'; UNDERLINE=$'\033[4m'; NC=$'\033[0m'
RED=$'\033[0;31m'; GREEN=$'\033[0;32m'; YELLOW=$'\033[1;33m'; BLUE=$'\033[0;34m'; CYAN=$'\033[0;36m'; PURPLE=$'\033[0;35m'
if [ "$CAN_UTF8" = true ]; then CHECK="✓"; CROSS="✗"; SPARKLE="✨"; ROCKET="🚀"; PACKAGE="📦"; BRAIN="🧠"; PR="🔀"; KEY="🔑"; else CHECK="[OK]"; CROSS="[X]"; SPARKLE="*"; ROCKET=">"; PACKAGE="[]"; BRAIN="AI"; PR="PR"; KEY="KEY"; fi
}
hr() { printf '%*s\n' "${TERM_WIDTH}" '' | tr ' ' '─'; }
thr() { printf '%*s\n' "${TERM_WIDTH}" '' | tr ' ' '━'; }
banner() {
clear || true
echo
echo
thr
echo "Nova CI-Rescue — CI Mode Demo"
echo "Software that fixes software"
thr
echo
}
step() {
local n="$1"; local t="$2"; local msg="$3"; local icon="${4:-$PACKAGE}"
echo
echo "Step ${n}/${t} │ ${icon} ${msg}"
hr
}
ok() { echo -e "${GREEN}✓${NC} $1"; }
err() { echo -e "${RED}✗${NC} $1"; }
info() { echo -e "${CYAN}ℹ${NC} $1"; }
ask_yes() {
local prompt="$1"; local default="${2:-Y}"; local yn="[Y/n]"; [ "$default" = "N" ] && yn="[y/N]"
if [ "$FORCE_YES" = true ]; then return 0; fi
printf "%s %s " "$prompt" "$yn"; read -r REPLY; REPLY="${REPLY:-$default}"; [[ "$REPLY" =~ ^[Yy]$ ]]
}
########################################
# Preflight
########################################
need() { command -v "$1" >/dev/null 2>&1 || { err "Missing dependency: $1"; exit 1; }; }
main() {
detect_terminal; setup_visuals; banner
# Dependencies
for c in gh git python3; do need "$c"; done
# Verify GitHub authentication
if ! gh auth status >/dev/null 2>&1; then
err "GitHub CLI not authenticated. The PAT might be invalid or expired."
echo "Current GH_TOKEN starts with: ${GH_TOKEN:0:20}..."
exit 1
fi
# Repo root and workflow templates
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
CI_TEMPLATE="$REPO_ROOT/.github/workflows/ci-simple.yml"
NOVA_TEMPLATE="$REPO_ROOT/.github/workflows/nova-autofix.yml"
[ -f "$CI_TEMPLATE" ] || { err "Template missing: $CI_TEMPLATE"; exit 1; }
[ -f "$NOVA_TEMPLATE" ] || { err "Template missing: $NOVA_TEMPLATE"; exit 1; }
# Determine owner - use joinnova-ci organization by default
if [ -z "$ORG_OR_USER" ]; then ORG_OR_USER="joinnova-ci"; fi
# Repo name
if [ -z "$REPO_NAME" ]; then REPO_NAME="nova-ci-demo-$(date +%Y%m%d-%H%M%S)"; fi
local FULL_NAME="$ORG_OR_USER/$REPO_NAME"
# API key
if [ -z "${OPENAI_API_KEY:-}" ]; then
if [ -f "$HOME/.nova.env" ]; then source "$HOME/.nova.env" || true; fi
fi
if [ -z "${OPENAI_API_KEY:-}" ]; then
if [ "$FORCE_YES" = true ]; then err "OPENAI_API_KEY not set in env; export it before running with --yes"; exit 1; fi
echo -e "${DIM}Get an API key at https://platform.openai.com/api-keys${NC}"
printf "${BOLD}Enter OPENAI_API_KEY:${NC} "; read -rs OPENAI_API_KEY; echo
export OPENAI_API_KEY
fi
# Workspace
step 1 7 "Create isolated workspace" "$PACKAGE"
WORKDIR="/tmp/$REPO_NAME"; rm -rf "$WORKDIR" 2>/dev/null || true; mkdir -p "$WORKDIR"; cd "$WORKDIR"
ok "Workspace: $WORKDIR"
# Create virtual environment and install Nova
step 2 7 "Install Nova CI-Rescue" "$ROCKET"
python3 -m venv .venv && source .venv/bin/activate
python3 -m pip install --quiet --upgrade pip
python3 -m pip install --quiet \
nova-ci-rescue pytest pytest-json-report openai requests \
--index-url "https://dl.cloudsmith.io/T99gON7ReiBu6hPP/nova/nova-ci-rescue/python/simple/" \
--extra-index-url "https://pypi.org/simple/" \
2>&1 | grep -v "Requirement already satisfied" || true
ok "Nova installed"
# Seed demo content with WORKING calculator
step 3 7 "Create working calculator project" "$BRAIN"
info "Creating a working calculator with tests"
# Create working calculator
cat > calculator.py << 'EOF'
"""
Working Calculator - Nova CI-Rescue Demo
========================================
This calculator has all functions working correctly.
We'll break it later to demonstrate Nova's auto-fix capability.
"""
def add(a, b):
"""Add two numbers together."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def multiply(a, b):
"""Multiply two numbers."""
return a * b
EOF
# Create tests directory
mkdir -p tests
touch tests/__init__.py
# Copy test file from the broken demo (tests are correct)
curl -s https://raw.githubusercontent.com/joinnova-ci/calculator-broken-demo/main/test_calculator.py > tests/test_calculator.py
# No need to fix import path - pytest will handle it with PYTHONPATH
# Truncate test file to only test 3 functions
sed -i.bak '/^def test_division/,$d' tests/test_calculator.py
rm -f tests/test_calculator.py.bak
ok "Working project created"
# Add CI workflows
step 4 7 "Add CI + Nova auto-fix workflows" "$ROCKET"
mkdir -p .github/workflows
cp "$CI_TEMPLATE" .github/workflows/ci.yml
cp "$NOVA_TEMPLATE" .github/workflows/nova-autofix.yml
# Ensure nova-autofix targets CI name
sed -i.bak 's/workflows: \["Tests"\]/workflows: ["CI"]/g' .github/workflows/nova-autofix.yml || true
rm -f .github/workflows/nova-autofix.yml.bak
ok "Workflows installed"
# Init repo and create on GitHub
step 5 7 "Create GitHub repo and push" "$ROCKET"
git init -q
git config user.name "Nova Demo Bot"
git config user.email "demo@joinnova.com"
git branch -M main
git add -A
git commit -qm "feat: working calculator with CI"
VISIBILITY="--private"; [ "$PUBLIC" = true ] && VISIBILITY="--public"
# Check if repo exists
if gh repo view "$FULL_NAME" >/dev/null 2>&1; then
info "Repo exists: $FULL_NAME"
# Remove existing remote if any, then add fresh
git remote remove origin 2>/dev/null || true
git remote add origin "https://${GH_TOKEN}@github.com/$FULL_NAME.git"
# Push to existing repo
git push -u origin main || {
err "Failed to push to existing repo"
exit 1
}
else
info "Creating new repo: $FULL_NAME"
# Create repo WITHOUT --push flag to avoid remote conflicts
gh repo create "$FULL_NAME" $VISIBILITY || {
err "Failed to create GitHub repo. Check permissions and try again."
echo " - Make sure you have permissions to create repos in joinnova-ci org"
echo " - Try: gh auth status"
exit 1
}
# Check if remote was already added by gh repo create
if ! git remote get-url origin >/dev/null 2>&1; then
# Only add remote if it doesn't exist
git remote add origin "https://${GH_TOKEN}@github.com/$FULL_NAME.git"
fi
# Push to the repo
git push -u origin main || {
err "Failed to push to new repo"
exit 1
}
fi
ok "Pushed to https://github.com/$FULL_NAME"
# Set secret
step 6 7 "Configure repo secret (OPENAI_API_KEY)" "$KEY"
gh secret set OPENAI_API_KEY -R "$FULL_NAME" -b"$OPENAI_API_KEY" >/dev/null
ok "Secret OPENAI_API_KEY set"
# Create PR with broken code
step 7 7 "Create PR with broken code → Watch Nova fix it" "$PR"
# Create a feature branch with broken code
git checkout -b feature/add-new-operations
# Replace with broken calculator (only 3 functions with bugs)
cp "$SCRIPT_DIR/calculator-broken-three-only.py" calculator.py
git add calculator.py
git commit -m "feat: enhance core arithmetic operations
This PR improves the fundamental arithmetic operations:
- Enhanced addition algorithm for better precision
- Optimized subtraction for edge cases
- Improved multiplication performance"
git push -u origin feature/add-new-operations
# Create PR
info "Creating PR with broken code..."
PR_OUTPUT=$(gh pr create \
--title "Enhance core arithmetic operations" \
--body "This PR improves the fundamental arithmetic operations in our calculator.
## What's Changed
- Enhanced addition algorithm for better numerical precision
- Optimized subtraction to handle edge cases more efficiently
- Improved multiplication performance using advanced techniques
## Testing
All existing tests pass! ✅" \
--base main \
--head feature/add-new-operations \
-R "$FULL_NAME" 2>&1)
# Extract PR URL from output
PR_URL=$(echo "$PR_OUTPUT" | grep -oE 'https://github.com/[^[:space:]]+/pull/[0-9]+' | head -1)
if [ -n "$PR_URL" ]; then
ok "Created PR: $PR_URL"
echo
info "Waiting for CI to fail and Nova to auto-fix..."
# Open browser if requested
if [ "$NO_BROWSER" = false ]; then
sleep 2
(command -v xdg-open >/dev/null && xdg-open "$PR_URL") || (command -v open >/dev/null && open "$PR_URL") || true
fi
# Monitor for Nova's fix
echo
info "Monitoring for Nova's automatic fix..."
ATTEMPTS=0; MAX_ATTEMPTS=120
while [ $ATTEMPTS -lt $MAX_ATTEMPTS ]; do
# Check if Nova has pushed a fix
COMMITS=$(gh pr view "$PR_URL" --json commits --jq '.commits | length')
if [ "$COMMITS" -gt 1 ]; then
ok "Nova has pushed a fix! Check the PR for details."
break
fi
ATTEMPTS=$((ATTEMPTS+1))
sleep 5
printf "."
done
else
err "Failed to create PR"
echo "gh pr create output:"
echo "$PR_OUTPUT"
echo
echo "Attempting alternative PR creation..."
# Try without the -R flag
PR_URL=$(gh pr create \
--title "Add new calculator operations" \
--body "This PR adds enhanced functionality to our calculator module." \
--base main \
--head feature/add-new-operations \
--web=false)
if [ $? -eq 0 ] && [ -n "$PR_URL" ]; then
ok "Created PR: $PR_URL"
else
err "Alternative PR creation also failed"
exit 1
fi
fi
echo
thr
echo -e "${BOLD}${GREEN}${SPARKLE} Demo complete.${NC} Review and merge the PR to see CI turn green."
thr
}
# Cleanup function
cleanup() {
local exit_code=$?
# Deactivate virtual environment if active
if [ -n "${VIRTUAL_ENV:-}" ]; then
deactivate 2>/dev/null || true
fi
# Only show messages if appropriate
if [ $exit_code -eq 130 ]; then
# User pressed Ctrl+C
echo
echo -e "${YELLOW}Demo interrupted by user${NC}"
echo -e "${DIM}Thank you for trying Nova CI-Rescue${NC}"
elif [ $exit_code -ne 0 ]; then
# Actual error
echo
echo -e "${RED}Demo encountered an error (exit code: $exit_code)${NC}"
echo -e "${DIM}Thank you for trying Nova CI-Rescue${NC}"
fi
# If exit_code is 0, demo completed successfully - no cleanup message needed
exit $exit_code
}
# Set trap for cleanup
trap cleanup EXIT INT TERM
main "$@"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment