Files
KimiCodeSwitch/docs/superpowers/plans/2026-07-29-multi-platform-packaging.md
T
954801926@qq.com 63ae664e72 docs(plan): 多平台打包实施计划
10 个任务覆盖:
- tauri.conf.json 改造 + main.rs 平台条件
- TDD 平台资产选择器 + 单元测
- check_for_update / download_update 接入
- 版本一致性脚本
- GitHub Actions release workflow
- Docker Linux 构建
- BUILD.md
- README 三平台下载 + workflow 徽章
- E2E 验证 (pre-release tag)
2026-07-30 00:15:12 +08:00

24 KiB
Raw Blame History

Kimi Switch Multi-Platform Packaging Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add macOS + Linux packaging support on top of existing Windows MSI; release driven by GitHub Actions tag-push workflow.

Architecture: Source-level changes are minimal (Tauri config tweaks + platform-conditional Rust + asset picker). Build infrastructure is the heavy lifting: one GH Actions workflow (version-check + 3-platform matrix using tauri-action) and a local Docker setup for Linux dev iteration.

Tech Stack: Tauri 2.x, Rust 2021, React 18 + TypeScript 5.5, GitHub Actions (tauri-apps/tauri-action), Docker (Ubuntu 22.04 + webkit2gtk-4.1), bash.

Global Constraints

  • macOS assets: .app + .dmg, unsigned (no Apple Developer ID provided)
  • Linux assets: .deb + .AppImage + .rpm (all three)
  • Windows assets: .msi (existing)
  • Bundle target string in tauri.conf.json: "all" (allow per-platform build)
  • macOS minimum system version: 10.15
  • Update checker picks asset by file extension: .msi / .dmg / .AppImage
  • Temp filename for downloaded installer: KimiSwitch_update.<ext> (ext from OS)
  • Version consistency: git tag must equal package.json:version = Cargo.toml:version = tauri.conf.json:version
  • Cargo behavior: keep tauri = "2.0.0", tauri-plugin-opener = "2.0.0", tauri-plugin-single-instance = "2.0.0"
  • No new lint failures; no new dependencies beyond what's already in Cargo.toml
  • Spec location: docs/superpowers/specs/2026-07-29-multi-platform-packaging-design.md

Task 1: Tauri config — bundle targets and macOS section

Files:

  • Modify: src-tauri/tauri.conf.json

Interfaces:

  • Consumes: existing Windows-only config

  • Produces: bundle.targets = "all", bundle.macOS.minimumSystemVersion, expanded icon list

  • Step 1: Update tauri.conf.json

Replace the existing bundle block with:

{
  "bundle": {
    "active": true,
    "targets": "all",
    "icon": [
      "icons/32x32.png",
      "icons/128x128.png",
      "icons/128x128@2x.png",
      "icons/icon.ico",
      "icons/icon.icns"
    ],
    "windows": {
      "webviewInstallMode": {
        "type": "downloadBootstrapper"
      },
      "nsis": null
    },
    "macOS": {
      "minimumSystemVersion": "10.15"
    },
    "linux": {
      "deb": {
        "depends": []
      },
      "appimage": {
        "bundleMediaFramework": false
      }
    }
  }
}
  • Step 2: Verify JSON is valid

Run: node -e "JSON.parse(require('fs').readFileSync('src-tauri/tauri.conf.json','utf8'))" Expected: no output (silent success)

  • Step 3: Commit
git add src-tauri/tauri.conf.json
git commit -m "feat(bundle): enable all-platform targets + macOS/Linux config"

Task 2: Platform-conditional windows_subsystem in main.rs

Files:

  • Modify: src-tauri/src/main.rs:1

Interfaces:

  • Consumes: existing #![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

  • Produces: Windows-only windows_subsystem; macOS/Linux keep console subsystem so logs are visible

  • Step 1: Replace the directive at line 1

Replace:

#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]

With:

#![cfg_attr(
    all(not(debug_assertions), target_os = "windows"),
    windows_subsystem = "windows"
)]
  • Step 2: Verify Windows build still compiles

Run: cd src-tauri && cargo check --message-format=short 2>&1 | tail -5 Expected: Finished \dev` profile [unoptimized + debuginfo] target(s)` with no errors

  • Step 3: Verify Linux target compiles (cross-check)

Run: cd src-tauri && cargo check --target x86_64-unknown-linux-gnu --message-format=short 2>&1 | tail -5 Expected: finishes clean (may need rustup target add x86_64-unknown-linux-gnu first; if rustup target is missing, run that and retry)

  • Step 4: Commit
git add src-tauri/src/main.rs
git commit -m "fix(build): only set windows_subsystem on Windows targets"

Task 3: TDD — platform-aware asset picker

Files:

  • Modify: src-tauri/src/commands.rs (append at end)

Interfaces:

  • Produces (public to module): fn pick_asset_for_current_os(assets: &[serde_json::Value]) -> Option<String>

  • Pure function, no side effects; testable in isolation

  • Step 1: Write failing tests

Add to the end of src-tauri/src/commands.rs:

#[cfg(test)]
mod asset_picker_tests {
    use super::*;
    use serde_json::json;

    fn assets(names: &[&str]) -> Vec<serde_json::Value> {
        names
            .iter()
            .map(|n| {
                json!({
                    "name": n,
                    "browser_download_url": format!("https://example.com/{n}"),
                })
            })
            .collect()
    }

    #[test]
    fn windows_picks_msi() {
        let assets = assets(&["KimiSwitch_0.7.0_x64_en-US.msi", "KimiSwitch_0.7.0_aarch64.dmg", "KimiSwitch_0.7.0_amd64.AppImage"]);
        let url = pick_asset_for_current_os(&assets);
        assert_eq!(url.as_deref(), Some("https://example.com/KimiSwitch_0.7.0_x64_en-US.msi"));
    }

    #[test]
    fn macos_picks_dmg() {
        let assets = assets(&["KimiSwitch_0.7.0_x64_en-US.msi", "KimiSwitch_0.7.0_aarch64.dmg", "KimiSwitch_0.7.0_amd64.AppImage"]);
        let url = pick_asset_for_current_os(&assets);
        assert_eq!(url.as_deref(), Some("https://example.com/KimiSwitch_0.7.0_aarch64.dmg"));
    }

    #[test]
    fn linux_picks_appimage() {
        let assets = assets(&["KimiSwitch_0.7.0_x64_en-US.msi", "KimiSwitch_0.7.0_aarch64.dmg", "KimiSwitch_0.7.0_amd64.AppImage"]);
        let url = pick_asset_for_current_os(&assets);
        assert_eq!(url.as_deref(), Some("https://example.com/KimiSwitch_0.7.0_amd64.AppImage"));
    }

    #[test]
    fn missing_target_returns_none() {
        let assets = assets(&["KimiSwitch_0.7.0_x64_en-US.msi"]);
        let url = pick_asset_for_current_os(&assets);
        assert_eq!(url, None);
    }

    #[test]
    fn empty_assets_returns_none() {
        let url = pick_asset_for_current_os(&[]);
        assert_eq!(url, None);
    }
}
  • Step 2: Run tests to verify they fail

Run: cd src-tauri && cargo test asset_picker_tests --message-format=short 2>&1 | tail -10 Expected: compile error function \pick_asset_for_current_os` not found`

  • Step 3: Implement the function

Add to src-tauri/src/commands.rs (above the test module):

/// Pick the GitHub release asset matching the current OS by file extension.
/// Preference for Linux is AppImage (portable, no install). If the preferred
/// extension is not present, returns None so the UI can fall back to
/// "open release page" instead of guessing a less-preferred format.
fn pick_asset_for_current_os(assets: &[serde_json::Value]) -> Option<String> {
    let target_ext = match std::env::consts::OS {
        "macos" => "dmg",
        "linux" => "AppImage",
        "windows" => "msi",
        _ => return None,
    };
    assets
        .iter()
        .find(|a| {
            a.get("name")
                .and_then(|n| n.as_str())
                .map(|n| n.ends_with(target_ext))
                .unwrap_or(false)
        })
        .and_then(|a| a.get("browser_download_url"))
        .and_then(|v| v.as_str())
        .map(|s| s.to_string())
}

/// Build the platform-specific temp filename for the downloaded installer.
fn update_temp_filename() -> String {
    let ext = match std::env::consts::OS {
        "macos" => "dmg",
        "linux" => "AppImage",
        "windows" => "msi",
        _ => "bin",
    };
    format!("KimiSwitch_update.{ext}")
}
  • Step 4: Run tests to verify they pass

Run: cd src-tauri && cargo test asset_picker_tests --message-format=short 2>&1 | tail -10 Expected: test result: ok. 5 passed; 0 failed

  • Step 5: Commit
git add src-tauri/src/commands.rs
git commit -m "feat(update): TDD platform-aware asset picker + tests"

Task 4: Wire asset picker into check_for_update and download_update

Files:

  • Modify: src-tauri/src/commands.rs (the check_for_update and download_update functions)

Interfaces:

  • Consumes: pick_asset_for_current_os (Task 3) and update_temp_filename (Task 3)

  • Produces: check_for_update returns download_url for the current OS only; download_update writes to a platform-specific filename

  • Step 1: Replace the asset-selection block in check_for_update

In check_for_update, find the existing block:

    let download_url = first
        .get("assets")
        .and_then(|a| a.as_array())
        .and_then(|a| a.first())
        .and_then(|a| a.get("browser_download_url"))
        .and_then(|v| v.as_str())
        .map(|s| s.to_string());

Replace with:

    let download_url = first
        .get("assets")
        .and_then(|a| a.as_array())
        .and_then(|a| pick_asset_for_current_os(a));
  • Step 2: Replace the temp filename in download_update

In download_update, find the existing line:

    let file_path = temp_dir.join("KimiSwitch_update.msi");

Replace with:

    let file_path = temp_dir.join(update_temp_filename());
  • Step 3: Verify on Windows

Run: cd src-tauri && cargo check --message-format=short 2>&1 | tail -5 Expected: finishes clean

  • Step 4: Run the unit tests

Run: cd src-tauri && cargo test asset_picker_tests --message-format=short 2>&1 | tail -5 Expected: 5 passed; 0 failed

  • Step 5: Commit
git add src-tauri/src/commands.rs
git commit -m "feat(update): use platform-aware asset and filename in update flow"

Task 5: Version consistency check script

Files:

  • Create: scripts/check-version.sh

Interfaces:

  • Inputs: expected_version from CLI arg; reads package.json, src-tauri/Cargo.toml, src-tauri/tauri.conf.json

  • Output: exit 0 if all match, exit 1 otherwise (with diff to stderr)

  • Step 1: Write the script

Create scripts/check-version.sh:

#!/usr/bin/env bash
# Verify that the version across the project matches the expected version.
# Usage: check-version.sh <expected>
# Reads:
#   package.json                  : root
#   src-tauri/Cargo.toml          : version field
#   src-tauri/tauri.conf.json     : version field
set -euo pipefail

EXPECTED="${1:-}"

if [ -z "$EXPECTED" ]; then
  echo "usage: check-version.sh <expected>" >&2
  exit 2
fi

# Strip leading 'v' for comparison (we expect semver without v)
EXPECTED_BARE="${EXPECTED#v}"

PKG_VERSION=$(node -p "require('./package.json').version")
CARGO_VERSION=$(grep -E '^version\s*=' src-tauri/Cargo.toml | head -1 | sed -E 's/.*"([^"]+)".*/\1/')
TAURI_VERSION=$(node -p "require('./src-tauri/tauri.conf.json').version")

fail=0
check() {
  local label="$1"
  local actual="$2"
  if [ "$actual" != "$EXPECTED_BARE" ]; then
    echo "FAIL: $label = $actual (expected $EXPECTED_BARE)" >&2
    fail=1
  fi
}

check "package.json"               "$PKG_VERSION"
check "src-tauri/Cargo.toml"       "$CARGO_VERSION"
check "src-tauri/tauri.conf.json"  "$TAURI_VERSION"

if [ $fail -ne 0 ]; then
  exit 1
fi

echo "OK: all versions = $EXPECTED_BARE"
  • Step 2: Make it executable

Run: chmod +x scripts/check-version.sh Expected: no output

  • Step 3: Test happy path

Run: ./scripts/check-version.sh v0.6.0 Expected: OK: all versions = 0.6.0

  • Step 4: Test mismatch

Run: ./scripts/check-version.sh v9.9.9 Expected: 3 lines starting with FAIL: and exit code 1

  • Step 5: Commit
git add scripts/check-version.sh
git commit -m "ci: add version-consistency check script"

Task 6: GitHub Actions release workflow

Files:

  • Create: .github/workflows/release.yml

Interfaces:

  • Trigger: push: tags: ['v*']

  • Two jobs: version-check (sequential) and build (matrix ubuntu/macos/windows)

  • Step 1: Create the workflow file

Create .github/workflows/release.yml:

name: Release

on:
  push:
    tags:
      - 'v*'

permissions:
  contents: write

jobs:
  version-check:
    name: Version consistency
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - name: Verify tag matches package.json / Cargo.toml / tauri.conf.json
        run: |
          TAG="${GITHUB_REF_NAME}"
          chmod +x scripts/check-version.sh
          ./scripts/check-version.sh "$TAG"

  build:
    name: Build (${{ matrix.platform }})
    needs: version-check
    strategy:
      fail-fast: false
      matrix:
        include:
          - platform: ubuntu-latest
            args: --bundles deb,appimage,rpm
          - platform: macos-latest
            args: --bundles app,dmg
          - platform: windows-latest
            args: --bundles msi

    runs-on: ${{ matrix.platform }}
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - uses: actions/setup-rust@v1
        with:
          cache: true

      - name: Install Linux build dependencies
        if: matrix.platform == 'ubuntu-latest'
        run: |
          sudo apt-get update
          sudo apt-get install -y \
            libwebkit2gtk-4.1-dev \
            libappindicator3-dev \
            librsvg2-dev \
            patchelf \
            build-essential \
            curl \
            wget \
            file \
            libssl-dev \
            libgtk-3-dev \
            libayatana-appindicator3-dev

      - name: Build and upload release
        uses: tauri-apps/tauri-action@v0
        with:
          tagName: ${{ github.ref_name }}
          releaseName: ${{ github.ref_name }}
          releaseBody: |
            ## ${{ github.ref_name }}

            ### Downloads by platform

            - **Windows**: `.msi` installer
            - **macOS**: `.dmg` (unsigned — first launch needs right-click → Open) + `.app`
            - **Linux**: `.deb` / `.AppImage` / `.rpm`

            Full changelog: see commit history.
          releaseDraft: true
          prerelease: ${{ contains(github.ref_name, '-rc') || contains(github.ref_name, '-beta') || contains(github.ref_name, '-alpha') }}
          args: ${{ matrix.args }}
  • Step 2: Sanity-check the file is in place

Run: ls -la .github/workflows/release.yml && head -5 .github/workflows/release.yml Expected: file exists and the first line is name: Release (GitHub Actions will catch any YAML syntax errors at runtime)

  • Step 3: Commit
git add .github/workflows/release.yml
git commit -m "ci: add multi-platform release workflow on tag push"

Task 7: Local Docker for Linux builds

Files:

  • Create: Dockerfile
  • Create: docker-compose.yml

Interfaces:

  • Input: source tree at /workspace

  • Output: built packages at /workspace/src-tauri/target/release/bundle/{deb,appimage,rpm}/

  • Step 1: Create the Dockerfile

Create Dockerfile:

# Build Kimi Switch Linux packages (deb / AppImage / rpm) inside a clean
# Ubuntu 22.04 container. Used both locally (`docker compose run`) and as a
# reference for GH Actions' ubuntu-latest job.
FROM ubuntu:22.04

ENV DEBIAN_FRONTEND=noninteractive
ENV CARGO_TERM_COLOR=always
ENV RUSTUP_HOME=/usr/local/rustup
ENV CARGO_HOME=/usr/local/cargo
ENV PATH=/usr/local/cargo/bin:$PATH

RUN apt-get update && apt-get install -y \
    curl \
    wget \
    git \
    build-essential \
    pkg-config \
    libssl-dev \
    libgtk-3-dev \
    libayatana-appindicator3-dev \
    librsvg2-dev \
    libwebkit2gtk-4.1-dev \
    libsoup-3.0-dev \
    libjavascriptcoregtk-4.1-dev \
    patchelf \
    file \
    nodejs \
    npm \
    python3 \
    && rm -rf /var/lib/apt/lists/*

RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --default-toolchain stable --profile minimal

RUN cargo install tauri-cli --version "^2.0" --locked

WORKDIR /workspace

CMD ["/bin/bash"]
  • Step 2: Create docker-compose.yml

Create docker-compose.yml:

services:
  build-linux:
    build: .
    container_name: kimiswitch-build-linux
    volumes:
      - .:/workspace
      - cargo-cache:/usr/local/cargo/registry
      - target-cache:/workspace/src-tauri/target
    working_dir: /workspace

volumes:
  cargo-cache:
  target-cache:
  • Step 3: Build the Docker image

Run: docker compose build build-linux 2>&1 | tail -10 Expected: ends with naming to docker.io/library/...build-linux done (or similar successful build output)

  • Step 4: Commit
git add Dockerfile docker-compose.yml
git commit -m "ci: add Linux Docker build environment"

Task 8: Build documentation

Files:

  • Create: docs/BUILD.md

Interfaces:

  • Audience: contributors who want to build the project locally

  • Output: instructions for Windows / macOS / Linux local builds + Docker Linux build

  • Step 1: Write the doc

Create docs/BUILD.md:

# Build

Docker (Linux), native (any platform), and CI (multi-platform) build instructions.

## Local builds by host

### Windows (current primary dev environment)

Prerequisites: Rust stable, Node 20, Microsoft C++ Build Tools, WebView2.

```bash
npm ci
npm run tauri build -- --bundles msi
```

Output: `src-tauri/target/release/bundle/msi/`.

### macOS

Prerequisites: Xcode command-line tools, Rust stable, Node 20.

```bash
npm ci
npm run tauri build -- --bundles app,dmg
```

Output: `src-tauri/target/release/bundle/{macos,dmg}/`.

Unsigned: first launch requires right-click → Open to bypass Gatekeeper.

### Linux (native)

Prerequisites: see `Dockerfile` for the full apt-get list (webkit2gtk-4.1, librsvg2, etc.).

```bash
npm ci
npm run tauri build -- --bundles deb,appimage,rpm
```

Output: `src-tauri/target/release/bundle/{deb,appimage,rpm}/`.

### Linux via Docker (works on Windows / macOS too)

```bash
docker compose build build-linux
docker compose run --rm build-linux bash -c '
  npm ci &&
  npm run tauri build -- --bundles deb,appimage,rpm
'
```

Output is mounted back to `src-tauri/target/release/bundle/` on the host.

## CI: GitHub Actions

`.github/workflows/release.yml` runs on `git tag v* && git push origin v*`.

1. `version-check` runs `scripts/check-version.sh` to ensure tag matches `package.json`, `Cargo.toml`, `tauri.conf.json`.
2. Three parallel jobs build for `ubuntu-latest`, `macos-latest`, `windows-latest` using `tauri-action`.
3. Artifacts are uploaded to the GitHub Release (auto-draft for non-prerelease tags).

To cut a release:

```bash
# 1. Bump version in three places
#    package.json:               "version": "0.7.0"
#    src-tauri/Cargo.toml:       version = "0.7.0"
#    src-tauri/tauri.conf.json:  "version": "0.7.0"
# 2. Commit
git commit -am "chore(release): bump to v0.7.0"
# 3. Tag and push
git tag v0.7.0
git push origin v0.7.0
```

## Update checker asset selection

`src-tauri/src/commands.rs::pick_asset_for_current_os` picks the right asset by file extension:

| OS      | Extension  |
|---------|------------|
| Windows | `.msi`     |
| macOS   | `.dmg`     |
| Linux   | `.AppImage` (preferred; deb / rpm downloadable manually from the release page) |

If the preferred extension is missing, the UI falls back to opening the release page.
  • Step 2: Commit
git add docs/BUILD.md
git commit -m "docs: build instructions for all platforms + Docker + CI"

Files:

  • Modify: README.md
  • Modify: README_EN.md

Interfaces:

  • Adds a "下载" / "Downloads" section with three platform bullets

  • Adds a build-from-source link to docs/BUILD.md

  • Adds a GitHub Actions badge

  • Step 1: Add the badges (after the existing license badge)

In README.md, locate the line:

[![License](https://img.shields.io/badge/license-MIT-green)](./LICENSE)

Add two new badges below it:

[![Release](https://github.com/billowliu2/KimiSwitch/actions/workflows/release.yml/badge.svg)](https://github.com/billowliu2/KimiSwitch/releases/latest)

(Do the same in README_EN.md.)

  • Step 2: Add a "下载" section right after "这是什么"

In README.md, after the paragraph immediately above ## 核心特性, insert:

## 下载

- **Windows**: [GitHub Releases](https://github.com/billowliu2/KimiSwitch/releases/latest) → `.msi`
- **macOS**: [GitHub Releases](https://github.com/billowliu2/KimiSwitch/releases/latest) → `.dmg`(**未签名** —— 首次启动请右键 → 打开绕过 Gatekeeper)
- **Linux**: [GitHub Releases](https://github.com/billowliu2/KimiSwitch/releases/latest) → `.deb` / `.AppImage` / `.rpm`

历史版本(v0.5.x 及之前)请前往 [git.codingplan.site 仓库](https://git.codingplan.site/admin/KimiCodeSwitch/releases)。

构建说明请见 [`docs/BUILD.md`](./docs/BUILD.md)。

In README_EN.md, insert the English equivalent:

## Downloads

- **Windows**: [GitHub Releases](https://github.com/billowliu2/KimiSwitch/releases/latest) → `.msi`
- **macOS**: [GitHub Releases](https://github.com/billowliu2/KimiSwitch/releases/latest) → `.dmg` (**unsigned** — first launch needs right-click → Open to bypass Gatekeeper)
- **Linux**: [GitHub Releases](https://github.com/billowliu2/KimiSwitch/releases/latest) → `.deb` / `.AppImage` / `.rpm`

Historical versions (v0.5.x and earlier) live on the [git.codingplan.site repository](https://git.codingplan.site/admin/KimiCodeSwitch/releases).

Build instructions: [`docs/BUILD.md`](./docs/BUILD.md).
  • Step 3: Commit
git add README.md README_EN.md
git commit -m "docs(readme): multi-platform downloads + workflow badge"

Task 10: End-to-end test with a pre-release tag

Files:

  • (No file changes — pure verification)

Goal: Confirm the GH Actions workflow actually produces all 6 assets. Use a pre-release tag so it doesn't pollute the main v0.6.0 release.

  • Step 1: Confirm current version is in sync

Run: ./scripts/check-version.sh v0.6.0 Expected: OK: all versions = 0.6.0

  • Step 2: Push all un-pushed commits
git push origin main
git push origin --tags
  • Step 3: Tag a pre-release on the current commit
git tag v0.6.0-rc1
git push origin v0.6.0-rc1
  • Step 4: Watch the workflow run

Open: https://github.com/billowliu2/KimiSwitch/actions

Expected: version-check job passes, then 3 build jobs run in parallel. Total time roughly 10-15 minutes per platform.

  • Step 5: Verify the pre-release

Open: https://github.com/billowliu2/KimiSwitch/releases/tag/v0.6.0-rc1

Expected: 6 assets attached (.msi, .dmg, .app, .deb, .AppImage, .rpm); release marked as "Pre-release".

  • Step 6: Verify version mismatch fails the workflow

On a clean branch, change only package.json version to 0.9.0, commit, then:

git tag v0.6.0-rc2
git push origin v0.6.0-rc2

Open the new workflow run. Expected: version-check job fails with FAIL: package.json = 0.9.0 (expected 0.6.0). Build jobs are skipped.

Then revert the package.json change and delete the rc2 tag:

git checkout main -- package.json
git tag -d v0.6.0-rc2
git push origin :refs/tags/v0.6.0-rc2
  • Step 7: Clean up the rc1 tag

After verifying the rc1 assets are good, delete the pre-release to keep the release list clean:

git tag -d v0.6.0-rc1
git push origin :refs/tags/v0.6.0-rc1
# Then delete the GitHub Release via the web UI (or leave it as a historical rc)
  • Step 8: Final commit — none if Tasks 1-9 are clean

If no edits were needed, no commit. Otherwise, commit any fixups.


Summary

After all 10 tasks, the project ships with:

  • macOS + Linux + Windows builds produced from a single git tag v0.7.0 push
  • Local Docker for Linux dev iteration
  • Per-platform asset picker in the update checker
  • README + BUILD docs covering all three platforms
  • Version-consistency gate preventing drift between tag and the three version files