Posted on
Artificial Intelligence

Artificial Intelligence Release Notes with Artificial Intelligence

Author
  • User
    linuxbash
    Posts by this author
    Posts by this author

Artificial Intelligence Release Notes with Artificial Intelligence (AI + Bash)

If you’ve ever stared down a deadline while cobbling together release notes from a mountain of commits, PRs, and “drive-by” fixes, you know the pain. Release notes are essential, but they’re also repetitive, time-consuming, and easy to get wrong. What if your shell could do the heavy lifting—and an AI could turn raw diffs into crisp, user-ready release notes?

In this guide, you’ll wire up a lightweight Bash workflow that:

  • Gathers changes since your last tag from Git or GitHub

  • Feeds those changes into an AI model (local or cloud)

  • Generates structured, concise release notes automatically

  • Optionally publishes a GitHub Release—all from the command line

You’ll get actionable scripts you can drop into CI or run locally in seconds.

Why this works (and why it’s worth it)

  • Consistency and speed: AI is excellent at summarizing and structuring content. You keep a familiar Bash workflow; the AI produces polished text.

  • Accurate scope: Release notes are sourced straight from Git history and/or the GitHub API. You keep humans in the loop without the manual drudgery.

  • Flexible deployment: Use a local model (privacy-friendly) via Ollama, or a cloud API if you prefer.

  • Fits your stack: It’s all standard Linux tools plus one AI runtime.

What you’ll build

  • A portable Bash script that: 1) Detects what changed since the last tag 2) Crafts a precise AI prompt using commit messages (and optionally PRs) 3) Outputs structured markdown for release notes 4) Optionally creates a GitHub Release

Prerequisites and installation

We’ll use git, curl, and jq. Install them with your package manager:

  • Debian/Ubuntu (apt):

    sudo apt update
    sudo apt install -y git curl jq
    
  • Fedora/RHEL/CentOS (dnf):

    sudo dnf install -y git curl jq
    
  • openSUSE/SLES (zypper):

    sudo zypper refresh
    sudo zypper install -y git curl jq
    

Optional AI backends (choose one):

  • Local (Ollama; runs models on your machine):

    curl -fsSL https://ollama.com/install.sh | sh
    # Example model (good balance of speed and quality):
    ollama pull llama3.1:8b
    
  • Cloud (OpenAI-compatible API via environment variables):

    # Example using OpenAI:
    # Create an API key and export it (or put in your shell profile)
    export OPENAI_API_KEY="sk-..."
    # Choose a model name you have access to, e.g.:
    export OPENAI_MODEL="gpt-4o-mini"
    

You’ll also need access to your repo’s GitHub API if you want PR titles/links:

export GITHUB_TOKEN="ghp_..."

Tip: Store secrets in your CI’s secret manager; don’t commit them.


Step 1 — Collect changes since the last tag

This minimal approach uses only your local Git history. It finds the most recent tag, diffs against HEAD, and collects commit messages.

#!/usr/bin/env bash
set -euo pipefail

# Config
PREV_TAG="${PREV_TAG:-$(git describe --tags --abbrev=0 2>/dev/null || echo '')}"
NEW_REF="${NEW_REF:-HEAD}"  # Use a new tag here if you’ve just created one
REPO_DIR="${REPO_DIR:-.}"

cd "$REPO_DIR"

if [[ -z "${PREV_TAG}" ]]; then
  echo "No previous tag found; using initial commit as baseline."
  RANGE="$(git rev-list --max-parents=0 HEAD)..${NEW_REF}"
else
  RANGE="${PREV_TAG}..${NEW_REF}"
fi

COMMITS="$(git log --pretty=format:'- %s (%h) by %an' ${RANGE} || true)"

if [[ -z "$COMMITS" ]]; then
  echo "No commits found in range ${RANGE}."
  COMMITS="- No changes detected."
fi

printf "%s\n" "$COMMITS" > .changes.txt
echo "Wrote commit list to .changes.txt"

Want PR details too (titles/links)? Use GitHub’s compare API. This approach tries to derive owner/repo from your origin remote:

#!/usr/bin/env bash
set -euo pipefail

: "${GITHUB_TOKEN:?Set GITHUB_TOKEN for GitHub API access}"

# Derive owner/repo from origin URL (works with SSH or HTTPS remotes)
ORIGIN_URL="$(git remote get-url origin)"
if [[ "$ORIGIN_URL" =~ github.com[:/](.+)/(.+)(\.git)?$ ]]; then
  OWNER="${BASH_REMATCH[1]}"
  REPO="${BASH_REMATCH[2]}"
else
  echo "Could not parse origin as GitHub remote: $ORIGIN_URL" >&2
  exit 1
fi

PREV_TAG="${PREV_TAG:-$(git describe --tags --abbrev=0 2>/dev/null || echo '')}"
NEW_REF="${NEW_REF:-$(git rev-parse --short=12 HEAD)}"

COMPARE_A="${PREV_TAG:-$(git rev-list --max-parents=0 HEAD | tail -n1)}"
COMPARE_B="${NEW_REF}"

API="https://api.github.com/repos/${OWNER}/${REPO}/compare/${COMPARE_A}...${COMPARE_B}"

JSON="$(curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "Accept: application/vnd.github+json" "$API")"

# Commit messages
echo "$JSON" | jq -r '.commits[].commit.message' | awk '{print "- " $0}' > .changes.txt || true

# Naive extraction of PR numbers from merge commits like "Merge pull request #123 ..."
PRS="$(echo "$JSON" | jq -r '.commits[].commit.message' | grep -Eo "#[0-9]{1,7}" | tr -d '# ' | sort -n | uniq || true)"

# Fetch PR titles for any PR numbers we saw in merge commits
> .prs.txt
for n in $PRS; do
  PRAPI="https://api.github.com/repos/${OWNER}/${REPO}/pulls/$n"
  PRJSON="$(curl -fsSL -H "Authorization: Bearer ${GITHUB_TOKEN}" -H "Accept: application/vnd.github+json" "$PRAPI" || true)"
  TITLE="$(echo "$PRJSON" | jq -r '.title // empty')"
  HTML_URL="$(echo "$PRJSON" | jq -r '.html_url // empty')"
  if [[ -n "$TITLE" ]]; then
    echo "- PR #$n: $TITLE ($HTML_URL)" >> .prs.txt
  fi
done

echo "Wrote commits to .changes.txt and PRs to .prs.txt (if found)."

This keeps dependencies minimal and avoids GraphQL. It’s “good enough” for many repos.


Step 2 — Craft a robust AI prompt

Good release notes are opinionated. We’ll specify strict structure and guardrails to avoid hallucinations.

cat > .prompt.txt <<'PROMPT'
You are a release-notes generator. Produce concise, accurate markdown for the changes provided.
Rules:

- Do not invent features or fixes not present in the input.

- Group items under: Highlights, Fixes, Performance, Docs, Breaking Changes, Upgrade Notes.

- If a section has no items, omit the section.

- Keep the tone neutral and helpful. Use short bullet points.

- Reference PRs as [#123](link) if provided.
Input follows:

=== COMMITS ===
{{COMMITS}}

=== PULL REQUESTS ===
{{PRS}}

Now output only the release notes in markdown. No preamble, no closing remarks.
PROMPT

Then splice in the change lists:

COMMITS_CONTENT="$(cat .changes.txt 2>/dev/null || echo "")"
PRS_CONTENT="$(cat .prs.txt 2>/dev/null || echo "")"

sed "s|{{COMMITS}}|$(printf "%s" "$COMMITS_CONTENT" | sed 's|[&/\]|\\&|g')|g; s|{{PRS}}|$(printf "%s" "$PRS_CONTENT" | sed 's|[&/\]|\\&|g')|g" \
  .prompt.txt > .prompt.filled.txt

Step 3 — Generate release notes with your AI of choice

Option A: Local with Ollama (private, no external calls):

MODEL="${MODEL:-llama3.1:8b}"
RELEASE_NOTES="$(ollama run "$MODEL" < .prompt.filled.txt)"
printf "%s\n" "$RELEASE_NOTES" > RELEASE_NOTES.md
echo "Wrote RELEASE_NOTES.md (Ollama)"

Option B: Cloud via an OpenAI-compatible endpoint:

: "${OPENAI_API_KEY:?Set OPENAI_API_KEY}"
MODEL="${OPENAI_MODEL:-gpt-4o-mini}"

RELEASE_NOTES="$(
  curl -fsSL https://api.openai.com/v1/chat/completions \
    -H "Authorization: Bearer ${OPENAI_API_KEY}" \
    -H "Content-Type: application/json" \
    -d @- <<JSON | jq -r '.choices[0].message.content'
{
  "model": "${MODEL}",
  "temperature": 0.2,
  "messages": [
    {"role": "system", "content": "You produce accurate, concise release notes. Do not fabricate content."},
    {"role": "user", "content": $(jq -Rs . < .prompt.filled.txt)}
  ]
}
JSON
)"

printf "%s\n" "$RELEASE_NOTES" > RELEASE_NOTES.md
echo "Wrote RELEASE_NOTES.md (Cloud API)"

Tip: Set temperature low to reduce creative drift.


Step 4 — Automate tagging and optional GitHub release

You can append metadata and publish:

VERSION="${VERSION:-$(git describe --tags --abbrev=0 2>/dev/null || date +v%Y.%m.%d)}"
DATE="$(date -u +%Y-%m-%d)"

# Prepend header to the notes
TMP="$(mktemp)"
{
  echo "## ${VERSION} — ${DATE}"
  echo
  cat RELEASE_NOTES.md
} > "$TMP" && mv "$TMP" RELEASE_NOTES.md

git add RELEASE_NOTES.md
git commit -m "chore(release-notes): update for ${VERSION}" || true
git tag -f "${VERSION}" || true
git push --follow-tags || true

Create a GitHub Release with the generated notes:

: "${GITHUB_TOKEN:?Set GITHUB_TOKEN}"
ORIGIN_URL="$(git remote get-url origin)"
if [[ "$ORIGIN_URL" =~ github.com[:/](.+)/(.+)(\.git)?$ ]]; then
  OWNER="${BASH_REMATCH[1]}"
  REPO="${BASH_REMATCH[2]}"
else
  echo "Could not parse origin as GitHub remote." >&2
  exit 1
fi

BODY="$(jq -Rs . < RELEASE_NOTES.md)"
curl -fsSL -X POST "https://api.github.com/repos/${OWNER}/${REPO}/releases" \
  -H "Authorization: Bearer ${GITHUB_TOKEN}" \
  -H "Accept: application/vnd.github+json" \
  -d "{
    \"tag_name\": \"${VERSION}\",
    \"name\": \"${VERSION}\",
    \"body\": ${BODY},
    \"draft\": false,
    \"prerelease\": false
  }" | jq -r '.html_url'

Example output

## v1.4.0 — 2026-07-11

### Highlights

- Add dark mode toggle to settings panel (commit 8fd3a21 by J. Kim)

- Improve API pagination for large datasets [#482](https://github.com/acme/app/pull/482)

### Fixes

- Resolve race condition in web socket reconnect logic (commit 4c19bb0 by A. Singh)

- Correct typo in CLI help text [#489](https://github.com/acme/app/pull/489)

### Performance

- Reduce database round-trips during batch imports (commit e31b0c5 by L. Chen)

### Docs

- Update quickstart with containerized workflow [#493](https://github.com/acme/app/pull/493)

### Breaking Changes

- Remove deprecated `--legacy-auth` flag from CLI.

### Upgrade Notes

- If you relied on `--legacy-auth`, migrate to PAT-based login. See docs/migration-auth.md.

Tips for reliable, useful notes

  • Keep prompts strict: tell the model what sections to produce and to avoid speculation.

  • Feed the right data: commits are good; PRs often add context and links users want.

  • Bound the range: always compare from the last release tag to a known ref.

  • Make it idempotent: cache the commit list for a given tag so re-runs are stable.

  • Review before publish: AI speeds you up; a human spot-check keeps quality high.


Troubleshooting

  • Empty notes: ensure PREV_TAG is correct or create an initial tag like git tag v0.1.0 && git push --tags.

  • API rate limits: export a valid GITHUB_TOKEN with repo scope; reduce extra API calls.

  • Privacy: prefer Ollama/local models if your policy restricts external data sharing.

  • Model quality: try a slightly larger local model (llama3.1:8b or qwen2.5) or a higher-quality cloud model if summaries feel too thin.


Call to action

Drop these scripts into your repo’s .ci/ or scripts/ directory and run them before cutting a tag. Then wire them into your CI to auto-generate and publish release notes on every release. Start simple with local Git commits; add PR fetching when you’re ready. Your future self—and your users—will thank you.

If you want a ready-made starter, copy the snippets above into a single generate-release-notes.sh, make it executable (chmod +x generate-release-notes.sh), and run it. Iterate on the prompt until it matches your project’s voice.