Production ready toolkit to run AI locally
# AGENTS.md
Guidance for AI coding assistants working in this repository.
> **`AGENTS.md` is the real file; each `CLAUDE.md` is a committed symlink to the
> `AGENTS.md` beside it.** Editing either name edits the same bytes. If you create a new
> `AGENTS.md` in a directory that doesn't have one, run
> `bash scripts/validation/gates/check_agents_claude_sync.sh --fix` from the repo root to
> create its `CLAUDE.md` symlink β `pr-build.yml` fails any tracked `AGENTS.md` missing one.
> Never hand-create a `CLAUDE.md` as a real file.
## Working conventions
- **Focus on simplicity.** Clean, SOLID-ish separation of concerns; reuse over
reinvention. No mock implementations and no new unit tests unless the task explicitly
asks for them.
- **Plan first for non-trivial work.** Write the plan to
`thoughts/shared/plans/{descriptive_name}.md`, get it approved before implementing, and
keep it updated (append what changed) as you go so the next engineer can pick it up.
- **Structured types over raw strings.** Enums/sealed classes/data classes for
configuration and results β this is enforced per-SDK by lint (ktlint/detekt, SwiftLint,
ESLint) so ad hoc strings tend to get caught anyway.
- **Read files fully** before editing them; only use offset/limit on files too large to
read at once.
- **Use full local build parallelism** (`-j "$(sysctl -n hw.logicalcpu)"` / equivalent),
scaling down only under real memory/thermal pressure β not because load average looks
high.
- **Multi-agent workflows run on Sonnet.** When fanning work out to subagents (the
`Workflow` tool, or several parallel `Agent` calls), pass `model: 'sonnet'` on every
`agent(...)` call rather than letting them inherit the session model. A fan-out of
8-10 probes reading large files is exactly the shape where Sonnet is the right
cost/latency trade, and these agents are doing bounded, well-specified retrieval and
analysis rather than open-ended reasoning. Reserve the session's stronger model for
the synthesis step that consumes their reports, and for anything where a wrong answer
is expensive and hard to detect β a final design decision, an adversarial verification
pass, or a security-relevant judgement.
```js
// in a Workflow script
const findings = await parallel(LENSES.map((l) => () =>
agent(l.prompt, { label: `probe:${l.key}`, schema: FINDINGS, model: 'sonnet' })))
// the synthesis that reads them can stay on the session model
const plan = await agent(synthesisPrompt, { schema: PLAN, effort: 'high' })
```
## The most important architectural rule: logic lives at the lowest layer that serves everyone
Each feature/modality (LLM, STT, TTS, VAD, VLM, RAG, LoRA, Voice) is invoked through
**one** SDK entry point; the SDK, and below it C++ commons, does all the heavy lifting. If
an example app builds a multi-step bootstrap sequence, hardcodes a model/engine constant,
or post-processes model output, that is a bug in the SDK, not the app β fix it down a
layer.
1. **C++ commons (`core/`)** β cross-platform, non-I/O logic: model lifecycle, registry,
download orchestration, RAG session management, inference routing. All 5 SDKs get the
fix for free.
2. **Platform SDK layer** β platform-specific I/O or runtime bridging only (Web OPFS,
iOS Keychain, Android Keystore, WASM MEMFS mirroring).
3. **Example apps** β UI rendering, navigation, thin SDK calls. No business logic, no
internal-SDK knowledge (path patterns, frameworkβdirectory mappings), no workarounds.
**iOS is the source of truth.** When behavior is ambiguous in any other SDK, check the
iOS Swift implementation first and copy the logic exactly, adapting only syntax.
---
## Repository overview
Cross-platform on-device AI SDK monorepo. A single C/C++ core (`runanywhere-commons`,
~118K first-party LOC plus ~420K generated proto bindings) implements all AI business
logic behind a pure C ABI (`rac_*` prefix). Platform SDKs are thin bridges that supply
platform services (file I/O, HTTP, Keychain, audio) via an inversion-of-control struct and
call into the C core for all inference. Protobuf IDL schemas generate type-safe bindings
for every language.
**Current version**: `0.20.37` (canonical source: `core/VERSION`)
| SDK | Path | Bridge mechanism | Platforms |
|-----|------|-------------------|-----------|
| Swift | `bindings/swift/` | XCFramework + CRACommons module map | iOS 17.5+, macOS 14.5+ |
| Kotlin | `bindings/kotlin/` | JNI (`librunanywhere_jni.so`) | Android (min API 24) |
| Flutter | `bindings/flutter/` | Dart FFI | iOS, Android |
| React Native | `bindings/react-native/` | NitroModules (JSI HybridObject) | iOS 17.5+, Android arm64 |
| Web | `bindings/web/` | Emscripten WASM + TypeScript | Chrome, Safari, Firefox |
| Electron | `bindings/electron/` | Node-API addon | Windows x64 (preview) |
| Python | `bindings/python/` | pybind11 extension (`runanywhere._core`) | Windows, macOS, Linux (alpha) |
Each has its own `AGENTS.md` with full architecture, build commands, and conventions β
read it before working in that SDK.
| Native dir | Contents |
|-----------|----------|
| `core/` | C/C++ core library: all AI logic, plugin registry, event system |
| `engines/` | 7 backend plugins: llamacpp, sherpa, onnx, cloud, mlx, qhexrt, neurt |
| `runtimes/` | 3 runtime adapters: cpu (always), onnxrt, coreml |
| `idl/` | 39 Protobuf schemas + per-language codegen scripts |
### Consumer apps live in separate repos
The four full consumer apps were extracted into standalone repos (history preserved); PR
against them there, not here: [runanywhere-ios](https://github.com/RunanywhereAI/runanywhere-ios),
[runanywhere-android](https://github.com/RunanywhereAI/runanywhere-android),
[runanywhere-web](https://github.com/RunanywhereAI/runanywhere-web),
[runanywhere-electron](https://github.com/RunanywhereAI/runanywhere-electron).
Two example apps remain in-tree (`bindings/flutter/example/`, `bindings/react-native/example/`).
All example apps share brand orange `#FF6900`, documented in `docs/DESIGN_GUIDELINE.md`.
### Minimal examples are how you verify an SDK change locally
Each in-repo example consumes its SDK from local source (no staging/publishing needed to
see an edit): Swift via `RUNANYWHERE_USE_LOCAL_NATIVES=1`, Kotlin via a Gradle composite
build (`includeBuild` + `dependencySubstitution`), Web via Vite aliases into
`packages/*/src`. They're deliberately minimal (one prompt in, one streamed completion
out) β feature-complete UI belongs in the consumer repos above.
---
## Cross-platform architecture
`idl/*.proto` is the schema root; `idl/codegen/generate_all.sh` emits per-language
bindings (Swift, Kotlin/Wire, TS/ts-proto, Dart), all generated and none committed β see
[`docs/reference/generated-code-contract.md`](docs/reference/generated-code-contract.md)
for the full "what generates what, when" contract before touching `idl/`.
All SDKs reach `runanywhere-commons` through the `rac_*` C API. Commons holds the
component layer (lifecycle), the service layer (dispatch), and the plugin registry, and
reaches engines through `rac_engine_vtable_t`.
| Engine | Primitives |
|---|---|
| llamacpp | LLM, VLM |
| sherpa-onnx | STT, TTS, VAD |
| onnx | Embed, Segment |
| qhexrt | Hexagon NPU |
| neurt, cloud | Apple Neural Engine, HTTP |
Backend base priorities (highest wins per primitive via `rac_plugin_find()`, no
runtime/format scoring): qhexrt=150 (QNN-context models only), mlx=110 (Apple),
llamacpp=100, sherpa=90, onnx/cloud=50. An explicit engine name is honored through
`rac_plugin_find_for_engine()` regardless of priority.
### Non-obvious invariants that span every SDK
- **Platform adapter IoC**: `rac_platform_adapter_t` is a flat C struct of function
pointers populated by each SDK before `rac_init()`. C++ never calls platform APIs
directly β file I/O, HTTP, Keychain, logging, memory queries all pass through it.
- **Two-phase init**: every SDK does Phase 1 (sync: register adapter, load native libs,
configure logging) then Phase 2 (async: authenticate, register device, fetch model
assignments, discover downloaded models).
- **Plugin ABI**: `RAC_PLUGIN_API_VERSION = 9u`; a version mismatch is an immediate
rejection. Each vtable has 10 active primitive slots and 7 reserved. NULL slot = not
supported. `rerank_ops` (wire value 11) was promoted from `reserved_slot_2` in ABI v8;
wire value 6, retired in ABI v4, stays permanently retired β never reuse it. See
`core/AGENTS.md` for the full ABI history and how to add a primitive.
- **Static vs. dynamic plugins**: iOS and WASM force `RAC_STATIC_PLUGINS=ON` (no
`dlopen`), registered via `RAC_STATIC_PLUGIN_REGISTER(name)` + `-force_load` /
`--whole-archive`. Android/Linux/macOS default to dynamic loading via
`rac_registry_load_plugin()`.
- **Streaming fan-out**: C++ allows only one proto-byte callback per component handle.
Each SDK implements its own fan-out to multiplex that into multiple subscribers (Swift
`AsyncStream`, Kotlin `Flow`, Dart `StreamController`, TS `AsyncIterable`).
- **Proto types are canonical**: never hand-write enum values or structured types that
exist in `idl/*.proto` β use the generated types/typealiases and regenerate instead.
- **HTTP is platform-provided**: no libcurl. Each SDK registers a
`rac_http_transport_ops_t` (URLSession on Apple platforms, OkHttp on Android,
`emscripten_fetch` on Web).
- **NeuRT crosses a repo boundary as BYTES, not source**: the Apple engine is built and
published by the private `neurun` repo and consumed here as prebuilt archives, pinned by
tag + SHA-256 in `core/VERSIONS` (`NEURT_*`). Fetch with `scripts/build/download-neurt.sh`.
Each archive carries a receipt naming the `RAC_PLUGIN_API_VERSION` it was built against,
and both the downloader and CMake refuse a mismatch β a vtable-layout change relinks
cleanly and corrupts dispatch at runtime, so nothing else would catch it.
A side-by-side comparison of entry point / bridge / streaming / events / storage / HTTP
per SDK β useful when porting a fix across SDKs β lives in
[`docs/reference/cross-sdk-parity.md`](docs/reference/cross-sdk-parity.md).
---
## Building and running
The root `CMakeLists.txt` (version from `core/VERSION`) is the single entry point for
native builds; `CMakePresets.json` defines `macos-{debug,release}`, `linux-{debug,release,asan}`,
`ios-{device,simulator}`, `android-arm64`, `wasm`, `cpp-desktop-*`, and the `windows-*` presets.
```bash
cmake --preset macos-debug && cmake --build build/macos-debug && ctest --preset macos-debug
```
**`./run <group> [subcommand]`** is the unified wrapper for everything else β build,
lint, and run any SDK or example app from one CLI (`./run --help` for the full menu):
```bash
./run doctor # scan host toolchains, show what's buildable here
./run setup # provision env for every host-buildable target
./run sdk commons build-android # build C++ commons for all Android ABIs, stage .so files
./run sdk kotlin build # (etc. β see per-SDK AGENTS.md for direct tool commands)
./run example android install # build + install + launch the Kotlin minimal example
./run codegen # regenerate every language binding from idl/
```
For the direct `swift build` / `./gradlew` / `melos` / `yarn` / `npm` commands each SDK
uses under the hood, and its test/lint/publish targets, see that SDK's own `AGENTS.md`
(`bindings/<name>/AGENTS.md`) β the commands are kept there, not duplicated here, so they
can't drift.
Cross-platform build scripts worth knowing about at the root level:
```bash
./bindings/swift/scripts/build-core-xcframework.sh # also syncs into RN/Flutter plugin dirs
./scripts/build/build-core-android.sh # .so for all ABIs β every SDK's jniLibs/
./bindings/web/scripts/build-core-wasm.sh
./scripts/setup/setup-toolchain.sh # protoc/wire/ts-proto toolchain
./idl/codegen/generate_all.sh [--only <lang>] # swift|kotlin|dart|ts|cpp|python
```
---
## Version management and releases
Canonical version: `core/VERSION` (single-line semver). Bump everywhere with:
```bash
./scripts/release/sync-versions.sh <version>
```
The full release runbook (version bump β PR β tag β GitHub Release β npm/Maven/pub.dev/
SwiftPM publishing β cutting the `runanywhere-swift` SPM distribution repo β starter-app
smoke tests) is a multi-step process captured in three skills β use them rather than
improvising the steps: **sdk-release** (version bump through published GitHub Release),
**sdk-publish** (registry publishing + the `runanywhere-swift` dist repo cut), and
**sdk-test-starters** (post-release device/app smoke tests).
### Release pipeline lessons (v0.20.29 through v0.20.33 β read before cutting a release by hand)
- **`native_ios` and `native_android` always rebuild fresh archives on every `release.yml`
run β there is no reuse/skip mechanism for either job** (unlike `native_web`'s
`reuse_native_web_run_id` or the cpp-desktop kits' `reuse_cpp_desktop_run_id`). This makes
the Apple/Android TEMP-pin convention (Package.swift's `sdkVersion`, the Flutter podspecs'
`asset_version`, Kotlin's `nativeLibVersion` pinned to an old version "because this release
ships no new iOS/Android archives") **structurally unsound for any release where those two
jobs actually run β which is every release.** It surfaced as three separate bugs
(`auto-tag.yml`'s coherence check, `package-sdk.sh`'s podspec check, and `sync-checksums.sh`'s
publish-time verification) before the pattern was recognized. If a release genuinely ships
no new Apple/Android bytes, that has to be proven by diffing the actual built artifacts
against the previous release, not assumed from what changed in the diff.
- **`RunAnywhereMLXRuntime`'s stripped Mach-O is not byte-reproducible across builds**
(`RAC_CHECKSUM_SKIP=RunAnywhereMLXRuntime` in `release.yml`'s publish job exists because of
this, and is the ONLY archive exempted from the publish-time checksum gate). The corollary
that bit v0.20.29: **the checksum committed to `Package.swift` / the Flutter MLX podspec
can only be computed from a build that already happened, but the tag that freezes those
files has to be pushed before `native_ios` (which produces that exact artifact) even runs**
β so re-syncing checksums against an EARLIER, superseded build (e.g. after a tag had to be
moved and re-dispatched to fix an unrelated bug) commits a value that does not match what
the FINAL successful run actually publishes, and the skip means nothing catches it. v0.20.29
shipped this way (committed `d942788f...`, actually published `fc4b23a0...`) β caught only
because the Flutter `runanywhere_mlx` package hadn't been published to pub.dev yet for that
version. Before publishing any package that ships `RunAnywhereMLXRuntime`, re-run
`bindings/swift/scripts/sync-checksums.sh` against the artifacts from the SPECIFIC run whose
tag is being published β never reuse a checksum computed against an earlier attempt.
- **The Electron QHexRT backend never set `ADSP_LIBRARY_PATH`, and one comment in the
codebase confidently asserted Windows doesn't need it.** It does, for exactly one decode
path: `QHexRT/src/bonsai/fastrpc_win.cpp`'s custom FastRPC skel (the ternary/Bonsai
decoder, e.g. `qwen3.8-27b-1bit-npu`) resolves through `ADSP_LIBRARY_PATH βͺ cwd`, separately
from the standard QNN HTP graph path's `LoadLibraryW` + PATH search that `bridge.ts`'s
`addSidecarDirToDllSearch` already handled. Every OTHER QHexRT model worked fine in the
packaged app, which is exactly what let this ship undetected β validate the Bonsai/ternary
path specifically, not just "some NPU model works." Fixed in `bridge.ts`'s
`addSidecarDirToDspSearchPath` (#803).
- **`package-private-engine-overlay.sh`'s QAIRT-runtime copy step filtered on `{".dll",
".lib"}` only**, silently dropping every `.so`/`.cat` skel file (both the standard HTP skel
and, since it never read `engines/qhexrt/prebuilt/current/dsp/win-arm64/` at all, the
Bonsai skel too). This meant **RCLI could not have run any QHexRT model** β standard or
ternary β from a fresh private overlay, a gap invisible until someone actually tried to run
inference rather than just building. `bindings/electron/scripts/bundle-native.ts` already
had the correct pattern (copy `dsp/win-arm64/` flat, no extension filter); the overlay
script just never matched it. Fixed in #804.
- **A new NeuRT archive must be added in FOUR places, and three of them only fail in a build
configuration your Mac never produces.** v0.20.33 added `libneurt_rac_tts_ops.a` and took two
wasted release candidates to land, because each site fails somewhere different and late:
`engines/neurt/CMakeLists.txt` (probe list + imported target + `_neurt_extra_links`),
`build-core-xcframework.sh` (pre-flight guard AND `_NEURT_ROUTABLE_ARCHIVES` β two lists in one
file), `package-private-engine-overlay.sh`, and the `#if RAC_NEURT_ROUTABLE` guard around the
vtable entry in `rac_plugin_entry_neurt.cpp`. Miss the force-load lists and every native job goes
green while `validate_consumer_flutter` dies an hour later on `Undefined symbol:
_g_neurt_tts_ops` β the engine registers, all CMake targets compile, and only a CONSUMER's link
fails. Miss the vtable guard and the routable build is fine while every PR job WITHOUT
`NEURUN_TOKEN` fails, because a non-routable shell still references a symbol nothing defines.
A dev machine with the payload staged is routable, so it reproduces neither. `libneurt_rac_*.a`
archives now fail packaging by name if they are not in the list
(`neurt_assert_no_unlisted_archives`); the vtable guard has no such mechanism β audit all ten
slots when adding one. Reproduce a non-routable build by moving `core/third_party/neurt` aside.
- **`reuse_native_web_run_id` is SAME-VERSION-ONLY, whatever the diff looks like.** `racommons.wasm`
embeds `RAC_VERSION_STRING` and `validate_public_packages.py` asserts it against the version being
packaged, so reusing ANY prior release's run fails `sdk_web` with *"does not embed
RAC_VERSION_STRING"*. Worse than a red job: `native_web` is skipped, so that candidate never
uploads a web artifact and can never serve as `publish_from_run_id`. Only a re-dispatch at the
same version qualifies.
- **Verify checksums by NAME as well as by URL β the manifests use two different shapes.** The root
`Package.swift` interpolates `\(sdkVersion)` into each URL; the Flutter manifests bind a checksum
to a `name:` through a helper with no URL at the call site at all. A URL-only cross-check silently
validates 9 of 15 sites and reports success. Re-hash every site against the zip it names, and
remember `RAC_CHECKSUM_SKIP` hides `RunAnywhereMLXRuntime` from `publish` entirely, so the Flutter
MLX podspec is verifiable only by hand.
- **A green `rcli backends --json` proves nothing against the OSS kit.** NeuRT ships as a separate
private pack, so the public bottle lists no `neurt` at all β a routability check there passes
cleanly and means nothing. Apply the private overlay
(`cpp-desktop-macos-arm64-neurt-private` workflow artifact) before believing the result.
- **General pattern across all four of the above**: each one was invisible to the checks that
existed (a version-coherence check, a checksum-skip escape hatch, a "the model loaded"
smoke test, a "the build succeeded" CI job) because none of those checks actually ran the
specific thing that was broken. When validating a release, run the SPECIFIC flagship/hardest
model end-to-end on-device, not just confirm the pipeline completed.
- **The RACommons/RunAnywhereMLXRuntime checksum-staleness bug above recurred on v0.20.30 AND
v0.20.31** β same `mismatch: RACommonsBinary` failure in `publish`, same root cause both
times: the version-bump PR ran `sync-versions.sh` and merged/tagged directly, skipping the
**mandatory** "dispatch a release candidate off the unmerged branch, sync checksums from
its real build, THEN merge" sequence that the **`sdk-release` skill's step 2-3 already
document in detail** (including this exact failure signature). Documenting the lesson in
prose here was not enough to stop it recurring twice more β **if you are about to run
`scripts/release/sync-versions.sh` for ANY reason, including as one step of a larger
cross-repo release cascade, invoke the `sdk-release` skill and follow it from step 1**,
rather than reconstructing the sequence from memory or treating a "small" version bump as
exempt. v0.20.31 additionally caught `RunAnywhereMLXRuntime` in the Flutter podspec
independently stale in the SAME commit β `RAC_CHECKSUM_SKIP` exempts it from `publish`'s
check, so it would have shipped broken to Flutter consumers with no CI signal at all; check
every row of the checksum-location table, not only the one the CI error names.
- **QHexRT's `qhx_runtime_device()` (arch/soc lookup, needed to pick the `v75`/`v79`/`v81`
manifest directory in `session_open()`) shared its live-device cost with the standard QNN
graph path**, so a `host_only` model with ZERO QNN graphs β the Bonsai/Maple ternary decoder
(`qwen3.8-27b-1bit-npu`) β paid for a live QNN HTP device anyway, which then contended with
the decoder's own direct FastRPC session for the same Hexagon cDSP and made every generation
fail with a bare `HostOpFailed`. This shipped in v0.20.30 (the SAME release that fixed the
`ADSP_LIBRARY_PATH` bug for the same model) and was found only by running that SPECIFIC model
on real Windows-ARM64 hardware after the "fix" β reinforcing the point above: a green
`HostOpFailed`-free run of a DIFFERENT QHexRT model proves nothing about this one. Two red
herrings were chased and ruled out with live device tracing before the real cause: neither
`ADSP_LIBRARY_PATH` (confirmed correctly set) nor a QAIRT-version mismatch between the
shipped host DLLs and the on-device skel (confirmed present, but fixing it alone did not fix
the failure) was the actual cause. Fixed in `neurun` v0.20.31 (`Backend::profile()` now uses
a separate, device-handle-free `deviceGetPlatformInfo` query instead of sharing
`ensure_device()` with `device()`) + this repo's `qhexrt_session.cpp` (#810, a complementary
fix removing a second, redundant eager device query used only for a log line).
---
## CI/CD workflows (`.github/workflows/`)
| Workflow | Trigger | Purpose |
|----------|---------|---------|
| `pr-build.yml` | PR to main, push to main/feat branch | Parallel native builds (macOS/Linux/iOS/Android) + per-SDK typecheck + centralization/coherence gates |
| `release.yml` | Tag `v*.*.*` or manual | Full artifact build matrix, SDK packaging, consumer validation, draft Release |
| `auto-tag.yml` | PR merged to main with `release:*` label | Verifies the reviewed semver bump, pushes that exact git tag |
| `idl-drift-check.yml` | Changes to `idl/` or generated files | Regenerates protos, verifies schema lock + untracked generated trees |
| `legacy-files-blocklist.yml` | All PRs/pushes | Prevents specific deleted files from being re-introduced |
| `secret-scan.yml` | PRs and pushes to main | Incremental gitleaks scan on diff range |
| `check-no-pii-logging.yml` | PRs/pushes to main/master/feat-branch | Guards against logging signed URLs alongside active-download destination paths |
---
## Platform requirements
| Platform | Min version | Build tool | Key versions |
|----------|------------|------------|--------------|
| iOS / macOS | 17.5 / 14.5 | Xcode 26+ | Swift 6.2 |
| Kotlin SDK | Android API 24 | AGP 9.2.1 / Gradle 9.5.0 | Kotlin 2.4.0, NDK 27.3.13750724 |
| Flutter | 3.44.6 | Melos / AGP 9.0.1 / Gradle 9.1.0 | Dart 3.12.2+, NDK 28.2.13676358 |
| React Native | 0.85.3 (min 0.83.1) | Yarn Berry 3.6.1 | NitroModules, Hermes |
| Web | Chrome 86+ | Vite | Emscripten 6.0.2, Node 24 LTS |
| C++ core | N/A | CMake 3.24+ | C++20, Ninja |
---
## Non-obvious configuration details
- **NDK pin**: `core/VERSIONS::NDK_VERSION` (27.3.13750724) is the single source of truth,
mirrored into `bindings/kotlin/gradle.properties`. NDK 27 (r27d) gives 16 KB
page-alignment required by Android 15+ β NDK 25.x's 4 KB-aligned `libc++_shared.so` /
`libomp.so` trips Android 16's page-size enforcement. Mirror this pin whenever bumping
it; Flutter/RN Android build files carry their own fallback literals that can drift.
- **`useLocalNatives`** (Kotlin `gradle.properties`, similarly named flags elsewhere):
`true` builds native libs from source locally; CI and most non-local runs set it
`false` to download prebuilt `.so`/`.xcframework`/`.wasm` from GitHub Releases instead.
- **Web SDK** has several hard-won WASM/browser runtime workarounds (VLM worker crash
recovery, a Qwen2-VL WebGPU NaN bug, cross-origin isolation requirements) β see
[`docs/reference/web-runtime-gotchas.md`](docs/reference/web-runtime-gotchas.md).
- **Cloud/Linux dev VM** (e.g. Cursor Cloud): what's buildable there and the
clang/NDK/KVM gotchas are in
[`docs/reference/cursor-cloud-environment.md`](docs/reference/cursor-cloud-environment.md).
---
## Pre-commit hooks
```bash
pre-commit run --all-files # everything
pre-commit run ios-sdk-swiftlint --all-files # SwiftLint only
```
Configured hooks: gitleaks (secrets), trailing-whitespace, end-of-file-fixer, check-yaml,
check-added-large-files (1000 KB max), check-merge-conflict, object file detection,
SwiftLint (SDK + example app), periphery (unused Swift code detection).
---
## Where to look for current in-flight work
`thoughts/shared/plans/` holds active/completed execution plans (including any in-flight
SDK reorg or migration briefs β e.g. the Electron reorganization plan); `thoughts/shared/issues/`
holds tracked bug write-ups. Check these directories directly rather than trusting a
status summary written into this file β both change too often for a static snapshot here
to stay accurate.