@ondeinference / ed / commits / 5344369

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