chore: trim skills to rust, legal, sdk-swift-package, uniffi-rs
Co-Authored-By: siGit Code v1.6.6-acp <noreply@sigit.si>
Seto Elkahfi committed
Oct 10, 2026 at 08:41 UTC
5344369963b62f567f0c6604c59af6e28e154c32
4 files changed
-1888
.agents/skills/forked-dependencies/SKILL.md
deleted
-231
@@ -1,231 +0,0 @@
1
----
2
-name: forked-dependencies
3
-description: The complete inventory of forked crates and [patch.crates-io] overrides that the onde stack depends on — mistral.rs, candle, sysctl, mio, tqdm, core2 — including the retired ones. Explains what each fork is for, where it is consumed, how the git-vs-published two-tier model works, and how to update or add one. Apply whenever a build fails on a missing/incompatible crate for an Apple or Android target, when bumping the mistral.rs or candle fork, or when adding a new patched dependency.
4
-allowed-tools: Read, Write, Edit, Glob, Grep, Bash
5
-user-invocable: true
6
----
7
-
8
-# Skill: Forked Dependencies
9
-
10
-The onde stack pulls several crates from forks instead of crates.io. They exist
11
-for three reasons: **platform support** the upstream crate lacks (watchOS,
12
-visionOS), **unreleased APIs** a dependency needs before upstream cuts a release
13
-(candle), and **registry constraints** (`cargo publish` rejects git deps, so the
14
-mistral.rs fork is republished under `onde-*` names).
15
-
16
-This skill is the single source of truth for every fork and patch. When a build
17
-breaks on a missing symbol or unsupported target, or you are bumping a fork,
18
-start here.
19
-
20
----
21
-
22
-## The two-tier consumption model (read this first)
23
-
24
-There are two distinct ways the same dependency tree gets built, and they
25
-resolve forks differently. Almost every confusing bug in this area comes from
26
-mixing them up.
27
-
28
-1. **Building the mistral.rs fork from source** (its own CI, or `onde` local dev
29
- with the git override uncommented). Here the fork's `Cargo.toml` git deps and
30
- `[patch.crates-io]` entries apply directly.
31
-
32
-2. **Publishing to crates.io** (`cargo publish` of the `onde-mistralrs-*`
33
- crates). **`cargo publish` strips every `git =` field and the entire
34
- `[patch.crates-io]` table.** Published crates fall back to the `version = "..."`
35
- registry requirement on each dep. So a git-only dep is invisible to anyone
36
- installing from crates.io.
37
-
38
-3. **onde consuming the published crates.** onde depends on the registry
39
- `onde-mistralrs-*` crates, so it gets the *registry-resolved* tree — the
40
- stripped-down one from step 2, not the fork's git tree. onde then re-applies
41
- whatever patches it still needs in **its own** `[patch.crates-io]`. Those
42
- apply to onde's from-source CI builds (Swift XCFramework, Android/Kotlin,
43
- the `cargo check` matrix) but are themselves stripped when onde publishes to
44
- crates.io.
45
-
46
-**Consequence that used to bite (now fixed — kept as the canonical example):**
47
-originally the `tqdm` fork bumped crossterm to 0.29 (which pulls mio 1.x, which
48
-supports visionOS) but was a *git* dep of mistral.rs, so it was stripped on
49
-publish. onde therefore resolved crates.io `tqdm 0.8.0` → `crossterm 0.25` →
50
-`mio 0.8.11`, which does *not* support visionOS — so onde needed its own `mio`
51
-visionOS patch even though the tqdm fork "already fixed" crossterm upstream of
52
-it.
53
-
54
-The fix that removed the whole problem was to make the crossterm-0.29 tqdm a
55
-**registry** dep of the mistral.rs fork rather than a git one, since a registry
56
-dep survives publish: onde's resolved tree then gets `crossterm 0.29` → `mio
57
-1.x` and the `mio 0.8.11` line disappears entirely. That shipped first as the
58
-stopgap fork crate `onde-tqdm` (in `onde-mistralrs-*` 0.9.3), which let onde
59
-delete its `[patch.crates-io] mio` entry and retire the `mio` fork.
60
-
61
-Upstream has since merged the crossterm bump (mrlazy1708/tqdm#28) and released
62
-it as **`tqdm 0.8.1`**, so the fork crate is no longer needed: the mistral.rs
63
-fork depends on plain `tqdm = "0.8.1"` and **`onde-tqdm` is retired**. See the
64
-tqdm and mio entries.
65
-
66
----
67
-
68
-## Inventory
69
-
70
-| Crate | Fork / source | Ref | Purpose | Consumed where |
71
-|-------|---------------|-----|---------|----------------|
72
-| mistral.rs (12 crates) | `setoelkahfi/mistral.rs` | branch `fix/all-platform-fixes` | All-platform fixes; **published** as `onde-mistralrs-*` | onde deps (registry) |
73
-| candle-core, candle-nn | `setoelkahfi/candle` | branch `onde/candle-0.11.0-27f20fea` (rev `27f20fea`) | Post-0.11.0 candle APIs; **published** as `onde-candle-{core,nn}` | mistral.rs fork deps (registry) |
74
-| tqdm | *none — upstream* | crates.io `tqdm 0.8.1` | Crossterm 0.29 (→ mio 1.x) is upstream as of 0.8.1 | mistral.rs fork dep (registry) |
75
-| sysctl | `setoelkahfi/sysctl-rs` | branch `feature/watchos` | watchOS (`target_os = "watchos"`) support | `[patch.crates-io]` in onde **and** mistral.rs fork |
76
-| mio | `setoelkahfi/mio` | branch `feature/visionos` | visionOS support for the `mio 0.8.11` line | **RETIRED** — patch removed from onde once it consumed onde-mistralrs 0.9.3 (crossterm 0.29 → mio 1.x). Fork kept for history/upstream PR. |
77
-
78
-> **Retired:** `onde-tqdm` (`setoelkahfi/tqdm`, branch `release/onde-tqdm`,
79
-> published 0.8.1/0.8.2) — the stopgap republish of the crossterm-0.29 tqdm
80
-> fork. Upstream merged the bump and released `tqdm 0.8.1`, so the mistral.rs
81
-> fork now depends on upstream directly. See the tqdm entry.
82
-
83
-> **Retired:** `core2` (`bbqsrc/core2` rev `545e84bc`) — was a `[patch.crates-io]`
84
-> workaround for the yanked `core2 0.4.0`; removed from the mistral.rs fork after
85
-> `bitstream-io` 4.x dropped `core2`. No longer present.
86
-
87
----
88
-
89
-## Per-fork detail
90
-
91
-### mistral.rs → `onde-mistralrs-*`
92
-
93
-- **Fork:** `setoelkahfi/mistral.rs`, branch `fix/all-platform-fixes`.
94
-- **Why:** carries fixes upstream hasn't merged (iOS 32-bit memory limits,
95
- watchOS/visionOS Metal compile, Android 32-bit const overflows, HF cache
96
- seeding, metallib link flags, and more).
97
-- **Published as:** `onde-mistralrs`, `-core`, `-quant`, `-paged-attn`,
98
- `-flash-attn`, `-metal-compile`, `-code-exec`, `-sandbox`, `-macros`,
99
- `-vision`, `-audio`, `-mcp`. The rename is a `package = "onde-..."` override on
100
- each workspace dep entry, so Rust source still `use`s `mistralrs_*` unchanged.
101
-- **Publishing + bump flow:** see [[sdk-regressions]] (the 12-crate publish
102
- order, the candle Step 0, and the tqdm/core2 publish-verification snags).
103
-
104
-### candle → `onde-candle-{core,nn}`
105
-
106
-- **Fork:** `setoelkahfi/candle`, branch `onde/candle-<ver>-<rev>` (currently
107
- `onde/candle-0.11.0-27f20fea`).
108
-- **Why:** mistral.rs pins a candle git rev past the latest release for APIs no
109
- published candle has (`QTensor::indexed_gemv`, `gemv_fused_shared_lhs`,
110
- `BarrierPool::execute_chunked`). Publishing the `onde-mistralrs-*` crates
111
- fails verification (E0080/E0599) if their candle dep resolves to the released
112
- crate, so the pinned rev is republished under `onde-candle-*`.
113
-- **Only the diverged crates are renamed.** At the 0.11.0 rev only `candle-core`
114
- and `candle-nn` differ from the release; `candle-metal-kernels` and
115
- `candle-flash-attn-v3` are byte-identical, so the mistral.rs fork points those
116
- at the official `0.11.0` crates. `candle-nn` must be republished whenever
117
- `candle-core` is (even if unchanged) or the official `candle-nn` drags the
118
- official `candle-core` back into the graph → two `candle-core` crates → type
119
- mismatch.
120
-
121
-### sysctl → watchOS
122
-
123
-- **Fork:** `setoelkahfi/sysctl-rs`, branch `feature/watchos`. Upstream PR:
124
- johalun/sysctl-rs#74.
125
-- **Why:** upstream sysctl has no `target_os = "watchos"` support.
126
-- **Applied in:** `[patch.crates-io]` in **both** onde and the mistral.rs fork
127
- (each workspace root that builds for watchOS needs its own patch entry).
128
-
129
-### mio → visionOS (RETIRED)
130
-
131
-- **Status:** the onde `[patch.crates-io] mio` entry has been **removed**. onde
132
- now consumes `onde-mistralrs 0.9.3`, whose `onde-tqdm` dep pulls `crossterm
133
- 0.29 → mio 1.x`, so `mio 0.8.11` is gone from onde's tree. The fork below is
134
- kept only for history and a possible upstream PR; it is no longer wired into
135
- any build.
136
-- **Fork:** `setoelkahfi/mio`, branch `feature/visionos` (commit `683fca8`).
137
- Based on `mio 0.8.11`. This is the **official GitHub fork** of tokio-rs/mio
138
- (in tokio's fork network, so it can open upstream PRs). An earlier standalone
139
- clone that carried this branch was renamed to `setoelkahfi/mio-patch` and
140
- deleted; the branch now lives on the real fork.
141
-- **Why:** `mio 0.8.11` hits `compile_error!("unsupported target for
142
- mio::unix::pipe")` on visionOS, and its Apple cfg gates (kqueue `Filter`/
143
- `Flags` type aliases, `net.rs` `sin_len`/`sin6_len`, `tcp.rs` accept path,
144
- `uds/listener.rs` `accept4`/`SOCK_NONBLOCK`) omit visionos, breaking the Swift
145
- XCFramework build and the visionOS CI checks. The fork adds
146
- `target_os = "visionos"` to every cfg gate that already lists `tvos`/`watchos`.
147
- tokio's `mio 1.x` already supports visionOS, so **only the 0.8.x line** needs it.
148
-- **Where 0.8.11 came from:** `tqdm → crossterm 0.25 → mio 0.8.11`, and
149
- `signal-hook-mio → crossterm`. Not from tokio (that uses `mio 1.2.2`).
150
-- **Applied in:** *(historical)* was `[patch.crates-io]` in onde. The fork's
151
- version (`0.8.11`) stayed semver-compatible with the `^0.8` requirement it
152
- replaced; it did not touch tokio's `^1`.
153
-- **How it was retired (the real fix):** the `onde-tqdm` publish (below) removed
154
- the `mio 0.8.11` line from onde's registry-resolved tree entirely. Verified:
155
- with onde consuming the onde-tqdm-carrying `onde-mistralrs 0.9.3` crates, onde's
156
- lockfile resolves `mio 1.2.2` only — no 0.8.11 — and both visionOS targets
157
- (`aarch64-apple-visionos{,-sim}`) `cargo +nightly check -Z build-std` pass with
158
- the `mio` patch deleted.
159
-
160
-### tqdm → upstream (`onde-tqdm` RETIRED)
161
-
162
-- **Status:** no fork. `setoelkahfi/tqdm` branch `deps/bump-crossterm` was
163
- merged upstream as mrlazy1708/tqdm#28 and released as **`tqdm 0.8.1`**, which
164
- carries the crossterm 0.29 bump (→ mio 1.x) that the whole `mio 0.8.11`
165
- visionOS problem hinged on.
166
-- **Consumed in:** the mistral.rs fork's `[workspace.dependencies]` as plain
167
- `tqdm = "0.8.1"`. It must stay a **registry** dep — `cargo publish` strips git
168
- deps, so a git tqdm would never reach onde.
169
-- **The `onde-tqdm` stopgap (retired):** branch `release/onde-tqdm` set
170
- `package = "onde-tqdm"` (lib name stayed `tqdm`, so `use tqdm::…` and doctests
171
- were unchanged) and was published as `onde-tqdm` 0.8.1/0.8.2 — the same rename
172
- trick as `onde-mistralrs-*` and `onde-candle-*`, needed only because upstream
173
- hadn't released the bump yet. Its source was byte-identical to upstream
174
- `tqdm 0.8.1` apart from the package metadata, so the swap back is a drop-in.
175
-- **Which release drops it:** `onde-mistralrs-core` 0.9.2 used plain
176
- `tqdm ^0.8.0` (crossterm 0.25), 0.9.3 used `onde-tqdm 0.8.2`, and the next
177
- publish (0.9.4) goes back to upstream `tqdm 0.8.1`. The published `onde-tqdm`
178
- versions stay up so 0.9.3 keeps resolving; don't yank them.
179
-
180
-### core2 → removed
181
-
182
-- **Was:** `bbqsrc/core2` rev `545e84bc`, a `[patch.crates-io]` workaround for
183
- the yanked `core2 0.4.0` (pulled via `bitstream-io → rav1e → ravif → image`).
184
-- **Status:** **deleted** from the mistral.rs fork. `bitstream-io` 4.x moved off
185
- `core2` (to `no_std_io2`), so nothing pulls it. Removed in the same commit that
186
- wired `onde-tqdm` and bumped to 0.9.3.
187
-
188
----
189
-
190
-## Operational gotchas (hit these during the 1.2.1 release)
191
-
192
-- **A patched fork must be pushed to GitHub before the patch works in CI.** The
193
- `mio` fork existed only locally; the patch URL 404'd until it was pushed to
194
- `setoelkahfi/mio`. Verify with `git ls-remote <url> <branch>` before relying
195
- on a patch.
196
-- **Shallow clones cannot be pushed** (`remote unpack failed: index-pack
197
- failed`). If a fork was cloned shallow, `git fetch --unshallow <upstream>`
198
- first, then push.
199
-- **Local fork fetches need `CARGO_NET_GIT_FETCH_WITH_CLI=true`.** This machine
200
- rewrites GitHub URLs to SSH aliases; cargo's built-in fetcher can't auth, so
201
- export that var (or run the `cargo update` that pulls the fork with it). CI
202
- fetches public https forks fine without it — same as the long-standing sysctl
203
- patch.
204
-- **`[patch.crates-io]` version must be semver-compatible** with the requirement
205
- it replaces. A `0.8.11` fork replaces `^0.8`, not a `^1` requirement of the
206
- same crate — which is how the `mio` patch touches only the crossterm line and
207
- leaves tokio's `mio 1.x` alone.
208
-- **Forks are public** (mio, sysctl-rs, tqdm are MIT/Apache): keep the upstream
209
- `LICENSE` intact. See [[legal-and-trademarks]] for attribution obligations
210
- (candle and mistral.rs especially).
211
-
212
----
213
-
214
-## Adding a new patch
215
-
216
-1. Fork upstream, add the fix on a descriptive branch, **push it public** to
217
- `setoelkahfi/<crate>`, keep the `LICENSE`.
218
-2. Add to `[patch.crates-io]` in every workspace root that builds the affected
219
- target (onde, and the mistral.rs fork if it builds there too). Match the
220
- fork's crate version to the requirement being replaced.
221
-3. `CARGO_NET_GIT_FETCH_WITH_CLI=true cargo update -p <crate>@<ver>` to move the
222
- lockfile onto the git source; verify `source = "git+..."` in `Cargo.lock`.
223
-4. If it fixes a mistral.rs-fork build (not just onde), remember it is stripped
224
- on publish — onde will still need its own copy of the patch unless the fix
225
- also lands in a published `onde-*` crate.
226
-5. `cargo check` on host, then let CI validate the target that was failing.
227
-
228
----
229
-
230
-*Update this skill whenever a fork's branch/rev changes, a patch is added or
231
-removed, or a fork gets published under an `onde-*` name.*
.agents/skills/sdk-branding/SKILL.md
deleted
-355
@@ -1,355 +0,0 @@
1
----
2
-name: sdk-branding
3
-description: Onde Inference SDK branding rules. Covers copyright headers for every language, brand identity (logo, colors, badges), README structure, and file classification (generated vs hand-written). Apply when creating or editing any SDK source file, README, or documentation.
4
-allowed-tools: Read, Write, Edit, Glob, Grep
5
-user-invocable: false
6
----
7
-
8
-# SDK Branding
9
-
10
-Branding rules for the Onde Inference SDK. These apply across all languages and all SDK surfaces: source files, READMEs, package metadata, and CI output.
11
-
12
----
13
-
14
-## Copyright Header
15
-
16
-Every hand-written source file starts with this two-line block. No blank line before it. One blank line after it (before the first import, `package`, or code).
17
-
18
-
19
-### Format
20
-
21
-```
22
-// Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
23
-// SPDX-License-Identifier: MIT OR Apache-2.0
24
-
25
-```
26
-
27
-Use `//` comment syntax for all languages that support it: Dart, Swift, Kotlin, Rust, TypeScript, JavaScript, C, and C++.
28
-
29
-### Language reference
30
-
31
-**Dart**
32
-```dart
33
-// Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
34
-// SPDX-License-Identifier: MIT OR Apache-2.0
35
-
36
-import 'dart:async';
37
-```
38
-
39
-**Swift**
40
-```swift
41
-// Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
42
-// SPDX-License-Identifier: MIT OR Apache-2.0
43
-
44
-import Foundation
45
-```
46
-
47
-**Kotlin**
48
-```kotlin
49
-// Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
50
-// SPDX-License-Identifier: MIT OR Apache-2.0
51
-
52
-package com.ondeinference
53
-```
54
-
55
-**Rust**
56
-```rust
57
-// Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
58
-// SPDX-License-Identifier: MIT OR Apache-2.0
59
-
60
-use std::sync::Arc;
61
-```
62
-
63
-**TypeScript / JavaScript**
64
-```ts
65
-// Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
66
-// SPDX-License-Identifier: MIT OR Apache-2.0
67
-
68
-import OndeInferenceModule from "./OndeInferenceModule";
69
-```
70
-
71
-**C / C++**
72
-```cpp
73
-// Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
74
-// SPDX-License-Identifier: MIT OR Apache-2.0
75
-
76
-#include <flutter/plugin_registrar.h>
77
-```
78
-
79
-### Existing file-level comments
80
-
81
-If the file already has a descriptive comment block below the copyright position (e.g. `// OndeInferencePlugin.swift\n// Native iOS plugin...`), prepend the copyright header above it — do not replace it.
82
-
83
-```swift
84
-// Copyright 2026 Splitfire AB (Onde Inference). All rights reserved.
85
-// SPDX-License-Identifier: MIT OR Apache-2.0
86
-
87
-// OndeInferencePlugin.swift
88
-//
89
-// Native iOS plugin for onde_inference.
90
-//
91
-```
92
-
93
-When updating hand-written SDK bridge code, keep the header style consistent
94
-across the React Native bridge and the Kotlin/Rust bridge. If one side uses an
95
-older MIT-only prose header, normalize the touched bridge files to the two-line
96
-SPDX header above.
97
-
98
----
99
-
100
-## File Classification
101
-
102
-Apply the header only to hand-written files. Never add it to generated files.
103
-
104
-### Always add the header
105
-
106
-| File | Notes |
107
-|---|---|
108
-| `lib/src/engine.dart` | Core SDK logic |
109
-| `lib/src/types.dart` | SDK types |
110
-| `lib/onde_inference.dart` | Public barrel file |
111
-| `test/dart_test.dart` | Hand-written unit tests |
112
-| `example/lib/main.dart` | Example app entry point |
113
-| `ios/Classes/OndeInferencePlugin.swift` | iOS plugin |
114
-| `macos/Classes/OndeInferencePlugin.swift` | macOS plugin |
115
-| `android/src/main/kotlin/**/*.kt` | Android plugin |
116
-| `src/**/*.ts` / `src/**/*.js` | React Native TypeScript / JavaScript source |
117
-| `windows/**/*.cpp` | Windows plugin |
118
-| `linux/**/*.cc` / `linux/**/*.h` | Linux plugin |
119
-| `src/**/*.rs` | Rust crate source |
120
-
121
-### Never add the header — generated files
122
-
123
-These files are written by a tool on every codegen run. Adding a header would be overwritten or cause diffs noise.
124
-
125
-| File / Pattern | Generator |
126
-|---|---|
127
-| `lib/src/frb_generated.dart/**` | `flutter_rust_bridge_codegen` |
128
-| `rust/src/frb_generated.rs` | `flutter_rust_bridge_codegen` |
129
-| `**/*.freezed.dart` | `build_runner` + `freezed` |
130
-| `**/*.g.dart` | `build_runner` |
131
-| `example/*/Flutter/GeneratedPluginRegistrant.*` | `flutter pub get` |
132
-| `generated/**` (repo root) | `uniffi-bindgen` |
133
-| `onde.swift` in `onde-swift` | `uniffi-bindgen` |
134
-
135
-### Never add the header — platform scaffold
136
-
137
-These files are produced by `flutter create` or Xcode project templates. They are committed to git but were not hand-written by the team.
138
-
139
-| Pattern | Tool |
140
-|---|---|
141
-| `example/ios/Runner/AppDelegate.swift` | `flutter create` |
142
-| `example/ios/Runner/SceneDelegate.swift` | `flutter create` |
143
-| `example/ios/RunnerTests/RunnerTests.swift` | `flutter create` |
144
-| `example/macos/Runner/AppDelegate.swift` | `flutter create` |
145
-| `example/macos/Runner/MainFlutterWindow.swift` | `flutter create` |
146
-| `example/macos/RunnerTests/RunnerTests.swift` | `flutter create` |
147
-| `example/test/widget_test.dart` | `flutter create` |
148
-
149
-**Rule of thumb:** if the first meaningful line of a file is a Flutter or Xcode framework import with no project-specific logic, it is scaffold — skip it.
150
-
151
----
152
-
153
-## Brand Identity
154
-
155
-### Logo
156
-
157
-Always reference the logo via the raw GitHub URL so it renders on pub.dev, crates.io, and any mirror.
158
-
159
-```
160
-https://raw.githubusercontent.com/ondeinference/onde/main/assets/onde-inference-logo.svg
161
-```
162
-
163
-Width: `96` for top-level READMEs. Use `72` for secondary pages (example apps, sub-package docs).
164
-
165
-### Colors
166
-
167
-| Role | Hex |
168
-|---|---|
169
-| Brand green (foreground, badge color) | `#235843` |
170
-| Dark background (badge `labelColor`) | `#17211D` |
171
-
172
-### Badge row
173
-
174
-Badge policy depends on the README type.
175
-
176
-**Root README (`onde/README.md`)**
177
-
178
-Use the full ecosystem badge wall here. This is the hub page for the whole SDK family.
179
-
180
-```html
181
-<a href="https://crates.io/crates/onde"><img src="https://img.shields.io/crates/v/onde?style=flat-square&color=235843&labelColor=17211D&label=crates.io" alt="crates.io"></a>
182
-<a href="https://central.sonatype.com/artifact/com.ondeinference/onde-inference"><img src="https://img.shields.io/maven-central/v/com.ondeinference/onde-inference?style=flat-square&color=235843&labelColor=17211D&label=maven" alt="Maven Central"></a>
183
-<a href="https://swiftpackageindex.com/ondeinference/onde-swift"><img src="https://img.shields.io/badge/Swift%20Package%20Index-onde--swift-235843?style=flat-square&labelColor=17211D" alt="Swift Package Index"></a>
184
-<a href="https://pub.dev/packages/onde_inference"><img src="https://img.shields.io/pub/v/onde_inference?style=flat-square&color=235843&labelColor=17211D&label=pub.dev" alt="pub.dev"></a>
185
-<a href="https://www.npmjs.com/package/@ondeinference/react-native"><img src="https://img.shields.io/npm/v/@ondeinference/react-native?style=flat-square&color=235843&labelColor=17211D&label=npm" alt="npm"></a>
186
-<a href="https://ondeinference.com"><img src="https://img.shields.io/badge/ondeinference.com-235843?style=flat-square&labelColor=17211D" alt="Website"></a>
187
-<a href="https://apps.apple.com/se/developer/splitfire-ab/id1831430993"><img src="https://img.shields.io/badge/App%20Store-live-235843?style=flat-square&labelColor=17211D" alt="App Store"></a>
188
-```
189
-
190
-**SDK README**
191
-
192
-Use one primary registry badge for that SDK, plus `Website` and `License`. Do not repeat the full ecosystem badge wall on every SDK page.
193
-
194
-**Flutter / Dart SDK:**
195
-```html
196
-<a href="https://pub.dev/packages/onde_inference"><img src="https://img.shields.io/pub/v/onde_inference?style=flat-square&color=235843&labelColor=17211D&label=pub.dev" alt="pub.dev"></a>
197
-<a href="https://ondeinference.com"><img src="https://img.shields.io/badge/ondeinference.com-235843?style=flat-square&labelColor=17211D" alt="Website"></a>
198
-<a href="https://github.com/ondeinference/onde/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-235843?style=flat-square&labelColor=17211D" alt="License"></a>
199
-```
200
-
201
-**Rust SDK:**
202
-```html
203
-<a href="https://crates.io/crates/onde"><img src="https://img.shields.io/crates/v/onde?style=flat-square&color=235843&labelColor=17211D&label=crates.io" alt="crates.io"></a>
204
-<a href="https://ondeinference.com"><img src="https://img.shields.io/badge/ondeinference.com-235843?style=flat-square&labelColor=17211D" alt="Website"></a>
205
-<a href="https://github.com/ondeinference/onde/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-235843?style=flat-square&labelColor=17211D" alt="License"></a>
206
-```
207
-
208
-**Kotlin Multiplatform SDK:**
209
-```html
210
-<a href="https://central.sonatype.com/artifact/com.ondeinference/onde-inference"><img src="https://img.shields.io/maven-central/v/com.ondeinference/onde-inference?style=flat-square&color=235843&labelColor=17211D&label=maven" alt="Maven Central"></a>
211
-<a href="https://ondeinference.com"><img src="https://img.shields.io/badge/ondeinference.com-235843?style=flat-square&labelColor=17211D" alt="Website"></a>
212
-<a href="https://github.com/ondeinference/onde/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-235843?style=flat-square&labelColor=17211D" alt="License"></a>
213
-```
214
-
215
-**React Native SDK:**
216
-```html
217
-<a href="https://www.npmjs.com/package/@ondeinference/react-native"><img src="https://img.shields.io/npm/v/@ondeinference/react-native?style=flat-square&color=235843&labelColor=17211D&label=npm" alt="npm"></a>
218
-<a href="https://ondeinference.com"><img src="https://img.shields.io/badge/ondeinference.com-235843?style=flat-square&labelColor=17211D" alt="Website"></a>
219
-<a href="https://github.com/ondeinference/onde/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-235843?style=flat-square&labelColor=17211D" alt="License"></a>
220
-```
221
-
222
-**Swift SDK:**
223
-```html
224
-<a href="https://swiftpackageindex.com/ondeinference/onde-swift"><img src="https://img.shields.io/badge/Swift%20Package%20Index-onde--swift-235843?style=flat-square&labelColor=17211D" alt="Swift Package Index"></a>
225
-<a href="https://ondeinference.com"><img src="https://img.shields.io/badge/ondeinference.com-235843?style=flat-square&labelColor=17211D" alt="Website"></a>
226
-<a href="https://github.com/ondeinference/onde/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT%20OR%20Apache--2.0-235843?style=flat-square&labelColor=17211D" alt="License"></a>
227
-```
228
-
229
-### Cross-SDK nav row
230
-
231
-Every SDK README should include a centered text-link row under the badges. These sibling links matter for discovery, branding, and cross-SDK navigation.
232
-
233
-Use this pattern and adjust the current page's own SDK label if needed:
234
-
235
-```html
236
-<p align="center">
237
- <a href="https://github.com/ondeinference/onde">Rust SDK</a> · <a href="https://github.com/ondeinference/onde-swift">Swift SDK</a> · <a href="https://central.sonatype.com/artifact/com.ondeinference/onde-inference">Kotlin Multiplatform SDK</a> · <a href="https://pub.dev/packages/onde_inference">Flutter SDK</a> · <a href="https://www.npmjs.com/package/@ondeinference/react-native">React Native SDK</a> · <a href="https://ondeinference.com">Website</a>
238
-</p>
239
-```
240
-
241
-### Footer
242
-
243
-Every README ends with this line, inside a centered `<p>`:
244
-
245
-```html
246
-<p align="center">
247
- <sub>© 2026 <a href="https://ondeinference.com">Onde Inference</a> — MIT License</sub>
248
-</p>
249
-```
250
-
251
-Use `— MIT License` on sub-pages (example apps, package-level docs). Omit it on the top-level repo README (it has its own license section).
252
-
253
----
254
-
255
-## README Structure
256
-
257
-### Top-level SDK README (e.g. `sdk/dart/README.md`)
258
-
259
-1. Centered logo block
260
-2. Centered `<h1>` with SDK name
261
-3. Centered tagline (`<p><strong>...</strong></p>`)
262
-4. Centered badge row with the SDK's primary registry badge, `Website`, and `License`
263
-5. Centered cross-SDK nav links (`Rust SDK · Swift SDK · Kotlin Multiplatform SDK · Flutter SDK · React Native SDK · Website`)
264
-6. `---` divider
265
-7. A direct intro paragraph that explains what runs where and what the user gets
266
-8. `## Platform support` — table: Platform | GPU backend | Default model | Notes
267
-9. `## Quick start` — install snippet, initialization, then usage sections
268
-10. `## Model selection` — table + code examples when relevant
269
-11. `## Sampling` — presets table + code when relevant
270
-12. `## Error handling` when relevant
271
-13. `## Contributing`
272
-14. `## License`
273
-15. Footer
274
-
275
-### Example app README
276
-
277
-1. Centered logo block (width `96`)
278
-2. Centered `<h1>` with `Example App` in the title
279
-3. Centered one-line description (bold) + second line: `No server. No API key. No data leaving the device.`
280
-4. Centered badge row for the parent SDK or example context
281
-5. `---` divider
282
-6. `## What this example shows` — feature table mapping feature → file/function
283
-7. `## Running the example` — Prerequisites, Steps, Platform notes table
284
-8. `## SDK quick reference` — minimal happy-path snippet (load → stream → unload)
285
-9. `## Project structure` — directory tree
286
-10. `## Learn more` — links to the package page, ondeinference.com, and GitHub
287
-11. Footer
288
-
289
-### Taglines
290
-
291
-Use these as the default direction, but keep them natural if the page needs slightly different wording. Avoid stiff marketing language and avoid em dashes in prose.
292
-
293
-| Context | Tagline |
294
-|---|---|
295
-| Rust crate | `Run LLMs on-device, with first-class support for Apple silicon.` |
296
-| Flutter SDK | `Run LLMs on-device from Flutter and Dart. Metal on iOS and macOS, CPU everywhere else.` |
297
-| Kotlin SDK | `Run LLMs on-device from Kotlin. No cloud, no API key, and no user data leaving the device.` |
298
-| React Native SDK | `Run LLMs on-device from React Native. Metal on iOS, CPU on Android, and no user data leaving the device.` |
299
-| Swift SDK | `Run LLMs on-device from Swift, with first-class support for Apple silicon.` |
300
-| Example app | `A Flutter chat app that runs LLM inference fully on-device.` |
301
-
302
----
303
-
304
-## Package Metadata Branding
305
-
306
-### `pubspec.yaml`
307
-
308
-```yaml
309
-description: >-
310
- On-device LLM inference for Flutter & Dart. Run Qwen 2.5 models locally
311
- with Metal on iOS and macOS, CPU on Android and desktop. No cloud, no API key.
312
-repository: https://github.com/ondeinference/onde/
313
-homepage: https://ondeinference.com
314
-issue_tracker: https://github.com/ondeinference/onde/issues
315
-```
316
-
317
-### `Cargo.toml`
318
-
319
-```toml
320
-description = "On-device inference engine for Apple silicon."
321
-license = "MIT"
322
-repository = "https://github.com/ondeinference/onde"
323
-homepage = "https://ondeinference.com"
324
-documentation = "https://docs.rs/onde"
325
-keywords = ["inference", "on-device", "chat", "llm", "mistral"]
326
-categories = ["science::ml", "api-bindings"]
327
-```
328
-
329
----
330
-
331
-## Version Synchronisation
332
-
333
-The version in `Cargo.toml` and `sdk/dart/pubspec.yaml` must always be identical. When bumping a version, update both files in the same commit. The CI workflows for Swift and Dart both validate that the git tag equals the version in their respective metadata file — a mismatch fails fast.
334
-
335
-```
336
-Cargo.toml version = "X.Y.Z"
337
-sdk/dart/pubspec.yaml version: X.Y.Z
338
-git tag X.Y.Z
339
-```
340
-
341
----
342
-
343
-## Applying the Copyright Header to an Existing Codebase
344
-
345
-When asked to add copyright headers to a set of files:
346
-
347
-1. Run `find` or `Glob` to list all `.dart`, `.swift`, `.kt`, `.rs`, `.cpp`, `.cc`, `.h` files under the SDK path.
348
-2. For each file, read its first line.
349
-3. Skip if the first line already starts with `// Copyright`.
350
-4. Skip if the first line contains `Generated` or `Do not edit`.
351
-5. Skip if the file path matches any generated or scaffold pattern above.
352
-6. Prepend the two-line header followed by a blank line.
353
-7. If the file begins with an existing file-description comment block, insert the header above that block.
354
-
355
-Do all qualifying files in one pass. Do not ask for confirmation per file.
.agents/skills/sdk-regressions/SKILL.md
deleted
-632
@@ -1,632 +0,0 @@
1
----
2
-name: sdk-regressions
3
-description: Cross-compilation regression checks for the onde crate. Covers macOS, iOS, tvOS (nightly), Android, Windows, and Linux target verification. Run before merging any change that touches platform-gated code, Cargo.toml, .cargo/config.toml, or build.rs. Also documents the onde-mistralrs fork publishing strategy and cargo publish workflow.
4
-allowed-tools: Read, Edit, Glob, Grep, Terminal
5
-user-invocable: true
6
----
7
-
8
-# SKILL: SDK Cross-Compilation Regression Checks
9
-
10
-> Always run these checks before merging changes that touch `onde/src/`,
11
-> `onde/Cargo.toml`, `onde/.cargo/config.toml`, `onde/build.rs`, or any
12
-> platform-gated dependency.
13
-
14
----
15
-
16
-## Why This Exists
17
-
18
-The `onde` crate compiles for **6 target OSes** with heavy `#[cfg(target_os)]`
19
-branching (106+ occurrences in `engine.rs` alone). A change that compiles on
20
-macOS can silently break iOS, tvOS, or Android because:
21
-
22
-- Different `mistralrs` feature sets per platform (`["metal"]` vs `[]`)
23
-- Platform-conditional dependencies (`hf-hub` only on Android)
24
-- Tier-3 targets (tvOS) require nightly + `-Z build-std`
25
-- Assembly stubs (`tvos_chkstk.s`) and `build.rs` gating
26
-- `.cargo/config.toml` sets `+fp16` rustflags on 5 Apple targets
27
-
28
-See also: **[`cargo publish` workflow](#cargo-publish-workflow)** at the bottom
29
-of this file — must be followed when releasing a new `onde` version.
30
-
31
----
32
-
33
-## Tier 1 — Must Pass on Every PR (stable toolchain)
34
-
35
-These use the default stable toolchain and must always succeed:
36
-
37
-```bash
38
-# macOS — host platform, Metal backend
39
-cargo check
40
-
41
-# iOS device — Metal backend, +fp16
42
-cargo check --target aarch64-apple-ios
43
-
44
-# iOS simulator — Metal backend, +fp16
45
-cargo check --target aarch64-apple-ios-sim
46
-```
47
-
48
-### Quick one-liner
49
-
50
-```bash
51
-cargo check && \
52
-cargo check --target aarch64-apple-ios && \
53
-cargo check --target aarch64-apple-ios-sim
54
-```
55
-
56
----
57
-
58
-## Tier 2 — tvOS (nightly toolchain, `-Z build-std`)
59
-
60
-tvOS targets are tier-3 in Rust — they are **not** in `rustup target list` and
61
-require nightly with `-Z build-std` and the `rust-src` component.
62
-
63
-```bash
64
-# tvOS device
65
-cargo +nightly check -Z build-std --target aarch64-apple-tvos
66
-
67
-# tvOS simulator
68
-cargo +nightly check -Z build-std --target aarch64-apple-tvos-sim
69
-```
70
-
71
-### tvOS-specific regression risks
72
-
73
-| Risk | What breaks | How to detect |
74
-|------|-------------|---------------|
75
-| `scripts/tvos_chkstk.s` deleted | `Undefined symbol: ___chkstk_darwin` at link time | `test -f scripts/tvos_chkstk.s` |
76
-| `cc` build-dep removed from `Cargo.toml` | `build.rs` can't compile the assembly stub | `grep 'cc' Cargo.toml` |
77
-| `build.rs` tvOS block removed | Same linker error | `grep 'tvos' build.rs` |
78
-| nightly `rust-src` not installed | `-Z build-std` fails immediately | `rustup +nightly component list --installed \| grep rust-src` |
79
-| `.cargo/config.toml` `+fp16` removed | Silent precision loss or candle kernel panics | `grep 'fp16' .cargo/config.toml` |
80
-
81
----
82
-
83
-## Tier 3 — Android, Windows, Linux (stable, needs cross toolchains)
84
-
85
-These check that CPU-only code paths compile. For this crate, `cargo check`
86
-still runs C build scripts from dependencies such as `aws-lc-sys` and `ring`,
87
-so absent cross GCC toolchains are real blockers, not harmless link-only
88
-failures.
89
-
90
-```bash
91
-# Android arm64 — CPU, requires hf-hub sandbox workaround
92
-cargo check --target aarch64-linux-android
93
-
94
-# Windows MSVC — CPU, exact CI target; run on Windows or a Windows CI runner
95
-cargo check --target x86_64-pc-windows-msvc
96
-
97
-# Linux — CPU
98
-cargo check --target x86_64-unknown-linux-gnu
99
-```
100
-
101
-On macOS hosts without cross GCC installed, expect Linux checks to fail looking
102
-for `x86_64-linux-gnu-gcc`, and Windows GNU checks to fail looking for
103
-`x86_64-w64-mingw32-gcc`. Use the Colima/Docker flow below instead of treating
104
-those as code regressions.
105
-
106
-### macOS host fallback: Colima/Docker
107
-
108
-Use this when the goal is to run the Linux and Windows cross-checks on a macOS
109
-host and the host does not have cross GCC toolchains. On Apple Silicon, run a
110
-native `linux/arm64` container. Avoid `linux/amd64` emulation for this crate;
111
-the dependency build can crash under QEMU before reaching `onde` code.
112
-
113
-Give Colima enough memory before running these checks:
114
-
115
-```bash
116
-colima status
117
-colima stop
118
-colima start --memory 8
119
-```
120
-
121
-Run Linux and the local Windows GNU proxy check in the container:
122
-
123
-```bash
124
-docker run --rm --platform linux/arm64 \
125
- -v "$PWD:/workspace" \
126
- -v onde-cargo-registry:/usr/local/cargo/registry \
127
- -v onde-cargo-git:/usr/local/cargo/git \
128
- -w /workspace \
129
- rust:1.92-bookworm \
130
- bash -lc '
131
- export PATH=/usr/local/cargo/bin:$PATH
132
- apt-get update >/dev/null
133
- DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
134
- gcc-x86-64-linux-gnu \
135
- libc6-dev-amd64-cross \
136
- linux-libc-dev-amd64-cross \
137
- gcc-mingw-w64-x86-64 \
138
- binutils-mingw-w64-x86-64 >/dev/null
139
- rustup target add x86_64-unknown-linux-gnu x86_64-pc-windows-gnu >/dev/null
140
- CARGO_BUILD_JOBS=1 cargo check --target x86_64-unknown-linux-gnu
141
- CARGO_BUILD_JOBS=1 cargo check --target x86_64-pc-windows-gnu
142
- '
143
-```
144
-
145
-`x86_64-pc-windows-gnu` exercises `#[cfg(target_os = "windows")]` and the
146
-CPU-only Windows path locally, but it is not a substitute for the CI
147
-`x86_64-pc-windows-msvc` check. The MSVC ABI check needs a Windows/MSVC
148
-toolchain, normally GitHub Actions `windows-latest`.
149
-
150
-If the container reaches `Checking onde ...` and then reports Rust errors, the
151
-cross toolchain is working. Fix the crate error and rerun the check.
152
-
153
----
154
-
155
-## Full Regression Suite (copy-paste)
156
-
157
-```bash
158
-#!/usr/bin/env bash
159
-set -euo pipefail
160
-
161
-echo "═══ Tier 1: macOS + iOS (stable) ═══"
162
-cargo check
163
-cargo check --target aarch64-apple-ios
164
-cargo check --target aarch64-apple-ios-sim
165
-
166
-echo "═══ Tier 2: tvOS (nightly + build-std) ═══"
167
-cargo +nightly check -Z build-std --target aarch64-apple-tvos
168
-cargo +nightly check -Z build-std --target aarch64-apple-tvos-sim
169
-
170
-echo "═══ Tier 3: Android / Windows / Linux ═══"
171
-cargo check --target aarch64-linux-android
172
-cargo check --target x86_64-pc-windows-msvc
173
-cargo check --target x86_64-unknown-linux-gnu
174
-
175
-echo "✅ All cross-compilation checks passed"
176
-```
177
-
178
----
179
-
180
-## Required Rustup Targets
181
-
182
-Install these once on a development machine:
183
-
184
-```bash
185
-# Tier 1 (stable)
186
-rustup target add aarch64-apple-darwin x86_64-apple-darwin
187
-rustup target add aarch64-apple-ios aarch64-apple-ios-sim
188
-
189
-# Tier 3 (stable)
190
-rustup target add aarch64-linux-android armv7-linux-androideabi
191
-rustup target add x86_64-linux-android i686-linux-android
192
-rustup target add x86_64-pc-windows-msvc
193
-rustup target add x86_64-pc-windows-gnu
194
-rustup target add x86_64-unknown-linux-gnu
195
-
196
-# Tier 2 — tvOS (nightly, no `rustup target add` — uses -Z build-std)
197
-rustup toolchain install nightly
198
-rustup +nightly component add rust-src
199
-```
200
-
201
----
202
-
203
-## `.cargo/config.toml` — Critical Settings
204
-
205
-These rustflags are set at the workspace level and **must not be removed**:
206
-
207
-```toml
208
-[target.aarch64-apple-ios]
209
-rustflags = ["-C", "target-feature=+fp16"]
210
-
211
-[target.aarch64-apple-ios-sim]
212
-rustflags = ["-C", "target-feature=+fp16"]
213
-
214
-[target.aarch64-apple-tvos]
215
-rustflags = ["-C", "target-feature=+fp16"]
216
-
217
-[target.aarch64-apple-tvos-sim]
218
-rustflags = ["-C", "target-feature=+fp16"]
219
-
220
-[target.x86_64-apple-tvos]
221
-rustflags = ["-C", "target-feature=+fp16"]
222
-```
223
-
224
-Removing `+fp16` causes silent precision degradation or runtime panics in
225
-candle's Metal/NEON kernels.
226
-
227
----
228
-
229
-## Platform Dependency Matrix
230
-
231
-| `target_os` | `mistralrs` features | Extra deps | GPU | Default model |
232
-|-------------|---------------------|------------|-----|---------------|
233
-| `macos` | `["metal"]` | — | Metal | Qwen 2.5 3B |
234
-| `ios` | `["metal"]` | `hf-hub`, `mistralrs-core` | Metal | Qwen 2.5 1.5B |
235
-| `tvos` | `["metal"]` | `hf-hub`, `mistralrs-core` | Metal | Qwen 2.5 1.5B |
236
-| `android` | `[]` | `hf-hub`, `mistralrs-core` | CPU | Qwen 2.5 1.5B |
237
-| `windows` | `[]` | — | CPU | Qwen 2.5 3B |
238
-| `linux` | `[]` | — | CPU | Qwen 2.5 3B |
239
-
240
-`hf-hub` and `mistralrs-core` are required on all **sandboxed** platforms
241
-(iOS, tvOS, Android) for the `GLOBAL_HF_CACHE` workaround — `~/.cache` is
242
-outside the app container on iOS/tvOS, and `dirs::home_dir()` panics on Android.
243
-The re-exports in `src/lib.rs` (`pub use hf_hub; pub use mistralrs_core;`) are
244
-gated to `#[cfg(any(target_os = "android", target_os = "ios", target_os = "tvos"))]`.
245
-
246
----
247
-
248
-## Common Regression Patterns
249
-
250
-### 1. New model added but not to all 6 platform `cfg` blocks
251
-
252
-`engine.rs` has separate `#[cfg(target_os = "...")]` blocks for model builder
253
-logic. A new model constructor added only under `macos` will fail on `ios`.
254
-
255
-**Detection:** Tier 1 checks catch iOS; Tier 2 catches tvOS; Tier 3 catches
256
-Android/Windows/Linux.
257
-
258
-### 2. Dependency added without platform gating
259
-
260
-Adding a dep that uses `SystemConfiguration.framework` (e.g., `hyper_util`
261
-proxy detection) works on macOS but causes linker errors on iOS/tvOS unless the
262
-podspec declares `s.frameworks = 'SystemConfiguration'`.
263
-
264
-**Detection:** Tier 1 `cargo check` passes (no linking), but `flutter build ios`
265
-fails. Must also test a full `flutter build` periodically.
266
-
267
-### 3. Fork branch divergence
268
-
269
-All `mistralrs` deps point to a fork:
270
-`git = "https://github.com/setoelkahfi/mistral.rs"`, branch
271
-`fix/all-platform-fixes`. If that branch is rebased, deleted, or diverges from
272
-upstream, **all** platforms break.
273
-
274
-**Detection:** Any `cargo check` fails with "could not find branch".
275
-
276
-### 4. UniFFI version mismatch
277
-
278
-`uniffi = "=0.31.0"` must be pinned identically in three places:
279
-- `onde/Cargo.toml` dependencies
280
-- `onde/Cargo.toml` build-dependencies
281
-- `onde/uniffi-bindgen/Cargo.toml`
282
-
283
-Mixing versions causes bindgen panics at codegen time.
284
-
285
-**Detection:** `cargo build` in `uniffi-bindgen/` or `build.rs` panics.
286
-
287
-### 5. Android `home_dir()` sandbox panic
288
-
289
-`dirs::home_dir()` panics in the Android sandbox. The `hf-hub` dep is added
290
-explicitly on Android so `HF_HOME` can be set programmatically. Never call
291
-`dirs::home_dir()` or `home::home_dir()` in code paths reachable on Android.
292
-
293
-**Detection:** Tier 3 `cargo check --target aarch64-linux-android` catches
294
-compile-time issues; runtime panics need an Android emulator test.
295
-
296
----
297
-
298
-## Dart SDK Cross-Build Checks
299
-
300
-When changes touch `sdk/dart/`, also verify the Flutter plugin builds:
301
-
302
-```bash
303
-cd sdk/dart/example
304
-
305
-# macOS (requires Xcode)
306
-flutter build macos --debug
307
-
308
-# iOS (requires Xcode + iOS SDK)
309
-flutter build ios --debug --no-codesign
310
-```
311
-
312
-### Known environment pitfall
313
-
314
-**Never export Android NDK toolchain binaries globally in `.zshrc`:**
315
-
316
-```bash
317
-# ❌ BREAKS Xcode builds — NDK's ld/ar/strip shadow Apple's tools
318
-export LD=$TOOLCHAIN/bin/ld
319
-export AR=$TOOLCHAIN/bin/llvm-ar
320
-
321
-# ✅ Safe — env vars only, no PATH/tool overrides
322
-export ANDROID_NDK_HOME="$HOME/Library/Android/sdk/ndk/29.0.14206865"
323
-```
324
-
325
-If Xcode invokes the NDK's `ld` instead of Apple's, you get:
326
-`ld: error: unknown argument '-Xlinker'`
327
-
328
-The fix: comment out the tool exports, use Cargo's
329
-`sdk/dart/rust/.cargo/config.toml` for Android linker configuration instead.
330
-
331
----
332
-
333
-## Cache Path Consistency (Cross-SDK)
334
-
335
-All Onde-powered apps share downloaded models via the App Group
336
-`group.com.ondeinference.apps`. The cache layout inside the shared container
337
-**must be identical** across every SDK and app type:
338
-
339
-```
340
-<group container>/
341
-├── models/ ← HF_HOME
342
-│ └── hub/ ← HF_HUB_CACHE (GGUF files live here)
343
-└── tmp/ ← TMPDIR (iOS sandbox restricts system TMPDIR)
344
-```
345
-
346
-**The subdirectory is `models/`, NOT `huggingface/`.**
347
-
348
-Files that set this path (all must agree):
349
-
350
-| File | Variable | Expected value |
351
-|------|----------|----------------|
352
-| `onde/sdk/dart/rust/src/api.rs` (`configure_cache_dir`) | `hf_home` | `data_dir.join("models")` |
353
-| `onde/src/hf_cache.rs` (`download_model` — general) | `hf_home` | `data_dir.join("models")` |
354
-| `onde/src/hf_cache.rs` (`download_model` — Android) | `hf_home` | `resolved_app_data.join("models")` |
355
-| Tauri apps (`setup_application_filesystem.rs`) | `models_home` | `container_dir.join("models")` |
356
-| `onde/src/hf_cache.rs` (`hf_cache_dir` — fallback) | default | `~/.cache/huggingface/hub` (non-sandboxed only, OK to differ) |
357
-
358
-**Regression check:** `grep -rn 'join("huggingface")' src/hf_cache.rs sdk/dart/rust/src/api.rs`
359
-should return **only** the non-sandboxed fallback in `hf_cache_dir()` (line ~164).
360
-Any other match means a sandboxed path is using the wrong subdirectory.
361
-
362
----
363
-
364
----
365
-
366
-## Publishing to crates.io
367
-
368
-### Why we don't use upstream `mistralrs` directly
369
-
370
-The short version: our PRs aren't merged yet.
371
-
372
-`onde` depends on a personal fork of Eric Buehler's `mistral.rs`, kept at
373
-`github.com/setoelkahfi/mistral.rs` on the `fix/all-platform-fixes` branch.
374
-The fork is ahead of upstream by ~41 commits carrying fixes that only matter
375
-for Apple and Android targets — things upstream hasn't needed to care about:
376
-
377
-| Fix | Status |
378
-|-----|--------|
379
-| iOS Metal 3.0 support | PR open |
380
-| Android 32-bit memory limit constants | PR open |
381
-| HF_HOME propagation in diffusion/flux pipelines | PR open |
382
-| `metallib` link step `--sdk` / `-std` flags | PR open |
383
-
384
-Dropping the fork and pointing at upstream `mistralrs 0.8.1` would silently
385
-break iOS on entry-level devices and Android on 32-bit targets. Don't do it
386
-until those PRs land.
387
-
388
-**Legal note:** The fork is a derivative of Eric Buehler's MIT-licensed
389
-`mistral.rs`. His `LICENSE` file — including `Copyright (c) 2024 Eric Buehler`
390
-— must remain intact and unmodified in the fork at all times. See
391
-[`legal-and-trademarks/SKILL.md`](../legal-and-trademarks/SKILL.md) for full
392
-attribution obligations.
393
-
394
-### The `onde-mistralrs` workaround
395
-
396
-> For the full inventory of every fork and `[patch.crates-io]` override in the
397
-> stack (mistral.rs, candle, sysctl, mio, tqdm, core2) and how the
398
-> git-vs-published tiers resolve differently, see the **forked-dependencies**
399
-> skill. This section covers only the mistral.rs publish mechanics.
400
-
401
-`cargo publish` strips every `git =` field before uploading to crates.io.
402
-That means a git-only dep is useless to anyone who installs `onde` from the
403
-registry — they can't resolve it.
404
-
405
-The solution: publish the fork to crates.io under the name `onde-mistralrs`
406
-(and `onde-mistralrs-core`, etc.), then reference it with `package =` so the
407
-rest of the code still compiles unchanged with `use mistralrs::...`:
408
-
409
-```toml
410
-# In onde/Cargo.toml — each platform target section looks like this.
411
-# Cargo resolves it as "onde-mistralrs" from crates.io,
412
-# but the Rust code sees it as `mistralrs`.
413
-mistralrs = { version = "0.8.2", package = "onde-mistralrs", features = ["metal"] }
414
-```
415
-
416
-This is the only supported publish path until the upstream PRs merge. Don't
417
-swap it for plain `mistralrs` from crates.io without checking every platform
418
-target first.
419
-
420
----
421
-
422
-### Step 0 — check if the candle snapshot needs an `onde-candle-*` publish
423
-
424
-Since 0.9.x, upstream mistral.rs pins `candle` to a git rev past the latest
425
-released candle (e.g. rev `27f20fea`, 19 commits past `0.11.0`, adding
426
-`QTensor::indexed_gemv`, `gemv_fused_shared_lhs`, `BarrierPool::execute_chunked`).
427
-`cargo publish` strips git pins, so publish verification resolves candle from
428
-crates.io and fails with E0599 "method not found" if the pinned rev has APIs the
429
-released candle lacks.
430
-
431
-When that happens, publish the pinned rev under `onde-candle-*` names from the
432
-local candle checkout (`~/Repositories/candle`, branch pattern
433
-`onde/candle-<version>-<rev>`):
434
-
435
-```bash
436
-# Which candle crates actually diverged from the released version?
437
-git diff --stat <release-bump-commit> <pinned-rev> -- candle-core candle-nn candle-metal-kernels
438
-
439
-# Rename ONLY the diverged crates (0.11.0 era: candle-core + candle-nn; the
440
-# rest are byte-identical, official crates serve them). candle-nn must be
441
-# republished whenever candle-core is, even if unchanged, or the official
442
-# candle-nn drags the official candle-core into the graph -> two candle-core
443
-# crates -> type mismatch.
444
-cargo publish -p onde-candle-core
445
-cargo publish -p onde-candle-nn
446
-```
447
-
448
-Then in the mistral.rs fork workspace `Cargo.toml`:
449
-
450
-```toml
451
-candle-core = { version = "0.11.0", package = "onde-candle-core" }
452
-candle-nn = { version = "0.11.0", package = "onde-candle-nn" }
453
-candle-flash-attn-v3 = { version = "0.11.0" } # unchanged at rev -> official
454
-candle-metal-kernels = { version = "0.11.0" } # unchanged at rev -> official
455
-```
456
-
457
-### Step 1 — check if `onde-mistralrs` needs a new publish
458
-
459
-First, see how far the fork has drifted from upstream:
460
-
461
-```bash
462
-curl -s "https://api.github.com/repos/setoelkahfi/mistral.rs/compare/EricLBuehler:mistral.rs:master...setoelkahfi:mistral.rs:fix%2Fall-platform-fixes" \
463
- | python3 -c "import sys,json; d=json.load(sys.stdin); print('ahead:', d['ahead_by'], 'behind:', d['behind_by'])"
464
-```
465
-
466
-If there are new commits since the last `onde-mistralrs` publish, or if
467
-upstream cut a new release that the fork has rebased onto, bump the workspace
468
-version in the fork and publish. As of 0.9.1 the set is **12 crates** (upstream
469
-split out flash-attn/metal-compile/code-exec/sandbox, and vision/audio/mcp must
470
-be onde-published too because crates.io only has upstream's old versions).
471
-The rename lives in the workspace dep table (`package = "onde-..."` on each
472
-entry, local dep keys unchanged so imports stay `mistralrs_*`).
473
-
474
-```bash
475
-# 1. Edit the root Cargo.toml in setoelkahfi/mistral.rs
476
-# bump version, e.g. 0.9.0 → 0.9.1
477
-
478
-# 2. Publish in dependency order (leaves first):
479
-cargo publish -p onde-mistralrs-macros
480
-cargo publish -p onde-mistralrs-metal-compile
481
-cargo publish -p onde-mistralrs-sandbox
482
-cargo publish -p onde-mistralrs-flash-attn
483
-cargo publish -p onde-mistralrs-vision
484
-cargo publish -p onde-mistralrs-audio
485
-cargo publish -p onde-mistralrs-mcp
486
-cargo publish -p onde-mistralrs-paged-attn
487
-cargo publish -p onde-mistralrs-quant
488
-cargo publish -p onde-mistralrs-code-exec
489
-cargo publish -p onde-mistralrs-core
490
-cargo publish -p onde-mistralrs
491
-
492
-# 3. cargo waits for index propagation automatically after each publish.
493
-```
494
-
495
-Known publish-verification snags (hit during 0.9.1):
496
-
497
-| Error | Fix |
498
-|-------|-----|
499
-| E0599 on candle APIs (`indexed_gemv`, ...) | Step 0 — publish `onde-candle-*` and repoint |
500
-| `dependency tqdm does not specify a version` | tqdm must be a registry dep, not git — it is plain `tqdm = "0.8.1"` since the crossterm bump landed upstream |
501
-| `core2 0.4.0 is yanked` | `cargo update -p bitstream-io` (4.10.0 dropped core2) |
502
-
503
----
504
-
505
-### Step 2 — update `onde/Cargo.toml`
506
-
507
-Every platform target section has its own `onde-mistralrs` line. They all need
508
-to move to the new version together — leaving one target on an older version
509
-causes a dependency conflict.
510
-
511
-```bash
512
-# All occurrences should print the same version number:
513
-grep 'onde-mistralrs' onde/Cargo.toml
514
-```
515
-
516
----
517
-
518
-### Step 3 — dry-run first
519
-
520
-```bash
521
-cd onde
522
-cargo publish --dry-run --allow-dirty 2>&1 | tail -20
523
-```
524
-
525
-A clean run ends with `Finished` and no errors. Common things that go wrong:
526
-
527
-| Error | What to do |
528
-|-------|------------|
529
-| `all dependencies must have a version` | add `version = "..."` to the dep |
530
-| `no matching package named onde-mistralrs` | publish the fork first (step 1) |
531
-| `git specification will be removed` | expected — cargo is just telling you the git field gets stripped |
532
-| `files in working directory contain changes` | add `--allow-dirty` |
533
-
534
----
535
-
536
-### Step 4 — publish `onde`, then update `sigit`
537
-
538
-```bash
539
-cargo publish
540
-```
541
-
542
-Then bump the version in `sigit/Cargo.toml`:
543
-
544
-```toml
545
-# git = is used for local dev; crates.io consumers resolve via version =
546
-onde = { version = "0.1.9", git = "https://github.com/ondeinference/onde", branch = "development" }
547
-```
548
-
549
-Keep both fields. The `git =` field takes precedence during local development;
550
-crates.io strips it and falls back to `version =` for anyone installing from
551
-the registry.
552
-
553
----
554
-
555
-### GresIQ credentials and `HF_TOKEN`
556
-
557
-People building `onde` from crates.io don't need to do anything about secrets.
558
-Here's why.
559
-
560
-`build.rs` passes credentials to the compiler via `option_env!()`. That macro
561
-resolves at compile time — if the env var isn't set, it compiles to `None` and
562
-the feature that needs it is silently disabled. No crash, no partial state, no
563
-mystery error at runtime.
564
-
565
-```rust
566
-// pulse/client.rs — GresIQ telemetry (Onde Inference internal infrastructure)
567
-const EMBEDDED_API_KEY_DEV: Option<&str> = option_env!("GRESIQ_API_KEY_DEV");
568
-const EMBEDDED_API_SECRET_DEV: Option<&str> = option_env!("GRESIQ_API_SECRET_DEV");
569
-
570
-// token.rs — HuggingFace Hub authentication
571
-const BUILD_TIME_HF_TOKEN: Option<&str> = option_env!("HF_TOKEN");
572
-```
573
-
574
-Who gets what:
575
-
576
-| Who is building | Env vars present? | What happens |
577
-|-----------------|-------------------|--------------|
578
-| Our CI (official builds) | yes, via GitHub secrets | telemetry on; HF token baked into binary |
579
-| Someone who ran `cargo add onde` | no | telemetry off; HF token read from `~/.cache/huggingface/token` at runtime |
580
-| iOS/tvOS builds via XCFramework | yes, via `.env` file | HF token baked in — required because there's no writable filesystem on device |
581
-
582
-The GresIQ credentials are Onde Inference's own infrastructure secrets — they
583
-exist so the SDK can phone home for usage telemetry on official builds. External
584
-consumers don't have them, don't need them, and won't be affected by not having
585
-them.
586
-
587
-Never put `GRESIQ_API_KEY_*` or `HF_TOKEN` in `README.md`, `.env.example`, or
588
-any committed file. The `.env` in the crate root is git-ignored for a reason:
589
-
590
-```bash
591
-# onde/.env — git-ignored, never commit
592
-GRESIQ_API_KEY_DEV=...
593
-GRESIQ_API_SECRET_DEV=...
594
-GRESIQ_API_KEY_PRODUCTION=...
595
-GRESIQ_API_SECRET_PRODUCTION=...
596
-HF_TOKEN=hf_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
597
-```
598
-
599
----
600
-
601
-### Working against the git fork locally
602
-
603
-If you're making changes to the fork and want `onde` to pick them up without
604
-publishing, uncomment the patch block in `onde/Cargo.toml`:
605
-
606
-```toml
607
-[patch.crates-io]
608
-onde-mistralrs = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", package = "mistralrs" }
609
-onde-mistralrs-core = { git = "https://github.com/setoelkahfi/mistral.rs", branch = "fix/all-platform-fixes", package = "mistralrs-core" }
610
-```
611
-
612
-Re-comment it before running `cargo publish`. Cargo strips `[patch.crates-io]`
613
-from the published manifest automatically, so it won't affect registry
614
-consumers either way — but leaving it active makes the dry-run confusing.
615
-
616
----
617
-
618
-### Pre-publish checklist
619
-
620
-- [ ] all `onde-mistralrs` version numbers in `onde/Cargo.toml` are the same
621
-- [ ] `onde/Cargo.toml` `[package] version` is bumped
622
-- [ ] `sigit/Cargo.toml` `onde` version matches
623
-- [ ] `[patch.crates-io]` git override is commented out
624
-- [ ] `cargo publish --dry-run --allow-dirty` passes clean
625
-- [ ] `.env` is not listed in the dry-run's packaged file output
626
-- [ ] Eric Buehler's `LICENSE` in the `mistral.rs` fork is unmodified
627
-
628
----
629
-
630
-*Last updated: July 2026 — added Colima/Docker Linux and Windows GNU
631
-cross-check guidance for macOS hosts without cross GCC, plus the MSVC CI
632
-caveat.*
.agents/skills/sdk-releases/SKILL.md
deleted
-670
@@ -1,670 +0,0 @@
1
----
2
-name: sdk-releases
3
-description: End-to-end SDK release process for all Onde distribution channels. Covers version bump checklist, CI/CD pipelines for Rust (crates.io), Swift (XCFramework → onde-swift), Dart (pub.dev), and npm, tag validation, GitHub Release creation, manual publish fallback, and common pitfalls. Apply whenever bumping versions or troubleshooting release workflows.
4
-allowed-tools: Read, Write, Edit, Glob, Grep, Bash
5
-user-invocable: true
6
----
7
-
8
-# Skill: SDK Releases
9
-
10
-## What This Skill Covers
11
-
12
-The complete release pipeline for shipping Onde across all registries:
13
-
14
-- **Rust crate** → crates.io (`onde`)
15
-- **Swift package** → Swift Package Index (`onde-swift`)
16
-- **Dart/Flutter package** → pub.dev (`onde_inference`)
17
-- **React Native** → npm (`@ondeinference/react-native`)
18
-- **Kotlin Multiplatform** → Maven Central (`com.ondeinference:onde-inference`)
19
-
20
-All five SDK workflows share the same tag trigger. A single tag push fires all
21
-five in parallel. When only a subset needs (re-)publishing, use the **manual
22
-publish fallback** documented below to avoid collateral re-runs.
23
-
24
----
25
-
26
-## Version Bump Checklist
27
-
28
-Every release starts with a version bump. **All version sources must be updated
29
-in a single commit before tagging.**
30
-
31
-| # | File | Field | Example |
32
-|---|------|-------|---------|
33
-| 1 | `Cargo.toml` (root) | `version` | `version = "0.1.3"` |
34
-| 2 | `sdk/dart/pubspec.yaml` | `version` | `version: 0.1.3` |
35
-| 3 | `sdk/dart/CHANGELOG.md` | New `## 0.1.3` section | Prepend at top of file |
36
-| 4 | `sdk/react-native/package.json` | `version` | `"version": "0.1.3"` |
37
-| 5 | `sdk/react-native/rust/Cargo.toml` | `version` | `version = "0.1.3"` |
38
-| 6 | `sdk/react-native/CHANGELOG.md` | New `## 0.1.3` section | Prepend at top of file |
39
-| 7 | `sdk/kotlin/gradle.properties` | `VERSION_NAME` | `VERSION_NAME=0.1.3` |
40
-| 8 | `sdk/dart/rust/Cargo.lock` | `onde` package version | Run `cd sdk/dart/rust && cargo update -p onde` |
41
-| 9 | `sdk/react-native/rust/Cargo.lock` | `onde` package version | Run `cd sdk/react-native/rust && cargo update -p onde` |
42
-| 10 | `Cargo.lock` (root) | `onde` package version | Run `cargo check` at repo root |
43
-| 11 | `CHANGELOG.md` (root) | New `## 0.1.3` section | Prepend at top of file. `sync-release-version.py --check` gates on it since 1.2.5; before that nothing did, which is why the file jumps from 1.1.1 to 1.2.5 |
44
-
45
-### Files you do NOT manually edit
46
-
47
-| File | Why |
48
-|------|-----|
49
-| `onde-swift/Package.swift` | CI rewrites the `url:` + `checksum:` automatically |
50
-| `onde-swift/Sources/Onde/onde.swift` | CI copies the freshly generated UniFFI glue |
51
-
52
-### Quick bump commands
53
-
54
-```bash
55
-# 1. Edit Cargo.toml version (manual)
56
-# 2. Edit sdk/dart/pubspec.yaml version (manual)
57
-# 3. Prepend new section to sdk/dart/CHANGELOG.md (manual)
58
-# 4. Edit sdk/react-native/package.json version (manual)
59
-# 5. Edit sdk/react-native/rust/Cargo.toml version (manual)
60
-# 6. Prepend new section to sdk/react-native/CHANGELOG.md (manual)
61
-# 7. Edit sdk/kotlin/gradle.properties VERSION_NAME (manual)
62
-# 8. Prepend new section to CHANGELOG.md at the repo root (manual)
63
-
64
-# 9. Sync lockfiles
65
-cargo check # updates root Cargo.lock
66
-cd sdk/dart/rust && cargo update -p onde # updates Dart SDK's Cargo.lock
67
-cd sdk/react-native/rust && cargo update -p onde # updates React Native SDK's Cargo.lock
68
-
69
-# 10. Verify
70
-grep '^version' Cargo.toml # "0.1.3"
71
-grep '^version:' sdk/dart/pubspec.yaml # 0.1.3
72
-grep '"version"' sdk/react-native/package.json # "0.1.3"
73
-grep '^version' sdk/react-native/rust/Cargo.toml # "0.1.3"
74
-grep 'VERSION_NAME' sdk/kotlin/gradle.properties # 1.0.0
75
-grep 'name = "onde"' -A1 Cargo.lock # version = "0.1.3"
76
-grep 'name = "onde"' -A1 sdk/dart/rust/Cargo.lock # version = "0.1.3"
77
-grep 'name = "onde"' -A1 sdk/react-native/rust/Cargo.lock # version = "0.1.3"
78
-
79
-# 11. Commit and tag
80
-git add -A
81
-git commit -m "0.1.3"
82
-git tag 0.1.3
83
-git push origin main 0.1.3
84
-```
85
-
86
----
87
-
88
-## Tag Format
89
-
90
-All tags are **bare semver** — no `v` prefix.
91
-
92
-```
93
-0.1.3 ✅ correct
94
-v0.1.3 ❌ will NOT trigger CI
95
-0.1.3-beta ❌ will NOT match the tag pattern
96
-```
97
-
98
-All five CI workflows use the same trigger pattern:
99
-
100
-```yaml
101
-on:
102
- push:
103
- tags:
104
- - "[0-9]+.[0-9]+.[0-9]+"
105
-```
106
-
107
----
108
-
109
-## CI Pipeline: Rust SDK (crates.io)
110
-
111
-**Workflow:** `.github/workflows/release-sdk-rust.yml`
112
-**Trigger:** Tag push matching `[0-9]+.[0-9]+.[0-9]+` or `workflow_dispatch`
113
-**Runner:** `macos-26`
114
-
115
-### Flow
116
-
117
-```
118
-Tag push (e.g. 1.0.0)
119
- │
120
- ├─ 1. Checkout + install stable Rust
121
- │
122
- ├─ 2. Validate tag == Cargo.toml version
123
- │ └─ Fails fast if mismatched
124
- │
125
- ├─ 3. cargo publish --dry-run
126
- │ └─ Compiles and packages; catches errors before real publish
127
- │
128
- └─ 4. cargo publish (tag push only)
129
- └─ Authenticates via CARGO_REGISTRY_TOKEN secret
130
-```
131
-
132
-### Key details
133
-
134
-- **`workflow_dispatch`** runs the dry-run only (the publish step has
135
- `if: startsWith(github.ref, 'refs/tags/')`) — useful for validation.
136
-- **Runner is `macos-26`** because the crate depends on `mistralrs` with Metal
137
- features, which requires macOS + Xcode to compile.
138
-- **crates.io is immutable** — once a version is published, it cannot be
139
- re-published. You must bump to the next version. `cargo yank` hides a version
140
- from dependency resolution but does not delete it.
141
-
142
-### Required secret
143
-
144
-`CARGO_REGISTRY_TOKEN` — a crates.io API token scoped to `publish-update` for
145
-the `onde` crate. Create at https://crates.io/settings/tokens. Best practice:
146
-scope it to a single crate rather than using a global token.
147
-
148
----
149
-
150
-## CI Pipeline: Swift SDK
151
-
152
-**Workflow:** `.github/workflows/release-sdk-swift.yml`
153
-**Trigger:** Tag push matching `[0-9]+.[0-9]+.[0-9]+`
154
-**Runner:** `macos-15`
155
-
156
-### Flow
157
-
158
-```
159
-Tag push (e.g. 0.1.3)
160
- │
161
- ├─ 1. Build XCFramework
162
- │ └─ .github/scripts/build-swift-xcframework.sh
163
- │ ├─ Extracts version from Cargo.toml via tomllib
164
- │ ├─ Compiles staticlibs for 5 Apple targets
165
- │ ├─ Generates UniFFI Swift bindings
166
- │ ├─ Assembles XCFramework, zips, computes SHA-256
167
- │ └─ Writes dist/swift/{version.txt, OndeFramework.checksum.txt}
168
- │
169
- ├─ 2. Read version + checksum into step outputs
170
- │
171
- ├─ 3. Validate tag == Cargo.toml version
172
- │ └─ Fails fast if mismatched (e.g. tagged 0.1.3 but Cargo.toml says 0.1.2)
173
- │
174
- ├─ 4. Upload CI artifacts (every run, including workflow_dispatch)
175
- │
176
- ├─ 5. Create GitHub Release on `onde` (tag push only)
177
- │ └─ Attaches OndeFramework.xcframework.zip + checksum
178
- │
179
- ├─ 6. Checkout ondeinference/onde-swift (using ONDE_SWIFT_PAT)
180
- │
181
- ├─ 7. Rewrite Package.swift
182
- │ └─ Python regex replaces .binaryTarget url: + checksum:
183
- │
184
- ├─ 8. Copy generated onde.swift → onde-swift/Sources/Onde/
185
- │
186
- └─ 9. Commit + tag onde-swift
187
- ├─ git commit -m "Release 0.1.3" (skipped if no changes)
188
- ├─ git push origin HEAD:main
189
- └─ git tag -a "0.1.3" -m "Release 0.1.3" (skipped if tag exists)
190
- └─ Annotated tag, never force-pushed
191
-```
192
-
193
-### What happens on `onde-swift` after the push
194
-
195
-The `onde` CI pushes a commit (to `main`) and a tag (e.g. `0.1.3`) to `onde-swift`.
196
-This triggers **two** workflows on `onde-swift`:
197
-
198
-1. **`ci.yml`** (on push to `main`) — validates manifest + builds all 5 platforms
199
-2. **`release.yml`** (on tag push) — validates package, then creates a GitHub
200
- Release with installation instructions and a link to the upstream `onde` changelog
201
-
202
-Both run concurrently without conflict — they use different concurrency groups
203
-(`ci-refs/heads/main` vs the tag ref).
204
-
205
-### Tag immutability
206
-
207
-Tags on `onde-swift` are **never force-pushed**. SPM caches the resolved commit
208
-SHA per tag in `Package.resolved`. Moving a tag silently breaks every consumer's
209
-lockfile. If a release needs to be redone:
210
-
211
-1. Delete the remote tag: `git push origin :refs/tags/0.1.3`
212
-2. Delete the GitHub Release on both `onde` and `onde-swift`
213
-3. Fix the issue, re-tag, push again
214
-
215
----
216
-
217
-## CI Pipeline: npm SDK (React Native)
218
-
219
-**Workflow:** `.github/workflows/release-sdk-npm.yml`
220
-**Trigger:** Tag push matching `[0-9]+.[0-9]+.[0-9]+` or `workflow_dispatch`
221
-**Runner:** `ubuntu-latest`
222
-
223
-### Flow
224
-
225
-```
226
-Tag push (e.g. 1.0.0)
227
- │
228
- ├─ validate job:
229
- │ ├─ Set up Node.js 22
230
- │ ├─ Read version from sdk/react-native/package.json
231
- │ ├─ Validate tag == package.json version
232
- │ ├─ npm install + npm run build
233
- │ └─ npm publish --dry-run --access public
234
- │
235
- └─ publish job (tag push only, needs: validate):
236
- ├─ npm install + npm run build
237
- ├─ Check if version already exists on npm
238
- │ └─ If yes → skip publish (idempotent, exits 0)
239
- └─ npm publish --access public
240
-```
241
-
242
-### npm idempotency guard
243
-
244
-The publish step checks `npm view <package>@<version>` before publishing. If the
245
-version already exists, it **skips** with a success exit code. This makes the npm
246
-workflow safe to re-run — unlike Dart/Rust, it won't fail on a duplicate version.
247
-
248
-### No secret: trusted publishing
249
-
250
-Since 1.2.5 the npm workflow publishes over OIDC rather than a long-lived token,
251
-so there is no `NPM_TOKEN` to rotate. Two things have to line up instead:
252
-
253
-- The `publish` job declares `permissions: id-token: write`.
254
-- The trusted publisher on
255
- npmjs.com/package/@ondeinference/react-native/access names repo
256
- `ondeinference/onde`, workflow `release-sdk-npm.yml`, and no environment.
257
-
258
-If those drift apart, npm answers the publish with a 404 on PUT rather than an
259
-auth error, which reads as a missing package and sends you looking in the wrong
260
-place. Adding an `environment:` to the job without setting the matching
261
-Environment name field on that npm page is the usual way to cause it.
262
-
263
-The job also upgrades npm before publishing: Node 22 ships npm 10, and trusted
264
-publishing needs 11.5.1 or newer.
265
-
266
----
267
-
268
-## CI Pipeline: Dart SDK
269
-
270
-**Workflow:** `.github/workflows/release-sdk-dart.yml`
271
-**Trigger:** Tag push matching `[0-9]+.[0-9]+.[0-9]+`
272
-**Runner:** `ubuntu-latest`
273
-
274
-### Flow
275
-
276
-```
277
-Tag push (e.g. 0.1.3)
278
- │
279
- ├─ validate job:
280
- │ ├─ Set up Flutter (version from sdk/dart/.flutter-version)
281
- │ ├─ Read version from pubspec.yaml
282
- │ ├─ Validate tag == pubspec.yaml version
283
- │ │ └─ Fails fast if mismatched
284
- │ ├─ flutter pub get
285
- │ ├─ flutter analyze --no-fatal-infos
286
- │ └─ flutter pub publish --dry-run
287
- │
288
- └─ publish job (tag push only, needs: validate):
289
- ├─ Set up Flutter
290
- ├─ flutter pub get
291
- ├─ Write PUB_CREDENTIALS to ~/.config/dart/pub-credentials.json
292
- └─ flutter pub publish --force
293
-```
294
-
295
-### Tag validation (Dart)
296
-
297
-The Dart workflow independently validates the tag against `pubspec.yaml`:
298
-
299
-```yaml
300
-if [ "$TAG" != "$PUBSPEC_VERSION" ]; then
301
- echo "::error::Tag '$TAG' does not match pubspec.yaml version '$PUBSPEC_VERSION'."
302
- exit 1
303
-fi
304
-```
305
-
306
-This means **both** `Cargo.toml` and `pubspec.yaml` must have the same version
307
-as the git tag, or both workflows fail.
308
-
309
-### Required secret
310
-
311
-`PUB_CREDENTIALS` — the full JSON content of `~/.config/dart/pub-credentials.json`
312
-from a machine that has run `dart pub login`. Add as a repository secret on `onde`.
313
-
314
----
315
-
316
-## GitHub Releases (two repos)
317
-
318
-| Repo | Created by | Contains |
319
-|------|-----------|----------|
320
-| `onde` | `release-sdk-swift.yml` (step 5) | XCFramework zip + checksum, auto-generated release notes |
321
-| `onde-swift` | `onde-swift` `release.yml` | Installation snippet, platform table, link to upstream changelog |
322
-
323
-Both repos need GitHub Releases because they serve different audiences:
324
-
325
-- **`onde`** — Rust consumers, asset hosting for the XCFramework binary
326
-- **`onde-swift`** — Swift developers, Swift Package Index surfaces these as version notes
327
-
328
----
329
-
330
-## Version Sources and Validation Matrix
331
-
332
-| Source | Read by | Validated against |
333
-|--------|---------|-------------------|
334
-| `Cargo.toml` `version` | `build-swift-xcframework.sh` (tomllib) | Git tag (Swift workflow) |
335
-| `sdk/dart/pubspec.yaml` `version` | `release-sdk-dart.yml` (grep + awk) | Git tag (Dart workflow) |
336
-| Git tag (`github.ref_name`) | Both workflows | Both version files above |
337
-| `onde-swift` tag | Created by `release-sdk-swift.yml` | Same value as the `onde` tag |
338
-
339
-All four must match. If any pair diverges, the relevant CI job fails.
340
-
341
----
342
-
343
-## Secrets Required
344
-
345
-| Secret | Used by | Purpose |
346
-|--------|---------|---------|
347
-| `CARGO_REGISTRY_TOKEN` | `release-sdk-rust.yml` | Authenticate with crates.io for `cargo publish`. Scoped to `publish-update` for the `onde` crate. Create at https://crates.io/settings/tokens. |
348
-| `ONDE_SWIFT_PAT` | `release-sdk-swift.yml` | Push commits + tags to `ondeinference/onde-swift`. Must be a PAT (not `GITHUB_TOKEN`) so it triggers workflows on `onde-swift`. Needs `contents: write` scope. |
349
-| `PUB_CREDENTIALS` | `release-sdk-dart.yml` | Authenticate with pub.dev for `flutter pub publish`. Full JSON from `~/.config/dart/pub-credentials.json`. |
350
-| `ORG_GRADLE_PROJECT_MAVENCENTRALUSERNAME` | `release-sdk-kotlin.yml` | Sonatype Central Portal token username. Generate at central.sonatype.com → Profile → Generate User Token. |
351
-| `ORG_GRADLE_PROJECT_MAVENCENTRALPASSWORD` | `release-sdk-kotlin.yml` | Sonatype Central Portal token password. Same dialog as above. |
352
-| `ORG_GRADLE_PROJECT_SIGNINGKEYID` | `release-sdk-kotlin.yml` | Last 8 chars of GPG key fingerprint used to sign Maven Central artifacts. |
353
-| `ORG_GRADLE_PROJECT_SIGNINGKEY` | `release-sdk-kotlin.yml` | ASCII-armored GPG private key. Export with `gpg --armor --export-secret-keys <KEY_ID>`. |
354
-| `ORG_GRADLE_PROJECT_SIGNINGPASSWORD` | `release-sdk-kotlin.yml` | Passphrase for the GPG signing key. |
355
-| `GRESIQ_API_KEY_DEV` | All release workflows | Build-time env for dev environment API key. |
356
-| `GRESIQ_API_SECRET_DEV` | All release workflows | Build-time env for dev environment API secret. |
357
-| `GRESIQ_API_KEY_PRODUCTION` | All release workflows | Build-time env for production API key. |
358
-| `GRESIQ_API_SECRET_PRODUCTION` | All release workflows | Build-time env for production API secret. |
359
-| `HF_TOKEN` | All release workflows | HuggingFace token baked in at build time (`option_env!`). |
360
-
361
----
362
-
363
-## Common Pitfalls
364
-
365
-| Pitfall | Symptom | Fix |
366
-|---------|---------|-----|
367
-| Forgot to bump `pubspec.yaml` | Dart CI fails: "Tag '0.1.3' does not match pubspec.yaml version '0.1.2'" | Bump `sdk/dart/pubspec.yaml`, amend commit, re-tag |
368
-| Forgot to bump `sdk/react-native/package.json` | npm CI fails: "Tag '0.1.3' does not match package.json version '0.1.2'" | Bump `sdk/react-native/package.json`, amend commit, re-tag |
369
-| Forgot to bump `sdk/react-native/rust/Cargo.toml` | npm SDK's Rust bridge crate version drifts from the main crate | Bump `sdk/react-native/rust/Cargo.toml`, run `cd sdk/react-native/rust && cargo update -p onde` |
370
-| Forgot to update `sdk/dart/rust/Cargo.lock` | Dart SDK's Rust bridge builds against stale `onde` version | Run `cd sdk/dart/rust && cargo update -p onde` |
371
-| Forgot to update `sdk/react-native/rust/Cargo.lock` | npm SDK's Rust bridge builds against stale `onde` version | Run `cd sdk/react-native/rust && cargo update -p onde` |
372
-| Forgot to update `sdk/dart/CHANGELOG.md` | pub.dev shows stale changelog | Prepend new `## 0.1.3` section before tagging |
373
-| Forgot to bump `sdk/react-native/CHANGELOG.md` | npm shows stale changelog | Prepend new `## 0.1.3` section before tagging |
374
-| Forgot to update the root `CHANGELOG.md` | Preflight fails: "CHANGELOG.md: no '## 0.1.3' section" | Prepend the new section before tagging |
375
-| crates.io publish reports success but nothing is published | `cargo publish` step logs "already on crates.io — skipping" for a version that is not there | Fixed in 1.2.5. The guard used `cargo info`, which resolves against the local workspace. If it recurs, check that `scripts/crates-io-published.py` is still what the workflow calls |
376
-| Forgot to bump `sdk/kotlin/gradle.properties` `VERSION_NAME` | Kotlin CI fails: tag vs gradle.properties mismatch | Bump `VERSION_NAME`, amend commit, re-tag |
377
-| Tag has `v` prefix (`v0.1.3`) | CI does not trigger — tag pattern requires bare semver | Delete the tag, re-tag without `v` |
378
-| `CARGO_REGISTRY_TOKEN` missing | `cargo publish` fails with auth error | Create a scoped token at crates.io/settings/tokens, add as repo secret |
379
-| `ONDE_SWIFT_PAT` expired | `onde-swift` push fails with 403 | Regenerate PAT at github.com/settings/tokens, update repo secret |
380
-| `PUB_CREDENTIALS` expired | `flutter pub publish` fails with 401 | Run `dart pub login` locally, copy new credentials JSON to repo secret |
381
-| npm trusted publisher does not match the workflow | `npm publish` fails with a 404 on PUT, not a 401 | Check repo, workflow filename, and environment on npmjs.com/package/@ondeinference/react-native/access against what the `publish` job declares |
382
-| crates.io version already published | `cargo publish` fails with "already uploaded" | crates.io is immutable — bump to next version. `cargo yank` hides but doesn't delete. |
383
-| npm version already published | npm publish step skips (idempotency guard) | Safe — the workflow exits 0. No action needed. |
384
-| pub.dev version already published | `flutter pub publish` fails | pub.dev is immutable — bump to next version |
385
-| Maven Central version already published | `publishAndReleaseToMavenCentral` fails | Maven Central is immutable — bump to next version |
386
-| Maven Central namespace not verified | Deployment fails: "Namespace 'com.ondeinference' is not allowed" | Verify namespace on central.sonatype.com (add DNS TXT record, wait for Sonatype to poll) |
387
-| Tag pushed before workflow exists on `main` | Workflow never fires for that tag | Use `workflow_dispatch` for dry-run, then publish manually. See **Manual Publish Fallback**. |
388
-| All 5 workflows fire but only 1–2 need re-publishing | Collateral re-runs of Swift/npm/Dart/Rust/Kotlin | Don't delete+re-push the tag. Use **Manual Publish Fallback** for the specific SDKs. |
389
-| `onde-swift` tag already exists | Tag push skipped (warning emitted) | Delete remote tag first: `git push origin :refs/tags/0.1.3` |
390
-| Force-pushed a tag on `onde-swift` | SPM users get stale `Package.resolved` | Never force-push. Delete + re-create instead. Advise consumers to run `swift package resolve` |
391
-| XCFramework URL 404 | `swift package resolve` fails on consumer side | Ensure the `onde` GitHub Release was created BEFORE the `onde-swift` tag was pushed (the workflow handles this order automatically) |
392
-| `Package.swift` regex didn't match | Python `RuntimeError` in CI | Check that `onde-swift/Package.swift` still has a `.binaryTarget(name: "OndeFramework", ...)` block |
393
-| Dart `flutter analyze` fails | `validate` job fails, `publish` is skipped | Fix analysis issues, re-tag |
394
-
395
----
396
-
397
-## Manual Publish Fallback
398
-
399
-When a tag was pushed before a workflow existed on `main`, or when you need to
400
-publish only a subset of SDKs without re-triggering all four workflows:
401
-
402
-### Rust (crates.io)
403
-
404
-```bash
405
-# Dry-run first
406
-cargo publish --dry-run
407
-
408
-# Publish
409
-cargo publish
410
-```
411
-
412
-Requires `~/.cargo/credentials.toml` or `CARGO_REGISTRY_TOKEN` env var.
413
-
414
-### Dart (pub.dev)
415
-
416
-```bash
417
-cd sdk/dart
418
-
419
-# Dry-run
420
-flutter pub publish --dry-run
421
-
422
-# Publish
423
-flutter pub publish --force
424
-```
425
-
426
-Requires `~/.config/dart/pub-credentials.json` from `dart pub login`.
427
-
428
-### Kotlin (Maven Central)
429
-
430
-```bash
431
-cd sdk/kotlin
432
-
433
-# Dry-run
434
-./gradlew :lib:publishToMavenCentral --dry-run --no-configuration-cache
435
-
436
-# Publish and release
437
-./gradlew :lib:publishAndReleaseToMavenCentral --no-configuration-cache
438
-```
439
-
440
-Requires `~/.gradle/gradle.properties` with `mavenCentralUsername`,
441
-`mavenCentralPassword`, `signingKeyId`, `signingKey`, `signingPassword`.
442
-Also requires the `com.ondeinference` namespace to be verified on
443
-central.sonatype.com.
444
-
445
-### npm (React Native)
446
-
447
-```bash
448
-cd sdk/react-native
449
-npm run build
450
-npm publish --access public
451
-```
452
-
453
-Requires `npm login`. Trusted publishing is CI-only, so a manual publish falls
454
-back to your own npm credentials.
455
-
456
-### When to use manual publish
457
-
458
-| Scenario | Approach |
459
-|----------|----------|
460
-| Tag exists but workflow wasn't on `main` yet | Merge workflow, then publish manually |
461
-| One SDK's CI failed, others succeeded | Fix the issue, publish that SDK manually |
462
-| Need to avoid re-triggering Swift XCFramework build | Publish Rust/Dart/npm/Kotlin manually |
463
-| Testing a new workflow before first real tag | Use `workflow_dispatch` for dry-run only |
464
-
465
----
466
-
467
-## Re-releasing a Version (Emergency Fix)
468
-
469
-If a release was botched and you need to re-publish the same version:
470
-
471
-### Swift (onde-swift)
472
-
473
-```bash
474
-# 1. Delete the remote tag on onde-swift
475
-git -C /path/to/onde-swift push origin :refs/tags/0.1.3
476
-
477
-# 2. Delete the GitHub Release on onde-swift (via GitHub UI or gh CLI)
478
-gh release delete 0.1.3 --repo ondeinference/onde-swift --yes
479
-
480
-# 3. Delete the GitHub Release on onde
481
-gh release delete 0.1.3 --repo ondeinference/onde --yes
482
-
483
-# 4. Delete the local and remote tag on onde
484
-git tag -d 0.1.3
485
-git push origin :refs/tags/0.1.3
486
-
487
-# 5. Fix the issue, commit, re-tag, push
488
-git tag 0.1.3
489
-git push origin main 0.1.3
490
-```
491
-
492
-### Dart (pub.dev) / Rust (crates.io) / Kotlin (Maven Central)
493
-
494
-**pub.dev, crates.io, and Maven Central are all immutable.** Once a version is
495
-published, it cannot be re-published. You must bump to `0.1.4` instead.
496
-
497
-- **crates.io:** `cargo yank --version 0.1.3` hides the version from dependency
498
- resolution but does not delete it.
499
-- **pub.dev:** No yank equivalent. The version remains visible and installable.
500
-
501
-If the `publish` job failed (i.e. the package was NOT published), you can
502
-safely re-tag after fixing the issue.
503
-
504
-### npm
505
-
506
-The npm workflow's idempotency guard means re-running it is safe — it skips
507
-if the version already exists. However, npm also does not allow re-publishing
508
-the same version with different content. Bump to the next version if the
509
-published content was wrong.
510
-
511
----
512
-
513
-## End-to-End Release Timeline
514
-
515
-```
516
-Developer merges feature → development → main (--no-ff) then tags
517
- │
518
- ├─── release-sdk-rust.yml fires ──────────────────────────────────────┐
519
- │ ~2 min (dry-run + publish to crates.io) │
520
- │ │
521
- ├─── release-sdk-swift.yml fires ──────────────────────────────────────┤
522
- │ ~15 min (XCFramework build) │
523
- │ Creates GitHub Release on onde │
524
- │ Pushes commit + tag to onde-swift │
525
- │ │ │
526
- │ ├─ onde-swift ci.yml fires (push to main) │
527
- │ │ ~5 min (validate + build 5 platforms) │
528
- │ │ │
529
- │ └─ onde-swift release.yml fires (tag 1.0.0) │
530
- │ ~5 min (validate + create GitHub Release) │
531
- │ │
532
- ├─── release-sdk-dart.yml fires ───────────────────────────────────────┤
533
- │ ~3 min (validate + publish to pub.dev) │
534
- │ │
535
- ├─── release-sdk-npm.yml fires ───────────────────────────────────────┤
536
- │ ~3 min (validate + publish to npm) │
537
- │ │
538
- ├─── release-sdk-kotlin.yml fires ────────────────────────────────────┤
539
- │ ~10 min (Android ABIs + JVM dylib + publish to Maven Central) │
540
- │ │
541
- └─── Swift Package Index picks up onde-swift tag ──────────────────────┘
542
- ~30 min (SPI polling interval)
543
- klibs.io picks up Maven Central artifact within ~30 days
544
-```
545
-
546
-All five CI workflows fire **in parallel** from the single tag push. The only
547
-ordering dependency is that `release-sdk-swift` must create the `onde` GitHub
548
-Release (with XCFramework assets) **before** pushing the tag to `onde-swift` —
549
-this is guaranteed by the step order within the workflow.
550
-
551
-### Merge train
552
-
553
-Always merge through the branch train before tagging. Use `--no-ff` to
554
-create explicit merge commits — they serve as bookmarks in the history
555
-showing where each feature landed.
556
-
557
-```bash
558
-git checkout development && git merge feature/my-branch --no-ff --no-edit
559
-git checkout main && git merge development --no-ff --no-edit
560
-git push origin development main
561
-git tag X.Y.Z && git push origin X.Y.Z
562
-```
563
-
564
-Never tag from a feature branch. The tag must point to a commit on `main` that
565
-contains all workflow files and fixes. If a workflow is added on a feature branch
566
-and the tag is pushed before merging, the workflow won't exist at the tagged
567
-commit and CI won't fire.
568
-
569
-**Why `--no-ff`:** Multi-commit feature branches (like the Kotlin SDK with 35
570
-files across 6 commits) look like they were committed directly to `main` with
571
-fast-forward merges. `--no-ff` preserves the branch context. It also allows
572
-reverting an entire feature with a single `git revert -m 1 <merge-commit>`.
573
-
574
----
575
-
576
-## Distribution Registry Reference
577
-
578
-| Registry | Package Name | Import | Workflow | Immutable? |
579
-|----------|-------------|--------|----------|------------|
580
-| crates.io | `onde` | `onde = "1.0"` | `release-sdk-rust.yml` | Yes (yank hides, doesn't delete) |
581
-| Swift Package Index | `onde-swift` (org: `ondeinference`) | `import Onde` | `release-sdk-swift.yml` → `onde-swift/release.yml` | Tags are immutable by convention |
582
-| pub.dev | `onde_inference` | `import 'package:onde_inference/onde_inference.dart'` | `release-sdk-dart.yml` | Yes (no yank) |
583
-| npm | `@ondeinference/react-native` | `import { OndeChatEngine } from "@ondeinference/react-native"` | `release-sdk-npm.yml` | Yes (unpublish within 72h only) |
584
-| Maven Central | `com.ondeinference:onde-inference` | `implementation("com.ondeinference:onde-inference:1.0.0")` | `release-sdk-kotlin.yml` | Yes (cannot re-publish same version) |
585
-| klibs.io | `onde-inference` | Same Maven Central coordinate | Auto-indexed from Maven Central | N/A (discovery only) |
586
-
587
----
588
-
589
----
590
-
591
-## React Native npm SDK
592
-
593
-**Package:** `@ondeinference/react-native` (scoped under `@ondeinference` org on npm)
594
-**Location:** `sdk/react-native/`
595
-**Architecture:** Expo native module wrapping Rust via C FFI
596
-
597
-### How it works
598
-
599
-```
600
-TypeScript (React Native)
601
- │ import { OndeChatEngine } from "@ondeinference/react-native"
602
- ▼
603
-Expo Module (Swift / Kotlin)
604
- │ OndeInferenceModule — calls C FFI functions via @_silgen_name (iOS) / JNI (Android)
605
- ▼
606
-Rust C FFI bridge (sdk/react-native/rust/)
607
- │ extern "C" functions with JSON serialization, global tokio::Runtime
608
- ▼
609
-onde crate (src/)
610
- │ ChatEngine — tokio::sync::Mutex, mistral.rs inference
611
- ▼
612
-mistral.rs → Metal (iOS) / CPU (Android)
613
-```
614
-
615
-### Key files
616
-
617
-| File | Purpose |
618
-|------|---------|
619
-| `sdk/react-native/package.json` | npm package — `@ondeinference/react-native` |
620
-| `sdk/react-native/expo-module.config.json` | Expo autolinking config |
621
-| `sdk/react-native/src/index.ts` | Public TypeScript API — `OndeChatEngine`, free functions, JSON ↔ camelCase conversion |
622
-| `sdk/react-native/src/types.ts` | TypeScript type definitions mirroring Rust types |
623
-| `sdk/react-native/src/OndeInferenceModule.ts` | `requireNativeModule("OndeInference")` bridge |
624
-| `sdk/react-native/rust/src/lib.rs` | C FFI exports (`extern "C"`) + Android JNI wrappers |
625
-| `sdk/react-native/ios/OndeInferenceModule.swift` | Swift Expo module — calls Rust via `@_silgen_name` |
626
-| `sdk/react-native/android/.../OndeInferenceModule.kt` | Kotlin Expo module — calls Rust via JNI `external fun` |
627
-| `sdk/react-native/scripts/build-rust.sh` | Cross-compile Rust for iOS (staticlib) + Android (cdylib) |
628
-
629
-### Building native libraries
630
-
631
-```bash
632
-# iOS (requires rustup targets: aarch64-apple-ios, aarch64-apple-ios-sim)
633
-cd sdk/react-native && ./scripts/build-rust.sh ios
634
-
635
-# Android (requires ANDROID_NDK_HOME)
636
-cd sdk/react-native && ./scripts/build-rust.sh android
637
-
638
-# Both
639
-cd sdk/react-native && ./scripts/build-rust.sh all
640
-```
641
-
642
-### Publishing to npm
643
-
644
-```bash
645
-cd sdk/react-native
646
-npm run build # TypeScript → build/
647
-npm publish --access public
648
-```
649
-
650
-The `--access public` flag is required for scoped packages on first publish.
651
-
652
----
653
-
654
-## Registry Immutability Summary
655
-
656
-All four registries are effectively immutable once a version is published:
657
-
658
-- **crates.io** — `cargo yank` removes from resolver but the tarball stays forever
659
-- **pub.dev** — No retraction mechanism at all
660
-- **npm** — `npm unpublish` works within 72 hours, but only if no dependents
661
-- **Maven Central** — No retraction mechanism. Once published, it's permanent.
662
-- **Swift (onde-swift tags)** — Convention: never force-push tags. Delete + re-create if needed.
663
-
664
-**Bottom line:** get the release right before publishing. Use `--dry-run` locally
665
-and via `workflow_dispatch` before pushing a real tag.
666
-
667
----
668
-
669
-*Update this skill when adding new SDK targets (Python, Ruby, Kotlin), changing
670
-CI runners, or modifying the tag/version validation logic.*
\ No newline at end of file