Referenta

Release Automation

How a Release Candidate flows from dev to production, covering versioning, release notes, the Linear and QA handover, and the tags, GitHub Releases, and dashboard release notes it produces.

Referenta release notes are generated from the GitHub release process, not from manually edited dashboard content.

Overview

The pipeline runs from dev to main. Feature branches merge into dev, which is staging: anything on dev is a shippable Release Candidate. The single pull request from dev into main is that Release Candidate, and merging it ships production. Every version number, its release notes, and its QA state are driven from that one PR.

End to end:

  1. Open the RC PR (devmain): the title sets the version, the body is the release notes.
  2. Hand it to QA in Linear by adding the sync-to-linear label. The RC's issues are mirrored into a Linear release at the Staging stage.
  3. QA signs off in Linear, moving each issue through the board.
  4. Merge into main: CI cuts the git tag and GitHub Release, and the Linear release is completed.
  5. The dashboard reflects it: the in-app release-notes page reads the new GitHub Release.

The rest of this page details each step.

Trigger

The release automation lives in .github/workflows/ci.yml and only runs when all of the following are true:

  • the event is a pull request,
  • the pull request is closed,
  • the pull request was merged, and
  • the base branch is main.

This means the release flow is tied to the promotion step into production rather than to everyday feature branches.

Title And Description Rules

For the release job to create a version tag, the merged PR title must match one of these formats:

Release Candidate: vX.Y.Z
RC: vX.Y.Z
Release: vX.Y.Z

The version must include the leading v (e.g. v3.3.4, not 3.3.4). A title that looks like a release (starts with Release Candidate:, RC:, or Release:) but does not match <Prefix>: vX.Y.Z fails the check loudly rather than silently producing no tag or release — so a near-miss like a missing v or missing space is caught instead of shipping unnoticed. The same guard gates the Linear sync. A title that is not release-shaped at all is ignored.

Version numbers follow SemVer (MAJOR.MINOR.PATCH): bump the minor for the features accumulated on dev, the patch for a fix-only release.

The workflow extracts the version directly from the title. The pull request body becomes the GitHub Release description.

Operationally, that makes the PR itself the release handoff artifact:

  • the title defines the version number,
  • the description defines the public release content, and
  • the merge into main triggers the publication step.

What The Workflow Creates

When the title matches and the PR is merged into main, the workflow:

  1. checks out the repo with tag push permissions,
  2. extracts the version from the PR title,
  3. creates an annotated git tag on the merge commit if the tag does not already exist, and
  4. creates a GitHub Release whose title is the version and whose notes come from the PR body.

This keeps releases traceable to the exact merge commit and avoids manually creating production tags outside the CI flow.

Linear Release Sync

Alongside the tag automation, .github/workflows/linear-release.yml mirrors the Release Candidate into Linear's Releases product (pipeline Referenta App, stages StagingReleased) so QA can work from the release object.

  • The Staging sync is opt-in via the sync-to-linear label. Add that label to the open RC PR (devmain, title matching the formats above) to start it; the sync then attaches every Linear issue found in the new commit range to the release for that version at the Staging stage, and re-runs on each subsequent title/body edit or push to dev while the label is present. Without the label, nothing syncs during Staging. Issues are discovered from commit messages, branch names, and squash-merge PR references ((#123)) resolved through the Linear GitHub integration — no special commit format is required.
  • When the RC PR merges, the workflow runs a final sync and completes the release, landing it on the Released stage — independent of the label, so a release is always recorded on merge (the final sync creates it if the RC was never labeled during Staging). Linear release automations then move the contained issues to their final status.

If a sync or promotion run fails partway (for example between the final sync and completion), re-run the failed job from the Actions tab — steps address the release by version, so a re-run resumes where it failed. If an RC PR is retitled to a different version after syncs ran, archive the stray old release manually in Linear.

Feature-branch PRs into dev need no special handling: their squash commit's PR reference is resolved to linked issues at sync time. Direct pushes to dev are only attached to issues when the commit message or branch names a Linear identifier.

The workflow can be exercised manually via workflow_dispatch (inputs: version, dry_run, base_ref) from the Actions tab; dry_run defaults to true for a mutation-free preview — uncheck it to perform a real sync. The optional base_ref overrides where the commit scan starts (normally the last release's commit) — useful to backfill a release's issue list from a known point. It must be a commit on the dev history: a version tag will not work, because release tags sit on main's merge commits, which are not ancestors of dev. To backfill from a shipped version, find its dev-side anchor locally with git merge-base origin/dev vX.Y.Z and pass the resulting commit SHA as base_ref.

Release notes in Linear

The RC PR body is the hand-written, customer-facing release notes (see the referenta-release-notes skill) and the single source of truth. Both jobs mirror that body into the Linear release's notes (the action's release_notes input, with the Codesmith footer stripped): the sync job mirrors it while the release is at Staging — so QA sees the current description as it is written and edited on the open RC PR — and the promote job mirrors the final body on completion. The result is that the Linear changelog, the GitHub Release, and the dashboard all show the same text. Mirroring only runs when the body has content, so an empty body never clears notes already mirrored.

Because the notes are supplied explicitly, auto-generate-on-completion should be turned off in the pipeline settings (Settings → Releases → Referenta App) so Linear does not overwrite the mirrored notes with agent-generated ones.

If you ever want Linear to draft notes instead — e.g. a release with no hand-written body — generate them manually from the Changelog tab. The house-style template below is kept version-controlled for that case; paste it into the pipeline's Template field:

You are writing user-facing release notes for Referenta, a SaaS platform for political and public-sector work. The audience is end users, not developers — describe what changed for them, never how it was built. Analyze the issues included in this release and produce notes in the exact format below.

LANGUAGE: Bilingual. Write German first, then a line with only `---`, then English. The English section mirrors the German one section-for-section. Use the formal German register ("Sie").

STRUCTURE (apply within each language):
1. Lead with the release's main feature as a heading: `### <Headline feature> — <short tagline>`, followed by one framing sentence using this release's version number:
   - DE: "Das Highlight von <version>: <one sentence>."
   - EN: "The headline of <version>: <one sentence>."
   Then bold-lead sub-bullets: "- **<capability>** — <what it does for the user>."
2. Add further `### <themed section>` headings for other notable features, grouped by product area (e.g. Assistent, Recherche, Knowledgebase, Press Monitor, Organisationsverwaltung). Use bullets, with bold lead-ins where they help.
3. Close with `### Feinschliff & Fehlerbehebungen` (DE) / `### Polish & fixes` (EN): list only user-visible fixes, and put the internal catch-all line as the LAST bullet.

RULES:
- Classify each issue. User-facing → describe concretely. Internal (refactors, security, tooling/CI, dependency bumps, tests, infra, performance with no visible effect) → do NOT itemize; fold all of it into a single final bullet, verbatim:
   - DE: "Interne Architektur- und Developer-Experience-Verbesserungen."
   - EN: "Internal architecture and developer-experience improvements."
- Never include ticket IDs (REF-N), PR or commit references, branch names, or internal jargon.
- Do not restate the version in section headings (only in the framing line).
- Keep it concise, professional, and benefit-first.

QA Handover And Sign-Off

Once the Staging sync has run, QA works the Release Candidate entirely in Linear. The release object lists every issue in the RC, and each issue carries a QA status. The board flow is:

In ReviewOn StagingQA ApprovedMerged on Prod

Each transition is driven by a different mechanism:

  • On Staging is set automatically when a feature PR merges into dev. A Linear Git automation (a branch rule where merging to dev sets the status to On Staging) marks the issue as landed on staging and ready to test. Merging to dev also advances the RC PR, so the Staging sync re-runs.
  • QA Approved is set manually by QA after testing the issue on the staging deploy. This is the only human step in the flow. If an issue fails, QA moves it back to In Progress; the developer fixes it, re-merges to dev, and the next sync returns it to On Staging for re-test.
  • Merged on Prod is set automatically when the release is completed (on merge to main). A Linear release automation moves every issue in the completed release to its final status.

QA status lives natively in Linear, which every developer can already see, so it is deliberately not mirrored onto the GitHub PR: GitHub carries the code and the release, Linear carries the issues and their QA state.

How The Dashboard Uses Release Data

The Referenta dashboard reads release information directly from GitHub:

  • getGithubReleasePayload fetches release and tag data from the repository,
  • /release-notes renders that data through ReleaseNotesView,
  • the page body is taken from the GitHub Release description, and
  • the sidebar version badge uses /api/v1/github/latest-tag together with useGithubTag.

In product terms, GitHub Releases are the content source for the dashboard's release communication. Updating a GitHub Release updates what the dashboard can display.

Freshness Model

The dashboard behavior is automatic, but it is not true push-based realtime.

The current implementation uses cached fetches and revalidation:

  • the release API route is revalidated on a timed cache window,
  • the latest-tag API response is cached server-side, and
  • the client hook stores the latest tag in session storage for the current browser session.

So release updates propagate automatically on subsequent fetches and refreshes, but not through a live websocket-style broadcast channel.

Practical Release Flow

The expected release workflow is:

  1. validate changes on dev,
  2. open a PR from dev to main,
  3. title it Release Candidate: vX.Y.Z (or RC: vX.Y.Z / Release: vX.Y.Z),
  4. write the release notes in the PR body,
  5. merge the PR, and
  6. let GitHub Actions create the tag and GitHub Release that the dashboard consumes.

That gives Referenta one release source of truth and keeps versioning, release notes, and dashboard display aligned.

On this page