Files
2026-07-05 23:17:00 -07:00

369 lines
20 KiB
JSON

{
"version": 1,
"cases": [
{
"id": "clean-domain-ui-import",
"notes": "Expected smell: domain use case imports SwiftUI type.\nExpected direction: return plain domain/presentation data and map to UI in presentation layer.\n",
"prompt": "Use `swift-architecture-skill` to review this Clean Architecture snippet.\n\nContext:\n- Team wants strict dependency rule\n- Domain layer should not depend on UI frameworks\n\n```swift\nimport Foundation\nimport SwiftUI\n\nstruct OrderSummaryUseCase {\n func makeBadgeText(total: Double) -> Text {\n Text(\"Total: \\(total)\")\n }\n}\n```\n\nRequest:\n1. Identify the dependency issue.\n2. Provide a boundary-correct fix.\n3. Keep recommendation practical for a staged refactor.\n",
"required_keywords": [
"domain",
"swiftui",
"dependency rule",
"presentation"
],
"forbidden_keywords": [
"this is clean architecture",
"text in usecase is fine"
]
},
{
"id": "mvvm-uikit-navigation",
"notes": "Expected smell: ViewModel directly owns UIKit navigation objects.\nExpected direction: move navigation execution to coordinator/router while keeping ViewModel value-oriented.\n",
"prompt": "Use `swift-architecture-skill` to review this code and suggest architecture fixes.\n\nContext:\n- Existing pattern: MVVM\n- UI stack: mixed SwiftUI/UIKit\n- Goal: keep ViewModel testable and framework-independent\n\n```swift\nimport Foundation\nimport UIKit\n\n@MainActor\nfinal class FeedViewModel {\n private let api: FeedAPI\n private weak var navigationController: UINavigationController?\n\n init(api: FeedAPI, navigationController: UINavigationController?) {\n self.api = api\n self.navigationController = navigationController\n }\n\n func didTapCompose() {\n let compose = ComposeViewController()\n navigationController?.pushViewController(compose, animated: true)\n }\n}\n```\n\nRequest:\n1. Identify architecture smells.\n2. Recommend an MVVM-consistent fix.\n3. Show a small refactor plan (2-4 steps).\n",
"required_keywords": [
"uikit",
"viewmodel",
"coordinator",
"navigation"
],
"forbidden_keywords": [
"no issues",
"looks good",
"keep as is"
]
},
{
"id": "reactive-nested-subscriptions",
"notes": "Expected smell: subscribe-inside-subscribe with weak cancellation semantics.\nExpected direction: compose operators (`map` + `switchToLatest`) and keep lifecycle-bound cancellation.\n",
"prompt": "Use `swift-architecture-skill` to review this reactive implementation.\n\nContext:\n- Search feature with Combine\n- Requirement: replace stale requests, avoid leaks\n\n```swift\nimport Combine\n\nfinal class SearchPresenter {\n private let query = PassthroughSubject<String, Never>()\n private var cancellables = Set<AnyCancellable>()\n\n init(service: SearchService) {\n query.sink { value in\n service.search(value).sink(\n receiveCompletion: { _ in },\n receiveValue: { results in\n print(results)\n }\n ).store(in: &self.cancellables)\n }\n .store(in: &cancellables)\n }\n}\n```\n\nRequest:\n1. Identify architecture/reactive smells.\n2. Recommend a better stream composition pattern.\n3. Mention cancellation behavior.\n",
"required_keywords": [
"nested subscription",
"switchtolatest",
"cancellation",
"lifecycle"
],
"required_any": [
[
"nested subscription",
"nested sink",
"subscribe-inside-subscribe",
"subscribe inside subscribe"
],
[
"switchtolatest",
"switchToLatest",
"switch to latest"
],
[
"cancellation",
"cancel"
],
[
"lifecycle",
"deinit",
"dealloc",
"teardown"
]
],
"forbidden_keywords": [
"no problem",
"nested sink is fine"
]
},
{
"id": "swiftui-view-does-network",
"notes": "Expected smell: view performs data fetching directly and bypasses DI.\nExpected direction: move effects to ViewModel/service boundary with explicit error/cancel behavior.\n",
"prompt": "Use `swift-architecture-skill` to review this SwiftUI feature.\n\nContext:\n- Team wants MVVM boundaries\n- App uses async/await and dependency injection\n\n```swift\nimport SwiftUI\n\nstruct ProfileView: View {\n @State private var name = \"\"\n\n var body: some View {\n VStack {\n Text(name)\n Button(\"Load\") {\n Task {\n let user = try? await URLSession.shared\n .data(from: URL(string: \"https://example.com/user\")!)\n name = \"Loaded\"\n }\n }\n }\n }\n}\n```\n\nRequest:\n1. Identify smells.\n2. Propose a better architecture boundary.\n3. Mention async error-handling and cancellation expectations.\n",
"required_keywords": [
"viewmodel",
"dependency injection",
"error",
"cancellation"
],
"forbidden_keywords": [
"urlsession.shared is fine",
"keep network calls in view",
"no change needed"
]
},
{
"id": "tca-singleton-dependency",
"notes": "Expected smell: reducer calling global singleton directly.\nExpected direction: inject analytics via TCA dependency system for testability.\n",
"prompt": "Use `swift-architecture-skill` to review this TCA reducer.\n\nContext:\n- Existing architecture: TCA\n- Testing requirement: deterministic TestStore tests\n\n```swift\nimport ComposableArchitecture\n\n@Reducer\nstruct SettingsFeature {\n @ObservableState\n struct State: Equatable {\n var isEnabled = false\n }\n\n enum Action: Equatable {\n case toggleChanged(Bool)\n }\n\n var body: some ReducerOf<Self> {\n Reduce { state, action in\n switch action {\n case let .toggleChanged(value):\n state.isEnabled = value\n Analytics.shared.track(\"toggle\", [\"value\": value])\n return .none\n }\n }\n }\n}\n```\n\nRequest:\n1. Identify architectural smell(s).\n2. Propose the TCA-correct dependency approach.\n3. Explain testing impact.\n",
"required_keywords": [
"singleton",
"@dependency",
"teststore",
"side effect"
],
"forbidden_keywords": [
"analytics.shared is acceptable",
"no architecture issue"
]
},
{
"id": "mvp-strong-view-reference",
"notes": "Expected smell: Presenter holds a strong reference to the View protocol, creating a retain cycle when the ViewController owns the Presenter.\nExpected direction: view reference must be weak; the View protocol must be class-constrained.\n",
"prompt": "Use `swift-architecture-skill` to review this MVP module.\n\nContext:\n- UIKit codebase migrating from MVC\n- Goal: testable Presenter with a fully passive View\n\n```swift\nimport UIKit\n\nprotocol ProfileView {\n func showName(_ name: String)\n func showError(_ message: String)\n}\n\nfinal class ProfilePresenter {\n var view: ProfileView\n private let service: UserService\n\n init(view: ProfileView, service: UserService) {\n self.view = view\n self.service = service\n }\n\n func load() async {\n do {\n let user = try await service.fetchUser()\n view.showName(user.name)\n } catch {\n view.showError(error.localizedDescription)\n }\n }\n}\n\nfinal class ProfileViewController: UIViewController, ProfileView {\n private let presenter: ProfilePresenter\n\n init(service: UserService) {\n self.presenter = ProfilePresenter(view: self, service: service)\n super.init(nibName: nil, bundle: nil)\n }\n\n required init?(coder: NSCoder) { fatalError() }\n\n func showName(_ name: String) {}\n func showError(_ message: String) {}\n}\n```\n\nRequest:\n1. Identify the memory management smell.\n2. Recommend the correct MVP ownership and reference pattern.\n3. Show the minimal fix.\n",
"required_keywords": [
"retain cycle",
"weak",
"anyobject",
"presenter"
],
"required_any": [
[
"retain cycle",
"strong cycle",
"reference cycle",
"circular reference"
],
[
"weak",
"weak var",
"weakly"
],
[
"anyobject",
"class-constrained",
"class constraint"
]
],
"forbidden_keywords": [
"no issue",
"this is fine",
"strong is acceptable"
]
},
{
"id": "mvi-missing-request-id",
"notes": "Expected smell: Store dispatches async effects without tracking a request ID, so stale responses can overwrite newer state.\nExpected direction: add a latestRequestID counter to State and guard reducer callbacks so only the most recent response is applied.\n",
"prompt": "Use `swift-architecture-skill` to review this MVI store.\n\nContext:\n- SwiftUI feature with a search input\n- Users can trigger rapid sequential loads\n- Goal: deterministic unidirectional state, no stale overwrites\n\n```swift\nimport Foundation\n\nenum SearchAction {\n case search(String)\n case loaded([String])\n case failed(Error)\n}\n\nstruct SearchState {\n var results: [String] = []\n var isLoading = false\n var error: Error? = nil\n}\n\n@MainActor\nfinal class SearchStore: ObservableObject {\n @Published private(set) var state = SearchState()\n private let service: SearchService\n\n init(service: SearchService) {\n self.service = service\n }\n\n func send(_ action: SearchAction) {\n switch action {\n case let .search(query):\n state.isLoading = true\n state.error = nil\n Task {\n do {\n let results = try await service.search(query)\n send(.loaded(results))\n } catch {\n send(.failed(error))\n }\n }\n case let .loaded(results):\n state.isLoading = false\n state.results = results\n case let .failed(error):\n state.isLoading = false\n state.error = error\n }\n }\n}\n```\n\nRequest:\n1. Identify the concurrency smell.\n2. Explain why rapid sequential searches can corrupt state.\n3. Recommend the MVI-correct fix with a request ID guard.\n",
"required_keywords": [
"stale",
"request id",
"latestRequestID",
"cancellation"
],
"required_any": [
[
"stale",
"stale response",
"out-of-order",
"race"
],
[
"request id",
"requestID",
"latestRequestID",
"generation counter"
],
[
"cancellation",
"cancel",
"task cancellation"
]
],
"forbidden_keywords": [
"no issue",
"this is safe",
"task order is guaranteed"
]
},
{
"id": "coordinator-navigation-in-viewcontroller",
"notes": "Expected smell: ViewController owns and executes navigation transitions directly, coupling screen logic to flow logic.\nExpected direction: extract navigation responsibility to a Coordinator; ViewController delegates via a protocol or closure.\n",
"prompt": "Use `swift-architecture-skill` to review this UIKit navigation pattern.\n\nContext:\n- Multi-screen UIKit app\n- Goal: reusable flows, deep-link support, testable routing\n\n```swift\nimport UIKit\n\nfinal class FeedViewController: UIViewController {\n private let service: FeedService\n private var items: [FeedItem] = []\n\n init(service: FeedService) {\n self.service = service\n super.init(nibName: nil, bundle: nil)\n }\n\n required init?(coder: NSCoder) { fatalError() }\n\n func didSelectItem(_ item: FeedItem) {\n let detail = DetailViewController(item: item)\n navigationController?.pushViewController(detail, animated: true)\n }\n\n func didTapCompose() {\n let compose = ComposeViewController(service: service)\n let nav = UINavigationController(rootViewController: compose)\n present(nav, animated: true)\n }\n\n func didTapProfile() {\n let profile = ProfileViewController()\n navigationController?.pushViewController(profile, animated: true)\n }\n}\n```\n\nRequest:\n1. Identify the architecture smell.\n2. Explain why this blocks reuse and deep linking.\n3. Show how a Coordinator would own these transitions instead.\n",
"required_keywords": [
"coordinator",
"navigation",
"decouple",
"deep link"
],
"required_any": [
[
"coordinator",
"flow coordinator",
"app coordinator"
],
[
"decouple",
"decoupled",
"separation"
],
[
"deep link",
"deep linking",
"reusable flow"
],
[
"delegate",
"closure",
"callback"
]
],
"forbidden_keywords": [
"no issue",
"navigationcontroller in viewcontroller is fine",
"this is standard"
]
},
{
"id": "viper-retain-cycle",
"notes": "Expected smell: strong bidirectional references in module wiring.\nExpected direction: apply weak references and explicit ownership boundaries.\n",
"prompt": "Use `swift-architecture-skill` to review this VIPER wiring.\n\nContext:\n- Large UIKit codebase\n- Concern: memory leaks during repeated navigation\n\n```swift\nprotocol ProfileView: AnyObject {}\n\nfinal class ProfilePresenter {\n var view: ProfileView?\n let router: ProfileRouter\n\n init(router: ProfileRouter) {\n self.router = router\n }\n}\n\nfinal class ProfileRouter {\n var presenter: ProfilePresenter?\n}\n\nfunc build() {\n let router = ProfileRouter()\n let presenter = ProfilePresenter(router: router)\n router.presenter = presenter\n}\n```\n\nRequest:\n1. Identify architectural smell(s).\n2. Recommend VIPER-correct ownership rules.\n3. Provide concrete fix guidance.\n",
"required_keywords": [
"retain cycle",
"weak",
"ownership",
"router"
],
"required_any": [
[
"retain cycle",
"strong cycle",
"reference cycle"
],
[
"weak",
"weakly"
],
[
"ownership",
"owns",
"ownership rules"
],
[
"router"
]
],
"forbidden_keywords": [
"no leak",
"this is fine"
]
},
{
"id": "recommendation-simple-settings-tca-overkill",
"notes": "Expected recommendation: explicit TCA request should be fit-checked and likely marked mismatch/overkill for a tiny settings screen unless the app already uses TCA.\nExpected direction: recommend MVVM as closest fit, while noting TCA is acceptable if existing codebase convention requires it.\n",
"prompt": "Use `swift-architecture-skill` to recommend an architecture.\n\nContext:\n- New SwiftUI settings screen\n- Two toggles and one Save button\n- No child features, no async effects beyond saving preferences\n- Team has not adopted TCA yet and does not want new dependencies unless needed\n\nRequest:\nWe planned to use TCA. Is that the right call? Give a fit result and a practical alternative if it is overkill.\n",
"required_any": [
[
"mismatch",
"overkill",
"not the best fit"
],
[
"MVVM",
"mvvm"
],
[
"dependency",
"new dependency",
"framework dependency"
],
[
"fit result",
"fit:"
]
],
"forbidden_keywords": [
"use TCA because it was requested",
"no trade-off",
"always use TCA"
]
},
{
"id": "recommendation-live-feed-persistence-combination",
"notes": "Expected recommendation: combine patterns with explicit ownership, such as Clean Architecture for domain/data boundaries plus Reactive or MVVM/Reactive for stream presentation.\nExpected direction: identify primary and secondary playbooks rather than choosing Reactive alone.\n",
"prompt": "Use `swift-architecture-skill` to recommend architecture for this feature.\n\nContext:\n- SwiftUI live activity feed\n- WebSocket updates, offline cache, pagination, and pull-to-refresh\n- Data layer must be replaceable because the backend API is unstable\n- Team is comfortable with MVVM and Combine, but not TCA\n\nRequest:\nChoose the architecture and explain which playbook owns presentation, streams, and data boundaries.\n",
"required_any": [
[
"Clean Architecture",
"clean architecture"
],
[
"Reactive",
"Combine",
"stream"
],
[
"primary",
"secondary",
"owns"
],
[
"repository",
"data boundary",
"use case"
]
],
"forbidden_keywords": [
"Reactive alone",
"only use Reactive",
"TCA is required"
]
},
{
"id": "recommendation-navigation-heavy-mvvm-coordinator",
"notes": "Expected recommendation: preserve MVVM for screen state and add Coordinator as a secondary navigation concern.\nExpected direction: avoid replacing MVVM wholesale when the main smell is navigation ownership.\n",
"prompt": "Use `swift-architecture-skill` to recommend a refactor path.\n\nContext:\n- Existing mixed UIKit/SwiftUI app uses MVVM successfully for screen state\n- The main pain is deep links and reusable onboarding flows\n- ViewModels currently contain push/present calls\n- Team wants minimal churn\n\nRequest:\nShould we replace MVVM, or combine it with another pattern? Include the selected reference files and migration path.\n",
"required_any": [
[
"MVVM",
"mvvm"
],
[
"Coordinator",
"coordinator"
],
[
"primary",
"secondary",
"navigation concern"
],
[
"migration",
"incremental",
"minimal churn"
]
],
"forbidden_keywords": [
"replace MVVM entirely",
"rewrite everything",
"switch to VIPER"
]
},
{
"id": "recommendation-user-insists-viper-swiftui",
"notes": "Expected recommendation: validate the explicit VIPER request, mark mismatch for SwiftUI-first feature, recommend MVVM/MVI/TCA depending on constraints, and proceed only with risks if user insists.\n",
"prompt": "Use `swift-architecture-skill` to fit-check a requested architecture.\n\nContext:\n- New SwiftUI-only checkout screen\n- Moderate form state and async validation\n- No UIKit module boundaries\n- Team requested VIPER because they used it years ago\n\nRequest:\nIs VIPER a good fit here? If not, recommend the closest alternative and explain the risk if we insist on VIPER.\n",
"required_any": [
[
"mismatch",
"poor fit",
"not a good fit"
],
[
"SwiftUI",
"swiftui"
],
[
"MVVM",
"MVI",
"TCA"
],
[
"risk",
"boilerplate",
"UIKit-native"
]
],
"forbidden_keywords": [
"VIPER is an excellent fit",
"no risk",
"use VIPER without caveats"
]
}
]
}