@coralai/sps-cli 0.42.0 → 0.43.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/README.md +34 -3
  2. package/dist/commands/projectInit.d.ts.map +1 -1
  3. package/dist/commands/projectInit.js +40 -53
  4. package/dist/commands/projectInit.js.map +1 -1
  5. package/dist/commands/skillCommand.d.ts +2 -0
  6. package/dist/commands/skillCommand.d.ts.map +1 -0
  7. package/dist/commands/skillCommand.js +235 -0
  8. package/dist/commands/skillCommand.js.map +1 -0
  9. package/dist/core/skillStore.d.ts +46 -0
  10. package/dist/core/skillStore.d.ts.map +1 -0
  11. package/dist/core/skillStore.js +197 -0
  12. package/dist/core/skillStore.js.map +1 -0
  13. package/dist/core/skillStore.test.d.ts +2 -0
  14. package/dist/core/skillStore.test.d.ts.map +1 -0
  15. package/dist/core/skillStore.test.js +190 -0
  16. package/dist/core/skillStore.test.js.map +1 -0
  17. package/dist/main.js +19 -17
  18. package/dist/main.js.map +1 -1
  19. package/package.json +1 -1
  20. package/skills/architecture-decision-records/SKILL.md +207 -0
  21. package/skills/backend/SKILL.md +62 -0
  22. package/skills/backend/references/api-design.md +168 -0
  23. package/skills/backend/references/caching.md +181 -0
  24. package/skills/backend/references/data-access.md +173 -0
  25. package/skills/backend/references/layering.md +181 -0
  26. package/skills/backend/references/observability.md +190 -0
  27. package/skills/backend/references/resilience.md +201 -0
  28. package/skills/backend/references/security.md +186 -0
  29. package/skills/backend-architect/SKILL.md +119 -0
  30. package/skills/code-reviewer/SKILL.md +143 -0
  31. package/skills/coding-standards/SKILL.md +60 -0
  32. package/skills/coding-standards/references/clean-code.md +258 -0
  33. package/skills/coding-standards/references/code-review.md +192 -0
  34. package/skills/coding-standards/references/commits-and-prs.md +226 -0
  35. package/skills/coding-standards/references/error-strategy.md +193 -0
  36. package/skills/coding-standards/references/naming.md +185 -0
  37. package/skills/coding-standards/references/tdd.md +171 -0
  38. package/skills/database/SKILL.md +53 -0
  39. package/skills/database/references/indexing.md +190 -0
  40. package/skills/database/references/migrations.md +199 -0
  41. package/skills/database/references/nosql.md +185 -0
  42. package/skills/database/references/queries.md +295 -0
  43. package/skills/database/references/scaling.md +203 -0
  44. package/skills/database/references/schema.md +191 -0
  45. package/skills/database-optimizer/SKILL.md +168 -0
  46. package/skills/debugging-workflow/SKILL.md +244 -0
  47. package/skills/devops/SKILL.md +55 -0
  48. package/skills/devops/references/ci-cd.md +204 -0
  49. package/skills/devops/references/containers.md +272 -0
  50. package/skills/devops/references/deploy.md +201 -0
  51. package/skills/devops/references/iac.md +252 -0
  52. package/skills/devops/references/observability.md +228 -0
  53. package/skills/devops/references/secrets.md +178 -0
  54. package/skills/devops-automator/SKILL.md +164 -0
  55. package/skills/frontend/SKILL.md +52 -0
  56. package/skills/frontend/references/accessibility.md +222 -0
  57. package/skills/frontend/references/components.md +206 -0
  58. package/skills/frontend/references/performance.md +219 -0
  59. package/skills/frontend/references/routing.md +209 -0
  60. package/skills/frontend/references/state.md +190 -0
  61. package/skills/frontend/references/testing.md +216 -0
  62. package/skills/frontend-developer/SKILL.md +115 -0
  63. package/skills/git-workflow/SKILL.md +355 -0
  64. package/skills/golang/SKILL.md +49 -0
  65. package/skills/golang/references/concurrency.md +284 -0
  66. package/skills/golang/references/errors.md +241 -0
  67. package/skills/golang/references/idioms.md +285 -0
  68. package/skills/golang/references/testing.md +238 -0
  69. package/skills/java/SKILL.md +50 -0
  70. package/skills/java/references/concurrency.md +194 -0
  71. package/skills/java/references/idioms.md +283 -0
  72. package/skills/java/references/testing.md +228 -0
  73. package/skills/kotlin/SKILL.md +47 -0
  74. package/skills/kotlin/references/coroutines.md +240 -0
  75. package/skills/kotlin/references/idioms.md +268 -0
  76. package/skills/kotlin/references/testing.md +219 -0
  77. package/skills/mobile/SKILL.md +50 -0
  78. package/skills/mobile/references/architecture.md +204 -0
  79. package/skills/mobile/references/navigation.md +158 -0
  80. package/skills/mobile/references/performance.md +152 -0
  81. package/skills/mobile/references/platform.md +166 -0
  82. package/skills/mobile/references/state-and-data.md +174 -0
  83. package/skills/python/SKILL.md +51 -0
  84. package/skills/python/THIRD_PARTY.md +14 -0
  85. package/skills/python/references/async.md +218 -0
  86. package/skills/python/references/error-handling.md +254 -0
  87. package/skills/python/references/idioms.md +279 -0
  88. package/skills/python/references/packaging.md +233 -0
  89. package/skills/python/references/testing.md +269 -0
  90. package/skills/python/references/typing.md +292 -0
  91. package/skills/qa-tester/SKILL.md +186 -0
  92. package/skills/rust/SKILL.md +50 -0
  93. package/skills/rust/references/async.md +224 -0
  94. package/skills/rust/references/errors.md +240 -0
  95. package/skills/rust/references/ownership.md +263 -0
  96. package/skills/rust/references/testing.md +274 -0
  97. package/skills/rust/references/traits.md +250 -0
  98. package/skills/security-engineer/SKILL.md +157 -0
  99. package/skills/swift/SKILL.md +48 -0
  100. package/skills/swift/references/concurrency.md +280 -0
  101. package/skills/swift/references/idioms.md +334 -0
  102. package/skills/swift/references/testing.md +229 -0
  103. package/skills/typescript/SKILL.md +51 -0
  104. package/skills/typescript/references/async.md +241 -0
  105. package/skills/typescript/references/errors.md +208 -0
  106. package/skills/typescript/references/idioms.md +246 -0
  107. package/skills/typescript/references/testing.md +225 -0
  108. package/skills/typescript/references/tooling.md +208 -0
  109. package/skills/typescript/references/types.md +259 -0
@@ -0,0 +1,280 @@
1
+ # Swift — Concurrency
2
+
3
+ `async/await`, `Task`, actors, `@MainActor`, `AsyncSequence`, cancellation.
4
+
5
+ ## `async/await`
6
+
7
+ ```swift
8
+ func fetchUser(id: String) async throws -> User {
9
+ let (data, _) = try await URLSession.shared.data(from: url(id))
10
+ return try JSONDecoder().decode(User.self, from: data)
11
+ }
12
+
13
+ // Caller
14
+ let u = try await fetchUser(id: "u1")
15
+ ```
16
+
17
+ `async` functions suspend at `await` without blocking a thread. `throws` propagates errors.
18
+
19
+ ## `Task`
20
+
21
+ `Task` creates a new concurrent context.
22
+
23
+ ```swift
24
+ // Fire-and-forget
25
+ Task {
26
+ await sendAnalytics()
27
+ }
28
+
29
+ // Get a result
30
+ let task = Task {
31
+ try await fetchUser(id: "u1")
32
+ }
33
+ let user = try await task.value
34
+
35
+ // Detached — no inherited context, rare
36
+ Task.detached(priority: .background) { await heavyWork() }
37
+ ```
38
+
39
+ Prefer structured `Task {}` (inherits parent's priority, cancellation, actor). `Task.detached` only when you genuinely need to break the hierarchy.
40
+
41
+ ## Structured concurrency — `async let`
42
+
43
+ ```swift
44
+ func loadScreen() async throws -> Screen {
45
+ async let user = fetchUser(id: id)
46
+ async let feed = fetchFeed()
47
+ async let prefs = fetchPrefs()
48
+ return Screen(user: try await user, feed: try await feed, prefs: try await prefs)
49
+ }
50
+ ```
51
+
52
+ All three requests start concurrently; `await` collects them. If one throws, the others are cancelled.
53
+
54
+ ## Task groups — dynamic parallelism
55
+
56
+ ```swift
57
+ func loadAll(ids: [String]) async throws -> [User] {
58
+ try await withThrowingTaskGroup(of: User.self) { group in
59
+ for id in ids {
60
+ group.addTask { try await fetchUser(id: id) }
61
+ }
62
+ var users: [User] = []
63
+ for try await u in group { users.append(u) }
64
+ return users
65
+ }
66
+ }
67
+ ```
68
+
69
+ On error: cancel the group or rethrow. Siblings complete (or get cancelled if you throw).
70
+
71
+ ### Bounded concurrency
72
+
73
+ ```swift
74
+ try await withThrowingTaskGroup(of: User.self) { group in
75
+ let limit = 10
76
+ var inFlight = 0
77
+ var iter = ids.makeIterator()
78
+
79
+ while let id = iter.next(), inFlight < limit {
80
+ group.addTask { try await fetchUser(id: id) }
81
+ inFlight += 1
82
+ }
83
+
84
+ var out: [User] = []
85
+ while let u = try await group.next() {
86
+ out.append(u)
87
+ if let id = iter.next() {
88
+ group.addTask { try await fetchUser(id: id) }
89
+ }
90
+ }
91
+ return out
92
+ }
93
+ ```
94
+
95
+ ## Cancellation
96
+
97
+ Cancellation is cooperative. A cancelled task continues unless it checks.
98
+
99
+ ```swift
100
+ func work() async throws {
101
+ for item in bigList {
102
+ try Task.checkCancellation() // throws CancellationError if cancelled
103
+ process(item)
104
+ }
105
+ }
106
+ ```
107
+
108
+ Most stdlib `async` calls check cancellation (`URLSession`, `Task.sleep`). Your own loops must opt in.
109
+
110
+ ## Timeouts
111
+
112
+ ```swift
113
+ func withTimeout<T>(_ seconds: Double, _ op: @escaping () async throws -> T) async throws -> T {
114
+ try await withThrowingTaskGroup(of: T.self) { group in
115
+ group.addTask { try await op() }
116
+ group.addTask {
117
+ try await Task.sleep(nanoseconds: UInt64(seconds * 1e9))
118
+ throw CancellationError()
119
+ }
120
+ guard let first = try await group.next() else { throw CancellationError() }
121
+ group.cancelAll()
122
+ return first
123
+ }
124
+ }
125
+ ```
126
+
127
+ (In Swift 6, `ContinuousClock` / `withTaskGroup`'s timeouts are cleaner.)
128
+
129
+ ## Actors — shared mutable state
130
+
131
+ An actor serializes access. Only one method runs at a time; readers wait.
132
+
133
+ ```swift
134
+ actor Cache {
135
+ private var data: [String: Data] = [:]
136
+
137
+ func get(_ key: String) -> Data? { data[key] }
138
+
139
+ func set(_ key: String, _ value: Data) {
140
+ data[key] = value
141
+ }
142
+ }
143
+
144
+ let cache = Cache()
145
+ let v = await cache.get("u1") // await — crossing actor boundary
146
+ ```
147
+
148
+ From outside, calls are `await`; inside the actor, they're synchronous. Don't expose internal mutable state; return values, not references to the actor's storage.
149
+
150
+ ## `@MainActor` — UI-thread isolation
151
+
152
+ ```swift
153
+ @MainActor
154
+ class ViewModel: ObservableObject {
155
+ @Published var state: State = .loading
156
+
157
+ func load() async {
158
+ state = .loading
159
+ do {
160
+ let u = try await service.fetch() // hops off main for network
161
+ state = .loaded(u) // back on main — safe
162
+ } catch {
163
+ state = .error(error)
164
+ }
165
+ }
166
+ }
167
+ ```
168
+
169
+ Any view model / UI code runs on `MainActor` by default. `await` on non-MainActor code hops off the main thread; results return via `await`, preserving main-thread safety.
170
+
171
+ Individual functions: `@MainActor func update(...) { ... }`.
172
+
173
+ ## `Sendable`
174
+
175
+ Types crossing actor / Task boundaries must be `Sendable` — "safe to transfer across concurrency domains".
176
+
177
+ ```swift
178
+ struct User: Sendable { ... } // struct of Sendable fields is Sendable
179
+ final class Foo: Sendable { ... } // needs: final, immutable, or internal sync
180
+ ```
181
+
182
+ Swift 6 enforces Sendable at the compiler level. Common fixes:
183
+ - Make the type a `struct` (value type, usually Sendable automatically).
184
+ - Make reference types `final` and all fields `let` + `Sendable`.
185
+ - For mutable types crossing boundaries, wrap in an `actor`.
186
+
187
+ ## `AsyncSequence` — streams
188
+
189
+ ```swift
190
+ for try await line in fileHandle.bytes.lines {
191
+ process(line)
192
+ }
193
+
194
+ // Build your own
195
+ struct Counter: AsyncSequence {
196
+ typealias Element = Int
197
+ struct Iterator: AsyncIteratorProtocol {
198
+ var i = 0
199
+ mutating func next() async -> Int? {
200
+ guard i < 10 else { return nil }
201
+ try? await Task.sleep(nanoseconds: 100_000_000)
202
+ defer { i += 1 }
203
+ return i
204
+ }
205
+ }
206
+ func makeAsyncIterator() -> Iterator { Iterator() }
207
+ }
208
+
209
+ for await n in Counter() { print(n) }
210
+ ```
211
+
212
+ Great for paginated APIs, file reads, WebSocket messages.
213
+
214
+ ## Continuations — bridging callback APIs
215
+
216
+ ```swift
217
+ func legacy(id: String, completion: @escaping (User?, Error?) -> Void) { ... }
218
+
219
+ func fetch(id: String) async throws -> User {
220
+ try await withCheckedThrowingContinuation { continuation in
221
+ legacy(id: id) { user, error in
222
+ if let user = user { continuation.resume(returning: user) }
223
+ else if let error = error { continuation.resume(throwing: error) }
224
+ else { continuation.resume(throwing: URLError(.unknown)) }
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ **Call `resume` exactly once.** Calling twice crashes; not calling leaks the coroutine. Use `withCheckedContinuation` during development (crashes on misuse); switch to `withUnsafeContinuation` in release if you need the perf.
231
+
232
+ ## Priority & throttling
233
+
234
+ Task priorities: `.userInitiated`, `.userInteractive`, `.utility`, `.background`. Lower priority in background tasks; the scheduler throttles.
235
+
236
+ ```swift
237
+ Task(priority: .background) { await indexDatabase() }
238
+ ```
239
+
240
+ ## Avoiding blocking
241
+
242
+ Don't call blocking APIs from async code.
243
+
244
+ ```swift
245
+ // ❌
246
+ Task { sleep(5); await doThing() }
247
+
248
+ // ✅
249
+ Task { try await Task.sleep(nanoseconds: 5_000_000_000); await doThing() }
250
+ ```
251
+
252
+ File I/O: use `URLSession` for network, `FileHandle.bytes` for async reads, or `Task.detached(priority: .utility)` for genuinely blocking calls.
253
+
254
+ ## Bridging to / from main thread
255
+
256
+ ```swift
257
+ // From background, update UI on main
258
+ Task { @MainActor in
259
+ self.state = .loaded
260
+ }
261
+
262
+ // Call a MainActor method from non-MainActor
263
+ await viewModel.update()
264
+ ```
265
+
266
+ Don't use `DispatchQueue.main.async` in new Swift code — `@MainActor` / `Task { @MainActor in ... }` is the native way.
267
+
268
+ ## Anti-patterns
269
+
270
+ | Anti-pattern | Fix |
271
+ |---|---|
272
+ | `DispatchSemaphore.wait` to bridge async → sync | Restructure caller as async; semaphores deadlock the runtime |
273
+ | `Task { await ... }.value` from sync code to "await" | Don't; make the caller `async` |
274
+ | `DispatchQueue.main.sync` from background | `await MainActor.run { ... }` or `@MainActor` |
275
+ | Unchecked continuations with complex flow | Use `withCheckedContinuation` during development |
276
+ | Shared mutable struct state across tasks | Use an actor |
277
+ | Detached tasks for everything | Structured `Task {}` inherits context; detached is an escape hatch |
278
+ | Ignoring `Task.isCancelled` / `checkCancellation` in loops | Cancellation is cooperative |
279
+ | `async` on sync functions "for consistency" | `async` has a cost; don't add without reason |
280
+ | Storing `Task` references without cancellation story | Keep a handle; cancel on deinit / view disappearance |
@@ -0,0 +1,334 @@
1
+ # Swift — Idioms
2
+
3
+ Value types, optionals, protocols, closures, `guard`, pattern matching.
4
+
5
+ ## `let` / `var`
6
+
7
+ `let` (constant) by default. `var` only when mutating.
8
+
9
+ ```swift
10
+ let name = "A"
11
+ var count = 0
12
+ ```
13
+
14
+ ## Value vs. reference types
15
+
16
+ - `struct`, `enum`, `tuple` — value types; copied on assignment.
17
+ - `class`, `actor` — reference types; shared.
18
+
19
+ Default to `struct`. Use `class` when you need inheritance (`UIView` subclasses, for framework integration) or shared identity with mutable state. Use `actor` for shared mutable state across concurrency.
20
+
21
+ ```swift
22
+ struct User { // value type
23
+ let id: String
24
+ var email: String
25
+ }
26
+
27
+ var a = User(id: "u1", email: "a@x.com")
28
+ var b = a // copy
29
+ b.email = "b@x.com" // a unchanged
30
+ ```
31
+
32
+ ## Optionals
33
+
34
+ Optional = "value or nothing". Different from `null`.
35
+
36
+ ```swift
37
+ var name: String? = nil
38
+ name.count // ❌ compile error
39
+
40
+ // Unwrap
41
+ if let n = name {
42
+ print(n.count)
43
+ }
44
+ guard let n = name else { return } // unwrap or exit scope
45
+ print(n.count)
46
+
47
+ name?.count // optional chaining → Int?
48
+ name ?? "unknown" // nil-coalescing
49
+ ```
50
+
51
+ `!` is a compile-time assertion that the value is non-nil. Crash at runtime if wrong. Avoid in normal code.
52
+
53
+ ## `guard`
54
+
55
+ Fail-fast at the top of a function; flat code after.
56
+
57
+ ```swift
58
+ func process(user: User?) {
59
+ guard let user = user, user.active else { return }
60
+ // user is now non-optional and active
61
+ work(user)
62
+ }
63
+ ```
64
+
65
+ Prefer `guard` over nested `if let`s. It keeps the happy path at the outermost scope.
66
+
67
+ ## Structs with behaviour
68
+
69
+ Structs can have methods, computed properties, and satisfy protocols.
70
+
71
+ ```swift
72
+ struct Email {
73
+ let raw: String
74
+ var isValid: Bool { raw.contains("@") }
75
+ func normalized() -> Email { Email(raw: raw.lowercased()) }
76
+ }
77
+ ```
78
+
79
+ ## Enums with associated values
80
+
81
+ Swift enums carry data. Discriminated unions, like Rust.
82
+
83
+ ```swift
84
+ enum Result<T> {
85
+ case success(T)
86
+ case failure(Error)
87
+ }
88
+
89
+ switch result {
90
+ case .success(let value):
91
+ use(value)
92
+ case .failure(let error):
93
+ log(error)
94
+ }
95
+ ```
96
+
97
+ Enums are exhaustive — the compiler forces every case. Add a case → every `switch` fails until updated.
98
+
99
+ ## Protocols — small, composable
100
+
101
+ ```swift
102
+ protocol Identifiable { var id: String { get } }
103
+ protocol Timestamped { var createdAt: Date { get } }
104
+
105
+ struct User: Identifiable, Timestamped {
106
+ let id: String
107
+ let createdAt: Date
108
+ }
109
+ ```
110
+
111
+ ### Protocol extensions — default implementations
112
+
113
+ ```swift
114
+ protocol Named { var name: String { get } }
115
+
116
+ extension Named {
117
+ func greet() -> String { "Hello \(name)" }
118
+ }
119
+
120
+ struct User: Named { let name: String }
121
+ User(name: "A").greet() // "Hello A"
122
+ ```
123
+
124
+ Default methods via extensions. Implementers can override.
125
+
126
+ ### Existentials vs. generics
127
+
128
+ ```swift
129
+ // Existential — heterogeneous collection
130
+ func renderAll(_ items: [any Renderable]) { ... }
131
+
132
+ // Generic — homogeneous; faster (static dispatch)
133
+ func renderAll<T: Renderable>(_ items: [T]) { ... }
134
+ ```
135
+
136
+ Swift 5.6+ requires `any` keyword for existentials. Prefer generics unless you need heterogeneity.
137
+
138
+ ## Closures
139
+
140
+ ```swift
141
+ let double: (Int) -> Int = { $0 * 2 }
142
+
143
+ users.map { $0.email }
144
+ users.filter { $0.active }
145
+
146
+ users.sorted { $0.name < $1.name }
147
+ ```
148
+
149
+ Trailing closure syntax is idiomatic. Name parameters when the closure is longer than one line:
150
+
151
+ ```swift
152
+ users.sorted { a, b in
153
+ a.name.compare(b.name, options: .caseInsensitive) == .orderedAscending
154
+ }
155
+ ```
156
+
157
+ ## `@escaping` closures
158
+
159
+ When a closure is stored or called after the function returns, it must be `@escaping`.
160
+
161
+ ```swift
162
+ func load(onComplete: @escaping (Result<User, Error>) -> Void) { ... }
163
+ ```
164
+
165
+ Inside an `@escaping` closure, `self` must be explicit — which is the language's way of nudging you to think about retain cycles.
166
+
167
+ ```swift
168
+ load { [weak self] result in
169
+ guard let self else { return }
170
+ self.update(result)
171
+ }
172
+ ```
173
+
174
+ For new code, prefer `async` over callbacks whenever possible.
175
+
176
+ ## Extensions
177
+
178
+ Add methods to existing types without subclassing.
179
+
180
+ ```swift
181
+ extension String {
182
+ func toSlug() -> String {
183
+ lowercased().replacingOccurrences(of: " ", with: "-")
184
+ }
185
+ }
186
+
187
+ "Hello World".toSlug() // "hello-world"
188
+ ```
189
+
190
+ Organize by feature. Common pattern:
191
+
192
+ ```swift
193
+ // File: User+Formatting.swift
194
+ extension User { func displayName() -> String { ... } }
195
+ ```
196
+
197
+ Don't add methods with generic names (`.isValid`) that collide across extensions.
198
+
199
+ ## Pattern matching
200
+
201
+ Beyond `switch` on enum cases:
202
+
203
+ ```swift
204
+ let pair = (1, 2)
205
+ switch pair {
206
+ case (0, 0): print("origin")
207
+ case (_, 0): print("x-axis")
208
+ case (0, _): print("y-axis")
209
+ case (x, y) where x == y: print("diagonal")
210
+ case let (x, y): print("(\(x), \(y))")
211
+ }
212
+
213
+ // if case / for case
214
+ if case .success(let v) = result { use(v) }
215
+ for case .success(let v) in results { use(v) }
216
+ ```
217
+
218
+ Use `where` clauses inside `switch` for guards.
219
+
220
+ ## Error handling — throwing functions
221
+
222
+ ```swift
223
+ enum ValidationError: Error {
224
+ case emptyEmail
225
+ case invalidAge(Int)
226
+ }
227
+
228
+ func validate(user: User) throws {
229
+ guard !user.email.isEmpty else { throw ValidationError.emptyEmail }
230
+ guard user.age >= 0 else { throw ValidationError.invalidAge(user.age) }
231
+ }
232
+
233
+ do {
234
+ try validate(user: u)
235
+ } catch ValidationError.emptyEmail {
236
+ show("email required")
237
+ } catch let e as ValidationError {
238
+ show("invalid: \(e)")
239
+ } catch {
240
+ log.error("unexpected: \(error)")
241
+ }
242
+ ```
243
+
244
+ `try?` → `Optional`, `try!` → crash on error (tests only).
245
+
246
+ ## `Result` type
247
+
248
+ For async callbacks and stored outcomes.
249
+
250
+ ```swift
251
+ func fetch(_ url: URL) async -> Result<Data, URLError> { ... }
252
+
253
+ switch await fetch(url) {
254
+ case .success(let data): use(data)
255
+ case .failure(let e): log(e)
256
+ }
257
+ ```
258
+
259
+ With `async throws`, you usually don't need `Result` — throws flows naturally.
260
+
261
+ ## Access control
262
+
263
+ | Level | Reach |
264
+ |---|---|
265
+ | `private` | Declaring scope only |
266
+ | `fileprivate` | Whole file |
267
+ | `internal` (default) | Module |
268
+ | `public` | Cross-module, not subclassable |
269
+ | `open` | Cross-module, subclassable |
270
+
271
+ Default to most-restrictive. Libraries: prefer `public` for API surface; `open` only when subclassing is a designed extension point.
272
+
273
+ ## Property wrappers
274
+
275
+ Encapsulate storage with behaviour: `@Published`, `@State`, `@AppStorage`, `@UserDefault`.
276
+
277
+ ```swift
278
+ @propertyWrapper
279
+ struct Clamped<V: Comparable> {
280
+ var wrappedValue: V {
281
+ didSet { wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound) }
282
+ }
283
+ let range: ClosedRange<V>
284
+ init(wrappedValue: V, _ range: ClosedRange<V>) {
285
+ self.range = range
286
+ self.wrappedValue = min(max(wrappedValue, range.lowerBound), range.upperBound)
287
+ }
288
+ }
289
+
290
+ struct Config { @Clamped(1...10) var threads: Int = 4 }
291
+ ```
292
+
293
+ Used heavily in SwiftUI; write your own sparingly.
294
+
295
+ ## Strings
296
+
297
+ - `String` — Unicode-correct by default. Subscripting requires `Index`, not `Int`.
298
+ - `Character` — a grapheme cluster (may be multiple code points).
299
+ - Use `.count` for grapheme count (slow-ish); `.unicodeScalars.count` for code points.
300
+
301
+ ```swift
302
+ let s = "héllo"
303
+ s.count // 5
304
+ s[s.startIndex] // "h"
305
+
306
+ // Index arithmetic
307
+ let i = s.index(s.startIndex, offsetBy: 2)
308
+ s[i] // "l"
309
+ ```
310
+
311
+ Avoid `.utf8.count` unless you're measuring bytes for serialization.
312
+
313
+ ## Collections — lazy vs. eager
314
+
315
+ ```swift
316
+ users.map { $0.email } // eager, allocates
317
+ users.lazy.map { $0.email }.filter { $0.contains("@x") }.first(where: ...)
318
+ ```
319
+
320
+ Use `.lazy` for chained operations on large collections where you only need a subset.
321
+
322
+ ## Anti-patterns
323
+
324
+ | Anti-pattern | Fix |
325
+ |---|---|
326
+ | `!` force-unwrap | Use `if let` / `guard let` / `??` |
327
+ | `try!` outside tests | `try` + error handling |
328
+ | Force-casting `as!` | Use `as?` and handle `nil`; test types you trust |
329
+ | `[String: Any]` in public API | Model a type |
330
+ | Class for a pure data type | Use `struct` |
331
+ | Storing closures without `[weak self]` | Retain cycle; always think capture semantics |
332
+ | `print()` for logging | `Logger` (`os.Logger`) in new code |
333
+ | `NSString` / `NSArray` in Swift code | Use native types; bridge at Objective-C boundary |
334
+ | `Error` protocol conformance without `LocalizedError` for user-facing errors | Implement `errorDescription` if you surface it in UI |