Gitea Action: AI Release Notes Generator

Reusable Gitea composite action that inspects git history between releases and calls an LLM provider (default: self-hosted OmniRoute or any OpenAI-compatible endpoint) to produce structured Markdown release notes.

Features

  • Automated Commit Discovery: Automatically finds previous release tags (e.g. v1.7.0 to v1.8.0) and extracts all commit messages in that range.
  • Smart Noise Filtering: Automatically filters out version bumps (chore: bump version, chore: release) and merge commit noise.
  • OmniRoute & OpenAI Compatible: Works out-of-the-box with OmniRoute (https://omni.freakma.com/v1, model auto/fast), local llama.cpp, or OpenAI/OpenRouter.
  • Thinking Tag Stripping: Automatically cleans <think>...</think> tags produced by reasoning models (Qwen, DeepSeek).
  • Graceful Fallback: Bounded timeouts with fallback text so CI release cuts never fail or hang if the LLM provider is temporarily unreachable.
  • Multi-Platform Support: Pure Python standard library implementation (urllib, json, subprocess). Zero external package dependencies; runs seamlessly on macOS (runs-on: macos) and Linux/Docker runners (runs-on: docker).

Quick Start

In your Gitea workflow (e.g. .gitea/workflows/release-cut.yml):

jobs:
  cut-release:
    runs-on: macos
    steps:
      - name: Checkout code
        uses: actions/checkout@v4
        with:
          token: ${{ secrets.TOKEN }}
          fetch-depth: 0

      - name: Bump & Tag
        run: |
          # Your version bump and tag commands...
          git tag "v1.8.0"

      - name: Generate Release Notes
        id: release_notes
        uses: https://git.mancinas.io/rmancinas/action-release-notes@main
        with:
          api_key: ${{ secrets.OMNI_API_KEY }}
          version: '1.8.0'

      - name: Create Gitea Release
        env:
          GITEA_TOKEN: ${{ secrets.TOKEN }}
          NOTES: ${{ steps.release_notes.outputs.notes }}
        run: |
          curl -sS -X POST "${{ gitea.server_url }}/api/v1/repos/${{ gitea.repository }}/releases" \
            -H "Authorization: token $GITEA_TOKEN" \
            -H "Content-Type: application/json" \
            -d "{\"tag_name\":\"v1.8.0\",\"name\":\"v1.8.0\",\"body\":$(echo "$NOTES" | jq -Rs .)}"

Inputs

Input Description Required Default
api_key API key for OmniRoute / LLM provider. If omitted, uses fallback notes. No ""
base_url OpenAI-compatible API base URL. No https://omni.freakma.com/v1
model Model name / ID to query. No auto/fast
version Target version string (e.g. 1.8.0) to include in prompt. No ""
from_ref Starting git ref or tag. Auto-discovered if omitted. No Auto-detected
to_ref Ending git ref. No HEAD
tag_pattern Pattern to match prior release tags. No v*
max_commits Max commits to scan if no prior tag found. No 50
fallback_notes Fallback text if LLM call fails or times out. No Maintenance updates and performance improvements.
timeout LLM HTTP request timeout in seconds. No 25
temperature Sampling temperature for the model. No 0.2
system_prompt Custom system prompt override. No Standard release notes prompt
save_to_file File path to write the release notes to. No ""

Outputs

Output Description
notes The generated Markdown release notes string.
notes_file Absolute path to the saved notes file (if save_to_file was used).

Using with Local llama.cpp

To route to a local llama.cpp instance (e.g. on your Mac mini runner or LAN):

- name: Generate Release Notes via llama.cpp
  uses: https://git.mancinas.io/rmancinas/action-release-notes@main
  with:
    api_key: ${{ secrets.LLAMA_API_KEY }}
    base_url: 'http://192.168.4.213:8080/v1'
    model: 'qwen3.6-35b-a3b'
    version: '1.8.0'

Standalone / CLI Usage

You can run the generator locally in any git repository without Actions:

python3 scripts/generate_notes.py \
  --api-key "$OMNI_API_KEY" \
  --version "1.8.0" \
  --save-to-file "release_notes.md"
S
Description
Reusable Gitea composite action that generates AI-powered release notes via OmniRoute or any OpenAI-compatible endpoint
Readme
42 KiB
0 Stars 1 Watchers 0 Forks
Languages
Python 100%