- 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 jqFedora/RHEL/CentOS (dnf):
sudo dnf install -y git curl jqopenSUSE/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:8bCloud (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_TAGis correct or create an initial tag likegit tag v0.1.0 && git push --tags.API rate limits: export a valid
GITHUB_TOKENwith 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:8borqwen2.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.