-
Notifications
You must be signed in to change notification settings - Fork 52
docs(agents): add development principles, slim release flow, correct branch contract #96
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -55,8 +55,8 @@ The workflow: | |
|
|
||
| 1. Computes the next SemVer from `package.json` + `bump`, builds `vX.Y.Z-rc.N`. | ||
| 2. Migrates every issue/PR in the rolling `Next Release` milestone into a fresh `vX.Y.Z` milestone. Each migrated item gets a hidden marker comment so re-running is idempotent. | ||
| 3. Commits `package.json` → `X.Y.Z-rc.N` on a fresh branch `release/vX.Y.Z-rc.N`. **The branch is NOT merged into `main`** — it stays frozen so the RC build only contains what was on `main` at the moment of cut. | ||
| 4. Pushes the tag `vX.Y.Z-rc.N` at the release branch tip. This triggers `build.yml`, which publishes a **GitHub pre-release** (badged as such, does not become "Latest"). macOS notarization is skipped on RC tags. | ||
| 3. Creates `release/vX.Y.Z` (at rc.1) or reuses it (rc.2+), and commits `package.json` → `X.Y.Z-rc.N` there. **The branch is NOT merged into `main`** — it stays frozen, so the RC contains the cut snapshot plus cherry-picks only. The branch is named for the **stable** version, with no `-rc.N` suffix, because `promote.yml` has to resolve the same ref. | ||
| 4. Pushes the tag `vX.Y.Z-rc.N` at the release branch tip, then **explicitly dispatches `build.yml` with `--ref` pinned to that tag**, which publishes a **GitHub pre-release** (badged as such, does not become "Latest"). Two reasons the dispatch is explicit and pinned: a `GITHUB_TOKEN` tag push does not fire `build.yml`'s `push:` trigger, and the build must check out the *tag* — `main` still carries the previous stable version and would fail the publish step's version guard. macOS notarization is skipped on RC tags. | ||
| 5. Posts in `#rc-testing` on Discord with the download link. | ||
|
|
||
| Tier 3 (homebrew/winget/nix/aur) does **not** run on pre-releases — they're already gated on `!prerelease`. | ||
|
|
@@ -65,9 +65,9 @@ Tier 3 (homebrew/winget/nix/aur) does **not** run on pre-releases — they're al | |
|
|
||
| Pin the pre-release link in `#rc-testing`. Get the maintainer team + a few early adopters to install and smoke-test. | ||
|
|
||
| **Between RC cut and promote**, the only thing that may happen on `release/vX.Y.Z-rc.N` is **cherry-picks of bugfixes** that address problems discovered in the RC. Features, refactors, and CI/docs changes are **not** applied to the release branch — they live on `main` and ship in the next release cycle. | ||
| **Between RC cut and promote**, the only thing that may happen on `release/vX.Y.Z` is **cherry-picks of bugfixes** that address problems discovered in the RC. Features, refactors, and CI/docs changes are **not** applied to the release branch — they live on `main` and ship in the next release cycle. | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✓ The "only cherry-picks of bugfixes" rule is the right contract. The wording "Features, refactors, and CI/docs changes are not applied to the release branch" is precise — those go to |
||
|
|
||
| If the RC has a regression, fix forward on `main`, then **cherry-pick the fix commit onto the release branch** with `git cherry-pick <sha>`, then re-cut as `vX.Y.Z-rc.(N+1)` (the rerun of `prerelease.yml` re-tags the release branch tip; no rebase required because the branch is frozen). The previous RC is auto-superseded by GitHub. | ||
| If the RC has a regression, fix forward on `main`, then **cherry-pick the fix commit onto the release branch** with `git cherry-pick <sha>`, then re-cut as `vX.Y.Z-rc.(N+1)` (the rerun of `prerelease.yml` reuses the frozen branch and re-tags its tip; no rebase required). The previous RC is auto-superseded by GitHub. | ||
|
|
||
| ### Step 3: promote to stable | ||
|
|
||
|
|
@@ -80,29 +80,30 @@ The workflow: | |
|
|
||
| 1. Validates the tag matches `^vX.Y.Z-(rc|beta|alpha)\.N$`. | ||
| 2. Closes the `vX.Y.Z` milestone (snapshotting it for the release notes). | ||
| 3. Checks out `release/vX.Y.Z-rc.N` (the frozen branch), strips `-rc.N` from `package.json`, and commits the bump there. The stable tag points at this tip — the released code is the exact RC + cherry-picks. | ||
| 4. Pushes the tag `vX.Y.Z` and triggers `build.yml` (full notarization). The `release: published` event fires Tier 3 (homebrew/winget/nix/aur) thanks to `OPENSCREEN_RELEASE_TOKEN`. | ||
| 3. Checks out `release/vX.Y.Z` (the frozen branch), strips `-rc.N` from `package.json`, and commits the bump there. The stable tag points at this tip — the released code is the exact RC + cherry-picks. | ||
| 4. Pushes the tag `vX.Y.Z` and dispatches `build.yml` pinned to that tag (full notarization). The `release: published` event fires Tier 3 (homebrew/winget/nix/aur) thanks to `OPENSCREEN_RELEASE_TOKEN`. | ||
| 5. Opens a **release-sync PR** (e.g. `release/v1.6.0-sync → main`) that brings `main` into line with the released snapshot. Rebase-merged via PAT (EtienneLescot is a ruleset bypass actor). | ||
| 6. Posts in `#announcements` on Discord with the release notes + a "Closed issues in this release" list pulled from the milestone. | ||
|
|
||
| The release branch itself **stays around** indefinitely — it is the frozen history of the release, useful for backports and forensics. Deletion happens only when a future major cuts over and supersedes it. | ||
|
|
||
| ### Release branches (the contract) | ||
|
|
||
| Every released version has a corresponding **frozen branch**: | ||
| Every released version has **exactly one frozen branch**, named for the stable version, living from the first RC cut onward: | ||
|
|
||
| ``` | ||
| release/vX.Y.Z-rc.N exists from RC cut until promote finishes | ||
| release/vX.Y.Z created at rc.1, frozen through promote, kept for backports | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✓ Branch-naming examples are correct: |
||
| release/vX.Y.Z-sync ephemeral, created by promote to merge into main | ||
| release/vX.Y.Z stable snapshot post-promote (kept for backports) | ||
| ``` | ||
|
|
||
| The name carries **no `-rc.N` suffix**. `prerelease.yml` and `promote.yml` must resolve the same ref, and every RC of a version re-cuts from this one branch. | ||
|
|
||
| Key rules: | ||
|
|
||
| 1. **`prerelease.yml` creates the branch.** Nothing else pushes to it except the cherry-pick workflow during the RC window. | ||
| 1. **`prerelease.yml` creates the branch at rc.1 and reuses it for later RCs.** It must never delete or recreate it: that would drop the cherry-picks and silently re-cut from `main`, which defeats the freeze this contract exists to guarantee. | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✓ Rule 1 — " |
||
| 2. **`promote.yml` is the only writer** that turns `-rc.N` into the stable version on the branch. | ||
|
Comment on lines
+103
to
104
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Clarify that This absolute wording conflicts with the documented manual fallback at Lines 122-127, where a maintainer directly performs the stable-version bump. Reword it to distinguish the workflow contract from the emergency procedure. 🤖 Prompt for AI Agents |
||
| 3. **`main` is never frozen.** Develop as usual. The release branch is the freeze. | ||
| 4. **Cherry-picks during the RC window** are committed manually by a maintainer (`git checkout release/vX.Y.Z-rc.N && git cherry-pick <sha>`), or rerun `prerelease.yml` to re-tag the branch tip with the same RC version (then bump rc_number). | ||
| 4. **Cherry-picks during the RC window** are committed manually by a maintainer (`git checkout release/vX.Y.Z && git cherry-pick <sha>`), then rerun `prerelease.yml` with the next `rc_number` to re-tag the branch tip. | ||
|
|
||
| This exists because of the v1.6.0 incident (2026-07-05): the original `promote.yml` checked out `main`, so the stable tag captured the post-RC tip of `main` rather than the RC snapshot. Twenty-three commits (Tiptap, NotesWindow, an in-recorder lint button, AI handoff) ended up in v1.6.0 without ever being in v1.6.0-rc.1. The re-release of v1.6.0 on 2026-07-05 used `release/v1.6.0` and cherry-picked only the truly safe commits. | ||
|
|
||
|
|
@@ -112,21 +113,21 @@ If the dispatch UI is unavailable, the workflow still works from a shell: | |
|
|
||
| ```bash | ||
| # Cut RC (skips milestone migration and Discord announce) | ||
| git checkout -b release/v1.5.0-rc.1 main | ||
| git checkout -b release/v1.5.0 main # rc.2+: git checkout release/v1.5.0 instead | ||
| sed -i -E 's|("version"[[:space:]]*:[[:space:]]*")[^"]*(")|\11.5.0-rc.1\2|' package.json | ||
| git add package.json && git commit -m "chore(release): bump to 1.5.0-rc.1 [skip ci]" | ||
| git push origin release/v1.5.0-rc.1 | ||
| git push origin v1.5.0-rc.1 | ||
| git push origin release/v1.5.0 | ||
| git tag v1.5.0-rc.1 && git push origin v1.5.0-rc.1 | ||
|
Comment on lines
+116
to
+120
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win Make the RC fallback work for rc.2+. The comment advertises rc.2 support, but the commands still hardcode Proposed fix-# Cut RC (skips milestone migration and Discord announce)
-git checkout -b release/v1.5.0 main # rc.2+: git checkout release/v1.5.0 instead
-sed -i -E 's|("version"[[:space:]]*:[[:space:]]*")[^"]*(")|\11.5.0-rc.1\2|' package.json
-git add package.json && git commit -m "chore(release): bump to 1.5.0-rc.1 [skip ci]"
+RC_NUMBER=1
+git checkout -b release/v1.5.0 main # rc.2+: git checkout release/v1.5.0 instead
+sed -i -E "s|(\"version\"[[:space:]]*:[[:space:]]*\")[^\"]*(\")|\11.5.0-rc.${RC_NUMBER}\2|" package.json
+git add package.json && git commit -m "chore(release): bump to 1.5.0-rc.${RC_NUMBER} [skip ci]"
git push origin release/v1.5.0
-git tag v1.5.0-rc.1 && git push origin v1.5.0-rc.1
+git tag "v1.5.0-rc.${RC_NUMBER}" && git push origin "v1.5.0-rc.${RC_NUMBER}"🤖 Prompt for AI Agents |
||
|
|
||
| # Promote (skips milestone close and Discord announce) | ||
| git checkout release/v1.5.0-rc.1 | ||
| git checkout release/v1.5.0 | ||
| sed -i -E 's|("version"[[:space:]]*:[[:space:]]*")[^"]*(")|\11.5.0\2|' package.json | ||
| git commit -am "chore(release): bump to 1.5.0 [skip ci]" | ||
| git push origin release/v1.5.0 | ||
| git push origin v1.5.0 | ||
| git tag v1.5.0 && git push origin v1.5.0 | ||
| ``` | ||
|
|
||
| The pipeline can't tell the difference between a manually-pushed tag and a workflow-pushed one — same `build.yml` runs either way. | ||
| A tag pushed manually with your own credentials **does** fire `build.yml`'s `push:` trigger, so the release publishes on its own. (The workflows push tags with `GITHUB_TOKEN`, which does *not* fire it — that's why they dispatch `build.yml` explicitly.) Either way the same `build.yml` builds and publishes. | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✓ Manual fallback correctly distinguishes PAT-pushed tags (which do fire |
||
|
|
||
| ### Backports / patch on a previous line | ||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,15 @@ OpenScreen is a free, open-source screen recorder and video editor (Electron + R | |
| - Format: `npm run format` (Biome, tabs, double quotes, 100-col) | ||
| - i18n check: `npm run i18n:check` (validates the 13 locale files) | ||
|
|
||
| **Use npm, not bun/pnpm/yarn/Deno.** Not a style preference: the native Swift (macOS) and C++ (Windows) capture helpers are rebuilt against Electron's ABI by electron-builder + `@electron/rebuild` resolving `package-lock.json`, and other package managers break that path. `packageManager` + `engines` in `package.json` pin the versions; CI installs with `npm ci`. | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✓ The npm-pin rationale is the right kind of explanation. The "not a style preference: ..." framing converts a vague imperative into a justified constraint, which is exactly what Minor: "the native Swift (macOS) and C++ (Windows) capture helpers are rebuilt against Electron's ABI by electron-builder + |
||
|
|
||
| ## Development principles | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✓ The "No mandated app-stack choice yet." qualifier with the asterisk'd "may be enforced later" is the right escape hatch. Tells future agents this is provisional, not a forever rule. Small wording nit: "Open to React/Vue/Svelte/whatever" is a bit informal. Consider "No mandated UI framework; React is current, but other choices are open" — same meaning, less jokey. |
||
|
|
||
| - Prefer the simplest solution that stays readable. No abstraction for hypothetical needs (YAGNI). | ||
| - **No mandated app-stack choice yet.** Contributors pick their own state/data library. Don't impose one across the codebase and don't refactor existing code onto a different one — keep each addition self-contained and consistent within its own module. A single choice may be enforced later. | ||
| - Don't optimize for line count. A dense one-liner that hides control flow is worse than the explicit version. | ||
| - Match the surrounding code's idiom rather than introducing a new pattern next to it. | ||
|
|
||
| ## Project layout | ||
|
|
||
| - `src/` — React app: UI, editor components, timeline, i18n, captioning/cursor/exporter libs | ||
|
|
@@ -85,15 +94,13 @@ Unit/browser tests can't exercise real capture (native screen recording, a physi | |
|
|
||
| ## Release flow | ||
|
|
||
| Two `workflow_dispatch` workflows cut a release with a pre-release candidate (RC) first, then promote to stable. Trunk-based, no extra branch. Full operational guide in `.harness/docs/git-workflow.md` § Release flow. | ||
|
|
||
| - **Cut RC**: Actions → "Cut a release candidate" → Run workflow. Inputs: `bump` (patch|minor|major), `rc_number` (default 1), optional `target_version` override. Snaps issues out of the rolling `Next Release` milestone into a versioned `vX.Y.Z` milestone, bumps `package.json`, pushes the `vX.Y.Z-rc.N` tag, which triggers the existing `build.yml` to publish a GitHub pre-release. Notarization is skipped on RCs. Notifies `#rc-testing` on Discord. | ||
| - **Promote RC**: Actions → "Promote RC to stable release" → Run workflow. Input: `rc_tag` (e.g. `v1.5.0-rc.2`), optional `release_notes_extra`. Closes the `vX.Y.Z` milestone, strips `-rc.N` from `package.json`, pushes `vX.Y.Z` tag, which triggers `build.yml` to publish a stable release (full notarization, Tier 3 homebrew/winget/nix/aur fires). Notifies `#announcements` on Discord. | ||
| - **Manual fallback**: `git tag vX.Y.Z-rc.N <sha> && git push origin vX.Y.Z-rc.N` does the same as Cut RC (minus the milestone migration and Discord announce) — useful for emergency cuts. | ||
| Two `workflow_dispatch` workflows: cut an RC, then promote it to stable. **Full operational guide, branch contract, cherry-pick rules, and manual fallback: `.harness/docs/git-workflow.md` § Release flow.** Read it before touching a release. | ||
|
|
||
| Both workflows require the `OPENSCREEN_RELEASE_TOKEN` secret (a fine-grained PAT with `contents: write` + `issues: write`). This is the standard fix for `release: published` not triggering downstream workflows when the release is created by `GITHUB_TOKEN`. See `docs/secrets.md`. | ||
| - **Cut RC**: Actions → "Cut a release candidate". Inputs: `bump` (patch|minor|major), `rc_number`, optional `target_version`. Migrates the rolling `Next Release` milestone into a versioned one, creates or reuses `release/vX.Y.Z`, bumps `package.json`, pushes `vX.Y.Z-rc.N`, then dispatches `build.yml` **pinned to that tag** to publish a GitHub pre-release. Notarization is skipped on RCs. Announces in `#rc-testing`. | ||
| - **Promote**: Actions → "Promote RC to stable release". Input: `rc_tag`. Closes the milestone, strips `-rc.N` on the same branch, tags `vX.Y.Z`, publishes a stable release (notarized; Tier 3 homebrew/winget/nix/aur fires), and opens a `release/vX.Y.Z-sync → main` PR. Announces in `#announcements`. | ||
| - **One release branch per stable version** (`release/vX.Y.Z`), created at rc.1 and **frozen** until promote: only cherry-picked bugfixes land on it, so anything merged to `main` after the cut ships in the next cycle. Later RCs re-cut from that same branch. | ||
|
|
||
| **Release branches freeze the build between cut and promote.** Every RC cut creates `release/vX.Y.Z-rc.N`. The branch is *not* merged into `main` until the stable tag is published; only cherry-picks of bugfixes land on the release branch during the RC window. The stable tag points at the branch tip (RC + cherry-picks), then `promote.yml` opens a `release/vX.Y.Z-sync → main` PR to bring main into line. This contract exists because of the v1.6.0 incident (2026-07-05) where the original promote workflow tagged `main` instead of the RC snapshot, causing 23 unreleased commits to ship in `v1.6.0`. Full rules in `.harness/docs/git-workflow.md` § Release branches. | ||
| Both workflows need the `OPENSCREEN_RELEASE_TOKEN` secret (see `docs/secrets.md`); `GITHUB_TOKEN`-created releases don't fire the downstream `release: published` workflows. | ||
|
|
||
| ## Security | ||
|
|
||
|
|
@@ -107,4 +114,4 @@ Both workflows require the `OPENSCREEN_RELEASE_TOKEN` secret (a fine-grained PAT | |
| - **Pixi.js v8** is the rendering engine. Filters come from `pixi-filters` and `@pixi/filter-drop-shadow`. GSAP + `motion` for animation. | ||
| - **i18n**: 13 locales in `src/i18n/locales/<locale>/` (e.g. `src/i18n/locales/en/settings.json`). The `i18n:check` script validates them — run it after touching translation files. | ||
| - **Build pipeline**: `npm run build` is full electron-builder. For iterating on renderer only, use `npm run build-vite` (Vite + tsc, no packaging). | ||
| - **README tone**: the project is explicitly "not production-grade" and free forever — don't add paywalls, premium tiers, or upsell language to UI/copy. | ||
| - **Product constraints**: the project is free forever and explicitly "not production-grade". Don't add paywalls, premium tiers, or feature-gating logic, and don't add upsell language to the README or UI copy. This is a hard constraint, not a judgement call. | ||
|
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ✓ The new The expansion to "feature-gating logic" beyond the previous |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
✓ Explicit
--refdispatch matchesprerelease.yml:136-139. The two cited reasons are accurate:GITHUB_TOKEN-pushed tags don't firebuild.yml'spush:trigger (correct — GitHub deliberately withholds that for security), and the build must check out the tag becausemainstill carries the previous stable version (correct — the publish step has a version guard inbuild.yml:304-308).