Skip to content

Commit 8d8bc78

Browse files
authored
Merge pull request #29 from Five3Apps/10-update-for-swift-concurrency-in-swift-6
Update for Swift Concurrency in Swift 6
2 parents f279449 + e40bb7d commit 8d8bc78

46 files changed

Lines changed: 3824 additions & 746 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
name: xcodebuildmcp
3+
description: Official skill for XcodeBuildMCP. Use when doing iOS/macOS/watchOS/tvOS/visionOS work (build, test, run, debug, log, UI automation).
4+
---
5+
6+
# XcodeBuildMCP
7+
8+
Use XcodeBuildMCP tools instead of raw `xcodebuild`, `xcrun`, or `simctl`.
9+
10+
Capabilities:
11+
- Session defaults: Configure project, scheme, simulator, and device defaults to avoid repetitive parameters
12+
- Project discovery: Find Xcode projects/workspaces, list schemes, inspect build settings
13+
- Simulator workflows: Build, run, test, install, and launch apps on iOS simulators; manage simulator state (boot, erase, location, appearance)
14+
- Device workflows: Build, test, install, and launch apps on physical devices with code signing
15+
- macOS workflows: Build, run, and test macOS applications
16+
- Log capture: Stream and capture logs from simulators and devices
17+
- LLDB debugging: Attach debugger, set breakpoints, inspect stack traces and variables, execute LLDB commands
18+
- UI automation: Capture screenshots, inspect view hierarchy with coordinates, perform taps/swipes/gestures, type text, press hardware buttons
19+
- SwiftPM: Build, run, test, and manage Swift Package Manager projects
20+
- Project scaffolding: Generate new iOS/macOS project templates
21+
22+
Only simulator workflow tools are enabled by default. If capabilities like device, macOS, debugging, or UI automation are not available, the user must configure XcodeBuildMCP to enable them. See https://xcodebuildmcp.com/docs/configuration for workflow configuration.
23+
24+
## Step 1: Establish Session Context
25+
26+
- Call `session_show_defaults` before the first build/run/test action in a session.
27+
- Use `discover_projs` only when defaults show missing or incorrect project/workspace context.
28+
- Do not run discovery speculatively or in parallel with `session_show_defaults`.
29+
- For simulator run intent, prefer the combined build-and-run tool instead of separate build then run calls.
30+
- Do not chain build-only then build-and-run unless the user explicitly requests both.
31+
32+
## Step 2: Understand Workflow-Scoped Tool Availability
33+
34+
- Not all tools are enabled by default; tool availability depends on enabled workflows.
35+
- If a tool is expected but missing, check enabled workflows first.
36+
- Update enabled workflows in `.xcodebuildmcp/config.yaml`, then ask user to reload/restart the session to surface refreshes.
37+
38+
## Step 3: Report Context Clearly
39+
40+
- Return the active defaults context used for execution (project/workspace, scheme, simulator/device).
41+
- For failures, include the exact failing step and the next actionable command/tool call.

.xcodebuildmcp/config.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
schemaVersion: 1
2+
enabledWorkflows:
3+
- macos
4+
- simulator
5+
- coverage
6+
- debugging
7+
- device
8+
- project-discovery
9+
- swift-package
10+
- xcode-ide
11+
debug: false
12+
sentryDisabled: false
13+
sessionDefaults:
14+
scheme: ReliaBLE Demo
15+
simulatorName: iPhone 17 Pro Max
16+
simulatorId: C0B74568-2B47-4981-ACA8-389277E1BB02
17+
deviceId: 3D0082F1-03D3-5FD1-8F46-55595E121B15
18+
workspacePath: /Users/jus10sb/Documents/Five3Apps/Projects/OpenSource/ReliaBLE/ReliaBLE.xcworkspace
19+
setupPreferences:
20+
platforms:
21+
- iOS
22+
- macOS

AGENTS.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to AI Agents (Claude Code, Codex, Grok Build, OpenCode, etc.) when working with code in this repository.
4+
5+
## Repository layout
6+
7+
Two related projects share this repo:
8+
9+
- **ReliaBLE library** (this directory) — Swift Package at `Package.swift` / `Sources/` / `Tests/`. swift-tools-version 6.1, iOS 18+ / macOS 10.15+, builds under **Swift 6 with complete concurrency checking**. This is the supported, shipped product.
10+
- **Demo app**`Demo/ReliaBLE Demo/`, consumes the library locally. Has its own `Demo/CLAUDE.md` with different conventions (looser concurrency, exploratory code). Treat it as a separate project — don't carry its patterns back into the library.
11+
12+
Open `ReliaBLE.xcworkspace` at the root to work on both together.
13+
14+
### Working on the Demo app
15+
16+
The Demo is a **separate project** with its own conventions and build tooling, kept out of the library's context on purpose:
17+
18+
- Before performing ANY Demo build/run/test task, **read `Demo/CLAUDE.md` first** — it documents the Demo's conventions and its required build tooling (XcodeBuildMCP, not raw `xcodebuild`).
19+
- For substantial Demo work, **delegate to a sub-agent** and instruct it to read `Demo/CLAUDE.md` first. This keeps Demo conventions in an isolated context so they don't pollute the main library session. (A sub-agent does not auto-load `Demo/CLAUDE.md` — tell it to read that file explicitly.)
20+
- Do not carry Demo patterns back into the library.
21+
22+
## Build, test
23+
24+
```sh
25+
swift build # build all targets
26+
swift test # run ReliaBLETests
27+
swift test --filter ReliaBLETests.correctFunction # single test
28+
```
29+
30+
## Architecture
31+
32+
### Three-target SPM trick for CoreBluetooth mocking
33+
34+
The package declares **three targets** that share a single source tree to make `CoreBluetooth` mockable in tests without polluting the production binary:
35+
36+
- `ReliaBLE` — production target. Uses real `CoreBluetooth`. Includes `Sources/ReliaBLE/CBCentralManagerFactory.swift`, a thin enum that returns a real `CBCentralManager`.
37+
- `ReliaBLEMock` — same sources as `ReliaBLE`, but **excludes** `CBCentralManagerFactory.swift` and the DocC catalog, and links `CoreBluetoothMock` (Nordic Semi). In `Sources/ReliaBLEMock/CoreBluetoothMockAliases.swift`, public typealiases rebind `CBCentralManager`, `CBPeripheral`, `CBCentralManagerFactory`, etc. to their `CBM*` mock counterparts.
38+
- `ReliaBLETests` — depends on `ReliaBLEMock` (not `ReliaBLE`).
39+
40+
**Consequence:** the library code only ever calls `CBCentralManagerFactory.instance(...)`, never `CBCentralManager(...)` directly. The factory's identity is swapped at compile time per target. When editing core BLE code, keep this constraint — `import CoreBluetooth` is fine, but instantiate the central via the factory.
41+
42+
### Swift Concurrency
43+
44+
The library is built with Swift 6 and **complete concurrency checking**. The `ReliaBLEManager` public API should be callable from `@MainActor`, but the library itself should avoid `@MainActor` and instead use `@BluetoothActor` (a custom actor defined in `BluetoothManager.swift`) to serialize all Bluetooth interactions. This keeps the library thread-safe and allows the integrating app to decide how to bridge to the main thread for UI updates.
45+
46+
### Logging
47+
48+
`LoggingService` wraps Willow's `Logger` with an async execution queue. The service is `Sendable` and passed by reference into both managers. Default writer is an `OSLogWriter` (`subsystem: com.five3apps.relia-ble`, `category: BLE`), configurable via `ReliaBLEConfig`. Logging is **disabled by default**`config.loggingEnabled` must be set to true. Log calls take a `tags: [LogTag]` array; use `.category(.scanning)`, `.peripheral(id)`, etc. rather than embedding the category in the message.
49+
50+
### Authorization flow
51+
52+
`ReliaBLEManager.init` does **not** instantiate `CBCentralManager` unless the user has already granted `.allowedAlways`. This is deliberate so the integrating app controls when the iOS permission prompt appears — callers invoke `authorizeBluetooth()` when they want the prompt. Preserve this lazy-init behavior when touching `BluetoothManager.setupCentralManager()`.
53+
54+
## Notes for editing
55+
56+
- Public API on `ReliaBLEManager` is the supported surface for external consumers. Adding/removing methods there is a breaking change.
57+
- `forceMock: true` is currently passed to `CBCentralManagerFactory.instance(...)` in `BluetoothManager`. The production factory ignores this parameter; the mock factory honors it. Don't "clean it up" — it's load-bearing for the test target.
58+
- DocC catalog lives at `Sources/ReliaBLE/Documentation.docc/`. The `swift-docc-plugin` is a package dep so `swift package generate-documentation` works. This documentation **must** be kept up to date with the public API on `ReliaBLEManager` and the overall architecture and usage patterns.

CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

Demo/AGENTS.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
# AGENTS.md
2+
3+
This file provides guidance to AI Agents (Claude Code, Codex, Grok Build, OpenCode, etc.) when working with code in this repository.
4+
5+
## What this project is
6+
7+
The **ReliaBLE Demo** is a sample iOS app whose only purpose is to exercise and show off the ReliaBLE library that lives one directory up. It is not shipped, not a product, and not held to the same bar as the library:
8+
9+
- **Looser style bar.** The target does build under Swift 6 complete concurrency checking, but `@Observable` view models, `DispatchQueue.main`-based Combine sinks, `try?` discards, and `print(...)` for error paths are all fine here. Don't port library patterns (actors, custom `LoggingService`, three-target mocking, etc.) into the demo unless they're needed to talk to the library's public API.
10+
- **Exploratory code is welcome.** Quick wiring, hardcoded values, `TODO` stubs, and view-model shortcuts are acceptable as long as they demonstrate a library feature clearly.
11+
- **Don't refactor demo code "for quality"** unless asked. If something looks wrong in the demo, it's often deliberately minimal.
12+
13+
If you're working on a feature here and it forces you to also change the library, surface that — it usually means the library's public API needs work, which is a separate conversation.
14+
15+
## Build / run
16+
17+
Uses `CoreBluetooth` (central) and `CBPeripheralManager` (peripheral), plus SwiftData. The root workspace must be used — not the project file — so the local `ReliaBLE` package resolves correctly.
18+
19+
**Preferred: XcodeBuildMCP**
20+
21+
When using XcodeBuildMCP, use the installed XcodeBuildMCP skill before calling XcodeBuildMCP tools. Always verify defaults before the first build.
22+
23+
```
24+
workspace: /path/to/ReliaBLE/ReliaBLE.xcworkspace
25+
scheme: ReliaBLE Demo
26+
```
27+
28+
- Simulator: `build_run_sim` (BLE radio unavailable — UI only)
29+
- Device: `build_run_device` with `-allowProvisioningUpdates`; on first run the user must trust the developer certificate at **Settings → General → VPN & Device Management**
30+
31+
Real device recommended for full BLE. The peripheral tab requires a device with a BLE radio to actually advertise.
32+
33+
To open in Xcode manually:
34+
35+
```sh
36+
open ../ReliaBLE.xcworkspace # from the Demo/ dir
37+
```
38+
39+
Scheme: **"ReliaBLE Demo"**.
40+
41+
## App structure
42+
43+
`ReliaBLE Demo.xcodeproj` lives at `Demo/ReliaBLE Demo/ReliaBLE Demo.xcodeproj`. The app is a 3-tab SwiftUI app:
44+
45+
- **Central** (`Central/`) — uses `ReliaBLEManager` (the library) to scan, surfaces discoveries into a SwiftData store. `CentralView` subscribes to the library's `AsyncStream` surfaces in `.task { for await … }` loops; `CentralViewModel` holds UI state and BLE actions only. All SwiftData writes go through `DeviceStoreActor` (manual `ModelActor` conformance). State observed via `@Observable`.
46+
- **Peripheral** (`Peripheral/`) — uses raw `CBPeripheralManager` directly (not the library — the library doesn't yet expose peripheral mode). This is intentional; the demo shows both sides of BLE even though only the central side flows through ReliaBLE.
47+
- **Settings** (`Settings/`) — app-level toggles.
48+
49+
### How the library is wired in
50+
51+
- `ReliaBLE_DemoApp.swift` constructs a single `ReliaBLEManager` with `loggingEnabled = true` and injects it via a custom `EnvironmentKey` (`@Environment(\.bleManager)`).
52+
- There's also a `BLEManagerKey.defaultValue` for previews — keep that working when changing the env injection.
53+
- A `ModelContainer` for `Device` and `DiscoveryEvent` is set on the root scene. `CentralView` creates a `DeviceStoreActor` off the main thread via `DeviceStoreActor.create(container:)` and routes all SwiftData writes through it as the library emits events. Reads stay on `@MainActor` via `@Query`.
54+
55+
## Swift Concurrency
56+
57+
The library is built with Swift 6 and **complete concurrency checking**. Therefore, `ReliaBLE Demo` is also built with Swift 6 and complete concurrency checking.
58+
59+
### SwiftData models
60+
61+
- `Device` — one row per unique discovered peripheral (by `ReliaBLE.Peripheral.id`), updated on each `discoveredPeripherals` stream tick via `DeviceStoreActor.syncDevices`.
62+
- `DiscoveryEvent` — one row per raw advertisement (every `peripheralDiscoveries` event), inserted via `DeviceStoreActor.insertDiscovery`. Grows quickly; "Clear All Data" in the Central view nukes both tables.
63+
64+
### Off-main persistence pattern
65+
66+
`DeviceStoreActor` is the canonical pattern for library consumers: hold `ReliaBLEManager` on the main actor, consume `AsyncStream`s in `.task`, and `await` the store for every SwiftData write. See the file-level comment in `Central/DeviceStoreActor.swift`.
67+
68+
## Don't
69+
70+
- Don't add the library's `CBCentralManagerFactory` indirection here — the demo imports `CoreBluetooth` directly for the peripheral side and that's fine.
71+
- Don't replace `print(...)` debug calls in `CentralViewModel` with a logging service. The library has logging; the demo doesn't need one.

Demo/CLAUDE.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
AGENTS.md

Demo/ReliaBLE Demo/ReliaBLE Demo.xcodeproj/project.pbxproj

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -180,7 +180,7 @@
180180
attributes = {
181181
BuildIndependentTargetsInParallel = 1;
182182
LastSwiftUpdateCheck = 1610;
183-
LastUpgradeCheck = 1620;
183+
LastUpgradeCheck = 1640;
184184
TargetAttributes = {
185185
8B771A932CEC3B9A002F0E31 = {
186186
CreatedOnToolsVersion = 16.1;
@@ -316,6 +316,7 @@
316316
COPY_PHASE_STRIP = NO;
317317
DEAD_CODE_STRIPPING = YES;
318318
DEBUG_INFORMATION_FORMAT = dwarf;
319+
DEVELOPMENT_TEAM = AU92CYH9BP;
319320
ENABLE_STRICT_OBJC_MSGSEND = YES;
320321
ENABLE_TESTABILITY = YES;
321322
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -338,7 +339,10 @@
338339
MTL_FAST_MATH = YES;
339340
ONLY_ACTIVE_ARCH = YES;
340341
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)";
342+
SWIFT_APPROACHABLE_CONCURRENCY = YES;
343+
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
341344
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
345+
SWIFT_STRICT_CONCURRENCY = complete;
342346
};
343347
name = Debug;
344348
};
@@ -378,6 +382,7 @@
378382
COPY_PHASE_STRIP = NO;
379383
DEAD_CODE_STRIPPING = YES;
380384
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
385+
DEVELOPMENT_TEAM = AU92CYH9BP;
381386
ENABLE_NS_ASSERTIONS = NO;
382387
ENABLE_STRICT_OBJC_MSGSEND = YES;
383388
ENABLE_USER_SCRIPT_SANDBOXING = YES;
@@ -392,7 +397,10 @@
392397
LOCALIZATION_PREFERS_STRING_CATALOGS = YES;
393398
MTL_ENABLE_DEBUG_INFO = NO;
394399
MTL_FAST_MATH = YES;
400+
SWIFT_APPROACHABLE_CONCURRENCY = YES;
395401
SWIFT_COMPILATION_MODE = wholemodule;
402+
SWIFT_DEFAULT_ACTOR_ISOLATION = MainActor;
403+
SWIFT_STRICT_CONCURRENCY = complete;
396404
};
397405
name = Release;
398406
};
@@ -406,7 +414,6 @@
406414
CURRENT_PROJECT_VERSION = 1;
407415
DEAD_CODE_STRIPPING = YES;
408416
DEVELOPMENT_ASSET_PATHS = "\"ReliaBLE Demo/Preview Content\"";
409-
DEVELOPMENT_TEAM = AU92CYH9BP;
410417
ENABLE_HARDENED_RUNTIME = YES;
411418
ENABLE_PREVIEWS = YES;
412419
GENERATE_INFOPLIST_FILE = YES;
@@ -424,10 +431,10 @@
424431
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
425432
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
426433
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
427-
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
434+
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
428435
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
429436
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
430-
MACOSX_DEPLOYMENT_TARGET = 14.0;
437+
MACOSX_DEPLOYMENT_TARGET = 15.0;
431438
MARKETING_VERSION = 1.0;
432439
PRODUCT_BUNDLE_IDENTIFIER = "com.five3apps.ReliaBLE-Demo";
433440
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -451,7 +458,6 @@
451458
CURRENT_PROJECT_VERSION = 1;
452459
DEAD_CODE_STRIPPING = YES;
453460
DEVELOPMENT_ASSET_PATHS = "\"ReliaBLE Demo/Preview Content\"";
454-
DEVELOPMENT_TEAM = AU92CYH9BP;
455461
ENABLE_HARDENED_RUNTIME = YES;
456462
ENABLE_PREVIEWS = YES;
457463
GENERATE_INFOPLIST_FILE = YES;
@@ -469,10 +475,10 @@
469475
"INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault;
470476
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
471477
INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight";
472-
IPHONEOS_DEPLOYMENT_TARGET = 17.6;
478+
IPHONEOS_DEPLOYMENT_TARGET = 18.0;
473479
LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks";
474480
"LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks";
475-
MACOSX_DEPLOYMENT_TARGET = 14.0;
481+
MACOSX_DEPLOYMENT_TARGET = 15.0;
476482
MARKETING_VERSION = 1.0;
477483
PRODUCT_BUNDLE_IDENTIFIER = "com.five3apps.ReliaBLE-Demo";
478484
PRODUCT_NAME = "$(TARGET_NAME)";
@@ -493,7 +499,6 @@
493499
CODE_SIGN_STYLE = Automatic;
494500
CURRENT_PROJECT_VERSION = 1;
495501
DEAD_CODE_STRIPPING = YES;
496-
DEVELOPMENT_TEAM = AU92CYH9BP;
497502
GENERATE_INFOPLIST_FILE = YES;
498503
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
499504
MACOSX_DEPLOYMENT_TARGET = 15.1;
@@ -517,7 +522,6 @@
517522
CODE_SIGN_STYLE = Automatic;
518523
CURRENT_PROJECT_VERSION = 1;
519524
DEAD_CODE_STRIPPING = YES;
520-
DEVELOPMENT_TEAM = AU92CYH9BP;
521525
GENERATE_INFOPLIST_FILE = YES;
522526
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
523527
MACOSX_DEPLOYMENT_TARGET = 15.1;
@@ -540,7 +544,6 @@
540544
CODE_SIGN_STYLE = Automatic;
541545
CURRENT_PROJECT_VERSION = 1;
542546
DEAD_CODE_STRIPPING = YES;
543-
DEVELOPMENT_TEAM = AU92CYH9BP;
544547
GENERATE_INFOPLIST_FILE = YES;
545548
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
546549
MACOSX_DEPLOYMENT_TARGET = 15.1;
@@ -563,7 +566,6 @@
563566
CODE_SIGN_STYLE = Automatic;
564567
CURRENT_PROJECT_VERSION = 1;
565568
DEAD_CODE_STRIPPING = YES;
566-
DEVELOPMENT_TEAM = AU92CYH9BP;
567569
GENERATE_INFOPLIST_FILE = YES;
568570
IPHONEOS_DEPLOYMENT_TARGET = 18.1;
569571
MACOSX_DEPLOYMENT_TARGET = 15.1;

Demo/ReliaBLE Demo/ReliaBLE Demo.xcodeproj/xcshareddata/xcschemes/ReliaBLE Demo.xcscheme

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="UTF-8"?>
22
<Scheme
3-
LastUpgradeVersion = "1620"
3+
LastUpgradeVersion = "1640"
44
version = "1.7">
55
<BuildAction
66
parallelizeBuildables = "YES"

0 commit comments

Comments
 (0)