Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
241c318
Model zone payload fields confirmed live in issue #444.
leogdion Aug 31, 2026
6f66f39
Add MistDemo integration phase for zone payload metadata.
leogdion Aug 31, 2026
a54baaa
Restore local MistKit path deps in example subrepos
leogdion Aug 31, 2026
b2478d1
git subrepo push Examples/BushelCloud
leogdion Aug 31, 2026
e7a4b5d
git subrepo push Examples/CelestraCloud
leogdion Aug 31, 2026
8896986
Promote zoneType to a closed ZoneType enum with fail-loud conversion.
leogdion Aug 31, 2026
5d6d1d3
Merge pull request #451 from brightdigit/444-zone-payloads
leogdion Aug 31, 2026
6e4f658
MistDemo web: zoneName/zoneOwner on query panel (#438, #453)
leogdion Aug 31, 2026
525e351
Extract MistKitConfiguration; converge all three examples on typed co…
leogdion Aug 31, 2026
9a7096d
Add zone-aware MistDemo writes and shared-zone live validation (#454).
leogdion Aug 31, 2026
00d74ba
[CodeFactor] Apply fixes
code-factor Sep 1, 2026
269b7e2
Add a repeatable release runbook (#461)
leogdion Sep 2, 2026
1f53458
Address code review: fixes, coverage, and lint tooling (#460)
leogdion Sep 2, 2026
6a86bf1
Consume CloudKit web auth token rotation from response headers (#462,…
leogdion Sep 2, 2026
3708e09
Record why FieldValue.bytes is a base64 String (#467, #468)
leogdion Sep 3, 2026
83877bc
Claude docs consolidation (#469)
leogdion Sep 3, 2026
716f572
Add VALIDATE to Reference.Action (#464).
leogdion Sep 3, 2026
cbeeced
Represent FieldValue.bytes as Data (#467).
leogdion Sep 3, 2026
e678b41
Verify asset downloads against fileChecksum (#473, #466)
leogdion Sep 4, 2026
4089616
Fix MistDemo Integration static build for Swift 6.4.
leogdion Sep 4, 2026
4a45b88
Resolve mistdemo bin path via SwiftPM in Integration CI
leogdion Sep 4, 2026
0410100
Fill out the Unreleased release notes section
leogdion Sep 4, 2026
1edd73a
Add the v1.0.0-beta.5 roadmap section to README
leogdion Sep 4, 2026
0eb79dd
fixup! Verify asset downloads against fileChecksum (#473, #466)
leogdion Sep 4, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
171 changes: 171 additions & 0 deletions .agents/skills/release/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
---
name: release
description: Run the MistKit release runbook — verify the vX.Y.Z release branch is green, assemble the ReleaseNotes.md section and README roadmap entry, merge to main, tag as X.Y.Z, publish the GitHub pre-release, and roll the Examples' MISTKIT_BRANCH pins. Use when asked to cut, prepare, or ship a release.
argument-hint: "Which release? e.g. v1.0.0-beta.5"
disable-model-invocation: true
---

# MistKit Release Runbook

## The naming rule

```text
release branch v1.0.0-beta.5 ← with v
release tag 1.0.0-beta.5 ← without v
```

This asymmetry is deliberate. `setup-mistkit` resolves `MISTKIT_BRANCH` with
`git ls-remote`, which matches **tags as well as branches**, so pinning the wrong
kind of ref succeeds silently and greens example CI without ever compiling the
code under release. The pin requirement therefore *inverts* at release time:

| Phase | `MISTKIT_BRANCH` must be |
|---|---|
| Before the release merge | the **branch** `v1.0.0-beta.5` |
| After publishing | the **tag** `1.0.0-beta.5` |

`Scripts/release.sh pins --expect-branch` / `--expect-tag` asserts the ref *kind*,
which is the check `setup-mistkit` itself cannot make.

## Stop conditions

Abort and ask the user if:

- `preflight` fails for any reason other than notes-not-yet-written.
- The milestone still has open issues (it warns; confirm the release is intended).
- The named branch is not the branch you are on.
- `main` has commits the release branch lacks.
- Any `git subrepo push` would be needed but the Example subrepos have local changes.

## Phases

Run everything from the release branch's own worktree
(`git trees add v1.0.0-beta.5 main` if it does not exist — never raw `git worktree`).

### 1. Preflight

```bash
./Scripts/release.sh preflight v1.0.0-beta.5
```

Checks branch shape, clean tree, tag availability, gating CI (`MistKit`,
`MistDemo Integration`, `Examples` — *not* `Claude Code Review`, which is advisory),
pins, open milestone issues, then local `swift build`/`swift test`/`Scripts/lint.sh`.
Use `--skip-local` only when re-running after a green local pass.

### 2. Fix the pre-release pins

Must happen **before** the merge, so example CI actually tests this branch.

```bash
./Scripts/release.sh pins --roll-to v1.0.0-beta.5
git add Examples/*/.github/workflows/*.yml
git commit -m "ci(examples): pin MISTKIT_BRANCH to v1.0.0-beta.5"
git subrepo push Examples/BushelCloud
git subrepo push Examples/CelestraCloud
git push
```

When `Packages/MistKitConfiguration` lands (#407), add its subrepo push here too.

Then confirm from a build log that `Setup MistKit` printed
`Pinning MistKit to v1.0.0-beta.5 @ <sha>` with a sha matching the branch tip,
and re-run `./Scripts/release.sh pins --expect-branch v1.0.0-beta.5`.

### 3. Assemble the notes

Release notes are a **flat bullet list** — no `###` category subsections.

```bash
./Scripts/release.sh notes-draft v1.0.0-beta.5
```

This writes the `## 1.0.0-beta.5` section to the top of `ReleaseNotes.md` and
prints README roadmap candidates. Then, by hand:

- Edit bullet wording and add issue refs: `* <desc> (#41, #42) by @user in <PR url>`.
- Add a `### v1.0.0-beta.5` section to the README Roadmap, above `### Backlog / Post-beta`.
- Bump the README `from:` snippet to the **currently released** tag (the new one does not exist yet).

```bash
./Scripts/release.sh check v1.0.0-beta.5 # must pass before proceeding
swift build && swift test && ./Scripts/lint.sh
git commit -am "docs: 1.0.0-beta.5 release notes and roadmap" && git push
```

### 4. Archive before merging

Load-bearing under squash: once squashed commits are no longer reachable from
`main`, the archive tag is the only preservation mechanism. Do this even if you
plan a merge commit.

```bash
git tag "backup/v1.0.0-beta.5-pre-merge" v1.0.0-beta.5
git push origin "backup/v1.0.0-beta.5-pre-merge"
```

### 5. Release PR — ask before merging

```bash
gh pr create --base main --head v1.0.0-beta.5 --title "v1.0.0 beta.5" \
--body-file <(./Scripts/release.sh publish 1.0.0-beta.5 --dry-run 2>/dev/null)
```

**Ask the user which merge shape to use.** Release branches are the documented
merge-commit case (`gh pr merge --merge`) — unlike feature PRs, which are always
rebase (1 commit) or squash (2+). Squash keeps `main` linear but makes the
archive tag from phase 4 the *only* preservation mechanism.

### 6. Tag — only after notes have landed

Locate the existing `main` worktree (`git trees list`) — do not `git checkout main`
from the release worktree. From that `main` worktree:

```bash
git pull --ff-only
./Scripts/release.sh verify-tag 1.0.0-beta.5 --at HEAD # must pass first
git tag 1.0.0-beta.5 # lightweight, no -a, no v
git push origin 1.0.0-beta.5
```

`verify-tag --at HEAD` reads `ReleaseNotes.md` in the tree about to be tagged.
This is the guardrail for beta.3 and beta.4, both of which were tagged without
their own notes section. The `Release Check` workflow re-asserts it after the push.

### 7. Publish

```bash
./Scripts/release.sh publish 1.0.0-beta.5
```

Builds the body from `ReleaseNotes.md`, swapping `## 1.0.0-beta.5` for
`## What's Changed`. Add the intro blurb by hand afterwards if wanted.

### 8. Roll the pins to the tag

```bash
./Scripts/release.sh pins --roll-to 1.0.0-beta.5
./Scripts/release.sh pins --expect-tag 1.0.0-beta.5
git commit -am "ci(examples): pin MISTKIT_BRANCH to released 1.0.0-beta.5"
git subrepo push Examples/BushelCloud
git subrepo push Examples/CelestraCloud
git push
```

When `Packages/MistKitConfiguration` lands (#407), add its subrepo push here too.

### 9. Open the next beta

Keep the released `v1.0.0-beta.5` branch and its worktree — do not delete either.
Create the next beta branch from `main`:

```bash
git trees add v1.0.0-beta.6 main
```

## This runbook will not

- Push the Example subrepos for you — it prints the `git subrepo push` commands.
- Delete a release branch after publication.
- Tag before `check` / `verify-tag` passes.
- Choose the merge shape without asking.
5 changes: 5 additions & 0 deletions .agents/skills/release/agents/openai.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
interface:
display_name: "Release"
short_description: "Run the MistKit release runbook"
policy:
allow_implicit_invocation: false
14 changes: 14 additions & 0 deletions .claude/agent-notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,3 +19,17 @@ Standing always/never directives and corrections from the human. Agents must rea
- Repo-wide CI version bumps (Xcode/simulator/toolchain) DO extend into the `Examples/` subrepos — update BushelCloud and CelestraCloud in the same pass rather than deferring them to their own repos.
- Follow sibling brightdigit repos (e.g. ConfigKeyKit) for current CI workflow shape before inventing a new one.
- Feature-branch PRs: NEVER merge-commit. ALWAYS rebase if the PR has exactly 1 commit (`gh pr merge --rebase`); ALWAYS squash if it has 2+ commits (`gh pr merge --squash`). Count commits before merging.
- Release PRs (a `v*` release branch into `main`) are the one merge-commit case — the "never merge-commit" rule above covers FEATURE PRs only; still confirm the shape with the human before merging.
- NEVER tag a release before its `ReleaseNotes.md` section exists in the tree being tagged — run `./Scripts/release.sh verify-tag <tag> --at HEAD` first (beta.3 and beta.4 both shipped tags with no notes).
- Release notes are a FLAT bullet list for **new** entries — do not add `###` category subsections; preserve the existing beta.1–beta.4 sections.
- NEVER delete a released beta branch — after publication, create the next beta branch from `main` with `git trees add`.
- ALWAYS manage worktrees with `git trees` (`add`/`rm`/`list`/`clean`), never raw `git worktree`.
- MistKitConfiguration#1 is merged and `1.0.0-beta.1` is tagged (ConfigKeyKit `1.0.0-beta.3`). The published `main` manifest must stay tag-only — `dependency-policy.yml` enforces this on non-draft PRs to `main`. Branch pins live on the `mistkit-beta.5` integration branch, which must NEVER be merged to `main` or tagged.
- MistDemo's own `resolveBool` may now be replaced by ConfigKeyKit `read(_:)`: the boolean fix (ConfigKeyKit#8) shipped in `1.0.0-beta.3` (2026-08-31). Verify behaviour before swapping; this supersedes the earlier "keep resolveBool until tagged" directive.
- `git subrepo push Packages/MistKitConfiguration` refuses ("new changes upstream") and would clobber the standalone `url:` MistKit line with the monorepo `path:` overlay; push subrepo-only changes by applying them to a clone of the standalone repo instead, then record the pushed SHA in `.gitrepo`'s `commit =`.
- NEVER switch `Examples/*/Package.swift` to a tagged `from:` just because a monorepo package was released — the Examples dogfood UNRELEASED MistKit and must keep `path:` for every in-monorepo package, with CI rewriting them to branch-HEAD `revision:` pins via `setup-mistkitconfiguration` (takes both `mistkit-branch` and `mistkitconfiguration-branch`). A published tag is for downstream consumers, not for the Examples.
- `Packages/MistKitConfiguration` is scaffolding for the `v1.0.0-beta.5` release line ONLY. Feature PRs merge into `v1.0.0-beta.5` with the subrepo intact; the release PR `v1.0.0-beta.5` → `main` MUST delete it (and its `examples.yml` lane) so no MistKit release ships a package tracking that same unreleased release.
- MistKitConfiguration test gaps belong in `Packages/MistKitConfiguration/` (subrepo) or the standalone `brightdigit/MistKitConfiguration` repo — NEVER add MKC unit tests to the monorepo root `Tests/` or chase MKC patch coverage in MistKit's codecov upload; `codecov.yml` already ignores that path.
- Lint findings for agents: use `LINT_REPORT=1 ./Scripts/lint.sh` (human summary on stderr + JSON between `### MISTKIT_LINT_REPORT_* ###` markers on stdout) or `LINT_REPORT=json ./Scripts/lint.sh` (JSON only on stdout). Report mode is read-only and walks swift-format, SwiftLint, `swift build`, and Periphery; all four lint tools run with `--strict` so any warning or error fails the pipeline; success requires exit 0 and `summary.totalFindings == 0`. See `.claude/skills/fix-lint/SKILL.md`.
- Prefer force unwraps (`!`) over verbose `guard let … else { preconditionFailure }` for compile-time-known-safe values (constant URL literals, test fixtures); suppress lint with `swift-format-ignore: NeverForceUnwrap` plus a `swiftlint:disable force_unwrapping` / `swiftlint:enable force_unwrapping` block when a doc comment sits between the disable and the unwrap (`:next` only when the unwrap is the immediate next line).
- NEVER use `Task.sleep(for:)` / `Duration` clocks in MistKitTests — package deployment target is iOS 14 (and peers); use `Task.sleep(nanoseconds:)` instead (CI iOS simulator build failed on CourierTests for this).
Loading
Loading