Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
## Summary

<!-- Write a concise, user-facing summary. The PR title appears in generated release notes, so make it descriptive on its own. -->

## Validation

<!-- List relevant tests, build checks, or manual verification. -->

## Release notes

Before merging, apply exactly one category label to this PR:

- `enhancement` — user-facing feature or improvement
- `bug` — behavior correction
- `breaking-change` — incompatible behavior or migration
- `documentation` — documentation-only change
- `maintenance` — internal tooling, dependencies, or maintenance
- `skip-release-notes` — no user-facing release note is needed

The release workflow includes PR titles, grouped by these labels, and identifies contributors automatically. Keep the title concise and avoid putting issue references alone in the title.
25 changes: 25 additions & 0 deletions .github/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
changelog:
exclude:
labels:
- skip-release-notes
authors:
- dependabot[bot]
categories:
- title: "⚠️ Breaking changes"
labels:
- breaking-change
- title: "✨ Features and improvements"
labels:
- enhancement
- title: "🐛 Bug fixes"
labels:
- bug
- title: "📚 Documentation"
labels:
- documentation
- title: "🧰 Maintenance"
labels:
- maintenance
- title: "Other changes"
labels:
- "*"
21 changes: 10 additions & 11 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ jobs:
runs-on: macos-26
permissions:
contents: write
pull-requests: read
env:
GH_TOKEN: ${{ github.token }}
HOMEBREW_NO_AUTO_UPDATE: "1"
Expand All @@ -36,6 +37,7 @@ jobs:
uses: actions/checkout@v5
with:
ref: main
fetch-depth: 0
submodules: recursive

- name: Verify release source and version
Expand Down Expand Up @@ -81,23 +83,20 @@ jobs:
set -euo pipefail
MAIN_SHA="$(git ls-remote origin refs/heads/main | awk '{print $1}')"
[[ "$MAIN_SHA" == "$GITHUB_SHA" ]] || { echo "main moved during the build; rerun from the current main commit." >&2; exit 1; }
PREVIOUS_TAG="$(gh release list --limit 100 \
--json tagName,isDraft,isPrerelease,publishedAt \
| jq -er '[.[] | select(.isDraft == false and .isPrerelease == false)] | sort_by(.publishedAt) | last | .tagName')"
NOTES_FILE="${RUNNER_TEMP}/release-notes.md"
cat >"${NOTES_FILE}" <<EOF
Cortexa ${RELEASE_VERSION} — macOS, Apple Silicon (arm64)

Source commit: ${GITHUB_SHA}

This draft was built by GitHub Actions for installation testing. It is not signed with an Apple Developer ID and is not notarized. macOS may require approving the first launch; see the included install notes. Speech models download from the app when needed.

Install the DMG and verify the app before publishing this draft.
EOF
GITHUB_REPOSITORY="${GITHUB_REPOSITORY}" \
./scripts/generate_macos_release_notes.sh \
"${RELEASE_VERSION}" "${GITHUB_SHA}" "${PREVIOUS_TAG}" "${NOTES_FILE}"
gh release create "v${RELEASE_VERSION}" \
"artifacts/mac/Cortexa-${RELEASE_VERSION}.dmg" \
"artifacts/mac/Cortexa-${RELEASE_VERSION}.dmg.sha256" \
"artifacts/mac/Cortexa-${RELEASE_VERSION}-install-notes.txt" \
--target main \
--target "${GITHUB_SHA}" \
--draft \
--title "Cortexa ${RELEASE_VERSION}" \
--title "Cortexa ${RELEASE_VERSION} — macOS, Apple Silicon" \
--notes-file "${NOTES_FILE}"

publish:
Expand Down
8 changes: 4 additions & 4 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,14 +25,14 @@ Bei **großen** Funktionsänderung Änderung an einer oder mehreren Dateien (Bug

Der aktuelle öffentliche Vertriebsweg ist ein von GitHub Actions auf `macos-26` mit Xcode 26+ gebauter Open-Source-DMG für Apple Silicon. Diese SDK-Version wird benötigt, damit das Icon Composer-App-Icon und Apple Foundation Models tatsächlich in die App kompiliert werden. Dafür braucht es kein Apple-Developer-Programm, keine Developer-ID und keine Notarisierung. Die App wird ad-hoc signiert, damit ihre Dateien geprüft werden können; macOS kann beim ersten Öffnen eine Freigabe verlangen. Nach Updates kann macOS Mikrofon- oder Bedienungshilfenrechte erneut abfragen. Sparkle/Appcast ist nicht Teil dieses Ablaufs.

Die Repository-Einstellungen lassen Pull Requests ausgeschaltet. GitHub Actions ist aktiviert, darf aber nur `actions/checkout@v5` verwenden; das `GITHUB_TOKEN`-Standardrecht ist read-only, und Workflows dürfen keine Pull Requests erstellen oder genehmigen. Beim Ergänzen eines weiteren Actions-Imports muss die Allowlist unter Settings → Actions → General bewusst angepasst werden.
Pull Requests sind im Repository aktiviert und sind künftig der Integrationsweg für Änderungen. Nutze PR-Titel, die die Änderung für Nutzer verständlich zusammenfassen, und wende vor dem Merge genau ein Release-Kategorie-Label gemäß `.github/PULL_REQUEST_TEMPLATE.md` an. GitHubs Release-Notes-API gruppiert gemergte PRs anhand `.github/release.yml`; der Release-Workflow ergänzt direkte Commits ohne PR und Beitragende. GitHub Actions darf weiterhin nur `actions/checkout@v5` importieren; das `GITHUB_TOKEN`-Standardrecht ist read-only, und Workflows dürfen keine Pull Requests erstellen oder genehmigen. Beim Ergänzen eines weiteren Actions-Imports muss die Allowlist unter Settings → Actions → General bewusst angepasst werden.

### Neue Version bauen und lokal testen

1. Auf dem passenden `feature/...`, `bugfix/...` oder `chore/...`-Branch arbeiten. `main` und andere Worktrees vor Build-Bereinigung prüfen; nie versehentlich deren Artefakte verwenden.
2. Nach erfolgreicher Implementierung und den relevanten Tests `VERSION` nach den obigen Regeln anheben und `changelog.md` mit Datum, geänderten Dateien, Verhalten und neuer Version aktualisieren. Release-Artefakte müssen dieselbe Version tragen.
3. Änderungen für den Build committen und nach `main` pushen. GitHub Actions baut den Release-Kandidaten auf einem frischen Apple-Silicon-Runner von genau diesem `main`-Commit; kein lokales oder altes Branch-Artefakt verwenden.
4. Paket-Build und betroffene Tests ausführen. Danach in GitHub Actions den Workflow **macOS Release** mit `action=prepare` und der Version aus `VERSION` auf `main` starten. Der Workflow baut den arm64-DMG, verifiziert Image und Prüfsumme und erstellt einen GitHub-Draft-Release:
3. Änderungen committen, einen PR nach `main` eröffnen und die erforderlichen Checks sowie Review abschließen. Nach dem Merge baut GitHub Actions den Release-Kandidaten auf einem frischen Apple-Silicon-Runner von genau diesem `main`-Commit; kein lokales oder altes Branch-Artefakt verwenden.
4. Paket-Build und betroffene Tests ausführen. Danach in GitHub Actions den Workflow **macOS Release** mit `action=prepare` und der Version aus `VERSION` auf `main` starten. Der Workflow baut den arm64-DMG, verifiziert Image und Prüfsumme, generiert Release-Notizen aus PRs und direkten Commits seit dem letzten veröffentlichten Tag und erstellt einen GitHub-Draft-Release:

```bash
swift build
Expand Down Expand Up @@ -61,7 +61,7 @@ Ein gemergter PR oder ein Commit auf `main` veröffentlicht **nicht automatisch*

### Nach ausdrücklicher Freigabe veröffentlichen

1. Den getesteten Branch-Commit integrieren und nach `main` pushen. PRs werden im Repository deaktiviert; keinen PR erstellen. Nach einem Merge/Push den Workflow-Ablauf oben verwenden.
1. Den getesteten Branch über einen PR nach `main` integrieren. Nach dem Merge den Workflow-Ablauf oben verwenden.
2. Nach manueller Installation und ausdrücklicher Nutzerfreigabe den Draft über den Workflow `action=publish` veröffentlichen.
3. GitHub-Release anschließend zurücklesen und die Assets erneut herunterladen. Prüfsumme und Image-Integrität müssen auch für die Downloads gültig sein:

Expand Down
8 changes: 8 additions & 0 deletions changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,14 @@

## Chores

- 2026-09-29: Whisper-CLI-CI-Smoke-Test verwendet ein festes Sprachsample statt macOS-Texte-to-Speech.
- Dateien: `scripts/verify_whisper_cli_runtime.sh`, `changelog.md`.
- Funktionalität: Der macOS-Smoke-Test transkribiert jetzt das im gepinnten whisper.cpp-Submodul enthaltene JFK-WAV-Sample. Dadurch hängt die CI-Prüfung nicht mehr von der auf GitHub-Runnern erzeugten `say`-Audioausgabe ab. Wenn Whisper keinen Text erzeugt, zeigt der Fehler zusätzlich die CLI-Ausgaben.

- 2026-09-29: Release-Notizen automatisch aus gemergten PRs und direkten Commits erstellen.
- Dateien: `.github/release.yml`, `.github/PULL_REQUEST_TEMPLATE.md`, `.github/workflows/release.yml`, `scripts/generate_macos_release_notes.sh`, `AGENTS.md`, `docs/distribution.md`, `changelog.md`.
- Funktionalität: Der manuelle macOS-Release-Workflow erzeugt jetzt kategorisierte GitHub-Release-Notizen einschließlich Beitragenden und Commit-Nachrichten ohne PR. Eine PR-Vorlage beschreibt verständliche Titel und erforderliche Release-Labels; die Notiz enthält außerdem kurze Installationshinweise und den maschinenlesbaren getesteten Quell-Commit. Produktversion unverändert, da nur Release-Tooling und Dokumentation geändert wurden.

- 2026-09-28: Publish-Prüfung für GitHub-Draft-Releases korrigiert.
- Dateien: `.github/workflows/release.yml`, `AGENTS.md`, `docs/distribution.md`, `changelog.md`.
- Funktionalität: Der Publish-Workflow erwartet keinen Git-Tag vor der Veröffentlichung, weil GitHub den Tag bei einem Draft noch nicht anlegt. Stattdessen prüft er den im Draft dokumentierten Quell-Commit gegen die aktuelle `main`-Historie, bindet beim Veröffentlichen den Tag explizit an diesen Commit und kontrolliert anschließend dessen Ziel. Bugfix-Version `0.47.1`.
Expand Down
6 changes: 4 additions & 2 deletions docs/distribution.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

- The visible product name and generated app bundle are **Cortexa** (`Cortexa.app`); the target and scheme retain the technical name `WisprLocalMac`, and the bundle identifier remains `com.wisprlocal.mac`.
- The open-source release is built on GitHub Actions' standard `macos-26` Apple Silicon runner with Xcode 26 or later, so the Icon Composer app icon and Apple Foundation Models integration are compiled into the arm64 app. No self-hosted machine or Apple credentials are needed. The workflow runs only when manually dispatched from `main`: `action=prepare` builds and verifies the arm64 DMG and creates a draft release; after installation and acceptance testing, `action=publish` publishes that exact draft. It requires no Apple Developer Program membership, Developer ID certificate, Apple account secret, or notarization. The app and bundled Whisper CLI are both arm64.
- Repository Actions settings allow only `actions/checkout@v5`; the default `GITHUB_TOKEN` permission is read-only. Release jobs request `contents: write` only where they create or publish a release. Workflows cannot create or approve pull requests, and the repository's Pull Requests feature is disabled.
- Repository Actions settings allow only `actions/checkout@v5`; the default `GITHUB_TOKEN` permission is read-only. Release jobs request `contents: write` to manage releases and `pull-requests: read` only to identify commits already represented by PRs. Workflows cannot create or approve pull requests. Pull Requests are enabled and are the integration path for future changes.
- The app bundle receives an ad-hoc code signature to seal its files and verify that packaging did not alter it. This is not an Apple developer identity. Gatekeeper may block the first launch; the user must choose **Open** from the app's context menu or approve it in **System Settings → Privacy & Security**.
- Ad-hoc signing has no stable Apple team identity. macOS may ask again for protected-resource permissions after a future app replacement. The bundle identifier stays `com.wisprlocal.mac`, but this does not guarantee TCC permission continuity without the same persistent signing identity.
- The local release command cleans previous files in this checkout's `artifacts/mac/`, verifies Xcode 26+ and the FoundationModels SDK, initializes the pinned Whisper submodule when needed, builds the CLI and app, validates the compiled `Cortexa.icns`, the `FoundationModels.framework` link, and runtime contents, creates a drag-and-drop DMG, verifies it, and writes a SHA-256 checksum and install notes. CI runs the same command on a fresh hosted runner, then attaches these three assets to the draft release.
Expand All @@ -21,7 +21,9 @@ Expected assets:

The Finder DMG layout uses the matching 800 × 500 SVG background and `dmgbuild` metadata. `create_macos_dmg.sh` verifies the app's ad-hoc signature after packaging. Release builds contain no speech model; onboarding downloads one on first launch. The Whisper CLI is linked statically against Whisper/GGML, and `validate_macos_app_runtime.sh` verifies the executable and runtime manifest.

To prepare a draft from `main`, run **Actions → macOS Release** with `action=prepare` and the value from `VERSION`. GitHub does not create the release tag while the release is a draft. To publish it after installation testing, run the same workflow with `action=publish` and the same version. The workflow checks branch, version, source commit, and draft state, verifies the assets, then creates the tag at the tested source commit while publishing. For manual local verification steps, see [`macos-release-checklist.md`](macos-release-checklist.md). The old Developer ID archive, export, notarization, stapling, and Sparkle appcast scripts remain available for a future signed distribution, but they are not part of the current release path.
To prepare a draft from `main`, run **Actions → macOS Release** with `action=prepare` and the value from `VERSION`. The workflow finds the previous published release, asks GitHub's Release Notes API to summarize merged PRs, groups them using `.github/release.yml`, and includes contributors plus a full changelog link. A helper adds commit subjects and authors for direct commits that have no associated PR, so older work is not lost. PR titles must be descriptive on their own; apply one category label listed in `.github/PULL_REQUEST_TEMPLATE.md` before merging. The generated body also includes concise install guidance and a machine-readable source commit used by the publish check. The notes are suitable both for the draft and published release; there is no internal “publish this draft” text.

GitHub does not create the release tag while the release is a draft. After installation testing, run the same workflow with `action=publish` and the same version. The workflow checks branch, version, source commit, draft state and assets, then verifies the downloaded checksum and DMG before creating the tag at the tested source commit while publishing. For manual local verification steps, see [`macos-release-checklist.md`](macos-release-checklist.md). The old Developer ID archive, export, notarization, stapling, and Sparkle appcast scripts remain available for a future signed distribution, but they are not part of the current release path.

## Updater configuration

Expand Down
73 changes: 73 additions & 0 deletions scripts/generate_macos_release_notes.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env bash
set -euo pipefail

if [[ "$#" -ne 4 ]]; then
echo "Usage: $0 <version> <source-commit> <previous-release-tag> <output-file>" >&2
exit 2
fi

RELEASE_VERSION="$1"
SOURCE_COMMIT="$2"
PREVIOUS_TAG="$3"
OUTPUT_FILE="$4"
GITHUB_REPOSITORY_VALUE="${GITHUB_REPOSITORY:-LeonStadler/Cortexa}"
RELEASE_TAG="v${RELEASE_VERSION}"

[[ "${RELEASE_VERSION}" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || {
echo "Release version must use MAJOR.MINOR.PATCH: ${RELEASE_VERSION}" >&2
exit 2
}
[[ "${SOURCE_COMMIT}" =~ ^[0-9a-f]{40}$ ]] || {
echo "Source commit must be a full lowercase Git SHA: ${SOURCE_COMMIT}" >&2
exit 2
}
[[ -n "${PREVIOUS_TAG}" ]] || {
echo "A previous published release tag is required to generate release notes." >&2
exit 2
}
git cat-file -e "${SOURCE_COMMIT}^{commit}"
git rev-parse --verify "refs/tags/${PREVIOUS_TAG}^{commit}" >/dev/null
git merge-base --is-ancestor "${PREVIOUS_TAG}" "${SOURCE_COMMIT}" || {
echo "Previous release ${PREVIOUS_TAG} is not an ancestor of ${SOURCE_COMMIT}." >&2
exit 1
}

NOTES_RESPONSE="$(gh api --method POST \
"repos/${GITHUB_REPOSITORY_VALUE}/releases/generate-notes" \
-f "tag_name=${RELEASE_TAG}" \
-f "target_commitish=${SOURCE_COMMIT}" \
-f "previous_tag_name=${PREVIOUS_TAG}" \
-f "configuration_file_path=.github/release.yml")"
GENERATED_NOTES="$(jq -r '.body' <<<"${NOTES_RESPONSE}")"

DIRECT_COMMITS="$(git rev-list --no-merges "${PREVIOUS_TAG}..${SOURCE_COMMIT}")"
DIRECT_NOTES=()
while IFS= read -r COMMIT; do
[[ -n "${COMMIT}" ]] || continue
ASSOCIATED_PR_COUNT="$(gh api "repos/${GITHUB_REPOSITORY_VALUE}/commits/${COMMIT}/pulls" --jq 'length')"
[[ "${ASSOCIATED_PR_COUNT}" == "0" ]] || continue

SUBJECT="$(git show -s --format=%s "${COMMIT}")"
AUTHOR="$(git show -s --format=%an "${COMMIT}")"
DIRECT_NOTES+=("- ${SUBJECT} — ${AUTHOR} (${COMMIT:0:7})")
done <<<"${DIRECT_COMMITS}"

{
printf '## Cortexa %s — macOS, Apple Silicon\n\n' "${RELEASE_VERSION}"

if [[ "${#DIRECT_NOTES[@]}" -gt 0 ]]; then
printf '### Direct commits\n\n'
printf '%s\n' "${DIRECT_NOTES[@]}"
printf '\n'
fi

printf '%s\n\n' "${GENERATED_NOTES}"

cat <<'EOF'
### Download and install

The DMG targets Apple Silicon (arm64). This open-source build is ad-hoc signed and is not notarized; macOS may ask you to approve the first launch. See the included install notes. Speech models are downloaded by the app when needed.
EOF

printf '\nSource commit: %s\n' "${SOURCE_COMMIT}"
} >"${OUTPUT_FILE}"
Loading
Loading