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
13 changes: 13 additions & 0 deletions .github/pull_request_template.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,19 @@

<!-- List automated tests and any manual OS/runtime scenarios exercised. -->

## Release note

<!--
Required: choose exactly one.

User-visible change:
Add one or more release-notes.d/*.md fragments.

No user-visible change:
Apply the release-note:none label and write:
None: <specific reason>
-->

## Release and security check

- [ ] I considered whether this changes permissions, downloaded software, executed commands, dependencies, configuration files, or release artifacts.
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
branches: ["main"]
pull_request:
branches: ["main"]
merge_group:
schedule:
- cron: "17 8 * * 1"
workflow_dispatch:
Expand Down
39 changes: 39 additions & 0 deletions .github/workflows/release-note-policy.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
name: Release-note policy

on:
pull_request:
branches: [main]
types: [opened, synchronize, reopened, edited, labeled, unlabeled]
merge_group:
push:
branches: [main]

permissions:
contents: read

jobs:
release-note-policy:
name: release-note-policy
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
fetch-depth: 0
persist-credentials: false

- name: Set up Node
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6.5.0
with:
node-version: 24

- name: Test release-note tooling
run: node --test tests/release-notes.test.mjs

- name: Validate stored fragments
run: node scripts/release-notes.mjs validate-fragments

- name: Validate pull request metadata
if: github.event_name == 'pull_request'
run: node scripts/release-notes.mjs validate-pr
23 changes: 22 additions & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,21 @@ jobs:
exit 1
fi

- name: Require reviewed release notes
shell: bash
run: |
notes="docs/releases/${GITHUB_REF_NAME}.md"
test -s "${notes}" || {
echo "Missing reviewed release notes: ${notes}"
exit 1
}
remaining=$(find release-notes.d -maxdepth 1 -type f -name '*.md' ! -iname 'README.md' -print)
test -z "${remaining}" || {
echo "Release-note fragments must be incorporated before tagging:"
echo "${remaining}"
exit 1
}

- uses: actions/setup-go@924ae3a1cded613372ab5595356fb5720e22ba16 # v6.5.0
with:
go-version-file: go.mod
Expand Down Expand Up @@ -280,6 +295,11 @@ jobs:
id-token: write
attestations: write
steps:
- name: Checkout release source
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
persist-credentials: false

- name: Download archives
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
Expand All @@ -304,6 +324,7 @@ jobs:
with:
files: dist/*
fail_on_unmatched_files: true
generate_release_notes: true
body_path: docs/releases/${{ github.ref_name }}.md
generate_release_notes: false
prerelease: ${{ contains(github.ref_name, '-') }}
make_latest: ${{ !contains(github.ref_name, '-') }}
18 changes: 18 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Contributing to Omnideck CLI

Keep pull requests focused, explain the outcome, and include the relevant
automated and manual verification.

## Pull request requirements

- Use a [Conventional Commit](https://www.conventionalcommits.org/en/v1.0.0/)
title such as `feat(desktop): add native zoom` or
`fix(setup): recover from an occupied port`.
- Add a user-facing file under `release-notes.d/`, or apply
`release-note:none` and explain `None: <reason>` under the pull request's
`## Release note` heading.
- Update documentation and tests when behavior changes.
- Run the repository's documented quality checks before requesting review.

Read [the release-note policy](docs/release-notes.md) for fragment categories,
examples, validation, and release generation.
32 changes: 23 additions & 9 deletions RELEASING.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,20 +23,34 @@ the default release.

## Publish a preview

1. Run `make verify` locally.
2. Merge the intended changes to `main` and ensure every required CI and CodeQL
1. Choose the next version, then generate its checked-in release draft from
the outstanding fragments:

```sh
VERSION=v0.8.0-alpha.1
node scripts/release-notes.mjs generate \
--version "${VERSION}" \
--output "docs/releases/${VERSION}.md"
```

Curate the generated file for user-visible outcomes, upgrade guidance, known
limitations, and preview feedback. Remove the fragments incorporated into
that file. The release pull request uses `release-note:none` with a reason
explaining that it only aggregates previously reviewed fragments.
2. Run `make verify` locally.
3. Merge the release change to `main` and ensure every required CI and CodeQL
check is green.
3. Choose the next prerelease identifier. Increment the final number for every
new build; never move or replace a published tag.
4. Create and push an annotated tag:
4. Increment the final prerelease number for every new build; never move or
replace a published tag.
5. Create and push an annotated tag:

```sh
git switch main
git pull --ff-only
git tag -a v0.8.0-alpha.1 -m "Omnideck CLI v0.8.0-alpha.1"
git push origin v0.8.0-alpha.1
```
5. Open the release workflow. Confirm that the source checks, vulnerability
6. Open the release workflow. Confirm that the source checks, vulnerability
scan, builds, SBOM generation, and provenance attestations passed. Approve
the protected `release` environment only after reviewing those results.

Expand Down Expand Up @@ -95,6 +109,6 @@ provenance, embedded version, and portable contract.
If any RC check fails after publication, fix forward on `main` and publish the
next RC number. Do not replace the failed RC.

GitHub-generated notes are a useful baseline. Curate the release description for
user-visible changes, upgrade notes, known limitations, and a short request for
preview feedback.
The tag workflow publishes the exact checked-in
`docs/releases/<version>.md` body. It does not generate a release description
from raw commit or pull request titles.
111 changes: 111 additions & 0 deletions docs/release-notes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Release-note policy

This repository uses [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/)
for change classification, [Semantic Versioning](https://semver.org/) where the
repository publishes versions, and the change categories from
[Keep a Changelog](https://keepachangelog.com/en/2.0.0/). Release notes are
written for people installing or using the omnideck command-line interface.

Every pull request must make an explicit release-note decision. The required CI
check accepts exactly one of:

1. one or more new, valid files under `release-notes.d/`; or
2. the `release-note:none` label plus `None: <reason>` under the pull
request's `## Release note` heading.

Features and breaking changes cannot use `release-note:none`.

## Pull request titles

The pull request title is the canonical machine-readable description and must
use this form:

```text
<type>[optional scope][optional !]: <description>
```

Allowed types are `build`, `chore`, `ci`, `docs`, `feat`, `fix`,
`perf`, `refactor`, `revert`, `style`, and `test`. Examples:

```text
feat(desktop): add native application zoom
fix(cli): preserve the selected runtime port
ci(release): verify published checksums
feat(api)!: remove the legacy profile schema
```

Use a concise technical title. Put polished user-facing copy in the fragment.

## Release-note fragments

Add a short, unique Markdown file such as
`release-notes.d/native-desktop-zoom.md`:

```markdown
---
type: added
area: desktop
---

Zoom the entire application with Ctrl/Cmd and +, -, or 0. Tabs, menus, and
previews remain aligned at every zoom level.
```

The required fields are:

- `type`: `added`, `changed`, `deprecated`, `removed`, `fixed`, or
`security`
- `area`: a lowercase kebab-case product or repository area
- body: plain, user-facing prose describing the outcome

Write what changed for the reader and why it matters. Avoid build systems,
test environments, commit hashes, internal refactors, and qualification detail
unless the repository's users must act on them. Include upgrade or migration
guidance when behavior is incompatible.

A pull request may add multiple fragments when it contains distinct notable
changes. Do not combine a fragment with `release-note:none`.

## Changes without release notes

For maintenance that has no externally visible outcome:

1. apply the `release-note:none` label; and
2. write a specific reason in the pull request body:

```markdown
## Release note

None: Expands release qualification only; shipped behavior is unchanged.
```

The explicit reason makes omissions reviewable. `feat` titles and titles with
`!` must provide fragments instead.

## Validation and generation

Run the shared local checks:

```sh
node --test tests/release-notes.test.mjs
node scripts/release-notes.mjs validate-fragments
```

Generate a release draft from all outstanding fragments:

```sh
node scripts/release-notes.mjs generate --version v1.2.3
node scripts/release-notes.mjs generate \
--version v1.2.3 \
--output docs/releases/v1.2.3.md
```

Generation groups fragments into the Keep a Changelog categories. The result is
a draft: before publication, add a short release theme when useful, remove
duplication, confirm upgrade guidance and known limitations, and keep the
language focused on the shipped product.

The release change consumes its fragments after their text is incorporated into
the checked-in release notes or changelog. That release pull request uses
`release-note:none` with a reason explaining that it only aggregates already
reviewed fragments.
14 changes: 14 additions & 0 deletions docs/releases/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Checked-in CLI release notes

Each release stores its reviewed GitHub release body here as
`v<version>.md`. Generate the first draft from outstanding fragments:

```sh
node scripts/release-notes.mjs generate \
--version v1.2.3 \
--output docs/releases/v1.2.3.md
```

Curate that draft, commit it with the release change, and remove the fragments
whose text it incorporates. The tag workflow requires the exact file and
publishes it as the GitHub release body.
20 changes: 20 additions & 0 deletions release-notes.d/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Release-note fragments

Every pull request must add a fragment here or explicitly use the
`release-note:none` label with a reason. See
[the release-note policy](../docs/release-notes.md) for the complete contract.

Use a unique lowercase kebab-case filename and this format:

```markdown
---
type: changed
area: setup
---

Setup now selects another available local port automatically when the saved
port is already in use.
```

Valid types are `added`, `changed`, `deprecated`, `removed`, `fixed`,
and `security`. Do not edit this README as a substitute for a fragment.
Loading