diff --git a/apps/cli/src/release-config.test.ts b/apps/cli/src/release-config.test.ts index 196c8b3321..bbeb68df90 100644 --- a/apps/cli/src/release-config.test.ts +++ b/apps/cli/src/release-config.test.ts @@ -362,6 +362,8 @@ describe("release desktop artifacts", () => { it("keeps the generated tap formula on the installed runtime smoke contract", () => { const formula = readRepoFile("scripts/release/kandev.rb"); + const updater = readRepoFile("scripts/release/update-homebrew-tap.sh"); + const versionDeclaration = 'version "__VERSION__"'; expect(formula).toContain( 'assert_equal "v#{version}", shell_output("#{bin}/kandev --version").strip', @@ -370,7 +372,11 @@ describe("release desktop artifacts", () => { expect(formula).toContain("/health"); expect(formula).toContain('"status":"ok"'); expect(formula).toContain("Kandev"); - expect(formula).not.toContain('version "__VERSION__"'); + expect(formula).toContain(versionDeclaration); + expect(formula.indexOf(versionDeclaration)).toBeLessThan( + formula.indexOf('license "AGPL-3.0-only"'), + ); + expect(updater).toContain('-e "s|__VERSION__|$VERSION|g"'); }); it("bumps desktop package and Tauri versions during release preparation", () => { diff --git a/docs/public/release-process.md b/docs/public/release-process.md index ff12d557f6..e75eb616cb 100644 --- a/docs/public/release-process.md +++ b/docs/public/release-process.md @@ -18,12 +18,12 @@ Kandev uses one semantic version across the Git tag, native runtime bundles, des The workflow has four mutually exclusive operating modes: -| Mode | Inputs | Result | -|---|---|---| -| Normal release | `bump=patch`, `minor`, or `major` | Creates and merges a release PR, tags its merge, builds, and publishes | -| Dry run | `dry_run=true` | Computes the next version and exercises CLI package/lock plus changelog generation in the runner; no PR, tag, artifact build, or publication | -| Desktop validation | `desktop_validation_only=true` | Builds web, five runtime bundles, and five desktop targets from the selected commit; no PR, tag, GitHub release, GHCR, npm, or Homebrew publication | -| Backfill | `backfill_tag=vX.Y.Z` | Rebuilds and repairs channels for the latest existing release tag without creating a version or tag | +| Mode | Inputs | Result | +| ------------------ | --------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | +| Normal release | `bump=patch`, `minor`, or `major` | Creates and merges a release PR, tags its merge, builds, and publishes | +| Dry run | `dry_run=true` | Computes the next version and exercises CLI package/lock plus changelog generation in the runner; no PR, tag, artifact build, or publication | +| Desktop validation | `desktop_validation_only=true` | Builds web, five runtime bundles, and five desktop targets from the selected commit; no PR, tag, GitHub release, GHCR, npm, or Homebrew publication | +| Backfill | `backfill_tag=vX.Y.Z` | Rebuilds and repairs channels for the latest existing release tag without creating a version or tag | `dry_run` and desktop validation are not release candidates. Backfill cannot be combined with either and accepts only the latest exact SemVer tag after version manifests are checked for agreement. @@ -65,7 +65,7 @@ Normal mode performs these stages: 5. **Build containers.** Publish amd64/arm64 base manifests, enforce the universal-image size gate, then publish multi-architecture universal images. 6. **Publish GitHub Release.** Attach runtime archives, checksums, desktop artifacts, notes, and the updater feed when eligible. 7. **Publish npm.** Use OIDC trusted publishing for five `@kdlbs/runtime-*` packages first, then the `kandev` launcher. Existing versions are skipped; the main package is not published after a runtime-package failure. -8. **Update Homebrew.** Push the formula update to `kdlbs/homebrew-kandev` using release checksums and the deploy key. +8. **Update Homebrew.** Push the formula update to `kdlbs/homebrew-kandev` using release checksums and the deploy key. The generated formula records `X.Y.Z` explicitly; platform archive suffixes such as `x64` are not version identifiers. GHCR images are built before the GitHub Release. npm and Homebrew start only after the GitHub Release and may run in parallel. A late failure can therefore leave some channels complete and others missing. @@ -105,7 +105,7 @@ After publication, verify: - the Git tag points at the generated release merge; - GitHub notes, five runtime archives, checksums, expected desktop installers, and conditional `latest.json`; - all five runtime npm packages plus `kandev`, including a clean `npx kandev@latest`; -- Homebrew install/upgrade and `kandev --version`; +- Homebrew install/upgrade, a `Cellar/kandev/X.Y.Z` install path, and `kandev --version`; - GHCR base and universal images on amd64 and arm64, including their immutable version tags; - desktop launch on affected platforms and signed/notarized status where configured; - backend health, a minimal task, agentctl startup, and Updates-screen behavior; diff --git a/docs/specs/homebrew-core/spec.md b/docs/specs/homebrew-core/spec.md index 63801619ba..918dfb8579 100644 --- a/docs/specs/homebrew-core/spec.md +++ b/docs/specs/homebrew-core/spec.md @@ -17,12 +17,12 @@ Kandev is currently installable via `brew install kdlbs/kandev/kandev` from the ## What - Add a package-manager build contract that produces the native runtime bundle from an already-installed source checkout. It builds the Vite SPA, syncs it into the Go embed input, compiles the host `kandev` and `agentctl` binaries, and cross-compiles the four Linux/macOS remote `agentctl` helpers. -- Keep package-manager dependency installation outside that contract. Homebrew declares Go, Node 24, and pnpm as build dependencies and Git as a runtime facility. +- Keep package-manager dependency installation outside that contract. Homebrew declares Go, Node 24, and pnpm 10 as build dependencies. Git is part of Homebrew's standard environment and is not declared as a formula dependency. - Compile only the host `kandev` binary with cgo and the `fts5` tag. `mattn/go-sqlite3` supplies the SQLite amalgamation, so the formula does not declare an external SQLite dependency unless build or linkage evidence requires it. - Merge and publish the source-build contract in a Stable Kandev release before authoring the Core formula. Core must consume the immutable GitHub tag archive for that release, never a branch or unreleased commit. - Install the bundle under `libexec/bin` and expose one `bin/kandev` wrapper produced by `write_env_script`. The wrapper sets `KANDEV_BUNDLE_DIR=` and `KANDEV_VERSION=`. - Use stable numeric tags for `livecheck`; prerelease and Nightly npm versions are not Homebrew channels. -- Keep `kdlbs/homebrew-kandev` as the upstream binary fast path alongside Homebrew Core's source-built bottles. The shared release-bundle validator must run before those binary archives are published, and the generated tap formula must smoke-test its version, readiness endpoint, and embedded SPA. +- Keep `kdlbs/homebrew-kandev` as the upstream binary fast path alongside Homebrew Core's source-built bottles. The generated tap formula must set the Stable SemVer explicitly because platform archive names end in architecture tokens such as `x64`, and it must smoke-test its version, readiness endpoint, and embedded SPA. The shared release-bundle validator runs before those binary archives are published. - Preserve all four remote `agentctl` helpers in custom-tap installations. The tap must use an exact-path Homebrew mismatched-binary audit allowlist rather than pruning helpers, so Docker and SSH targets can differ from the Homebrew host. Decision: [ADR-2026-08-05-homebrew-remote-helper-audit](../../decisions/2026-08-05-homebrew-remote-helper-audit.md). The runtime bundle contains exactly: @@ -44,6 +44,7 @@ The Darwin arm64 helper must carry a Mach-O code signature so Apple Silicon can - **GIVEN** a new kandev release `vX.Y.Z` is tagged, **WHEN** Homebrew's auto-bump worker runs, **THEN** `livecheck` resolves the new tag from GitHub Releases and a bump PR is opened against the formula. - **GIVEN** a maintainer reviews the PR, **WHEN** they run `brew install --build-from-source kandev` locally, **THEN** the build completes without network or sandbox failures and `brew test kandev` passes. - **GIVEN** a Stable release updates `kdlbs/homebrew-kandev`, **WHEN** its platform archive is built and the tap formula is tested, **THEN** the archive contains the complete executable runtime and the installed launcher serves both `/health` and the embedded Kandev page. +- **GIVEN** a Stable release updates `kdlbs/homebrew-kandev`, **WHEN** Homebrew evaluates a platform archive URL ending in `x64` or `arm64`, **THEN** the generated formula's explicit version keeps the Cellar path and `version` value at `X.Y.Z` rather than the architecture suffix. - **GIVEN** a tap archive contains remote helpers for CPU architectures other than the Homebrew host, **WHEN** Homebrew audits the installed formula on macOS or Linux, **THEN** only the four declared remote-helper paths are exempted and the complete runtime remains installed. ## Out of scope diff --git a/scripts/release/kandev.rb b/scripts/release/kandev.rb index 70b64ec3d6..d811d83bc5 100644 --- a/scripts/release/kandev.rb +++ b/scripts/release/kandev.rb @@ -3,6 +3,8 @@ class Kandev < Formula desc "Manage tasks, orchestrate agents, review changes, and ship value" homepage "https://github.com/kdlbs/kandev" + # Conditional asset names end in x64/arm64; without this, Homebrew uses version 64. + version "__VERSION__" license "AGPL-3.0-only" on_macos do diff --git a/scripts/release/update-homebrew-tap.sh b/scripts/release/update-homebrew-tap.sh index 8f9d6b058d..5a25a0c7a1 100755 --- a/scripts/release/update-homebrew-tap.sh +++ b/scripts/release/update-homebrew-tap.sh @@ -118,6 +118,7 @@ mkdir -p "$(dirname "$FORMULA_PATH")" GITHUB_BASE="https://github.com/kdlbs/kandev/releases/download/${TAG}" sed \ + -e "s|__VERSION__|$VERSION|g" \ -e "s|__GITHUB_BASE__|$GITHUB_BASE|g" \ -e "s|__SHA_MACOS_ARM64__|$SHA_MACOS_ARM64|g" \ -e "s|__SHA_MACOS_X64__|$SHA_MACOS_X64|g" \