@mmerterden/multi-agent-pipeline 20.36.0 → 20.37.1

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 (222) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/docs/facts.json +2 -2
  3. package/docs/features.md +1 -1
  4. package/docs/recovery-guide.md +1 -1
  5. package/install/catalog-history.json +1 -1
  6. package/manifest.json +225 -148
  7. package/package.json +1 -1
  8. package/pipeline/commands/multi-agent/SKILL.md +33 -154
  9. package/pipeline/commands/multi-agent/analysis/SKILL.md +14 -68
  10. package/pipeline/commands/multi-agent/analysis-jira/SKILL.md +5 -8
  11. package/pipeline/commands/multi-agent/autopilot-on/SKILL.md +38 -109
  12. package/pipeline/commands/multi-agent/channels/SKILL.md +38 -167
  13. package/pipeline/commands/multi-agent/complaint-analysis/SKILL.md +37 -144
  14. package/pipeline/commands/multi-agent/design-check/SKILL.md +27 -131
  15. package/pipeline/commands/multi-agent/garbage-collect/SKILL.md +22 -48
  16. package/pipeline/commands/multi-agent/ios-coding-standard/SKILL.md +37 -180
  17. package/pipeline/commands/multi-agent/jira/SKILL.md +16 -46
  18. package/pipeline/commands/multi-agent/manual-test/SKILL.md +12 -7
  19. package/pipeline/commands/multi-agent/refactor/SKILL.md +23 -184
  20. package/pipeline/commands/multi-agent/resume/SKILL.md +30 -136
  21. package/pipeline/commands/multi-agent/review/SKILL.md +24 -199
  22. package/pipeline/commands/multi-agent/setup/SKILL.md +25 -181
  23. package/pipeline/commands/multi-agent/stack/SKILL.md +9 -53
  24. package/pipeline/commands/multi-agent/status/SKILL.md +13 -2
  25. package/pipeline/commands/multi-agent/store-ready/SKILL.md +27 -226
  26. package/pipeline/commands/multi-agent/sync/SKILL.md +35 -164
  27. package/pipeline/commands/multi-agent/test-gen/SKILL.md +2 -4
  28. package/pipeline/commands/multi-agent/update/SKILL.md +18 -60
  29. package/pipeline/contract/CHANGELOG.md +9 -0
  30. package/pipeline/contract/fixtures/error-invalid-request.json +1 -1
  31. package/pipeline/contract/fixtures/error-unauthorized.json +1 -1
  32. package/pipeline/contract/fixtures/error-unsigned.json +1 -1
  33. package/pipeline/contract/fixtures/runs-awaiting-question.json +1 -1
  34. package/pipeline/contract/fixtures/runs-empty.json +1 -1
  35. package/pipeline/contract/fixtures/runs-failed.json +1 -1
  36. package/pipeline/contract/fixtures/runs-pr-opened-redacted.json +1 -1
  37. package/pipeline/contract/fixtures/runs-pr-opened.json +1 -1
  38. package/pipeline/contract/fixtures/runs-running.json +1 -1
  39. package/pipeline/contract/manifest.json +5 -5
  40. package/pipeline/contract/types/index.d.ts +3 -2
  41. package/pipeline/multi-agent-refs/audit-guide.md +20 -0
  42. package/pipeline/multi-agent-refs/autopilot-on/details.md +148 -0
  43. package/pipeline/multi-agent-refs/channels/generator.md +14 -0
  44. package/pipeline/multi-agent-refs/channels/preview.md +13 -0
  45. package/pipeline/multi-agent-refs/channels/reference.md +46 -0
  46. package/pipeline/multi-agent-refs/complaint-analysis/phases.md +159 -0
  47. package/pipeline/multi-agent-refs/design-check/export.md +1 -0
  48. package/pipeline/multi-agent-refs/design-check/init-inventory.md +1 -1
  49. package/pipeline/multi-agent-refs/design-check/scope-and-ledger.md +64 -0
  50. package/pipeline/multi-agent-refs/features/analysis-command-notes.md +53 -0
  51. package/pipeline/multi-agent-refs/features/commit-multi-repo.md +63 -0
  52. package/pipeline/multi-agent-refs/features/dev-multi-repo.md +51 -0
  53. package/pipeline/multi-agent-refs/features/test-gap.md +40 -0
  54. package/pipeline/multi-agent-refs/features/worktree-finalize.md +1 -1
  55. package/pipeline/multi-agent-refs/garbage-collect/phases.md +60 -0
  56. package/pipeline/multi-agent-refs/ios-coding-standard/plan.md +71 -0
  57. package/pipeline/multi-agent-refs/ios-coding-standard/resolve-and-scan.md +135 -0
  58. package/pipeline/multi-agent-refs/jira/picker-detail.md +71 -0
  59. package/pipeline/multi-agent-refs/orchestrator/copilot-pipeline.md +88 -0
  60. package/pipeline/multi-agent-refs/orchestrator/dispatcher.md +96 -1
  61. package/pipeline/multi-agent-refs/orchestrator/phase-3-user-test.md +12 -9
  62. package/pipeline/multi-agent-refs/phases/phase-1-plan.md +4 -4
  63. package/pipeline/multi-agent-refs/phases/phase-2-dev.md +8 -47
  64. package/pipeline/multi-agent-refs/phases/phase-3-review.md +21 -65
  65. package/pipeline/multi-agent-refs/phases/phase-4-commit.md +9 -59
  66. package/pipeline/multi-agent-refs/phases/phase-5-report.md +0 -31
  67. package/pipeline/multi-agent-refs/refactor/sweep.md +190 -0
  68. package/pipeline/multi-agent-refs/resume/details.md +115 -2
  69. package/pipeline/multi-agent-refs/review/inputs.md +33 -1
  70. package/pipeline/multi-agent-refs/review/module-guides.md +9 -1
  71. package/pipeline/multi-agent-refs/review/panel.md +75 -0
  72. package/pipeline/multi-agent-refs/review/valven.md +5 -1
  73. package/pipeline/multi-agent-refs/rules.md +1 -1
  74. package/pipeline/multi-agent-refs/setup/hooks-and-plugins.md +4 -0
  75. package/pipeline/multi-agent-refs/setup/token-save-flow.md +13 -0
  76. package/pipeline/multi-agent-refs/stack/background.md +72 -0
  77. package/pipeline/multi-agent-refs/store-ready/gates.md +139 -0
  78. package/pipeline/multi-agent-refs/store-ready/report.md +40 -2
  79. package/pipeline/multi-agent-refs/sync/office.md +10 -0
  80. package/pipeline/multi-agent-refs/sync/overview.md +61 -0
  81. package/pipeline/multi-agent-refs/sync/repo.md +91 -0
  82. package/pipeline/multi-agent-refs/update/details.md +83 -0
  83. package/pipeline/office/index.html +6 -3
  84. package/pipeline/office/toolkit.json +1 -1
  85. package/pipeline/rules/figma-pipeline.md +6 -3
  86. package/pipeline/schemas/prefs.schema.json +1 -1
  87. package/pipeline/schemas/runs-index.schema.json +2 -1
  88. package/pipeline/scripts/_memory-policy.mjs +27 -5
  89. package/pipeline/scripts/checkpoint-fold.sh +59 -0
  90. package/pipeline/scripts/contract-server.mjs +1 -1
  91. package/pipeline/scripts/gate-ledger.mjs +1 -1
  92. package/pipeline/scripts/office-control.mjs +41 -15
  93. package/pipeline/scripts/offload-ref.sh +1 -1
  94. package/pipeline/scripts/rework-rulings.mjs +5 -3
  95. package/pipeline/scripts/runs-index.mjs +34 -7
  96. package/pipeline/scripts/scope-check-gate.mjs +15 -2
  97. package/pipeline/scripts/status-overview.mjs +401 -0
  98. package/pipeline/scripts/user-test-handoff.sh +203 -0
  99. package/pipeline/scripts/validate-planning.mjs +5 -3
  100. package/pipeline/scripts/validate-triage.mjs +2 -1
  101. package/pipeline/scripts/write-state.mjs +13 -4
  102. package/pipeline/skills/.skill-manifest.json +64 -64
  103. package/pipeline/skills/shared/core/apple-archive-compliance/SKILL.md +7 -70
  104. package/pipeline/skills/shared/core/apple-archive-compliance/references/examples.md +67 -0
  105. package/pipeline/skills/shared/core/apple-archive-compliance/references/version-history.md +8 -0
  106. package/pipeline/skills/shared/core/google-play-compliance/SKILL.md +10 -72
  107. package/pipeline/skills/shared/core/google-play-compliance/references/examples.md +69 -0
  108. package/pipeline/skills/shared/core/google-play-compliance/references/notes.md +7 -0
  109. package/pipeline/skills/shared/core/google-play-compliance/references/version-history.md +5 -0
  110. package/pipeline/skills/shared/core/multi-agent/SKILL.md +39 -262
  111. package/pipeline/skills/shared/core/multi-agent-autopilot-on/SKILL.md +35 -73
  112. package/pipeline/skills/shared/core/multi-agent-channels/SKILL.md +37 -337
  113. package/pipeline/skills/shared/core/multi-agent-design-check/SKILL.md +27 -122
  114. package/pipeline/skills/shared/core/multi-agent-help/SKILL.md +8 -340
  115. package/pipeline/skills/shared/core/multi-agent-ios-coding-standard/SKILL.md +37 -180
  116. package/pipeline/skills/shared/core/multi-agent-manual-test/SKILL.md +12 -7
  117. package/pipeline/skills/shared/core/multi-agent-refactor/SKILL.md +23 -184
  118. package/pipeline/skills/shared/core/multi-agent-review/SKILL.md +14 -82
  119. package/pipeline/skills/shared/core/multi-agent-setup/SKILL.md +25 -277
  120. package/pipeline/skills/shared/core/multi-agent-status/SKILL.md +13 -2
  121. package/pipeline/skills/shared/core/multi-agent-sync/SKILL.md +73 -217
  122. package/pipeline/skills/shared/external/alarmkit/SKILL.md +11 -162
  123. package/pipeline/skills/shared/external/alarmkit/references/alarmkit-code.md +257 -0
  124. package/pipeline/skills/shared/external/android-architecture/SKILL.md +7 -58
  125. package/pipeline/skills/shared/external/android-architecture/references/code-examples.md +68 -0
  126. package/pipeline/skills/shared/external/app-intents/SKILL.md +3 -45
  127. package/pipeline/skills/shared/external/app-intents/references/code-examples.md +54 -0
  128. package/pipeline/skills/shared/external/app-store-optimization/SKILL.md +5 -81
  129. package/pipeline/skills/shared/external/app-store-optimization/references/engagement-and-localization.md +96 -0
  130. package/pipeline/skills/shared/external/app-store-review/SKILL.md +6 -112
  131. package/pipeline/skills/shared/external/app-store-review/references/monetization-and-submission.md +138 -0
  132. package/pipeline/skills/shared/external/apple-on-device-ai/SKILL.md +10 -117
  133. package/pipeline/skills/shared/external/apple-on-device-ai/references/foundation-models-code.md +186 -0
  134. package/pipeline/skills/shared/external/authentication/SKILL.md +3 -50
  135. package/pipeline/skills/shared/external/authentication/references/code-examples.md +59 -0
  136. package/pipeline/skills/shared/external/callkit-voip/SKILL.md +3 -84
  137. package/pipeline/skills/shared/external/callkit-voip/references/code-examples.md +93 -0
  138. package/pipeline/skills/shared/external/ci-cd-pipelines/SKILL.md +7 -263
  139. package/pipeline/skills/shared/external/ci-cd-pipelines/references/pipeline-examples.md +281 -0
  140. package/pipeline/skills/shared/external/clean-code/SKILL.md +8 -41
  141. package/pipeline/skills/shared/external/clean-code/references/code-examples.md +56 -0
  142. package/pipeline/skills/shared/external/cloudkit-sync/SKILL.md +8 -146
  143. package/pipeline/skills/shared/external/cloudkit-sync/references/cloudkit-code.md +214 -0
  144. package/pipeline/skills/shared/external/compose-components/SKILL.md +6 -80
  145. package/pipeline/skills/shared/external/compose-components/references/code-examples.md +89 -0
  146. package/pipeline/skills/shared/external/compose-navigation/SKILL.md +5 -64
  147. package/pipeline/skills/shared/external/compose-navigation/references/code-examples.md +70 -0
  148. package/pipeline/skills/shared/external/core-data/SKILL.md +9 -157
  149. package/pipeline/skills/shared/external/core-data/references/core-data-code.md +221 -0
  150. package/pipeline/skills/shared/external/core-nfc/SKILL.md +3 -89
  151. package/pipeline/skills/shared/external/core-nfc/references/code-examples.md +98 -0
  152. package/pipeline/skills/shared/external/coreml/SKILL.md +17 -133
  153. package/pipeline/skills/shared/external/coreml/references/coreml-code.md +208 -0
  154. package/pipeline/skills/shared/external/css-modern/SKILL.md +8 -131
  155. package/pipeline/skills/shared/external/css-modern/references/code-examples.md +142 -0
  156. package/pipeline/skills/shared/external/database-patterns/SKILL.md +6 -103
  157. package/pipeline/skills/shared/external/database-patterns/references/orm-examples.md +112 -0
  158. package/pipeline/skills/shared/external/html-semantic/SKILL.md +7 -48
  159. package/pipeline/skills/shared/external/html-semantic/references/code-examples.md +54 -0
  160. package/pipeline/skills/shared/external/humanizer/SKILL.md +6 -19
  161. package/pipeline/skills/shared/external/humanizer/references/voice.md +24 -0
  162. package/pipeline/skills/shared/external/ios-accessibility/SKILL.md +13 -170
  163. package/pipeline/skills/shared/external/ios-accessibility/references/assistive-tech-review.md +224 -0
  164. package/pipeline/skills/shared/external/ios-localization/SKILL.md +2 -24
  165. package/pipeline/skills/shared/external/ios-localization/references/code-examples.md +30 -0
  166. package/pipeline/skills/shared/external/ios-networking/SKILL.md +3 -58
  167. package/pipeline/skills/shared/external/ios-networking/references/code-examples.md +67 -0
  168. package/pipeline/skills/shared/external/ios-security/SKILL.md +4 -64
  169. package/pipeline/skills/shared/external/ios-security/references/code-examples.md +74 -0
  170. package/pipeline/skills/shared/external/live-activities/SKILL.md +4 -90
  171. package/pipeline/skills/shared/external/live-activities/references/code-examples.md +108 -0
  172. package/pipeline/skills/shared/external/localization-reuse-map/SKILL.md +9 -23
  173. package/pipeline/skills/shared/external/localization-reuse-map/references/project-settings.md +22 -0
  174. package/pipeline/skills/shared/external/mapkit-location/SKILL.md +5 -95
  175. package/pipeline/skills/shared/external/mapkit-location/references/code-examples.md +117 -0
  176. package/pipeline/skills/shared/external/nextjs-app-router/SKILL.md +7 -106
  177. package/pipeline/skills/shared/external/nextjs-app-router/references/app-router-examples.md +124 -0
  178. package/pipeline/skills/shared/external/permissionkit/SKILL.md +2 -31
  179. package/pipeline/skills/shared/external/permissionkit/references/code-examples.md +37 -0
  180. package/pipeline/skills/shared/external/play-store-review/SKILL.md +6 -74
  181. package/pipeline/skills/shared/external/play-store-review/references/signing-and-policies.md +83 -0
  182. package/pipeline/skills/shared/external/push-notifications/SKILL.md +8 -125
  183. package/pipeline/skills/shared/external/push-notifications/references/notification-code.md +176 -0
  184. package/pipeline/skills/shared/external/python-patterns/SKILL.md +7 -158
  185. package/pipeline/skills/shared/external/python-patterns/references/python-examples.md +176 -0
  186. package/pipeline/skills/shared/external/rest-api-design/SKILL.md +7 -157
  187. package/pipeline/skills/shared/external/rest-api-design/references/api-examples.md +175 -0
  188. package/pipeline/skills/shared/external/swift-architecture/SKILL.md +11 -230
  189. package/pipeline/skills/shared/external/swift-architecture/references/pattern-examples.md +295 -0
  190. package/pipeline/skills/shared/external/swift-codable/SKILL.md +17 -149
  191. package/pipeline/skills/shared/external/swift-codable/references/codable-recipes.md +207 -0
  192. package/pipeline/skills/shared/external/swift-concurrency/SKILL.md +12 -99
  193. package/pipeline/skills/shared/external/swift-concurrency/references/code-examples.md +100 -0
  194. package/pipeline/skills/shared/external/swift-concurrency/references/swift-6-2-additions.md +18 -0
  195. package/pipeline/skills/shared/external/swift-formatstyle/SKILL.md +9 -78
  196. package/pipeline/skills/shared/external/swift-formatstyle/references/formatstyle-examples.md +113 -0
  197. package/pipeline/skills/shared/external/swift-language/SKILL.md +3 -56
  198. package/pipeline/skills/shared/external/swift-language/references/code-examples.md +65 -0
  199. package/pipeline/skills/shared/external/swift-testing-pro/SKILL.md +1 -0
  200. package/pipeline/skills/shared/external/swiftdata/SKILL.md +6 -122
  201. package/pipeline/skills/shared/external/swiftdata/references/code-examples.md +140 -0
  202. package/pipeline/skills/shared/external/swiftui-uikit-interop/SKILL.md +12 -139
  203. package/pipeline/skills/shared/external/swiftui-uikit-interop/references/interop-code.md +198 -0
  204. package/pipeline/skills/shared/external/tailwind-css/SKILL.md +9 -81
  205. package/pipeline/skills/shared/external/tailwind-css/references/code-examples.md +91 -0
  206. package/pipeline/skills/shared/external/testing-backend/SKILL.md +7 -207
  207. package/pipeline/skills/shared/external/testing-backend/references/testing-examples.md +225 -0
  208. package/pipeline/skills/shared/external/typescript-patterns/SKILL.md +8 -89
  209. package/pipeline/skills/shared/external/typescript-patterns/references/code-examples.md +100 -0
  210. package/pipeline/skills/shared/external/vision-framework/SKILL.md +2 -20
  211. package/pipeline/skills/shared/external/vision-framework/references/code-examples.md +26 -0
  212. package/pipeline/skills/shared/external/vue-composition/SKILL.md +7 -169
  213. package/pipeline/skills/shared/external/vue-composition/references/vue-examples.md +187 -0
  214. package/pipeline/skills/shared/external/web-accessibility/SKILL.md +7 -174
  215. package/pipeline/skills/shared/external/web-accessibility/references/a11y-examples.md +192 -0
  216. package/pipeline/skills/shared/external/web-performance/SKILL.md +8 -66
  217. package/pipeline/skills/shared/external/web-performance/references/code-examples.md +77 -0
  218. package/pipeline/skills/shared/external/web-testing/SKILL.md +11 -186
  219. package/pipeline/skills/shared/external/web-testing/references/testing-examples.md +213 -0
  220. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/format-and-output.md +0 -0
  221. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/publish-and-snapshot.md +0 -0
  222. /package/pipeline/skills/shared/external/localization-reuse-map/{reference → references}/sources-and-recipes.md +0 -0
@@ -124,191 +124,33 @@ transformations of the same shared state.
124
124
  A view model sits between the view and the model: it prepares display values
125
125
  and receives user actions, and the view watches it.
126
126
 
127
- ```swift
128
- struct WorkoutRow: Identifiable {
129
- let id: Workout.ID
130
- let title: String
131
- let when: String
132
-
133
- init(_ workout: Workout) {
134
- id = workout.id
135
- title = workout.title
136
- when = workout.start.formatted(.dateTime.month().day())
137
- }
138
- }
139
-
140
- @MainActor
141
- @Observable
142
- final class WorkoutListModel {
143
- private(set) var rows: [WorkoutRow] = []
144
- private(set) var loading = false
145
- var filter = ""
146
-
147
- private var all: [Workout] = []
148
- private let store: any WorkoutStore
149
-
150
- init(store: any WorkoutStore) { self.store = store }
151
-
152
- var visibleRows: [WorkoutRow] {
153
- filter.isEmpty ? rows : rows.filter { $0.title.localizedStandardContains(filter) }
154
- }
155
-
156
- func load() async {
157
- loading = true
158
- defer { loading = false }
159
- all = (try? await store.allWorkouts()) ?? []
160
- rows = all.map(WorkoutRow.init)
161
- }
162
-
163
- func delete(rowsAt positions: IndexSet) async {
164
- let doomed = positions.map { visibleRows[$0].id }
165
- for id in doomed { try? await store.delete(id) }
166
- await load()
167
- }
168
- }
169
-
170
- struct WorkoutListScreen: View {
171
- @State private var model: WorkoutListModel
172
-
173
- init(store: any WorkoutStore) {
174
- _model = State(initialValue: WorkoutListModel(store: store))
175
- }
176
-
177
- var body: some View {
178
- List {
179
- ForEach(model.visibleRows, id: \.id) { row in
180
- LabeledContent(row.title, value: row.when)
181
- }
182
- .onDelete { positions in Task { await model.delete(rowsAt: positions) } }
183
- }
184
- .searchable(text: $model.filter)
185
- .task { await model.load() }
186
- }
187
- }
188
- ```
127
+ The payoff is a view model you can test without any view.
189
128
 
190
- The payoff is a view model you can test without any view:
191
-
192
- ```swift
193
- @Test @MainActor
194
- func filterMatchesTitle() async {
195
- let model = WorkoutListModel(store: FakeWorkoutStore(titles: ["Row", "Run", "Swim"]))
196
- await model.load()
197
- model.filter = "ru"
198
- #expect(model.visibleRows.count == 1)
199
- }
200
- ```
129
+ Code for this section: [Architecture pattern examples](references/pattern-examples.md).
201
130
 
202
131
  ## MVI
203
132
 
204
133
  Data moves one way. The view emits intents, a single function applies each
205
134
  intent to the state, and any work with side effects is spelled out there.
206
135
 
207
- ```swift
208
- @MainActor
209
- @Observable
210
- final class WorkoutFeed {
211
- struct State {
212
- var sessions: [Workout] = []
213
- var loading = false
214
- var message: String?
215
- }
216
-
217
- enum Intent {
218
- case appeared
219
- case remove(Workout.ID)
220
- case dismissMessage
221
- }
222
-
223
- private(set) var state = State()
224
- private let store: any WorkoutStore
225
-
226
- init(store: any WorkoutStore) { self.store = store }
227
-
228
- func send(_ intent: Intent) {
229
- Task { await apply(intent) }
230
- }
231
-
232
- private func apply(_ next: Intent) async {
233
- switch next {
234
- case .appeared:
235
- state.loading = true
236
- do { state.sessions = try await store.allWorkouts() }
237
- catch { state.message = error.localizedDescription }
238
- state.loading = false
239
- case .remove(let id):
240
- do {
241
- try await store.delete(id)
242
- state.sessions.removeAll { $0.id == id }
243
- } catch {
244
- state.message = error.localizedDescription
245
- }
246
- case .dismissMessage:
247
- state.message = nil
248
- }
249
- }
250
- }
251
- ```
252
-
253
136
  What it buys: every transition is predictable, intents can be logged and
254
137
  replayed, and "what happened" (the intent) is kept apart from "what changed"
255
138
  (the state).
256
139
 
140
+ Code for this section: [Architecture pattern examples](references/pattern-examples.md).
141
+
257
142
  ## TCA
258
143
 
259
144
  The Composable Architecture is an open-source library. Features are reducers
260
145
  that compose; dependencies are injected through a registry; effects are values
261
146
  the store runs; tests can assert every state change exhaustively.
262
147
 
263
- ```swift
264
- import ComposableArchitecture
265
-
266
- @Reducer
267
- struct WorkoutsFeature {
268
- @ObservableState
269
- struct State: Equatable {
270
- var sessions: IdentifiedArrayOf<Workout> = []
271
- var loading = false
272
- }
273
-
274
- enum Action {
275
- case onAppear
276
- case loaded([Workout])
277
- case removeTapped(Workout.ID)
278
- }
279
-
280
- @Dependency(\.workoutClient) var workoutClient
281
-
282
- var body: some ReducerOf<Self> {
283
- Reduce { state, action in
284
- switch action {
285
- case .onAppear where !state.loading:
286
- state.loading = true
287
- return .run { send in
288
- let sessions = try await workoutClient.fetchAll()
289
- await send(.loaded(sessions))
290
- }
291
- case .loaded(let sessions):
292
- state.loading = false
293
- state.sessions = IdentifiedArray(uniqueElements: sessions)
294
- return .none
295
- case .onAppear:
296
- return .none
297
- case .removeTapped(let id):
298
- state.sessions.remove(id: id)
299
- return .run { [id] _ in
300
- try await workoutClient.delete(id)
301
- }
302
- }
303
- }
304
- }
305
- }
306
- ```
307
-
308
148
  Choose it for complex flows that must be deterministic, effects that need
309
149
  sequencing, features composed from smaller features, reducer-level testing, and
310
150
  dependency injection across the whole app.
311
151
 
152
+ Code for this section: [Architecture pattern examples](references/pattern-examples.md).
153
+
312
154
  ## Clean Architecture
313
155
 
314
156
  Three layers with dependencies pointing inward:
@@ -318,51 +160,12 @@ Three layers with dependencies pointing inward:
318
160
  - **Data**: repository implementations, networking, persistence.
319
161
  - **Presentation**: views and view models.
320
162
 
321
- ```swift
322
- // Domain
323
- protocol WorkoutRepository: Sendable {
324
- func fetchAll() async throws -> [Workout]
325
- func save(_ workout: Workout) async throws
326
- func delete(_ id: Workout.ID) async throws
327
- }
328
-
329
- struct UpcomingWorkouts: Sendable {
330
- let repository: any WorkoutRepository
331
-
332
- func callAsFunction(after now: Date = .now) async throws -> [Workout] {
333
- let everything = try await repository.fetchAll()
334
- return everything
335
- .filter { $0.start > now }
336
- .sorted { $0.start < $1.start }
337
- }
338
- }
339
-
340
- // Data
341
- struct RemoteWorkoutRepository: WorkoutRepository {
342
- let api: FitnessAPI
343
-
344
- func fetchAll() async throws -> [Workout] { try await api.get("/workouts") }
345
- func save(_ workout: Workout) async throws { try await api.post("/workouts", body: workout) }
346
- func delete(_ id: Workout.ID) async throws { try await api.delete("/workouts/\(id)") }
347
- }
348
-
349
- // Presentation
350
- @MainActor
351
- @Observable
352
- final class PlannerModel {
353
- private(set) var upcoming: [Workout] = []
354
- private let upcomingWorkouts: UpcomingWorkouts
355
-
356
- init(upcomingWorkouts: UpcomingWorkouts) { self.upcomingWorkouts = upcomingWorkouts }
357
-
358
- func load() async { upcoming = (try? await upcomingWorkouts()) ?? [] }
359
- }
360
- ```
361
-
362
163
  Reach for it when rules or regulation demand hard separation, when domain code
363
164
  has to be tested with no framework present, or when an app, a widget and a
364
165
  watch app all run one set of business rules.
365
166
 
167
+ Code for this section: [Architecture pattern examples](references/pattern-examples.md).
168
+
366
169
  ## Coordinator pattern
367
170
 
368
171
  Coordinators take navigation out of views and controllers. They pay off mostly
@@ -374,35 +177,12 @@ in UIKit and hybrid apps.
374
177
  coordinator decides whether to push or present.
375
178
  - Feature models keep the business logic. The coordinator only routes.
376
179
 
377
- ```swift
378
- @MainActor
379
- final class OnboardingCoordinator {
380
- private let navigation: UINavigationController
381
- private let account: AccountService
382
-
383
- init(navigation: UINavigationController, account: AccountService) {
384
- self.navigation = navigation
385
- self.account = account
386
- }
387
-
388
- func start() {
389
- let welcome = WelcomeViewController(onContinue: { [weak self] in self?.showSignUp() })
390
- navigation.setViewControllers([welcome], animated: false)
391
- }
392
-
393
- private func showSignUp() {
394
- let form = SignUpViewController(account: account, onDone: { [weak self] in self?.finish() })
395
- navigation.pushViewController(form, animated: true)
396
- }
397
-
398
- private func finish() { navigation.dismiss(animated: true) }
399
- }
400
- ```
401
-
402
180
  In a pure SwiftUI app, a `NavigationStack` driven by a path usually does this
403
181
  job without a coordinator. Keep coordinators for UIKit integration or for
404
182
  navigation logic shared across platforms.
405
183
 
184
+ Code for this section: [Architecture pattern examples](references/pattern-examples.md).
185
+
406
186
  ## VIPER
407
187
 
408
188
  View, Interactor, Presenter, Entity, Router. Its value today is keeping
@@ -477,6 +257,7 @@ while adoption is in progress.
477
257
 
478
258
  ## References
479
259
 
260
+ - [Architecture pattern examples](references/pattern-examples.md): open when implementing or reviewing a feature in MVVM, MVI, TCA, Clean Architecture or with coordinators.
480
261
  - [Observation](https://developer.apple.com/documentation/observation)
481
262
  - [Observable()](https://developer.apple.com/documentation/observation/observable())
482
263
  - [Migrating from the Observable Object protocol to the Observable macro](https://developer.apple.com/documentation/swiftui/migrating-from-the-observable-object-protocol-to-the-observable-macro)
@@ -0,0 +1,295 @@
1
+ # Architecture pattern examples
2
+
3
+ <!-- toc -->
4
+ - [MVVM](#mvvm)
5
+ - [MVI](#mvi)
6
+ - [TCA](#tca)
7
+ - [Clean Architecture](#clean-architecture)
8
+ - [Coordinator pattern](#coordinator-pattern)
9
+ <!-- /toc -->
10
+
11
+ Worked code for MVVM, MVI, TCA, Clean Architecture and the Coordinator pattern. SKILL.md keeps the selection guidance and the rules for each pattern.
12
+
13
+ ## MVVM
14
+
15
+ A view model sits between the view and the model: it prepares display values
16
+ and receives user actions, and the view watches it.
17
+
18
+ ```swift
19
+ struct WorkoutRow: Identifiable {
20
+ let id: Workout.ID
21
+ let title: String
22
+ let when: String
23
+
24
+ init(_ workout: Workout) {
25
+ id = workout.id
26
+ title = workout.title
27
+ when = workout.start.formatted(.dateTime.month().day())
28
+ }
29
+ }
30
+
31
+ @MainActor
32
+ @Observable
33
+ final class WorkoutListModel {
34
+ private(set) var rows: [WorkoutRow] = []
35
+ private(set) var loading = false
36
+ var filter = ""
37
+
38
+ private var all: [Workout] = []
39
+ private let store: any WorkoutStore
40
+
41
+ init(store: any WorkoutStore) { self.store = store }
42
+
43
+ var visibleRows: [WorkoutRow] {
44
+ filter.isEmpty ? rows : rows.filter { $0.title.localizedStandardContains(filter) }
45
+ }
46
+
47
+ func load() async {
48
+ loading = true
49
+ defer { loading = false }
50
+ all = (try? await store.allWorkouts()) ?? []
51
+ rows = all.map(WorkoutRow.init)
52
+ }
53
+
54
+ func delete(rowsAt positions: IndexSet) async {
55
+ let doomed = positions.map { visibleRows[$0].id }
56
+ for id in doomed { try? await store.delete(id) }
57
+ await load()
58
+ }
59
+ }
60
+
61
+ struct WorkoutListScreen: View {
62
+ @State private var model: WorkoutListModel
63
+
64
+ init(store: any WorkoutStore) {
65
+ _model = State(initialValue: WorkoutListModel(store: store))
66
+ }
67
+
68
+ var body: some View {
69
+ List {
70
+ ForEach(model.visibleRows, id: \.id) { row in
71
+ LabeledContent(row.title, value: row.when)
72
+ }
73
+ .onDelete { positions in Task { await model.delete(rowsAt: positions) } }
74
+ }
75
+ .searchable(text: $model.filter)
76
+ .task { await model.load() }
77
+ }
78
+ }
79
+ ```
80
+
81
+ The payoff is a view model you can test without any view:
82
+
83
+ ```swift
84
+ @Test @MainActor
85
+ func filterMatchesTitle() async {
86
+ let model = WorkoutListModel(store: FakeWorkoutStore(titles: ["Row", "Run", "Swim"]))
87
+ await model.load()
88
+ model.filter = "ru"
89
+ #expect(model.visibleRows.count == 1)
90
+ }
91
+ ```
92
+
93
+ ## MVI
94
+
95
+ Data moves one way. The view emits intents, a single function applies each
96
+ intent to the state, and any work with side effects is spelled out there.
97
+
98
+ ```swift
99
+ @MainActor
100
+ @Observable
101
+ final class WorkoutFeed {
102
+ struct State {
103
+ var sessions: [Workout] = []
104
+ var loading = false
105
+ var message: String?
106
+ }
107
+
108
+ enum Intent {
109
+ case appeared
110
+ case remove(Workout.ID)
111
+ case dismissMessage
112
+ }
113
+
114
+ private(set) var state = State()
115
+ private let store: any WorkoutStore
116
+
117
+ init(store: any WorkoutStore) { self.store = store }
118
+
119
+ func send(_ intent: Intent) {
120
+ Task { await apply(intent) }
121
+ }
122
+
123
+ private func apply(_ next: Intent) async {
124
+ switch next {
125
+ case .appeared:
126
+ state.loading = true
127
+ do { state.sessions = try await store.allWorkouts() }
128
+ catch { state.message = error.localizedDescription }
129
+ state.loading = false
130
+ case .remove(let id):
131
+ do {
132
+ try await store.delete(id)
133
+ state.sessions.removeAll { $0.id == id }
134
+ } catch {
135
+ state.message = error.localizedDescription
136
+ }
137
+ case .dismissMessage:
138
+ state.message = nil
139
+ }
140
+ }
141
+ }
142
+ ```
143
+
144
+ What it buys: every transition is predictable, intents can be logged and
145
+ replayed, and "what happened" (the intent) is kept apart from "what changed"
146
+ (the state).
147
+
148
+ ## TCA
149
+
150
+ The Composable Architecture is an open-source library. Features are reducers
151
+ that compose; dependencies are injected through a registry; effects are values
152
+ the store runs; tests can assert every state change exhaustively.
153
+
154
+ ```swift
155
+ import ComposableArchitecture
156
+
157
+ @Reducer
158
+ struct WorkoutsFeature {
159
+ @ObservableState
160
+ struct State: Equatable {
161
+ var sessions: IdentifiedArrayOf<Workout> = []
162
+ var loading = false
163
+ }
164
+
165
+ enum Action {
166
+ case onAppear
167
+ case loaded([Workout])
168
+ case removeTapped(Workout.ID)
169
+ }
170
+
171
+ @Dependency(\.workoutClient) var workoutClient
172
+
173
+ var body: some ReducerOf<Self> {
174
+ Reduce { state, action in
175
+ switch action {
176
+ case .onAppear where !state.loading:
177
+ state.loading = true
178
+ return .run { send in
179
+ let sessions = try await workoutClient.fetchAll()
180
+ await send(.loaded(sessions))
181
+ }
182
+ case .loaded(let sessions):
183
+ state.loading = false
184
+ state.sessions = IdentifiedArray(uniqueElements: sessions)
185
+ return .none
186
+ case .onAppear:
187
+ return .none
188
+ case .removeTapped(let id):
189
+ state.sessions.remove(id: id)
190
+ return .run { [id] _ in
191
+ try await workoutClient.delete(id)
192
+ }
193
+ }
194
+ }
195
+ }
196
+ }
197
+ ```
198
+
199
+ Choose it for complex flows that must be deterministic, effects that need
200
+ sequencing, features composed from smaller features, reducer-level testing, and
201
+ dependency injection across the whole app.
202
+
203
+ ## Clean Architecture
204
+
205
+ Three layers with dependencies pointing inward:
206
+
207
+ - **Domain**: the entities, the use cases, and the protocols repositories must
208
+ satisfy. It imports no UI or persistence framework.
209
+ - **Data**: repository implementations, networking, persistence.
210
+ - **Presentation**: views and view models.
211
+
212
+ ```swift
213
+ // Domain
214
+ protocol WorkoutRepository: Sendable {
215
+ func fetchAll() async throws -> [Workout]
216
+ func save(_ workout: Workout) async throws
217
+ func delete(_ id: Workout.ID) async throws
218
+ }
219
+
220
+ struct UpcomingWorkouts: Sendable {
221
+ let repository: any WorkoutRepository
222
+
223
+ func callAsFunction(after now: Date = .now) async throws -> [Workout] {
224
+ let everything = try await repository.fetchAll()
225
+ return everything
226
+ .filter { $0.start > now }
227
+ .sorted { $0.start < $1.start }
228
+ }
229
+ }
230
+
231
+ // Data
232
+ struct RemoteWorkoutRepository: WorkoutRepository {
233
+ let api: FitnessAPI
234
+
235
+ func fetchAll() async throws -> [Workout] { try await api.get("/workouts") }
236
+ func save(_ workout: Workout) async throws { try await api.post("/workouts", body: workout) }
237
+ func delete(_ id: Workout.ID) async throws { try await api.delete("/workouts/\(id)") }
238
+ }
239
+
240
+ // Presentation
241
+ @MainActor
242
+ @Observable
243
+ final class PlannerModel {
244
+ private(set) var upcoming: [Workout] = []
245
+ private let upcomingWorkouts: UpcomingWorkouts
246
+
247
+ init(upcomingWorkouts: UpcomingWorkouts) { self.upcomingWorkouts = upcomingWorkouts }
248
+
249
+ func load() async { upcoming = (try? await upcomingWorkouts()) ?? [] }
250
+ }
251
+ ```
252
+
253
+ Reach for it when rules or regulation demand hard separation, when domain code
254
+ has to be tested with no framework present, or when an app, a widget and a
255
+ watch app all run one set of business rules.
256
+
257
+ ## Coordinator pattern
258
+
259
+ Coordinators take navigation out of views and controllers. They pay off mostly
260
+ in UIKit and hybrid apps.
261
+
262
+ - A coordinator is `@MainActor` and receives its dependencies when it is
263
+ created.
264
+ - View models and view controllers report user choices through callbacks; the
265
+ coordinator decides whether to push or present.
266
+ - Feature models keep the business logic. The coordinator only routes.
267
+
268
+ ```swift
269
+ @MainActor
270
+ final class OnboardingCoordinator {
271
+ private let navigation: UINavigationController
272
+ private let account: AccountService
273
+
274
+ init(navigation: UINavigationController, account: AccountService) {
275
+ self.navigation = navigation
276
+ self.account = account
277
+ }
278
+
279
+ func start() {
280
+ let welcome = WelcomeViewController(onContinue: { [weak self] in self?.showSignUp() })
281
+ navigation.setViewControllers([welcome], animated: false)
282
+ }
283
+
284
+ private func showSignUp() {
285
+ let form = SignUpViewController(account: account, onDone: { [weak self] in self?.finish() })
286
+ navigation.pushViewController(form, animated: true)
287
+ }
288
+
289
+ private func finish() { navigation.dismiss(animated: true) }
290
+ }
291
+ ```
292
+
293
+ In a pure SwiftUI app, a `NavigationStack` driven by a path usually does this
294
+ job without a coordinator. Keep coordinators for UIKit integration or for
295
+ navigation logic shared across platforms.