engsys 1.0.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 (173) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +202 -0
  3. package/core/agents/aaron.md +152 -0
  4. package/core/agents/bert.md +115 -0
  5. package/core/agents/isabelle.md +136 -0
  6. package/core/agents/jody.md +150 -0
  7. package/core/agents/leith.md +111 -0
  8. package/core/agents/marcelo.md +282 -0
  9. package/core/agents/melvin.md +101 -0
  10. package/core/agents/nyx.md +152 -0
  11. package/core/agents/otto.md +168 -0
  12. package/core/agents/patricia.md +283 -0
  13. package/core/commands/design-audit-local.md +155 -0
  14. package/core/commands/design-audit.md +235 -0
  15. package/core/commands/design-critique.md +96 -0
  16. package/core/commands/file-issue.md +22 -0
  17. package/core/commands/generate-project.md +45 -0
  18. package/core/commands/implement-issue.md +37 -0
  19. package/core/commands/implement-project.md +40 -0
  20. package/core/commands/naturalize.md +61 -0
  21. package/core/commands/pre-push.md +29 -0
  22. package/core/commands/prep-review-collect.md +130 -0
  23. package/core/commands/prep-review-finalize.md +121 -0
  24. package/core/commands/prep-review-publish.md +113 -0
  25. package/core/commands/prep-review.md +65 -0
  26. package/core/commands/project-closeout.md +25 -0
  27. package/core/skills/agentic-eval/SKILL.md +195 -0
  28. package/core/skills/chrome-devtools/SKILL.md +97 -0
  29. package/core/skills/code-review/SKILL.md +26 -0
  30. package/core/skills/gh-cli/SKILL.md +2202 -0
  31. package/core/skills/git-commit/SKILL.md +124 -0
  32. package/core/skills/git-workflow-agents/SKILL.md +462 -0
  33. package/core/skills/git-workflow-agents/reference.md +220 -0
  34. package/core/skills/github-actions/SKILL.md +190 -0
  35. package/core/skills/github-issues/SKILL.md +154 -0
  36. package/core/skills/llm-structured-outputs/SKILL.md +323 -0
  37. package/core/skills/llm-structured-outputs/references/provider-details.md +392 -0
  38. package/core/skills/pre-push/SKILL.md +115 -0
  39. package/core/skills/refactor/SKILL.md +645 -0
  40. package/core/skills/web-design-reviewer/SKILL.md +371 -0
  41. package/core/skills/webapp-testing/SKILL.md +127 -0
  42. package/core/skills/webapp-testing/test-helper.js +56 -0
  43. package/core/templates/CLAUDE.md.tmpl +98 -0
  44. package/core/templates/adr-template.md +67 -0
  45. package/core/templates/gh-issue-templates/bug.md +39 -0
  46. package/core/templates/gh-issue-templates/content.md +42 -0
  47. package/core/templates/gh-issue-templates/enhancement.md +36 -0
  48. package/core/templates/gh-issue-templates/feature.md +39 -0
  49. package/core/templates/gh-issue-templates/infrastructure.md +41 -0
  50. package/core/templates/post-edit-reminders.sh.tmpl +19 -0
  51. package/core/templates/settings.json.tmpl +90 -0
  52. package/core/templates/settings.local.json.tmpl +3 -0
  53. package/core/workflows/agent-implementation-workflow.md +346 -0
  54. package/core/workflows/generate-project.md +258 -0
  55. package/core/workflows/implement-project-workflow.md +190 -0
  56. package/core/workflows/issue-tracking.md +89 -0
  57. package/core/workflows/project-closeout-ceremony.md +77 -0
  58. package/core/workflows/review-workflow.md +266 -0
  59. package/engsys.config.example.yaml +46 -0
  60. package/install +202 -0
  61. package/lessons-library/README.md +80 -0
  62. package/lessons-library/async-callbacks-verify-liveness.md +15 -0
  63. package/lessons-library/change-isnt-done-until-every-surface-updated.md +15 -0
  64. package/lessons-library/claim-then-act-for-irreversible-ops.md +16 -0
  65. package/lessons-library/co-commit-entangled-work.md +15 -0
  66. package/lessons-library/dependabot-triage-playbook.md +17 -0
  67. package/lessons-library/deploy-by-digest-and-verify-the-running-revision.md +15 -0
  68. package/lessons-library/enforce-your-guarantee-at-your-boundary.md +16 -0
  69. package/lessons-library/gate-changes-on-measurement-not-vibes.md +15 -0
  70. package/lessons-library/iac-first-no-console-changes.md +15 -0
  71. package/lessons-library/independent-objective-review-gate.md +15 -0
  72. package/lessons-library/keep-an-immutable-source-of-truth.md +15 -0
  73. package/lessons-library/long-agent-runs-checkpoint-not-poll.md +15 -0
  74. package/lessons-library/model-identity-with-stable-ids-and-provenance.md +15 -0
  75. package/lessons-library/operator-choices-are-first-class.md +15 -0
  76. package/lessons-library/prefer-tool-enforced-structured-output.md +15 -0
  77. package/lessons-library/prove-causation-before-acting.md +15 -0
  78. package/lessons-library/re-read-state-before-acting.md +14 -0
  79. package/lessons-library/read-layer-tolerates-unbackfilled-rows.md +15 -0
  80. package/lessons-library/shell-safety-pipefail-and-validate-before-teardown.md +14 -0
  81. package/lessons-library/shift-correctness-left-and-distrust-false-greens.md +15 -0
  82. package/lessons-library/stray-control-bytes-hide-changes.md +14 -0
  83. package/lessons-library/tests-can-assert-the-bug.md +15 -0
  84. package/lessons-library/verify-ground-truth-not-reports.md +15 -0
  85. package/lessons-library/worktrees-need-bootstrap-from-origin-main.md +15 -0
  86. package/lib/commands.js +356 -0
  87. package/lib/generate-team-avatars.mjs +251 -0
  88. package/lib/manifest.js +155 -0
  89. package/lib/render.js +135 -0
  90. package/lib/selftest.js +90 -0
  91. package/lib/util.js +89 -0
  92. package/lib/yaml.js +156 -0
  93. package/optional-agents/gary.md +86 -0
  94. package/optional-agents/jos.md +136 -0
  95. package/optional-agents/sandy.md +101 -0
  96. package/optional-agents/steve.md +161 -0
  97. package/package.json +43 -0
  98. package/stacks/cloud/aws/claude.fragment.md +17 -0
  99. package/stacks/cloud/aws/settings.fragment.json +39 -0
  100. package/stacks/cloud/aws/skills/aws-deployment-preflight/SKILL.md +165 -0
  101. package/stacks/cloud/aws/skills/cloud-architecture-aws/SKILL.md +265 -0
  102. package/stacks/cloud/azure/claude.fragment.md +17 -0
  103. package/stacks/cloud/azure/settings.fragment.json +45 -0
  104. package/stacks/cloud/azure/skills/azure-deployment-preflight/SKILL.md +175 -0
  105. package/stacks/cloud/azure/skills/cloud-architecture-azure/SKILL.md +211 -0
  106. package/stacks/cloud/cloudflare/claude.fragment.md +21 -0
  107. package/stacks/cloud/cloudflare/settings.fragment.json +31 -0
  108. package/stacks/cloud/cloudflare/skills/cloud-architecture-cloudflare/SKILL.md +294 -0
  109. package/stacks/cloud/cloudflare/skills/cloudflare-deployment-preflight/SKILL.md +175 -0
  110. package/stacks/cloud/gcp/claude.fragment.md +17 -0
  111. package/stacks/cloud/gcp/settings.fragment.json +40 -0
  112. package/stacks/cloud/gcp/skills/cloud-architecture-gcp/SKILL.md +208 -0
  113. package/stacks/cloud/gcp/skills/gcp-deployment-preflight/SKILL.md +137 -0
  114. package/stacks/db/mongo/skills/mongo-conventions/SKILL.md +96 -0
  115. package/stacks/db/prisma/claude.fragment.md +49 -0
  116. package/stacks/db/prisma/skills/docker-database-package-copy/SKILL.md +44 -0
  117. package/stacks/db/prisma/skills/prisma-conventions/SKILL.md +37 -0
  118. package/stacks/domain/mobile-growth/skills/apple-ads/SKILL.md +184 -0
  119. package/stacks/domain/mobile-growth/skills/apple-ads/references/benchmark-notes.md +47 -0
  120. package/stacks/domain/mobile-growth/skills/apple-ads/references/official-links.md +53 -0
  121. package/stacks/domain/mobile-growth/skills/google-play-growth/SKILL.md +197 -0
  122. package/stacks/domain/mobile-growth/skills/google-play-growth/references/benchmark-notes.md +47 -0
  123. package/stacks/domain/mobile-growth/skills/google-play-growth/references/official-links.md +45 -0
  124. package/stacks/iac/bicep/claude.fragment.md +14 -0
  125. package/stacks/iac/bicep/settings.fragment.json +20 -0
  126. package/stacks/iac/bicep/skills/iac-bicep/SKILL.md +113 -0
  127. package/stacks/iac/cdk/claude.fragment.md +14 -0
  128. package/stacks/iac/cdk/settings.fragment.json +23 -0
  129. package/stacks/iac/cdk/skills/iac-cdk/SKILL.md +104 -0
  130. package/stacks/iac/terraform/claude.fragment.md +13 -0
  131. package/stacks/iac/terraform/settings.fragment.json +25 -0
  132. package/stacks/iac/terraform/skills/iac-terraform/SKILL.md +93 -0
  133. package/stacks/iac/terraform/skills/terraform-conventions/SKILL.md +87 -0
  134. package/stacks/lang/kotlin/skills/android-testing/SKILL.md +263 -0
  135. package/stacks/lang/kotlin/skills/jetpack-compose/SKILL.md +264 -0
  136. package/stacks/lang/kotlin/skills/kotlin-coroutines/SKILL.md +329 -0
  137. package/stacks/lang/python/skills/python-conventions/SKILL.md +61 -0
  138. package/stacks/lang/shell/skills/shell-scripting/SKILL.md +110 -0
  139. package/stacks/lang/swift/skills/swift-concurrency/SKILL.md +423 -0
  140. package/stacks/lang/swift/skills/swift-concurrency/references/approachable-concurrency.md +80 -0
  141. package/stacks/lang/swift/skills/swift-concurrency/references/concurrency-patterns.md +233 -0
  142. package/stacks/lang/swift/skills/swift-concurrency/references/swiftui-concurrency.md +187 -0
  143. package/stacks/lang/swift/skills/swift-concurrency/references/synchronization-primitives.md +341 -0
  144. package/stacks/lang/swift/skills/swift-testing/SKILL.md +497 -0
  145. package/stacks/lang/swift/skills/swift-testing/references/testing-advanced.md +106 -0
  146. package/stacks/lang/swift/skills/swift-testing/references/testing-patterns.md +504 -0
  147. package/stacks/lang/swift/skills/swiftdata/SKILL.md +334 -0
  148. package/stacks/lang/swift/skills/swiftdata/references/core-data-coexistence.md +504 -0
  149. package/stacks/lang/swift/skills/swiftdata/references/swiftdata-advanced.md +975 -0
  150. package/stacks/lang/swift/skills/swiftdata/references/swiftdata-queries.md +675 -0
  151. package/stacks/lang/swift/skills/swiftui-patterns/SKILL.md +371 -0
  152. package/stacks/lang/swift/skills/swiftui-patterns/references/architecture-patterns.md +486 -0
  153. package/stacks/lang/swift/skills/swiftui-patterns/references/deprecated-migration.md +1097 -0
  154. package/stacks/lang/swift/skills/swiftui-patterns/references/design-polish.md +780 -0
  155. package/stacks/lang/swift/skills/swiftui-patterns/references/platform-and-sharing.md +696 -0
  156. package/stacks/lang/typescript/skills/typescript-conventions/SKILL.md +91 -0
  157. package/stacks/platform/android/claude.fragment.md +40 -0
  158. package/stacks/platform/android/hooks/pre-push-gradle.sh +70 -0
  159. package/stacks/platform/android/settings.fragment.json +13 -0
  160. package/stacks/platform/android/skills/android-build-conventions/SKILL.md +247 -0
  161. package/stacks/platform/ios/claude.fragment.md +24 -0
  162. package/stacks/platform/ios/hooks/pre-push-xcodebuild.sh +82 -0
  163. package/stacks/platform/ios/settings.fragment.json +21 -0
  164. package/stacks/platform/ios/skills/xcodebuildmcp-simulator-logs/SKILL.md +76 -0
  165. package/stacks/platform/web/skills/frontend-testing/SKILL.md +246 -0
  166. package/stacks/platform/web/skills/react-conventions/SKILL.md +261 -0
  167. package/stacks/platform/web/skills/web-platform-conventions/SKILL.md +55 -0
  168. package/stacks/tooling/issue-tracker-github/claude.fragment.md +10 -0
  169. package/stacks/tooling/issue-tracker-github/settings.fragment.json +24 -0
  170. package/stacks/tooling/issue-tracker-github/skills/issue-tracker-github/SKILL.md +278 -0
  171. package/stacks/tooling/issue-tracker-linear/claude.fragment.md +17 -0
  172. package/stacks/tooling/issue-tracker-linear/settings.fragment.json +9 -0
  173. package/stacks/tooling/issue-tracker-linear/skills/issue-tracker-linear/SKILL.md +183 -0
@@ -0,0 +1,341 @@
1
+ # Synchronization Primitives
2
+
3
+ Low-level synchronization tools for protecting shared mutable state when actors
4
+ are not the right fit. All primitives discussed here are `Sendable` and safe
5
+ to use from multiple threads.
6
+
7
+ ## Contents
8
+
9
+ - [Mutex](#mutex)
10
+ - [OSAllocatedUnfairLock](#osallocatedunfairlock)
11
+ - [Atomic](#atomic)
12
+ - [Locks vs Actors: When to Use Each](#locks-vs-actors-when-to-use-each)
13
+
14
+ ## Mutex
15
+
16
+ **Module:** `Synchronization` · **Availability:** iOS 18.0+
17
+
18
+ `Mutex<Value>` is a synchronization primitive that protects shared mutable
19
+ state via mutual exclusion. It blocks threads attempting to acquire the lock,
20
+ ensuring only one execution context accesses the protected value at a time.
21
+
22
+ **Documentation:**
23
+ [sosumi.ai/documentation/synchronization/mutex](https://sosumi.ai/documentation/synchronization/mutex)
24
+
25
+ ### Basic Usage
26
+
27
+ ```swift
28
+ import Synchronization
29
+
30
+ class ImageCache: Sendable {
31
+ let storage = Mutex<[String: UIImage]>([:])
32
+
33
+ func image(forKey key: String) -> UIImage? {
34
+ storage.withLock { $0[key] }
35
+ }
36
+
37
+ func store(_ image: UIImage, forKey key: String) {
38
+ storage.withLock { $0[key] = image }
39
+ }
40
+
41
+ func removeAll() {
42
+ storage.withLock { $0.removeAll() }
43
+ }
44
+ }
45
+ ```
46
+
47
+ ### withLockIfAvailable
48
+
49
+ Use `withLockIfAvailable` to attempt acquisition without blocking. Returns
50
+ `nil` if the lock is already held.
51
+
52
+ ```swift
53
+ let counter = Mutex<Int>(0)
54
+
55
+ // Non-blocking attempt — returns nil if lock is contended
56
+ if let value = counter.withLockIfAvailable({ $0 }) {
57
+ print("Current count: \(value)")
58
+ } else {
59
+ print("Lock was busy, skipping")
60
+ }
61
+ ```
62
+
63
+ ### Key Properties
64
+
65
+ - **Generic over `Value`:** The protected state is stored inside the mutex,
66
+ making it clear what the lock protects.
67
+ - **`Sendable`:** `Mutex` conforms to `Sendable`, so it can be stored in
68
+ `Sendable` types (classes, actors, global state).
69
+ - **Non-recursive:** Attempting to lock a `Mutex` that you already hold on the
70
+ same thread is undefined behavior.
71
+ - **Synchronous only:** Do not `await` inside `withLock`. The lock is held for
72
+ the duration of the closure — blocking across a suspension point will
73
+ deadlock or starve other threads.
74
+
75
+ ## OSAllocatedUnfairLock
76
+
77
+ **Module:** `os` · **Availability:** iOS 16.0+
78
+
79
+ `OSAllocatedUnfairLock<State>` wraps `os_unfair_lock` in a safe Swift API.
80
+ It heap-allocates the underlying lock, avoiding the unsound address-of
81
+ problem that makes raw `os_unfair_lock` unusable from Swift.
82
+
83
+ **Documentation:**
84
+ [sosumi.ai/documentation/os/osallocatedunfairlock](https://sosumi.ai/documentation/os/osallocatedunfairlock)
85
+
86
+ ### State-Protecting Lock
87
+
88
+ ```swift
89
+ import os
90
+
91
+ enum LoadState: Sendable {
92
+ case idle
93
+ case loading
94
+ case complete(Data)
95
+ case failed(Error)
96
+ }
97
+
98
+ final class ResourceLoader: Sendable {
99
+ let state = OSAllocatedUnfairLock(initialState: LoadState.idle)
100
+
101
+ func beginLoading() {
102
+ state.withLock { $0 = .loading }
103
+ }
104
+
105
+ func completeLoading(with data: Data) {
106
+ state.withLock { $0 = .complete(data) }
107
+ }
108
+
109
+ var currentState: LoadState {
110
+ state.withLock { $0 }
111
+ }
112
+ }
113
+ ```
114
+
115
+ ### Stateless Lock
116
+
117
+ When protecting external state or a code section rather than a specific value:
118
+
119
+ ```swift
120
+ let lock = OSAllocatedUnfairLock()
121
+
122
+ lock.withLock {
123
+ // Critical section — no associated state
124
+ writeToSharedFile(data)
125
+ }
126
+ ```
127
+
128
+ ### Manual lock/unlock
129
+
130
+ Available but discouraged. Must unlock from the same thread that locked.
131
+ **Never** use across `await` suspension points.
132
+
133
+ ```swift
134
+ lock.lock()
135
+ defer { lock.unlock() }
136
+ // Critical section
137
+ ```
138
+
139
+ ### Mutex vs OSAllocatedUnfairLock
140
+
141
+ | | `Mutex<Value>` | `OSAllocatedUnfairLock<State>` |
142
+ |---|---|---|
143
+ | **Availability** | iOS 18+ | iOS 16+ |
144
+ | **Module** | `Synchronization` | `os` |
145
+ | **State model** | Value stored inside lock (generic `Value`) | Optional state via `initialState:` |
146
+ | **`withLockIfAvailable`** | Returns `nil` on contention | Returns `nil` on contention |
147
+ | **Ownership assertions** | Not available | `precondition(.owner)` / `precondition(.notOwner)` |
148
+ | **Manual lock/unlock** | Not available | Available (`lock()` / `unlock()`) |
149
+ | **Recommendation** | Preferred for iOS 18+ code | Use when targeting iOS 16–17 |
150
+
151
+ **Guideline:** Use `Mutex` for new code targeting iOS 18+. Use
152
+ `OSAllocatedUnfairLock` when you need to support iOS 16–17, or when you need
153
+ ownership assertions for debugging.
154
+
155
+ ## Atomic
156
+
157
+ **Module:** `Synchronization` · **Availability:** iOS 18.0+
158
+
159
+ `Atomic<Value>` provides lock-free atomic operations on values conforming to
160
+ `AtomicRepresentable`. Use atomics for simple counters, flags, and
161
+ compare-and-swap patterns where a full lock would be overkill.
162
+
163
+ **Documentation:**
164
+ [sosumi.ai/documentation/synchronization/atomic](https://sosumi.ai/documentation/synchronization/atomic)
165
+
166
+ ### Counter Example
167
+
168
+ ```swift
169
+ import Synchronization
170
+
171
+ final class RequestTracker: Sendable {
172
+ let activeRequests = Atomic<Int>(0)
173
+
174
+ func beginRequest() {
175
+ activeRequests.wrappingAdd(1, ordering: .relaxed)
176
+ }
177
+
178
+ func endRequest() {
179
+ activeRequests.wrappingSubtract(1, ordering: .relaxed)
180
+ }
181
+
182
+ var count: Int {
183
+ activeRequests.load(ordering: .relaxed)
184
+ }
185
+ }
186
+ ```
187
+
188
+ ### Boolean Flag
189
+
190
+ ```swift
191
+ let isShutdown = Atomic<Bool>(false)
192
+
193
+ func shutdown() {
194
+ let (exchanged, _) = isShutdown.compareExchange(
195
+ expected: false,
196
+ desired: true,
197
+ ordering: .acquiringAndReleasing
198
+ )
199
+ guard exchanged else { return } // Already shut down
200
+ performCleanup()
201
+ }
202
+ ```
203
+
204
+ ### Memory Ordering
205
+
206
+ Atomic operations require an explicit memory ordering:
207
+
208
+ | Ordering | Use case |
209
+ |---|---|
210
+ | `.relaxed` | Counters, statistics — no ordering guarantees needed |
211
+ | `.acquiring` | Read that must see all writes before a corresponding release |
212
+ | `.releasing` | Write that must be visible to a corresponding acquire |
213
+ | `.acquiringAndReleasing` | Compare-and-swap, read-modify-write |
214
+ | `.sequentiallyConsistent` | Strongest guarantee — rarely needed |
215
+
216
+ **Guideline:** Use `.relaxed` for simple counters. Use
217
+ `.acquiringAndReleasing` for compare-and-swap patterns. Avoid
218
+ `.sequentiallyConsistent` unless you have a proven need — it is the most
219
+ expensive ordering.
220
+
221
+ ### When to Use Atomics vs Mutex
222
+
223
+ - **Atomics:** Simple scalar values (Int, Bool, UInt64), single-field updates,
224
+ counters, flags. Lock-free and very fast.
225
+ - **Mutex:** Compound state (dictionaries, structs with multiple fields),
226
+ multi-step operations that must be atomic as a group.
227
+
228
+ ## Locks vs Actors: When to Use Each
229
+
230
+ ### Use Actors When:
231
+
232
+ - **Async isolation is natural.** The protected state is accessed from async
233
+ contexts and you can afford the hop.
234
+ - **Structured concurrency.** You want the compiler to enforce isolation
235
+ boundaries and prevent data races statically.
236
+ - **Most Swift code.** Actors are the default recommendation for shared mutable
237
+ state in Swift concurrency.
238
+ - **Complex state with multiple methods.** Actor isolation protects all
239
+ properties and methods automatically.
240
+
241
+ ```swift
242
+ // GOOD: Actor for a cache accessed from async contexts
243
+ actor ImageDownloader {
244
+ private var cache: [URL: UIImage] = [:]
245
+
246
+ func image(for url: URL) async throws -> UIImage {
247
+ if let cached = cache[url] { return cached }
248
+ let (data, _) = try await URLSession.shared.data(from: url)
249
+ let image = UIImage(data: data)!
250
+ cache[url] = image
251
+ return image
252
+ }
253
+ }
254
+ ```
255
+
256
+ ### Use Mutex / Locks When:
257
+
258
+ - **Synchronous access is required.** Callers cannot (or should not) be async.
259
+ Accessing an actor from synchronous code requires `Task` and introduces
260
+ unwanted asynchrony.
261
+ - **Performance-critical paths.** Lock acquisition is nanoseconds; actor hops
262
+ involve task scheduling. For tight loops or high-frequency access, a lock
263
+ may be significantly faster.
264
+ - **Bridging with C/ObjC.** C callbacks, delegate methods, or ObjC APIs that
265
+ cannot be made async.
266
+ - **Simple counters or flags.** `Atomic<Int>` or `Atomic<Bool>` is cheaper and
267
+ simpler than creating an actor for a single value.
268
+
269
+ ```swift
270
+ // GOOD: Mutex for synchronous, high-frequency access
271
+ final class MetricsCollector: Sendable {
272
+ let metrics = Mutex<[String: Int]>([:])
273
+
274
+ // Called from tight loops, C callbacks, or synchronous code
275
+ func increment(_ key: String) {
276
+ metrics.withLock { $0[key, default: 0] += 1 }
277
+ }
278
+
279
+ func snapshot() -> [String: Int] {
280
+ metrics.withLock { $0 }
281
+ }
282
+ }
283
+ ```
284
+
285
+ ### Decision Guide
286
+
287
+ ```text
288
+ Need shared mutable state protection?
289
+ ├── Can all access be async?
290
+ │ ├── Yes → Use an actor
291
+ │ └── No → Use Mutex or OSAllocatedUnfairLock
292
+ ├── Single scalar value (counter, flag)?
293
+ │ └── Use Atomic<Value>
294
+ ├── Performance-critical (nanosecond-level)?
295
+ │ └── Use Mutex or Atomic
296
+ └── Bridging C/ObjC callbacks?
297
+ └── Use Mutex or OSAllocatedUnfairLock
298
+ ```
299
+
300
+ ### Anti-Patterns
301
+
302
+ **Never put locks inside actors.** An actor already serializes access; adding
303
+ a lock creates double synchronization and risks deadlocks.
304
+
305
+ ```swift
306
+ // WRONG: Lock inside an actor — double synchronization
307
+ actor BadCache {
308
+ let lock = Mutex<[String: Data]>([:]) // Unnecessary!
309
+ // The actor already protects its state
310
+ }
311
+
312
+ // CORRECT: Just use the actor's built-in isolation
313
+ actor GoodCache {
314
+ var cache: [String: Data] = [:]
315
+
316
+ func store(_ data: Data, key: String) {
317
+ cache[key] = data
318
+ }
319
+ }
320
+ ```
321
+
322
+ **Never use `DispatchSemaphore` or `NSLock` in modern Swift.** Use `Mutex`
323
+ (iOS 18+) or `OSAllocatedUnfairLock` (iOS 16+) instead. Legacy lock types
324
+ have no `Sendable` conformance and are not designed for structured
325
+ concurrency.
326
+
327
+ **Never hold a lock across `await`.** This blocks the thread and can deadlock
328
+ the cooperative thread pool.
329
+
330
+ ```swift
331
+ // WRONG: Holding lock across suspension point
332
+ mutex.withLock { value in
333
+ value = await fetchData() // DEADLOCK RISK
334
+ }
335
+
336
+ // CORRECT: Fetch first, then lock to update
337
+ let data = await fetchData()
338
+ mutex.withLock { value in
339
+ value = data
340
+ }
341
+ ```