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
189 changes: 181 additions & 8 deletions .agents/skills/release/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,62 @@ without burning a notarization, TestFlight upload, or build number.
- **Do not wait forever.** If GitHub notarization or TestFlight processing
exceeds its normal window by a lot, preserve state, retry only the failed
phase when possible, or stop with a clear recovery command.
- **Do not serialize independent tiers.** Desktop GitHub Actions, iOS
TestFlight, and Cloudflare are independent once the shared docs/`ci-pass`
gate is behind you. Numbered phases are a catalog, not a queue. Follow
**Parallel schedule** below whenever more than one is in scope.

## Parallel schedule

This overrides the Phase 4 → 5 → 5.5 order when more than one tier is in
scope. Exclusive `gh run watch` is forbidden while another leg can make
progress — poll the run and keep working.

Stay serial only for:

1. Docs merge → green `ci-pass` on that SHA → desktop `v*` tag. Tagging
before `ci-pass` fails `verify`.
2. Draft asset verification → `--draft=false`. npm versions are immutable.
3. iOS IPA inspect / `altool --validate-app` → upload → `asc builds wait` →
group attach.
4. A Windows release host cannot archive iOS. Keep desktop and Cloudflare
in flight and say iOS needs a macOS host. Do not call the release done.

### During the `ci-pass` wait (docs PR just landed)

- Cloudflare: `deploy-web.yml` is already running for that SHA. Start
Phase 5.5 verification. Do not wait for a desktop tag.
- iOS: `asc doctor` and the App Clip preflight. Do not archive yet — a red
`ci-pass` would burn a build number.

### After the desktop tag (or immediately if desktop is out of scope)

Start every in-scope leg:

- Desktop: poll `release.yml` until the draft exists. If iOS or Cloudflare
is also in scope, do **not** sit in `gh run watch`.
- iOS (macOS host): archive, export, inspect, validate, upload against
`RELEASE_SHA`. TestFlight does not need the GitHub draft.
- Cloudflare: finish any remaining reconcile or manual deploy.

### Join before undraft

If desktop is in scope, the draft must be verified before it goes public.
iOS may still be in `asc builds wait`. Cloudflare may still be verifying.
That is expected — do not stall undraft on them.

### After undraft

Poll `publish-runtime-packages.yml` and `update-brew-tap.yml` together and
join both. Keep polling iOS processing and Cloudflare if those legs are not
done. Desktop is not done until `npm view @ade-dev/runtime version` equals
the tag.

### Join before `phase=done`

Every in-scope leg is green. A green GitHub release with a missing npm
package, a missing TestFlight build, or a drifted Worker is not a finished
release.

## Machine Notes

Expand Down Expand Up @@ -269,7 +325,7 @@ Desktop release is needed if any changed file matches:
- `apps/desktop/**`
- `apps/ade-cli/**`
- desktop/runtime release scripts under `apps/desktop/scripts/**`
- `.github/workflows/release*.yml`, `.github/workflows/update-brew-tap.yml`
- `.github/workflows/release*.yml`, `.github/workflows/update-brew-tap.yml`, `.github/workflows/publish-runtime-packages.yml`
- shared package files that desktop imports
- root package/build files that affect desktop packaging

Expand Down Expand Up @@ -421,7 +477,10 @@ changelog. An SDK-docs-only commit does not get a `v*` tag.

## Phase 4: Desktop GitHub Workflow Release

Do this only if desktop scope is `yes`.
Do this only if desktop scope is `yes`. If iOS or Cloudflare is also in
scope, start those legs as soon as the tag exists — see **Parallel
schedule**. This phase is the desktop leg, not a barrier in front of the
others.

The desktop happy path is GitHub Actions. Do not run local desktop release
commands such as `release:mac:local`, `dist:mac:universal:signed`,
Expand Down Expand Up @@ -473,7 +532,7 @@ gh run rerun "$RUN_ID" --repo arul28/ADE --failed
The pushed tag triggers `.github/workflows/release.yml`, which calls
`.github/workflows/release-core.yml` and creates a draft GitHub Release.

### Find and watch the workflow run
### Find and poll the workflow run

Find the run for the pushed tag/SHA:

Expand All @@ -484,13 +543,24 @@ gh run list --repo arul28/ADE --workflow release.yml --event push \
```

Choose the run whose `headBranch` is `v<VERSION>` or whose `headSha` matches
`RELEASE_SHA`, then watch it:
`RELEASE_SHA`.

If this is a desktop-only release, `gh run watch` is fine:

```bash
gh run view "$RUN_ID" --repo arul28/ADE --json status,conclusion,url,jobs
gh run watch "$RUN_ID" --repo arul28/ADE --interval 60
```

If iOS or Cloudflare is also in scope, poll instead and keep those legs
moving. `gh run watch` blocks the conductor for the whole notarization
window.

```bash
gh run view "$RUN_ID" --repo arul28/ADE --json status,conclusion,url,jobs
# Poll on a timer between iOS/Cloudflare steps; do not exclusive-watch.
```

Expected shape:

- runtime/resource jobs run first
Expand All @@ -501,7 +571,10 @@ Expected shape:
- `publish-release` (in `release-publish.yml`, called by `release.yml` after
`run-release` succeeds) merges the per-arch updater manifests and creates the
draft
- `update-brew-tap` runs after publication
- `update-brew-tap` and `Publish ADE runtime packages` both run after the
GitHub release is made public (`release.published`), not when the draft is
created. After undraft, poll both runs together and join both. Do not
finish the npm wait before starting the brew wait, or the reverse.

If `platforms=mac,win` and `build-win-release` did not run, stop. The gate and
the run disagree, and publishing would ship a macOS-only release under a
Expand Down Expand Up @@ -617,9 +690,98 @@ gh api repos/arul28/ADE/releases/latest \
--jq '{tag_name,draft,prerelease,html_url,asset_count:(.assets|length)}'
```

Undrafting publishes the GitHub release. That event starts
`publish-runtime-packages.yml` and `update-brew-tap.yml`. Poll both. npm
versions are immutable, which is why this waits for `--draft=false` instead of
the tag push. Desktop is not done until the npm job succeeds and
`npm view @ade-dev/runtime version` equals this tag.

Do not exclusive-watch either run if iOS processing or Cloudflare is still
in flight. Keep the run IDs and poll them between those legs. `gh run watch`
belongs only to a desktop-only release, or to the join after the other legs
have finished.

```bash
set -euo pipefail
# The release event can take a few seconds to enqueue the runs.
find_run() {
local workflow="$1"
gh run list --repo arul28/ADE --workflow "$workflow" --event release \
--json databaseId,headBranch,status,conclusion,url \
--jq "[.[] | select(.headBranch==\"v<VERSION>\")][0].databaseId"
}
run_id_ready() {
[ -n "${1:-}" ] && [ "$1" != "null" ]
}
BREW_EXPECTED=0
if [ "$(gh secret list --repo arul28/ADE --json name --jq 'any(.[]; .name == "HOMEBREW_TAP_DEPLOY_KEY")')" = "true" ]; then
BREW_EXPECTED=1
fi
for _ in 1 2 3 4 5 6; do
NPM_RUN_ID=$(find_run publish-runtime-packages.yml)
BREW_RUN_ID=$(find_run update-brew-tap.yml)
if run_id_ready "$NPM_RUN_ID"; then
if [ "$BREW_EXPECTED" != "1" ] || run_id_ready "$BREW_RUN_ID"; then
break
fi
fi
sleep 10
done
if ! run_id_ready "${NPM_RUN_ID:-}"; then
echo "Publish ADE runtime packages did not start for v<VERSION>"
exit 1
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
if [ "$BREW_EXPECTED" = "1" ] && ! run_id_ready "${BREW_RUN_ID:-}"; then
echo "update-brew-tap did not start for v<VERSION>"
exit 1
fi
```

While iOS or Cloudflare is still in flight, poll — do not `wait` yet:

```bash
gh run view "$NPM_RUN_ID" --repo arul28/ADE --json status,conclusion,url
if [ -n "${BREW_RUN_ID:-}" ] && [ "$BREW_RUN_ID" != "null" ]; then
gh run view "$BREW_RUN_ID" --repo arul28/ADE --json status,conclusion,url
fi
```

Desktop-only, or joining after the other legs finished. Watch both in
parallel so neither wait starts only after the other has already completed:

```bash
gh run watch "$NPM_RUN_ID" --repo arul28/ADE --interval 30 --exit-status &
NPM_WATCH=$!
if [ -n "${BREW_RUN_ID:-}" ] && [ "$BREW_RUN_ID" != "null" ]; then
gh run watch "$BREW_RUN_ID" --repo arul28/ADE --interval 30 --exit-status &
BREW_WATCH=$!
fi
wait "$NPM_WATCH"
[ -n "${BREW_WATCH:-}" ] && wait "$BREW_WATCH"
gh run view "$NPM_RUN_ID" --repo arul28/ADE --json conclusion,url --exit-status
if [ -n "${BREW_RUN_ID:-}" ] && [ "$BREW_RUN_ID" != "null" ]; then
gh run view "$BREW_RUN_ID" --repo arul28/ADE --json conclusion,url --exit-status
fi
RUNTIME_VERSION=$(npm view @ade-dev/runtime version)
DARWIN_VERSION=$(npm view @ade-dev/runtime-darwin-arm64 version)
if [ "$RUNTIME_VERSION" != "<VERSION>" ] || [ "$DARWIN_VERSION" != "<VERSION>" ]; then
echo "expected @ade-dev/runtime@<VERSION> and @ade-dev/runtime-darwin-arm64@<VERSION>, got $RUNTIME_VERSION / $DARWIN_VERSION"
exit 1
fi
```

If the npm run fails because Trusted Publisher / `RUNTIME_TRUSTED_PUBLISHING` is
not configured, that is a release blocker, not a skip. `workflow_dispatch` on
the same workflow (tag + confirm `publish`) is recovery only.

A missing brew run is not a skip when `HOMEBREW_TAP_DEPLOY_KEY` is configured;
treat a failed tap bump as a desktop-leg failure.

## Phase 5: iOS TestFlight Build and Distribution

Do this only if iOS scope is `yes`.
Do this only if iOS scope is `yes`. Start it after the shared `ci-pass` +
tag gate (or immediately on an iOS-only release). Do **not** wait for the
desktop draft, undraft, npm publish, or brew tap — see **Parallel schedule**.

Preflight:

Expand Down Expand Up @@ -707,6 +869,11 @@ asc builds wait \
--timeout 40m
```

When desktop is also in scope, do not park the whole conductor on this wait
if the IPA is already uploaded. Poll `asc builds list` until
`processingState=VALID` while undraft, npm, and brew proceed. `asc builds wait`
is the iOS-only happy path.

If automatic export fails due signing, use the repo's signing gotchas in
`AGENTS.md` and the `asc-*` skills. Fix signing/profiles; do not silently remove
targets.
Expand Down Expand Up @@ -765,6 +932,10 @@ Every release reconciles it, or production silently drifts (this bit v1.2.28 and
v1.2.29: a rewritten web client and two changed Workers sat undeployed while the
desktop shipped).

Start this phase as soon as the docs PR (or any in-scope `main` push) lands.
`deploy-web.yml` runs on that push. Do not wait for the desktop tag, the
draft, npm, or TestFlight — see **Parallel schedule**.

**A Cloudflare surface has two halves, and only one of them is in git.** The code
half is `wrangler.jsonc` plus `src/`. The account half — whether the R2 bucket
exists, whether it has a lifecycle rule, whether a D1 migration was actually
Expand Down Expand Up @@ -1272,8 +1443,10 @@ Desktop:
- If a GitHub notarization job stalls, do not restart everything. Cancel the
stuck run only when it has exceeded the cutoff, then use
`gh run rerun --failed`.
- If the publish job fails after mac artifacts succeeded, inspect the draft
release/assets and workflow logs before rerunning anything.
- If the runtime npm publish job fails after the GitHub release is public,
rerun `publish-runtime-packages.yml` with the same tag and confirm `publish`.
Skip-if-exists means packages that already landed are left alone. Do not
unpublish. Do not invent a new version.
- If `latest-mac.yml` references a missing asset, keep the release draft/private
until fixed.
- If `latest-mac.yml` references a universal ZIP, keep the release draft/private
Expand Down
42 changes: 26 additions & 16 deletions .github/workflows/publish-runtime-packages.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,36 @@
# @ade-dev/runtime-linux-x64 @ade-dev/runtime-linux-arm64
# @ade-dev/runtime-win32-x64 @ade-dev/runtime (the meta package)
#
# MANUAL ONLY, and deliberately so. This publishes the bytes of an EXISTING
# GitHub release, so it must run after that release is public and its assets
# are final. It never builds a runtime; it downloads one, verifies it against
# the release's own SHA256SUMS, and repackages it. The version published is the
# release tag with the leading `v` removed, which keeps the npm version and the
# ADE release version identical by construction.
# This publishes the bytes of an EXISTING GitHub release: it never builds a
# runtime. It downloads one, verifies it against the release's own SHA256SUMS,
# and repackages it. npm versions are immutable, so this must run only after
# the release is public and its assets are final — the same moment
# update-brew-tap.yml fires. `/release` undrafts; this workflow then publishes
# `@ade-dev/runtime*`. The npm version is the release tag with the leading `v`
# removed, so it stays identical to the ADE release by construction.
#
# Triggers:
# - `release.published` (not draft creation, not the v* tag push). Prereleases
# are skipped. This is the `/release` path.
# - `workflow_dispatch` with confirm=publish remains as recovery for one tag.
#
# Credentials — the same two modes as publish-sdk-packages.yml:
#
# 1. Trusted publishing (OIDC, no secret). Configure per package on npmjs.com →
# Settings → Trusted Publisher: repository `arul28/ADE`, workflow
# `publish-runtime-packages.yml`. npm requires the package to already exist
# to configure it, so the FIRST version of each of the six ships by mode 2
# (or from a maintainer machine). Then set the repo variable
# `publish-runtime-packages.yml`. Then set the repo variable
# `RUNTIME_TRUSTED_PUBLISHING=true`.
# 2. `NPM_TOKEN` secret — a granular access token scoped to the `@ade-dev`
# packages, with an expiry.
#
# With neither configured the run ends early with a notice instead of failing.
# With neither configured the run fails. A silent skip would look like a
# successful `/release` that never updated npm.

name: Publish ADE runtime packages

on:
release:
types: [published]
workflow_dispatch:
inputs:
tag:
Expand All @@ -57,7 +64,11 @@ concurrency:

jobs:
publish:
if: ${{ inputs.confirm == 'publish' }}
if: >-
${{
(github.event_name == 'release' && !github.event.release.prerelease)
|| (github.event_name == 'workflow_dispatch' && inputs.confirm == 'publish')
}}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
Expand All @@ -75,7 +86,7 @@ jobs:
- name: Resolve the release version
id: version
env:
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}
run: |
set -euo pipefail
case "$RELEASE_TAG" in
Expand Down Expand Up @@ -105,16 +116,15 @@ jobs:
echo "present=true" >> "$GITHUB_OUTPUT"
echo "mode=token" >> "$GITHUB_OUTPUT"
else
echo "Neither RUNTIME_TRUSTED_PUBLISHING nor NPM_TOKEN is configured; skipping publish." >> "$GITHUB_STEP_SUMMARY"
echo "present=false" >> "$GITHUB_OUTPUT"
echo "mode=none" >> "$GITHUB_OUTPUT"
echo "::error::Neither RUNTIME_TRUSTED_PUBLISHING nor NPM_TOKEN is configured; refusing to skip a runtime npm publish."
exit 1
fi

- name: Download the release artifacts
if: steps.token.outputs.present == 'true'
env:
GH_TOKEN: ${{ github.token }}
RELEASE_TAG: ${{ inputs.tag }}
RELEASE_TAG: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.tag }}
run: |
set -euo pipefail
mkdir -p release-artifacts
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Licensing is documented in one place. A new page states what the SDK package, the chat-ui package, the runtime binary, and ADE itself are licensed under, and CI now fails a release whose SPDX field, `LICENSE` file, tarball, and README disagree (#1211, #1212).
- `@ade-dev/sdk` and `@ade-dev/chat-ui` are MIT, and a proprietary app may ship the runtime binary. ADE itself, the desktop app, the CLI, and the runtime binary stay AGPL-3.0-only, but the new ADE Runtime Embedding Exception permits distributing an unmodified runtime binary inside a larger work that consumes it through the documented SDK interface. Re-signing the binary with your own identity does not count as modifying it. Modifying the runtime, or linking ADE source, keeps the AGPL in force. Every `@ade-dev/runtime-*` package now carries both documents (#1211).
- `@ade-dev/sdk` and `@ade-dev/chat-ui` are at 0.2.0. Every 0.1.x option keeps its exact behavior; everything above is additive (#1212).
- `@ade-dev/runtime*` publishes when a GitHub release is made public. `/release` undrafts; the same `release.published` event as the brew tap then packs and publishes the six platform packages at that tag's version. A separate Actions click is recovery, not the release path.

## [1.2.72] - 2026-09-02

Expand Down
Loading
Loading