Files
efremidze__swift-architectu…/tooling/evals/cases.json
2026-06-25 04:10:46 -07:00

247 lines
15 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"
]
}
]
}