GitHub Contribution Workflow

1 Overview

This page documents the GitHub issue-and-PR workflow used during this project. It serves as a personal reference for contributing to a repository — either your own or someone else’s — following open-source conventions.

The workflow modelled here is issue → branch → fix → PR → close. Every change is traceable back to a logged reason.


2 Step 1 — Find or raise an issue

Before touching code, the problem is recorded as a GitHub Issue. Issues are the paper trail: they explain why a change was made and link the fix back to the motivation.

# Create an issue from the command line using the GitHub CLI (gh)
gh issue create \
  --title "Short, specific description of the problem" \
  --body "$(cat <<'EOF'
## Description
What is broken and where.

## Expected behaviour
What should happen.

## Actual behaviour
What actually happens, including error output or screenshots.

## Steps to reproduce
Numbered list so someone else can confirm the bug.

## Suggested fix
Optional — your best guess at the solution.

## Acceptance criteria
- [ ] Checkbox list of what "done" looks like
EOF
)"

In a project you don’t own, you’d browse to the repo on GitHub, click Issues → New issue, and fill in the same information. The maintainer triages it, and if they accept it you get assigned or invited to submit a PR.


3 Step 2 — Create a branch

Never commit fixes directly to main. Create a named branch that references the issue.

# Convention: type/short-description
# Common prefixes: fix/, feat/, docs/, refactor/, chore/
git checkout -b fix/tests-qmd-dynamic-rendering

Naming conventions matter in collaborative repos — maintainers scan branch names to understand intent before reading code.

In someone else’s repo you would first fork the repository (top-right button on GitHub), clone your fork, then create the branch there:

# Fork happens on GitHub, then:
git clone https://github.com/YOUR-USERNAME/their-repo.git
cd their-repo
git remote add upstream https://github.com/ORIGINAL-OWNER/their-repo.git
git checkout -b fix/short-description

4 Step 3 — Make the fix

Edit files, run tests, confirm the fix works. Keep commits small and focused.

# Stage specific files (never git add -A blindly)
git add quarto/tests.qmd

# Write a commit message that explains WHY, not just WHAT
git commit -m "Load test-results.json dynamically in tests.qmd

Previously the page was static markdown with hardcoded checkmarks.
Now a Python code cell loads the JSON on every render so results
reflect the actual last test run.

Refs #1, #2"

Good commit messages follow the pattern:

Short imperative summary (≤72 chars)

Blank line, then a paragraph explaining the motivation — what was
wrong, why this approach was chosen, any tradeoffs.

Refs #N  ← links the commit to the issue in GitHub's UI

5 Step 4 — Push and open a Pull Request

# Push branch to remote (origin = your fork or your own repo)
git push -u origin fix/tests-qmd-dynamic-rendering

# Open a PR from the command line
gh pr create \
  --title "Fix: render test results dynamically from JSON" \
  --body "$(cat <<'EOF'
## Summary
- Replace hardcoded markdown in `tests.qmd` with Python code cells that load `test-results.json`
- Fix pytest output path so the JSON lands where the `.qmd` expects it

## Test plan
- [ ] Run `pytest tests/test_setup.py --json-report --json-report-file=test-results.json`
- [ ] Render `quarto/tests.qmd` and confirm results table appears
- [ ] Verify failed/skipped tests show error messages

Closes #1
Closes #2
EOF
)"

Closes #N is a magic keyword — when the PR is merged, GitHub automatically closes the linked issue. Other valid keywords: Fixes #N, Resolves #N.

In someone else’s repo, you’d open the PR from your fork’s branch targeting the upstream main. GitHub shows a “Compare & pull request” banner automatically after you push.


6 Step 5 — Review and merge

In a collaborative project a maintainer reviews the PR, leaves comments, and requests changes if needed. You push additional commits to the same branch — they appear in the PR automatically.

# If the reviewer requests a change:
# 1. Make the edit
git add quarto/tests.qmd
git commit -m "Address review: handle missing test-results.json gracefully"
git push   # same branch, PR updates automatically

Once approved:

  • The maintainer clicks Squash and merge (or Merge, depending on repo policy)
  • The linked issues close automatically
  • The branch can be deleted
# Clean up local branch after merge
git checkout main
git pull
git branch -d fix/tests-qmd-dynamic-rendering

7 Quick-reference command card

Action Command
Create issue gh issue create --title "..." --body "..."
List open issues gh issue list
View issue gh issue view 3
Create branch git checkout -b fix/description
Push branch git push -u origin fix/description
Open PR gh pr create --title "..." --body "..."
View PR status gh pr status
List PRs gh pr list
Merge PR gh pr merge --squash
Close issue manually gh issue close 3

8 CI — Continuous Integration with GitHub Actions

Every push to this repo triggers an automated test run on GitHub’s servers. This is called Continuous Integration (CI) — the idea that code is integrated (tested) continuously, not just before a release.

The workflow lives at .github/workflows/ci.yml:

on:
  push:
    branches: ["**"]   # every branch, every push
  pull_request:
    branches: [main]   # and every PR targeting main

jobs:
  test:
    runs-on: ubuntu-latest   # clean virtual machine, nothing pre-installed
    steps:
      - uses: actions/checkout@v4          # download the code
      - uses: astral-sh/setup-uv@v5        # install uv
      - run: uv sync                       # install Python dependencies
      - run: pytest tests/test_ingest.py -v --json-report --json-report-file=test-results.json
      - uses: actions/upload-artifact@v4   # save test-results.json forever on this run

What it proves: the tests pass on a clean machine, not just on a local laptop. Every commit in the git history has a green or red badge — a permanent, timestamped record of whether the code was working at that point.

Why test_setup.py is excluded from CI: those tests check for a running Ollama instance (localhost:11434), which doesn’t exist on GitHub’s servers. They remain as local smoke tests. test_ingest.py uses mocks for Ollama and ChromaDB, so it runs cleanly anywhere.


9 Branch Rulesets — enforcing the review process

A ruleset is a policy applied to a branch. For this project, main is protected by a ruleset that requires CI to pass before any merge is allowed.

How to set it up: Settings → Rules → Rulesets → New branch ruleset

  • Target: main
  • Enforcement: Active
  • Rule: Require status checks to pass → add the test check from ci.yml

Effect: the merge button on GitHub is physically blocked until the CI check is green. This means every commit that lands on main has been tested. It also forces a deliberate human decision to merge — you can’t accidentally push a broken change.


10 Human sign-off — creating an evidence trail

The CI check proves the code works. The human comment proves the code was reviewed. For the meta-AI strand of this project, it matters that Alan’s approval is clearly recorded separately from Claude’s actions.

Pattern used in this project:

  1. Claude opens a PR with a comment header: > **Generated by Claude Sonnet 4.6 — actioned on Alan McDonagh's approval**
  2. CI runs automatically — green tick or red cross appears on the PR
  3. Alan reviews the Actions output and leaves a comment: “Reviewed CI output — approving merge”
  4. Alan merges — the merge is timestamped under Alan’s GitHub account

This produces an audit trail where every actor (Claude, CI, Alan) is distinguishable and every action is timestamped.


11 Issue closing workflow (mandatory for this project)

Every issue must pass through four steps before it is closed:

Step What happens
1. Tests pass pytest green locally and in CI
2. Quarto demo A .qmd page shows the feature working with live code output
3. Nav updated The page is registered in quarto/_quarto.yml
4. Close with comment GitHub issue closed with AI attribution header + summary of what was done

The closing comment always includes:

> **Generated by Claude Sonnet 4.6 — actioned on Alan McDonagh's approval**

What was implemented, which tests cover it, which Quarto page demonstrates it.

12 This project’s issues raised via this workflow

Issue Title Status
#1 Ingest: Load course PDFs with PyPDFLoader Closed — superseded by CSV pivot
#2 Ingest: Chunk documents with RecursiveCharacterTextSplitter Closed — implemented in rag/ingest.py
#18 tests.qmd renders static markdown instead of live results Closed via PR
#19 test-results.json written to wrong path Closed via PR
#22 Switch knowledge base from course PDFs to CBI speeches corpus Closed — implemented
#24 Add GitHub Actions CI workflow Closed via PR #26