Home

02 Code Quality in Practice: Automation and CI/CD - Building Quality into Every Commit

lecture ci-cd github-actions automation workflow devops quality testing

1. Introduction: From Manual Checks to Automated Workflows

In Chapter 02 (Code Quality in Practice), you learned about code quality tools like Ruff and Pyright. In Chapter 02 (Feature Branch Development), you learned about feature branch workflows and pull requests. Now you can:

# Create a feature branch
uv run ruff check .
uv run ruff format .
uv run pyright

# Create a pull request

But here’s the problem: What happens when:

The answer: Automation. Specifically, Continuous Integration and Continuous Deployment (CI/CD).

This lecture is about moving from “please remember to run checks before creating a PR” to “checks run automatically on every PR, and code that doesn’t pass quality checks cannot be merged.”

We’ll build on the feature branch workflow you learned in Part 1 and add automated quality gates that ensure every change meets your standards before it reaches the main branch.


2. Learning Objectives

By the end of this lecture, you will:

  1. Understand CI/CD concepts and why they’re fundamental to modern software development
  2. Learn GitHub Actions - GitHub’s built-in automation platform
  3. Build automated quality checks that run on every push and pull request
  4. Create development workflows that enforce quality standards automatically
  5. Implement pre-commit hooks for local validation before code reaches GitHub
  6. Design a complete quality pipeline from local development to production

3. Part 1: What is CI/CD?

3.1 The Problem CI/CD Solves

Traditional Software Development (Pre-CI/CD):

Developer A writes code → Works on their machine ✅
Developer B writes code → Works on their machine ✅
Merge both → Integration Hell 🔥
                ↓
- Conflicting dependencies
- Different Python versions
- Untested integration points
- Manual testing takes days
- Deployment is a weekend event

Real Example:

# Developer A's machine (Python 3.12)
def process_data(items: list[str]) -> list[str]:
    return [item.removeprefix("data_") for item in items]  # Python 3.9+ feature

# Developer B's machine (Python 3.8)
# This code crashes with AttributeError: 'str' object has no attribute 'removeprefix'

Both developers think their code works. Only when they integrate does the problem appear.

3.2 What is Continuous Integration (CI)?

Continuous Integration (CI) is the practice of automatically integrating code changes from multiple contributors several times a day into a shared repository.

Key Principles:

  1. Frequent Integration - Merge code changes daily (or more often)
  2. Automated Build - Every integration triggers an automated build
  3. Automated Testing - Build includes running tests
  4. Fast Feedback - Developers know within minutes if integration breaks something
  5. Shared Responsibility - Everyone commits to fixing broken builds immediately

The CI Workflow:

Developer writes code
        ↓
Push to GitHub
        ↓
Automated checks run (CI)
    - Install dependencies
    - Run linters
    - Run tests
    - Check formatting
    - Type checking
        ↓
    Pass ✅ → Allow merge
    Fail ❌ → Block merge, notify developer

3.3 What is Continuous Deployment (CD)?

Continuous Deployment (CD) extends CI by automatically deploying every change that passes all tests to production.

Two Flavors:

Continuous Delivery:

Continuous Deployment:

The CD Workflow:

CI Pipeline Passes ✅
        ↓
Automated Deployment
    - Build production artifacts
    - Run integration tests
    - Deploy to staging
    - Run smoke tests
    - Deploy to production
        ↓
    Success ✅ → Code is live
    Fail ❌ → Automatic rollback

3.4 Benefits of CI/CD

1. Catch Issues Early

2. Faster Development

3. Higher Quality

4. Reduced Risk

5. Better Collaboration

Real-World Impact:

Metric Without CI/CD With CI/CD
Time to detect bugs Days to weeks Minutes
Deployment frequency Weekly/monthly Multiple times daily
Failed deployments 20-30% <5%
Time to fix production issues Hours to days Minutes
Developer satisfaction Lower (manual work) Higher (automation)

4. Part 2: Introduction to GitHub Actions

4.1 What is GitHub Actions?

GitHub Actions is GitHub’s built-in automation platform that lets you define workflows triggered by events in your repository.

Key Concepts:

Workflow:

Event:

Job:

Step:

Action:

Runner:

4.2 Workflow Syntax

Here’s a minimal GitHub Actions workflow:

name: My First Workflow                    # Workflow name (appears in GitHub UI)

on: [push, pull_request]                   # Events that trigger this workflow

jobs:                                       # Define jobs
  my-job:                                   # Job ID
    runs-on: ubuntu-latest                  # Runner to use
    
    steps:                                  # Steps in this job
      - name: Checkout code                 # Step 1: Get the code
        uses: actions/checkout@v4           # Use a predefined action
      
      - name: Run a command                 # Step 2: Run a shell command
        run: echo "Hello, World!"           # The command to run

Breaking it down:

name: My First Workflow
on: [push, pull_request]
jobs:
  my-job:
    runs-on: ubuntu-latest
steps:
  - name: Checkout code
    uses: actions/checkout@v4
  - name: Run a command
    run: echo "Hello, World!"

4.3 Finding and Choosing the Right Actions

One of the most powerful features of GitHub Actions is the GitHub Actions Marketplace - a vast ecosystem of reusable actions created by GitHub and the community. But how do you find the right action and know which version to use?

4.3.1 The GitHub Actions Marketplace

Where to find actions:

🔗 GitHub Marketplace - The official marketplace for GitHub Actions

How to search:

  1. Go to the Marketplace: Visit github.com/marketplace
  2. Filter by “Actions”: Use the left sidebar to show only Actions
  3. Search by purpose: Keywords like “Python”, “deploy”, “test”, “Docker”, etc.
  4. Sort by popularity: Choose “Most installed” to see battle-tested actions

Example search flow:

Search: "Python setup" → Find: actions/setup-python
Search: "Docker build" → Find: docker/build-push-action
Search: "AWS deploy" → Find: aws-actions/configure-aws-credentials

4.3.2 Evaluating Action Quality

Not all actions are created equal. Here’s how to evaluate if an action is trustworthy:

✅ Quality Indicators:

1. Official Actions (Highest Trust)

2. Verified Creators

3. Community Metrics

4. Documentation Quality

❌ Red Flags:

4.3.3 Understanding Action Versions

Actions use Git tags for versioning. You have several options:

Version Formats:

# ✅ RECOMMENDED: Major version (tracks latest compatible)
uses: actions/checkout@v4

# ⚠️ SPECIFIC: Exact version (safest, but misses updates)
uses: actions/checkout@v4.1.1

# ⚠️ BRANCH: Bleeding edge (can break without warning)
uses: actions/checkout@main

# ❌ NEVER: SHA only (hard to maintain)
uses: actions/checkout@8ade135a41bc03ea155e62e844d188df1ea18608

Best Practice Recommendations:

Version Type Syntax Updates Stability When to Use
Major version @v4 Auto-updates to v4.x.x ✅ High Default choice - Gets patches & features, avoids breaking changes
Exact version @v4.1.1 Never changes ✅ Highest Critical workflows, compliance requirements, reproducibility
Branch name @main Every commit ❌ Low Testing unreleased features (not production!)
Commit SHA @8ade135 Never changes ✅ Highest Security-critical workflows (but hard to maintain)

Why use major versions (@v4)?

Example of version evolution:

# You write this in 2025:
uses: actions/checkout@v4

# GitHub automatically uses latest v4.x.x:
# Jan 2025: v4.1.0 ✓
# Mar 2025: v4.1.5 ✓ (security patch - automatically applied!)
# Jun 2025: v4.2.0 ✓ (new feature - automatically available!)
# Jan 2026: v5.0.0 ✗ (breaking change - you need to update manually)

4.3.4 How to Find the Latest Version

Method 1: Marketplace Page

  1. Go to the action’s Marketplace page
  2. Look for “Latest version” badge in the sidebar
  3. Example: actions/checkout

Method 2: GitHub Releases

  1. Visit the action’s GitHub repository
  2. Click “Releases” in the right sidebar
  3. The top release is the latest version
  4. Example: github.com/actions/checkout/releases

Method 3: Repository Tags

  1. Go to the action’s repository
  2. Click the branch/tag dropdown
  3. Select “Tags” tab
  4. See all available versions sorted by date

Pro Tip: Check the README Most well-maintained actions show usage examples with the latest version in their README!

4.3.5 Common Actions You’ll Use

Here are the most popular actions you’ll encounter in this course and beyond:

Essential Actions for Python Projects:

# Clone your repository
- uses: actions/checkout@v4

# Set up uv (Python package manager)
- uses: astral-sh/setup-uv@v4

# Cache dependencies for speed
- uses: actions/cache@v4

# Upload artifacts (build outputs, logs, etc.)
- uses: actions/upload-artifact@v4

# Download artifacts from previous jobs
- uses: actions/download-artifact@v4

Why astral-sh/setup-uv instead of actions/setup-python?

In this course, we use uv as our Python package manager. The astral-sh/setup-uv@v4 action:

For other languages:

# Set up Node.js
- uses: actions/setup-node@v4

# Set up Go
- uses: actions/setup-go@v5

# Set up Java
- uses: actions/setup-java@v4

Where to find these:

4.3.6 Security Best Practices

Always review actions before using them!

# ❌ DON'T: Use random actions without checking
- uses: unknown-user/sketchy-action@v1

# ✅ DO: Use official or verified actions
- uses: actions/checkout@v4  # GitHub official
- uses: docker/build-push-action@v5  # Docker official (verified)

Security checklist:

Enable Dependabot for GitHub Actions:

Create .github/dependabot.yml:

version: 2
updates:
  - package-ecosystem: "github-actions"
    directory: "/"
    schedule:
      interval: "weekly"

This will automatically create PRs when action versions are outdated!

4.3.7 Practical Example: Choosing an Action

Scenario: You need to set up uv (Python package manager) in your workflow.

Step 1: Search

Step 2: Evaluate Results

Step 3: Check Latest Version

Step 4: Read Documentation

Step 5: Use in Your Workflow

steps:
  - uses: actions/checkout@v4
  
  - name: Set up uv
    uses: astral-sh/setup-uv@v4  # Use major version
  
  - name: Install dependencies
    run: uv sync --dev
  
  - name: Run checks
    run: uv run ruff check .

Why this works:

4.3.8 Quick Reference: Action Selection Guide

When you need to…

For Python Projects (This Course):

For Other Languages:

For Deployment:

Golden Rule: Start with official actions/* actions, then look for verified creators (like astral-sh for uv), then carefully evaluate community actions.


4.4 Creating Custom Actions

While we primarily use existing actions in this course, it’s worth knowing that you can create your own custom GitHub Actions for specialized workflows.

4.4.1 When to Create Custom Actions

Consider creating a custom action when:

Don’t create a custom action when:

4.4.2 Types of Custom Actions

1. JavaScript Actions

2. Docker Container Actions

3. Composite Actions

Example: Simple Composite Action

Create .github/actions/setup-project/action.yml:

name: 'Setup Project'
description: 'Install uv and project dependencies'

runs:
  using: 'composite'
  steps:
    - name: Set up uv
      uses: astral-sh/setup-uv@v4
      shell: bash
    
    - name: Install dependencies
      run: uv sync --dev
      shell: bash
    
    - name: Show environment info
      run: |
        uv --version
        uv run python --version
      shell: bash

Use it in workflows:

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: ./.github/actions/setup-project  # Use local action
      - run: uv run ruff check .

4.4.3 Publishing Custom Actions

For organizational use:

For public sharing:

4.4.4 Resources for Learning More

Creating custom actions is beyond the scope of this course, but if you’re interested:

For this course: Focus on using existing actions effectively. Custom actions are an advanced topic for when you’ve mastered workflow basics and have specific needs that existing actions don’t fulfill.


4.5 Common Workflow Triggers

# Trigger on push to specific branches
on:
  push:
    branches:
      - main
      - develop

# Trigger on pull requests targeting specific branches
on:
  pull_request:
    branches:
      - main

# Trigger on multiple events
on: [push, pull_request, workflow_dispatch]

# Scheduled workflows (cron syntax)
on:
  schedule:
    - cron: '0 0 * * *'  # Run daily at midnight UTC

# Manual trigger (workflow_dispatch)
on:
  workflow_dispatch:    # Adds "Run workflow" button in GitHub UI
    inputs:
      environment:
        description: 'Environment to deploy to'
        required: true
        type: choice
        options:
          - staging
          - production

4.6 GitHub Actions Pricing

For Public Repositories:

For Private Repositories:

For This Course:


5. Part 3: Building Your First GitHub Actions Workflow

Let’s build a real workflow for the Road Profile Viewer project that runs quality checks.

5.1 Step 1: Create the Workflow File

Create .github/workflows/quality.yml in your repository:

name: Code Quality

on:
  pull_request:
    branches: [ main ]

jobs:
  quality:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up uv
        uses: astral-sh/setup-uv@v4

      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}

      - name: Install dependencies
        run: uv sync --dev

      - name: Run Ruff linter
        run: uv run ruff check .

      - name: Check formatting
        run: uv run ruff format --check .

      - name: Run Pyright
        run: uv run pyright

5.2 Understanding Each Step

Step 1: Checkout Repository

- name: Checkout repository
  uses: actions/checkout@v4

Step 2: Set Up uv

- name: Set up uv
  uses: astral-sh/setup-uv@v4

Step 3: Cache Dependencies (Speed Optimization!)

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: ~/.cache/uv
    key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}

This step is a game-changer for CI speed. Here’s what happens:

First time your workflow runs:

Install dependencies → Takes ~30-45 seconds → Cache saved ✅

Subsequent workflow runs (if uv.lock unchanged):

Restore from cache → Takes ~3-5 seconds → Continue! 🚀

Why this works:

Remember that uv.lock file that always gets created in your repository? It’s not just documentation—it’s a precise snapshot of every package and version your project needs. The cache uses this file as its “fingerprint”:

The relief moment: “Wait, so uv.lock isn’t just clutter—it’s saving me 30 seconds on every CI run?” Exactly! This is why uv always creates it. The lock file ensures:

  1. Reproducible builds (everyone gets the same versions)
  2. Fast CI (cache knows exactly what to restore)
  3. Dependency tracking (see what changed between versions)

Pro tip: Never delete uv.lock or add it to .gitignore. It’s a critical part of your project’s infrastructure!

Step 4: Install Dependencies

- name: Install dependencies
  run: uv sync --dev

Steps 5-7: Run Quality Checks

- name: Run Ruff linter
  run: uv run ruff check .

- name: Check formatting
  run: uv run ruff format --check .

- name: Run Pyright
  run: uv run pyright

5.3 Committing and Pushing the Workflow

# Create the directory structure
mkdir -p .github/workflows

# Create the workflow file
# (paste the YAML content above)

# Commit and push
git add .github/workflows/quality.yml
git commit -m "Add GitHub Actions workflow for code quality"
git push origin main

What happens next:

  1. GitHub detects the new workflow file
  2. Workflow triggers on this push
  3. You can watch it run in the “Actions” tab
  4. Results appear next to your commit

5.4 Viewing Workflow Results

On GitHub:

  1. Go to your repository
  2. Click “Actions” tab
  3. See all workflow runs
  4. Click a run to see detailed logs
  5. Each step shows its output

Visual Indicators:

5.5 When a Check Fails

Example: Ruff finds style issues

Run uv run ruff check .
src/main.py:16:1: E401 [*] Multiple imports on one line
src/main.py:29:5: E225 [*] Missing whitespace around operator
Found 2 errors.
Error: Process completed with exit code 1.

GitHub’s Response:

Developer’s Response:

  1. Fix the issues locally
  2. Run checks locally to verify fix
  3. Push the fix
  4. Workflow runs automatically on the new commit
  5. If it passes: ✅ Ready to merge

6. Part 4: Advanced Workflow Patterns

6.1 Running Multiple Jobs in Parallel

Speed up your workflow by running independent checks in parallel:

name: Code Quality

on: [push, pull_request]

jobs:
  lint:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      - run: uv run ruff check .
  
  format:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      - run: uv run ruff format --check .
  
  typecheck:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      - run: uv run pyright

Benefits:

Drawback:

6.2 Matrix Testing (Multiple Python Versions)

Test your code against multiple Python versions using uv’s built-in Python management:

name: Test Multiple Python Versions

on: [push, pull_request]

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.12', '3.13']
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: astral-sh/setup-uv@v4
      
      - name: Set Python version
        run: uv python pin ${{ matrix.python-version }}
      
      - name: Install dependencies
        run: uv sync --dev
      
      - name: Run checks
        run: |
          uv run ruff check .
          uv run pyright

What happens:

Why use uv for Python version management?

6.3 Caching Dependencies for Speed

Speed up workflows by caching dependencies:

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - uses: astral-sh/setup-uv@v4
      
      - name: Cache uv dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
          restore-keys: |
            ${{ runner.os }}-uv-
      
      - run: uv sync --dev
      - run: uv run ruff check .

How it works:

6.4 Conditional Steps

Run steps only under certain conditions:

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      
      - name: Run linter
        run: uv run ruff check .
      
      - name: Auto-fix and commit (only on main branch)
        if: github.ref == 'refs/heads/main'
        run: |
          uv run ruff check --fix .
          git config user.name "GitHub Actions"
          git config user.email "actions@github.com"
          git add -A
          git diff --quiet && git diff --staged --quiet || git commit -m "Auto-fix: Apply Ruff suggestions"
          git push

Use cases:

6.5 Using Secrets

Store sensitive data securely:

jobs:
  deploy:
    runs-on: ubuntu-latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to production
        env:
          API_KEY: ${{ secrets.API_KEY }}
          DATABASE_URL: ${{ secrets.DATABASE_URL }}
        run: |
          echo "Deploying with API key: $API_KEY"
          # Actual deployment commands

Managing Secrets:

  1. Go to repository Settings → Secrets and variables → Actions
  2. Click “New repository secret”
  3. Add name and value
  4. Reference in workflow with ${{ secrets.SECRET_NAME }}
  5. Secret values are never exposed in logs

7. Part 5: Using GitHub Copilot to Create Workflows

7.1 Why Learn Both Manual and AI-Assisted Workflow Creation?

You’ve learned the fundamentals—now it’s time to leverage AI.

In Parts 3 and 4, you learned how to manually write GitHub Actions workflows. This foundational knowledge is critical because:

Understanding enables verification - You can review and validate what Copilot generates
Better prompts = better results - Knowing YAML syntax helps you write precise prompts
Debugging requires knowledge - When workflows fail, you need to understand why
AI is a tool, not magic - You guide the AI with your expertise

But we’re in the age of LLMs. Now that you understand workflows, let’s use GitHub Copilot to accelerate your work.

7.2 GitHub Copilot in VS Code

We use GitHub Copilot throughout this course as your AI pair programmer. For workflow creation, Copilot can:

Two ways to use Copilot for workflows:

  1. Inline Suggestions - As you type YAML, Copilot suggests completions
  2. Copilot Chat - Describe what you want, get complete workflows

7.3 Practical Example: Creating a Workflow with Copilot Chat

Let’s create the same quality workflow from Part 3, but using Copilot Chat.

Step 1: Open Copilot Chat

In VS Code:

  1. Press Ctrl+Alt+I (Windows/Linux) or Cmd+Alt+I (Mac)
  2. Or click the chat icon in the sidebar
  3. Or use Command Palette: “GitHub Copilot: Open Chat”

Step 2: Write a Clear Prompt

The better your prompt, the better the result. Use your workflow knowledge to write precise prompts.

❌ Vague prompt:

Create a GitHub Actions workflow for my Python project

✅ Specific prompt:

Create a GitHub Actions workflow for a Python project that:
- Triggers on push to main and pull requests
- Uses astral-sh/setup-uv@v4 to set up uv
- Installs dependencies with uv sync --dev
- Runs ruff check and ruff format --check
- Runs pyright type checker
- Uses Ubuntu runner
- Caches uv dependencies for faster runs

Save it as .github/workflows/quality.yml

Step 3: Review and Refine

Copilot generates:

name: Code Quality

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  quality:
    runs-on: ubuntu-latest
    
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
      
      - name: Set up uv
        uses: astral-sh/setup-uv@v4
      
      - name: Cache uv dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}
          restore-keys: |
            ${{ runner.os }}-uv-
      
      - name: Install dependencies
        run: uv sync --dev
      
      - name: Run Ruff linter
        run: uv run ruff check .
      
      - name: Check formatting
        run: uv run ruff format --check .
      
      - name: Run Pyright
        run: uv run pyright

✅ This is excellent! But always review:

If something needs adjustment:

Can you also trigger this workflow on pushes to the develop branch?

Copilot updates the workflow:

on:
  push:
    branches: [ main, develop ]  # ✅ Added develop
  pull_request:
    branches: [ main ]

Step 4: Create the File

Ask Copilot to create the file:

Create the file .github/workflows/quality.yml with this content

Copilot will:

  1. Create the directory structure if needed
  2. Generate the file
  3. Add the workflow content

Or create it manually:

  1. Create .github/workflows/ directory
  2. Create quality.yml file
  3. Paste the generated content

7.4 Advanced Prompting Techniques

Leverage your workflow knowledge for better prompts.

Technique 1: Reference Specific Actions

Instead of:

Add Python setup to my workflow

Use your knowledge:

Add astral-sh/setup-uv@v4 to install uv, then use uv python pin to set Python 3.12

Technique 2: Specify Matrix Testing

Create a workflow that tests my Python project against Python 3.12 and 3.13 
using a matrix strategy. Use uv python pin to switch versions.

Copilot generates:

jobs:
  test:
    runs-on: ubuntu-latest
    strategy:
      matrix:
        python-version: ['3.12', '3.13']
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - name: Set Python version
        run: uv python pin $
      - run: uv sync --dev
      - run: uv run pytest

Technique 3: Request Conditional Logic

Add a deployment step that only runs on main branch after tests pass. 
Use the "needs" keyword to create a dependency.

Technique 4: Ask for Explanations

Explain what this workflow does and why we use actions/cache@v4

Copilot explains the workflow, helping you learn while building.

7.5 Debugging Workflows with Copilot

When a workflow fails, use Copilot to debug.

Scenario: Workflow Error

You see this error in GitHub Actions:

Error: Unable to resolve action `astral-sh/setup-uv@v3`, 
unable to find version `v3`

Ask Copilot:

My workflow is failing with "unable to find version v3" for astral-sh/setup-uv. 
What's wrong and how do I fix it?

Copilot responds:

The error indicates that v3 doesn't exist for astral-sh/setup-uv. 
The latest version is @v4. Update your workflow:

- uses: astral-sh/setup-uv@v4  # Changed from @v3

You can verify the latest version at:
https://github.com/astral-sh/setup-uv/releases

Ask for Optimization

My workflow takes 5 minutes to run. How can I make it faster?

Copilot suggests:

7.6 Best Practices: Combining Manual Knowledge with AI

Do:

Learn the fundamentals first (you did this in Parts 3-4!)
Write specific prompts using correct terminology
Review all generated code - you’re responsible for what runs
Verify action versions in the marketplace
Test workflows before merging to main
Ask Copilot to explain unfamiliar patterns
Iterate with Copilot - refine prompts based on results

Don’t:

Blindly trust generated code without review
Skip learning fundamentals and only use AI
Use outdated actions Copilot suggests (verify versions!)
Ignore security implications of generated workflows
Accept the first suggestion - iterate for better results

7.7 Practical Exercise: Create Your Own Workflow

Now you try! Use Copilot to create a workflow for your Road Profile Viewer project.

Your task:

  1. Open Copilot Chat in VS Code
  2. Write a prompt for a workflow that:
    • Runs on pull requests to main
    • Sets up uv
    • Installs dependencies
    • Runs ruff, pyright, and pytest
    • Uploads test coverage as an artifact
  3. Review the generated workflow
  4. Ask Copilot to improve it (add caching, better naming, etc.)
  5. Create the file and test it

Example prompt to get started:

Create a GitHub Actions workflow for my Python project that runs quality checks 
and tests on pull requests. Use uv for dependency management, run ruff for 
linting and formatting checks, pyright for type checking, and pytest for tests. 
Upload test coverage results as an artifact. Add caching for faster runs.

7.8 The Modern Development Workflow

This is how modern software engineering works:

1. Understand fundamentals ✅ (Parts 3-4)
        ↓
2. Use AI to accelerate ⚡ (This part!)
        ↓
3. Review with expertise 🧠 (Your knowledge)
        ↓
4. Iterate and improve 🔄 (Refine prompts)
        ↓
5. Ship quality code 🚀

Key insight: AI amplifies your skills. The better you understand workflows, the better you can use Copilot to create them.

7.9 When to Use Manual vs. AI-Assisted Creation

Use manual creation when:

Use AI-assisted creation when:

Best approach: Combine both! Use your knowledge to guide AI, and use AI to speed up your work.


8. Part 6: Pre-commit Hooks - Local Quality Gates

8.1 Why Pre-commit Hooks?

Problem: Waiting for CI to fail is slow:

Write code → Commit → Push → CI runs (2 minutes) → CI fails ❌
    ↓
Fix issues → Commit → Push → CI runs (2 minutes) → CI passes ✅

Total time wasted: 4+ minutes

Solution: Catch issues before they reach GitHub using pre-commit hooks.

8.2 What are Pre-commit Hooks?

Pre-commit hooks are scripts that run automatically when you try to commit code.

Workflow:

git commit
    ↓
Pre-commit hook runs
    - Checks code quality
    - Runs linters
    - Checks formatting
    ↓
Pass ✅ → Commit proceeds
Fail ❌ → Commit blocked, fix issues

Benefits:

8.3 Installing Pre-commit

# Install pre-commit as a tool (not project dependency)
uv tool install pre-commit

# Verify installation
pre-commit --version

8.4 Configuring Pre-commit

Create .pre-commit-config.yaml in your project root:

repos:
  # Ruff linter and formatter
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  
  # Pyright type checker
  - repo: https://github.com/RobertCraigie/pyright-python
    rev: v1.1.388
    hooks:
      - id: pyright
  
  # General checks
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v5.0.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files

Install the hooks:

pre-commit install

This creates a .git/hooks/pre-commit script that runs automatically.

8.5 Using Pre-commit

Automatic on commit:

git add .
git commit -m "Add new feature"

# Pre-commit runs automatically:
# - Ruff linter checks code
# - Ruff formatter checks formatting
# - Pyright checks types
# - General checks run
#
# If all pass: Commit succeeds
# If any fail: Commit blocked

Manual run:

# Run on all files
pre-commit run --all-files

# Run specific hook
pre-commit run ruff --all-files

Skipping hooks (emergency only):

git commit --no-verify -m "Emergency hotfix"

8.6 Pre-commit Best Practices

DO:

DON’T:


9. Part 7: Building a Complete Quality Pipeline

Let’s combine everything into a comprehensive quality pipeline.

9.1 The Complete Setup

File: pyproject.toml

[project]
name = "road-profile-viewer"
version = "0.1.0"
description = "Interactive road profile visualization"
requires-python = ">=3.12"

dependencies = [
    "numpy>=2.0.0",
    "dash>=2.18.0",
    "plotly>=5.24.0",
]

[build-system]
requires = ["uv>=0.4.0"]
build-backend = "uv"

[tool.uv.dev-dependencies]
ruff = ">=0.8.0"
pyright = ">=1.1.388"
pre-commit = ">=4.0.0"

[tool.ruff]
line-length = 88
target-version = "py312"

[tool.ruff.lint]
select = [
    "E",   # pycodestyle errors
    "W",   # pycodestyle warnings
    "F",   # Pyflakes
    "I",   # isort
    "N",   # pep8-naming
    "B",   # flake8-bugbear
    "C4",  # flake8-comprehensions
    "UP",  # pyupgrade
]

[tool.pyright]
typeCheckingMode = "standard"
pythonVersion = "3.12"

File: .pre-commit-config.yaml

repos:
  - repo: https://github.com/astral-sh/ruff-pre-commit
    rev: v0.8.0
    hooks:
      - id: ruff
        args: [--fix]
      - id: ruff-format
  
  - repo: https://github.com/RobertCraigie/pyright-python
    rev: v1.1.388
    hooks:
      - id: pyright

File: .github/workflows/quality.yml

name: Code Quality

on:
  pull_request:
    branches: [ main ]

jobs:
  quality:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout repository
        uses: actions/checkout@v4

      - name: Set up uv
        uses: astral-sh/setup-uv@v4

      - name: Cache dependencies
        uses: actions/cache@v4
        with:
          path: ~/.cache/uv
          key: ${{ runner.os }}-uv-${{ hashFiles('**/uv.lock') }}

      - name: Install dependencies
        run: uv sync --dev

      - name: Run Ruff linter
        run: uv run ruff check .

      - name: Check formatting
        run: uv run ruff format --check .

      - name: Run Pyright
        run: uv run pyright

9.2 The Complete Developer Workflow

1. Initial Setup (once per developer):

# Clone repository
git clone https://github.com/your-username/road-profile-viewer.git
cd road-profile-viewer

# Set up environment
uv sync --dev

# Install pre-commit hooks
pre-commit install

2. Daily Development:

# Create feature branch
git checkout -b feature/new-visualization

# Write code with IDE feedback
# - VS Code + Pylance shows type errors in real-time
# - Ruff extension highlights style issues

# Run checks manually (optional, pre-commit will do this)
uv run ruff check --fix .
uv run ruff format .
uv run pyright

# Commit code
git add .
git commit -m "Add new visualization"

# Pre-commit hooks run automatically:
# ✅ Ruff linter - passed
# ✅ Ruff format - passed
# ✅ Pyright - passed
# 
# Commit succeeds!

# Push to GitHub
git push origin feature/new-visualization

3. GitHub Responds:

GitHub Actions workflow triggered:
  - Installing dependencies... ✅
  - Running Ruff linter... ✅
  - Checking formatting... ✅
  - Running Pyright... ✅

All checks passed! ✅

4. Code Review:

Reviewer sees:
- ✅ All automated checks passed
- Can focus on:
  - Business logic
  - Algorithm correctness
  - Architecture decisions
  - NOT style/formatting (already validated)

5. Merge:

# Merge pull request on GitHub
# Or via command line:
git checkout main
git merge feature/new-visualization
git push origin main

# CI runs again on main branch
# Confirms everything still works after merge

9.3 Quality Gates at Every Level

Level Tool When Speed Purpose
IDE Pylance, Ruff extension Real-time while typing Instant Catch errors as you write
Pre-commit pre-commit hooks Before each commit <10s Prevent bad commits
CI GitHub Actions After each push 1-3 min Verify in clean environment
Code Review Human reviewers Before merge Hours Check logic and design

The Funnel Effect:


10. Part 8: Branch Protection and Merge Requirements

10.1 What is Branch Protection?

Branch protection rules prevent direct pushes to important branches and enforce quality requirements before merging.

Common protections:

10.2 Setting Up Branch Protection

On GitHub:

  1. Go to repository Settings
  2. Click “Branches” in sidebar
  3. Click “Add branch protection rule”
  4. Configure:
Branch name pattern: main

☑ Require a pull request before merging
  ☑ Require approvals: 1
  ☑ Dismiss stale pull request approvals when new commits are pushed

☑ Require status checks to pass before merging
  ☑ Require branches to be up to date before merging
  Status checks required:
    - quality (your GitHub Actions workflow name)

☑ Require conversation resolution before merging

☐ Require signed commits (optional, more advanced)

☐ Require linear history (optional, prevents merge commits)

☐ Include administrators (recommended: yes)

Click “Create” to activate.

10.3 What Happens Now?

Before Branch Protection:

# Anyone can push directly to main
git checkout main
git commit -m "Quick fix"
git push origin main  # ✅ Works (but risky!)

After Branch Protection:

git push origin main  # ❌ Blocked!
# Error: Protected branch 'main' cannot be pushed to

Required workflow:

# 1. Create feature branch
git checkout -b feature/fix

# 2. Make changes and commit
git commit -m "Fix bug"

# 3. Push feature branch
git push origin feature/fix

# 4. Open pull request on GitHub

# 5. Wait for CI to pass

# 6. Request code review

# 7. Get approval

# 8. Merge via GitHub UI (now allowed)

10.4 Benefits of Branch Protection

Quality Enforcement:

Collaboration:

Safety:


11. Part 9: Advanced CI/CD Patterns

11.1 Deployment Pipeline

Extend your CI pipeline to include deployment:

name: CI/CD Pipeline

on:
  push:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: astral-sh/setup-uv@v4
      - run: uv sync --dev
      - run: uv run ruff check .
      - run: uv run pyright
      - run: uv run pytest  # Run tests
  
  deploy:
    needs: test  # Only run if test job succeeds
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Deploy to production
        env:
          DEPLOY_KEY: ${{ secrets.DEPLOY_KEY }}
        run: |
          echo "Deploying to production..."
          # Your deployment commands here

11.2 Notifications

Get notified when workflows fail:

jobs:
  quality:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      # ... your checks ...
      
      - name: Notify on failure
        if: failure()
        uses: 8398a7/action-slack@v3
        with:
          status: ${{ job.status }}
          text: 'Quality checks failed!'
          webhook_url: ${{ secrets.SLACK_WEBHOOK }}

11.3 Workflow Badges

Show build status in your README:

# Road Profile Viewer

![CI Status](https://github.com/username/repo/workflows/Code%20Quality/badge.svg)

Interactive road profile visualization tool.

The badge updates automatically:


12. Summary: Key Takeaways

12.1 CI/CD is Not Optional

In modern software development, CI/CD is fundamental:

12.2 Layer Your Quality Gates

Build a quality funnel:

  1. IDE - Real-time feedback while writing
  2. Pre-commit - Prevent bad commits
  3. CI - Verify in clean environment
  4. Code Review - Human validation
  5. Production Monitoring - Catch what escaped

12.3 Automate Everything

If humans need to remember to do it, it won’t get done consistently:

12.4 GitHub Actions is Powerful and Free

For public repositories (like student projects):

12.5 AI Amplifies Your Skills

In the age of LLMs:

Key insight: The better you understand CI/CD, the better you can use AI tools like GitHub Copilot to create sophisticated workflows.


13. Additional Resources

13.1 Official Documentation

13.2 Learning Resources

13.3 Tools and Extensions


14. References

[1] GitHub Actions Documentation. https://docs.github.com/en/actions

[2] pre-commit Framework Documentation. https://pre-commit.com/

[3] Martin Fowler - Continuous Integration. https://martinfowler.com/articles/continuousIntegration.html

[4] Astral - uv CI/CD Integration. https://docs.astral.sh/uv/guides/integration/github/

[5] Kim, Gene et al. “The DevOps Handbook.” IT Revolution Press, 2016.

[6] Humble, Jez and David Farley. “Continuous Delivery: Reliable Software Releases through Build, Test, and Deployment Automation.” Addison-Wesley, 2010.

[7] GitHub Branch Protection Documentation. https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches

[8] YAML Syntax Guide. https://yaml.org/spec/1.2/spec.html


Note: This lecture is based on GitHub Actions and tooling available as of October 2025. Platform features and specific actions may evolve over time.

© 2026 Dominik Mueller   •  Powered by Soopr   •  Theme  Moonwalk