Created
September 9, 2025 16:38
-
-
Save seabass011/205147a19f91690cc58a94563fc60a12 to your computer and use it in GitHub Desktop.
Nova Demo B - CI Mode with GitHub PR
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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