From 624d5caa313ef1114aefdcc523bf9c7ee96762ea Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Sun, 11 Jan 2026 16:22:40 +0900 Subject: [PATCH 01/11] =?UTF-8?q?update:=20=E5=AE=B6=E4=BA=8B=E3=82=92?= =?UTF-8?q?=E6=9C=AA=E5=AE=8C=E4=BA=86=E3=81=AB=E6=88=BB=E3=81=99=E3=83=A1?= =?UTF-8?q?=E3=82=BD=E3=83=83=E3=83=89=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Cohabitant/Housework/HouseworkItem.swift | 22 +++++++++++++++---- .../Housework/HouseworkListStore.swift | 16 +++++++++++++- 2 files changed, 33 insertions(+), 5 deletions(-) diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift b/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift index 274a78f5..e87c6445 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift +++ b/homete/Model/Domain/Cohabitant/Housework/HouseworkItem.swift @@ -23,16 +23,30 @@ struct HouseworkItem: Identifiable, Equatable, Sendable, Hashable, Codable { return indexedDate.value } - func updateState(_ nextState: HouseworkState, at now: Date, changer: String) -> Self { + func updatePendingApproval(at now: Date, changer: String) -> Self { return .init( id: id, indexedDate: indexedDate, title: title, point: point, - state: nextState, - executorId: nextState == .pendingApproval ? changer : executorId, - executedAt: nextState == .pendingApproval ? now : executedAt, + state: .pendingApproval, + executorId: changer, + executedAt: now, + expiredAt: expiredAt + ) + } + + func updateIncomplete() -> Self { + + return .init( + id: id, + indexedDate: indexedDate, + title: title, + point: point, + state: .incomplete, + executorId: nil, + executedAt: nil, expiredAt: expiredAt ) } diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift b/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift index 66f7b433..193c457b 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift +++ b/homete/Model/Domain/Cohabitant/Housework/HouseworkListStore.swift @@ -87,7 +87,7 @@ final class HouseworkListStore { preconditionFailure("Not found target item(id: \(targetId), indexedDate: \(targetIndexedDate))") } - let updatedItem = targetItem.updateState(.pendingApproval, at: now, changer: executor) + let updatedItem = targetItem.updatePendingApproval(at: now, changer: executor) try await houseworkClient.insertOrUpdateItem(updatedItem, cohabitantId) Task.detached { @@ -100,6 +100,20 @@ final class HouseworkListStore { } } + func returnToIncomplete(target: HouseworkItem, now: Date) async throws { + + let targetIndexedDate = target.indexedDate + let targetId = target.id + + guard let targetItem = items.item(targetId, targetIndexedDate) else { + + preconditionFailure("Not found target item(id: \(targetId), indexedDate: \(targetIndexedDate))") + } + + let updatedItem = targetItem.updateIncomplete() + try await houseworkClient.insertOrUpdateItem(updatedItem, cohabitantId) + } + func remove(_ target: HouseworkItem) async throws { try await houseworkClient.removeItem(target, cohabitantId) From 1adbfe9379a910bb3aead69251997ea9dede82f6 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Sun, 11 Jan 2026 16:28:32 +0900 Subject: [PATCH 02/11] =?UTF-8?q?update:=20=E6=9C=AA=E5=AE=8C=E4=BA=86?= =?UTF-8?q?=E3=81=AB=E6=88=BB=E3=81=99=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=BF?= =?UTF-8?q?=E3=83=83=E3=83=97=E3=81=A7=E6=9C=AA=E5=AE=8C=E4=BA=86=E3=81=AB?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../HouseworkDetailActionContent.swift | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift index e5bbea24..d6dd0d1a 100644 --- a/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift +++ b/homete/Views/HouseworkDetailView/SubViews/HouseworkDetailActionContent.swift @@ -33,6 +33,7 @@ struct HouseworkDetailActionContent: View { undoChangeStateButton() } } + .disabled(isLoading) .fullScreenCover(isPresented: $isPresentedApprovalView) { HouseworkApprovalView(item: item) } @@ -43,8 +44,10 @@ private extension HouseworkDetailActionContent { func requestReviewButton() -> some View { Button { + isLoading = true Task { await tappedRequestConfirmButton() + isLoading = false } } label: { Label("確認してもらう", systemImage: "paperplane.fill") @@ -55,7 +58,11 @@ private extension HouseworkDetailActionContent { func undoChangeStateButton() -> some View { Button { - // TODO: 未完了に戻す + isLoading = true + Task { + await tappedUndoStateButton() + isLoading = false + } } label: { Label("未完了に戻す", systemImage: "arrow.uturn.backward") .frame(maxWidth: .infinity) @@ -80,8 +87,6 @@ private extension HouseworkDetailActionContent { func tappedRequestConfirmButton() async { - isLoading = true - do { try await houseworkListStore.requestReview( target: item, @@ -92,8 +97,17 @@ private extension HouseworkDetailActionContent { catch { commonErrorContent = .init(error: error) } + } + + func tappedUndoStateButton() async { - isLoading = false + do { + + try await houseworkListStore.returnToIncomplete(target: item, now: .now) + } catch { + + commonErrorContent = .init(error: error) + } } } From 2383c97f287535d474a714a80688f46c840aaab3 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Sun, 11 Jan 2026 16:41:33 +0900 Subject: [PATCH 03/11] =?UTF-8?q?update:=20=E6=9C=AA=E5=AE=8C=E4=BA=86?= =?UTF-8?q?=E3=81=AB=E6=88=BB=E3=81=99=E5=87=A6=E7=90=86=E3=81=AE=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=82=B1=E3=83=BC=E3=82=B9=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Housework/HouseworkListStoreTest.swift | 52 ++++++++++++++++++- .../TestHelper/HouseworkItemHelper.swift | 4 +- 2 files changed, 52 insertions(+), 4 deletions(-) diff --git a/hometeTests/Domain/Housework/HouseworkListStoreTest.swift b/hometeTests/Domain/Housework/HouseworkListStoreTest.swift index 4e260db2..515846a3 100644 --- a/hometeTests/Domain/Housework/HouseworkListStoreTest.swift +++ b/hometeTests/Domain/Housework/HouseworkListStoreTest.swift @@ -46,7 +46,7 @@ struct HouseworkListStoreTest { // Assert - var waiterForUpdateItems = Task { + let waiterForUpdateItems = Task { await withCheckedContinuation { continuation in continuousObservationTracking { store.items @@ -56,7 +56,7 @@ struct HouseworkListStoreTest { } } - var inputHouseworkList: [HouseworkItem] = [ + let inputHouseworkList: [HouseworkItem] = [ .makeForTest(id: 1, indexedDate: now, expiredAt: now) ] continuation.yield(inputHouseworkList) @@ -176,6 +176,54 @@ struct HouseworkListStoreTest { } } + @Test("実施者、実施日をクリアして家事のステータスを未完了に戻す") + func returnToIncomplete() async throws { + + // Arrange + + let inputHouseworkItem = HouseworkItem.makeForTest( + id: 1, + state: .pendingApproval, + executorId: "dummyExecutor", + executedAt: .distantPast + ) + let requestedAt = Date() + let updatedHouseworkItem = inputHouseworkItem.updateProperties( + state: .incomplete, + executorId: nil, + executedAt: nil + ) + + try await confirmation(expectedCount: 1) { confirmation in + + let store = HouseworkListStore( + houseworkClient: .init( + insertOrUpdateItemHandler: { item, cohabitantId in + + // Assert + + #expect(item == updatedHouseworkItem) + #expect(cohabitantId == inputCohabitantId) + confirmation() + } + ), + cohabitantPushNotificationClient: .init { _, _ in + + Issue.record() + }, + items: [.makeForTest(items: [inputHouseworkItem])], + cohabitantId: inputCohabitantId + ) + + // Act + + try await store.returnToIncomplete( + target: inputHouseworkItem, + now: requestedAt + ) + } + } + @Test("家事削除時は家事を削除するAPIを実行する") func remove() async throws { diff --git a/hometeTests/TestHelper/HouseworkItemHelper.swift b/hometeTests/TestHelper/HouseworkItemHelper.swift index 4e044926..b09f13bb 100644 --- a/hometeTests/TestHelper/HouseworkItemHelper.swift +++ b/hometeTests/TestHelper/HouseworkItemHelper.swift @@ -47,8 +47,8 @@ extension HouseworkItem { let inputTitle = title ?? self.title let inputPoint = point ?? self.point let inputState = state ?? self.state - let inputExecutorId = executorId ?? self.executorId - let inputExecutedAt = executedAt ?? self.executedAt + let inputExecutorId = executorId + let inputExecutedAt = executedAt let inputExpiredAt = expiredAt ?? self.expiredAt return .init( From 5b54ba84bb137fa8d86525deff3a308e30a32ca1 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Mon, 12 Jan 2026 08:46:48 +0900 Subject: [PATCH 04/11] =?UTF-8?q?fix:=20=E5=AE=B6=E6=97=8F=E3=82=B0?= =?UTF-8?q?=E3=83=AB=E3=83=BC=E3=83=97=E3=81=AE=E7=9B=A3=E8=A6=96=E3=81=8C?= =?UTF-8?q?=E6=84=8F=E5=9B=B3=E3=81=9B=E3=81=9A=E7=B5=82=E4=BA=86=E3=81=97?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=ABreturn=E3=82=92con?= =?UTF-8?q?tinue=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/agents/ios-code-reviewer.md | 138 ++++++++++++++++++ .../Domain/Cohabitant/CohabitantStore.swift | 5 +- 2 files changed, 141 insertions(+), 2 deletions(-) create mode 100644 .claude/agents/ios-code-reviewer.md diff --git a/.claude/agents/ios-code-reviewer.md b/.claude/agents/ios-code-reviewer.md new file mode 100644 index 00000000..973a2215 --- /dev/null +++ b/.claude/agents/ios-code-reviewer.md @@ -0,0 +1,138 @@ +--- +name: ios-code-reviewer +description: "Use this agent when you have written or modified Swift code in the iOS project and need a comprehensive code review. This agent should be called proactively after completing a logical chunk of code implementation, such as:\\n\\n- After implementing a new feature (View, Store, Client, Service, or Domain model)\\n- After refactoring existing code\\n- After fixing a bug\\n- After making architectural changes\\n- After writing or updating tests\\n\\n**Examples:**\\n\\n\\nContext: User has just implemented a new SwiftUI View with its corresponding Store.\\nuser: \"新しいProfileEditViewとProfileEditStoreを実装しました\"\\nassistant: \"実装が完了したようですので、ios-code-reviewerエージェントを使用してコードレビューを実行します\"\\n\\nSince a significant piece of code was written, use the Task tool to launch the ios-code-reviewer agent to review the recently written code.\\n\\nassistant: \"では、TaskツールでiOS Code Reviewerエージェントを起動して、実装したコードをレビューします\"\\n\\n\\n\\nContext: User has refactored the Dependency Injection pattern in multiple files.\\nuser: \"DIパターンをリファクタリングして、3つのClientを更新しました\"\\nassistant: \"リファクタリングが完了しましたね。ios-code-reviewerエージェントでコードレビューを行いましょう\"\\n\\nSince code refactoring was performed, use the Task tool to launch the ios-code-reviewer agent to ensure the changes follow best practices.\\n\\nassistant: \"TaskツールでiOS Code Reviewerエージェントを起動して、リファクタリング内容をレビューします\"\\n\\n\\n\\nContext: User has written unit tests for a new feature.\\nuser: \"HouseworkBoardStoreのユニットテストを追加しました\"\\nassistant: \"テストコードが書けましたね。ios-code-reviewerエージェントでレビューを実施します\"\\n\\nSince test code was written, use the Task tool to launch the ios-code-reviewer agent to review the test implementation.\\n\\nassistant: \"TaskツールでiOS Code Reviewerエージェントを起動して、テストコードの品質を確認します\"\\n" +tools: Bash, mcp__mobile-mcp__mobile_list_available_devices, mcp__mobile-mcp__mobile_list_apps, mcp__mobile-mcp__mobile_launch_app, mcp__mobile-mcp__mobile_terminate_app, mcp__mobile-mcp__mobile_install_app, mcp__mobile-mcp__mobile_uninstall_app, mcp__mobile-mcp__mobile_get_screen_size, mcp__mobile-mcp__mobile_click_on_screen_at_coordinates, mcp__mobile-mcp__mobile_double_tap_on_screen, mcp__mobile-mcp__mobile_long_press_on_screen_at_coordinates, mcp__mobile-mcp__mobile_list_elements_on_screen, mcp__mobile-mcp__mobile_press_button, mcp__mobile-mcp__mobile_open_url, mcp__mobile-mcp__mobile_swipe_on_screen, mcp__mobile-mcp__mobile_type_keys, mcp__mobile-mcp__mobile_save_screenshot, mcp__mobile-mcp__mobile_take_screenshot, mcp__mobile-mcp__mobile_set_orientation, mcp__mobile-mcp__mobile_get_orientation, mcp__XcodeBuildMCP__build_device, mcp__XcodeBuildMCP__clean, mcp__XcodeBuildMCP__discover_projs, mcp__XcodeBuildMCP__get_app_bundle_id, mcp__XcodeBuildMCP__get_device_app_path, mcp__XcodeBuildMCP__install_app_device, mcp__XcodeBuildMCP__launch_app_device, mcp__XcodeBuildMCP__list_devices, mcp__XcodeBuildMCP__list_schemes, mcp__XcodeBuildMCP__show_build_settings, mcp__XcodeBuildMCP__start_device_log_cap, mcp__XcodeBuildMCP__stop_app_device, mcp__XcodeBuildMCP__stop_device_log_cap, mcp__XcodeBuildMCP__test_device, mcp__XcodeBuildMCP__doctor, mcp__XcodeBuildMCP__start_sim_log_cap, mcp__XcodeBuildMCP__stop_sim_log_cap, mcp__XcodeBuildMCP__build_macos, mcp__XcodeBuildMCP__build_run_macos, mcp__XcodeBuildMCP__get_mac_app_path, mcp__XcodeBuildMCP__get_mac_bundle_id, mcp__XcodeBuildMCP__launch_mac_app, mcp__XcodeBuildMCP__stop_mac_app, mcp__XcodeBuildMCP__test_macos, mcp__XcodeBuildMCP__scaffold_ios_project, mcp__XcodeBuildMCP__scaffold_macos_project, mcp__XcodeBuildMCP__session-clear-defaults, mcp__XcodeBuildMCP__session-set-defaults, mcp__XcodeBuildMCP__session-show-defaults, mcp__XcodeBuildMCP__boot_sim, mcp__XcodeBuildMCP__build_run_sim, mcp__XcodeBuildMCP__build_sim, mcp__XcodeBuildMCP__describe_ui, mcp__XcodeBuildMCP__get_sim_app_path, mcp__XcodeBuildMCP__install_app_sim, mcp__XcodeBuildMCP__launch_app_logs_sim, mcp__XcodeBuildMCP__launch_app_sim, mcp__XcodeBuildMCP__list_sims, mcp__XcodeBuildMCP__open_sim, mcp__XcodeBuildMCP__record_sim_video, mcp__XcodeBuildMCP__screenshot, mcp__XcodeBuildMCP__stop_app_sim, mcp__XcodeBuildMCP__test_sim, mcp__XcodeBuildMCP__erase_sims, mcp__XcodeBuildMCP__reset_sim_location, mcp__XcodeBuildMCP__set_sim_appearance, mcp__XcodeBuildMCP__set_sim_location, mcp__XcodeBuildMCP__sim_statusbar, mcp__XcodeBuildMCP__swift_package_build, mcp__XcodeBuildMCP__swift_package_clean, mcp__XcodeBuildMCP__swift_package_list, mcp__XcodeBuildMCP__swift_package_run, mcp__XcodeBuildMCP__swift_package_stop, mcp__XcodeBuildMCP__swift_package_test, mcp__XcodeBuildMCP__button, mcp__XcodeBuildMCP__gesture, mcp__XcodeBuildMCP__key_press, mcp__XcodeBuildMCP__key_sequence, mcp__XcodeBuildMCP__long_press, mcp__XcodeBuildMCP__swipe, mcp__XcodeBuildMCP__tap, mcp__XcodeBuildMCP__touch, mcp__XcodeBuildMCP__type_text, Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, ListMcpResourcesTool, ReadMcpResourceTool +model: opus +color: cyan +--- + +You are an elite iOS code reviewer specializing in Swift 6, SwiftUI, and modern iOS development practices. Your expertise encompasses clean architecture, strict concurrency, and Firebase integration patterns specifically for the homete iOS project. + +## Your Role and Responsibilities + +You are responsible for conducting thorough code reviews of recently written or modified Swift code. Your reviews should be constructive, educational, and aligned with the project's established patterns and best practices. + +## Project Context You Must Consider + +### Architecture Pattern +- **Clean Architecture with Custom DI**: Views → Stores (@Observable) → Clients (protocols) → Services (actors) → Domain Models +- **Dependency Injection**: All clients conform to `DependencyClient` with `.liveValue` and `.previewValue` +- **No direct Service access from Views**: Always go through Stores and Clients +- **State Management**: Use `@Observable` macro (not Combine or ObservableObject) +- **Concurrency**: Swift 6 strict concurrency enabled - enforce actor isolation and `@Sendable` + +### Critical Technical Requirements +- **Async/await only**: No completion handlers +- **Actor-based services**: Firestore and other services must be actors for thread safety +- **Protocol-based DI**: Every service must have a corresponding Client protocol +- **SwiftUI best practices**: Leverage modern SwiftUI patterns +- **Firebase integration**: Proper use of Firestore, Auth, and Cloud Messaging + +### File Organization Standards +- Views: `homete/Views/` organized by feature +- Domain Models: `homete/Model/Domain/` with subdirectories by domain area +- Clients: `homete/Model/Dependencies/` with protocol definitions +- Services: `homete/Model/Service/` with infrastructure code +- Stores: `homete/Model/Store/` with @Observable classes +- Tests: `hometeTests/` mirroring main app structure + +## Review Process + +When reviewing code, follow this systematic approach: + +### 1. Architecture Compliance +- Verify the code follows the Clean Architecture pattern (Views → Stores → Clients → Services) +- Ensure Views don't directly access Services +- Check that new functionality uses the DI pattern correctly +- Confirm Stores are @Observable and receive AppDependencies in initializer +- Validate that Clients have both `.liveValue` and `.previewValue` implementations + +### 2. Swift 6 Strict Concurrency +- Verify proper actor isolation for shared mutable state +- Check all types crossing concurrency boundaries are `@Sendable` +- Ensure async/await is used correctly (no completion handlers) +- Validate that Services are actors when managing shared state +- Look for potential data races or concurrency violations + +### 3. Code Quality and Best Practices +- Assess code readability and maintainability +- Check for proper error handling patterns +- Verify appropriate use of Swift language features (guard, if let, optional chaining) +- Ensure force unwrapping is avoided (unless truly impossible to fail) +- Review naming conventions (clear, descriptive, following Swift conventions) +- Check for code duplication that could be extracted + +### 4. SwiftUI Patterns +- Verify Views are composable and focused on presentation +- Check proper use of @Observable, @State, @Binding, @Environment +- Ensure View modifiers are applied appropriately +- Validate navigation patterns align with project structure +- Review Preview providers for completeness + +### 5. Testing Considerations +- Assess testability of the code +- Check if appropriate tests exist or need to be added +- Verify test coverage for critical paths +- Ensure mocks/previews are properly implemented for DI +- Validate snapshot tests for UI components when appropriate + +### 6. Firebase Integration +- Verify proper use of FirestoreService patterns +- Check collection paths are defined in CollectionPath.swift +- Ensure AsyncStream is used for real-time listeners +- Validate proper error handling for Firebase operations +- Review security and data validation + +### 7. Performance and Efficiency +- Identify potential performance bottlenecks +- Check for unnecessary computations or re-renders +- Verify efficient use of Firebase queries +- Look for memory leak possibilities +- Assess async operation efficiency + +## Output Format + +Provide your review in Japanese with the following structure: + +### 総合評価 +[Overall assessment: 良好 / 要改善 / 重大な問題あり] + +### アーキテクチャ +[Architecture compliance feedback] + +### Swift 6並行性 +[Concurrency and thread safety feedback] + +### コード品質 +[Code quality observations] + +### 改善提案 +[Specific, actionable improvement suggestions with code examples when helpful] + +### 良い点 +[Highlight what was done well to reinforce good practices] + +### 優先度の高い修正項目 +[Critical issues that must be addressed, if any] + +## Review Principles + +1. **Be Constructive**: Frame feedback positively and explain the reasoning behind suggestions +2. **Be Specific**: Provide concrete examples and code snippets when suggesting improvements +3. **Be Educational**: Explain why certain patterns are preferred in this project +4. **Prioritize**: Clearly distinguish between critical issues, important improvements, and nice-to-haves +5. **Acknowledge Good Work**: Always recognize well-implemented patterns and good practices +6. **Consider Context**: Focus on recently written/modified code, not the entire codebase unless explicitly asked +7. **Align with Project**: Ensure all feedback aligns with the project's established patterns from CLAUDE.md + +## Self-Verification Steps + +Before finalizing your review: +1. Have you checked all critical architectural patterns? +2. Have you verified Swift 6 concurrency compliance? +3. Are your suggestions specific and actionable? +4. Have you provided code examples where helpful? +5. Have you balanced critique with recognition of good work? +6. Is your feedback aligned with the project's established practices? + +You are thorough, knowledgeable, and dedicated to helping developers write excellent Swift code that aligns with the homete project's high standards. diff --git a/homete/Model/Domain/Cohabitant/CohabitantStore.swift b/homete/Model/Domain/Cohabitant/CohabitantStore.swift index b89da43d..21cb1352 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantStore.swift +++ b/homete/Model/Domain/Cohabitant/CohabitantStore.swift @@ -44,7 +44,7 @@ final class CohabitantStore { for await cohabitantDataList in stream { - guard let cohabitantData = cohabitantDataList.first else { return } + guard let cohabitantData = cohabitantDataList.first else { continue } for member in self.members.missingMemberIds(from: cohabitantData.members) { @@ -61,8 +61,9 @@ final class CohabitantStore { print("error occurred: \(error)") } } - print("finish listening cohabitant snapshot.") } + + print("finish listening cohabitant snapshot.") } } From 02906283eec5abc3e636a55273a76bf96fb89692 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Mon, 12 Jan 2026 08:52:28 +0900 Subject: [PATCH 05/11] =?UTF-8?q?fix:=20=E3=83=91=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=BC=E3=83=9E=E3=83=B3=E3=82=B9=E5=95=8F=E9=A1=8C=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homete/Model/Domain/Cohabitant/CohabitantMemberList.swift | 2 +- homete/Model/Domain/Cohabitant/CohabitantStore.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/homete/Model/Domain/Cohabitant/CohabitantMemberList.swift b/homete/Model/Domain/Cohabitant/CohabitantMemberList.swift index 8a2cbef2..6f1591b4 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantMemberList.swift +++ b/homete/Model/Domain/Cohabitant/CohabitantMemberList.swift @@ -17,7 +17,7 @@ struct CohabitantMemberList { /// 与えられたユーザーID配列の中から、まだvalueに存在しないユーザーIDのみを返します。 /// - Parameter userIds: 追加するユーザーIDの候補の配列 /// - Returns: 追加が必要なユーザーIDの配列 - func missingMemberIds(from userIds: [String]) -> [String] { + func missingMemberIds(from userIds: Set) -> Set { let existingIds = value.map(\.id) return userIds.filter { !existingIds.contains($0) } diff --git a/homete/Model/Domain/Cohabitant/CohabitantStore.swift b/homete/Model/Domain/Cohabitant/CohabitantStore.swift index 21cb1352..08d56c7a 100644 --- a/homete/Model/Domain/Cohabitant/CohabitantStore.swift +++ b/homete/Model/Domain/Cohabitant/CohabitantStore.swift @@ -46,7 +46,7 @@ final class CohabitantStore { guard let cohabitantData = cohabitantDataList.first else { continue } - for member in self.members.missingMemberIds(from: cohabitantData.members) { + for member in self.members.missingMemberIds(from: .init(cohabitantData.members)) { do { From dbb6435646d7caf3d1758e2609a6cdefdca9a509 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Mon, 12 Jan 2026 08:55:16 +0900 Subject: [PATCH 06/11] =?UTF-8?q?fix:=20HouseworkClient=E3=81=A7=E3=81=97?= =?UTF-8?q?=E3=81=8B=E4=BD=BF=E3=82=8F=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=83=97=E3=83=AD=E3=83=91=E3=83=86=E3=82=A3=E3=81=AF?= =?UTF-8?q?HouseworkClient=E3=81=A7=E5=87=BA=E5=8A=9B=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- homete/Model/Dependencies/HouseworkClient.swift | 4 ++-- .../Domain/Cohabitant/Housework/HouseworkIndexedDate.swift | 5 ----- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/homete/Model/Dependencies/HouseworkClient.swift b/homete/Model/Dependencies/HouseworkClient.swift index fe20a3c2..12384a1e 100644 --- a/homete/Model/Dependencies/HouseworkClient.swift +++ b/homete/Model/Dependencies/HouseworkClient.swift @@ -96,13 +96,13 @@ private extension HouseworkClient { let base = calendar.startOfDay(for: anchorDate) guard offsetDays >= 0 else { - return [HouseworkIndexedDate(base, locale: locale).mapValue] + return [["value": HouseworkIndexedDate(base, locale: locale).value]] } // -offset ... +offset の範囲を列挙 return (-offsetDays...offsetDays).compactMap { delta in guard let date = calendar.date(byAdding: .day, value: delta, to: base) else { return nil } - return HouseworkIndexedDate(date, locale: locale).mapValue + return ["value": HouseworkIndexedDate(base, locale: locale).value] } } } diff --git a/homete/Model/Domain/Cohabitant/Housework/HouseworkIndexedDate.swift b/homete/Model/Domain/Cohabitant/Housework/HouseworkIndexedDate.swift index 17c90bd6..d2f66ad0 100644 --- a/homete/Model/Domain/Cohabitant/Housework/HouseworkIndexedDate.swift +++ b/homete/Model/Domain/Cohabitant/Housework/HouseworkIndexedDate.swift @@ -9,11 +9,6 @@ import Foundation struct HouseworkIndexedDate: Equatable, Codable, Hashable { let value: String - - var mapValue: [String: String] { - - return ["value": value] - } } extension HouseworkIndexedDate { From ee80db551c98942b50f85abbb70e403c203e1e02 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Mon, 12 Jan 2026 09:29:17 +0900 Subject: [PATCH 07/11] =?UTF-8?q?fix:=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E6=99=82=E3=81=AE=E3=82=B3=E3=83=9E=E3=83=B3=E3=83=89=E3=81=AF?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AA=E3=81=AE=E3=81=A7=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CLAUDE.md | 9 --------- hometeTests/Domain/CohabitantStoreTest.swift | 16 ++++++++++++++++ 2 files changed, 16 insertions(+), 9 deletions(-) create mode 100644 hometeTests/Domain/CohabitantStoreTest.swift diff --git a/CLAUDE.md b/CLAUDE.md index 49aa2cd9..41e8f0ae 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -16,15 +16,6 @@ hometeは同居人(ルームメイト/家族)間で家事を管理するた ### iOS開発 ```bash -# Xcodeでプロジェクトを開く -open homete.xcodeproj - -# テスト実行(ユニット + スナップショット) -xcodebuild test -project homete.xcodeproj -scheme homete -testPlan CI.xctestplan -destination 'platform=iOS Simulator,name=iPhone 16' - -# スナップショットテストのみ実行 -xcodebuild test -project homete.xcodeproj -scheme homete -testPlan snapshotTesting.xctestplan -destination 'platform=iOS Simulator,name=iPhone 16' - # SwiftLint(CIでDanger経由で実行、スタンドアロンでは実行しない) swift run --package-path ProjectTools swiftlint lint --config .swiftlint.yml diff --git a/hometeTests/Domain/CohabitantStoreTest.swift b/hometeTests/Domain/CohabitantStoreTest.swift new file mode 100644 index 00000000..f9daf1eb --- /dev/null +++ b/hometeTests/Domain/CohabitantStoreTest.swift @@ -0,0 +1,16 @@ +// +// CohabitantStoreTest.swift +// hometeTests +// +// Created by Taichi Sato on 2026/01/12. +// + +import Testing + +struct CohabitantStoreTest { + + @Test func <#test function name#>() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + } + +} From b80e55394702573e160140cda80f86a88c8ea1dd Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Mon, 12 Jan 2026 09:34:41 +0900 Subject: [PATCH 08/11] =?UTF-8?q?update:=20CohabitantStoreTest=E3=81=AE?= =?UTF-8?q?=E7=9B=A3=E8=A6=96=E5=87=A6=E7=90=86=E3=81=AE=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hometeTests/Domain/CohabitantStoreTest.swift | 71 ++++++++++++++++++- .../Housework/HouseworkListStoreTest.swift | 17 +---- .../TestHelper/ObservationHelper.swift | 27 +++++++ 3 files changed, 96 insertions(+), 19 deletions(-) create mode 100644 hometeTests/TestHelper/ObservationHelper.swift diff --git a/hometeTests/Domain/CohabitantStoreTest.swift b/hometeTests/Domain/CohabitantStoreTest.swift index f9daf1eb..a94bea94 100644 --- a/hometeTests/Domain/CohabitantStoreTest.swift +++ b/hometeTests/Domain/CohabitantStoreTest.swift @@ -6,11 +6,76 @@ // import Testing +import Observation +@testable import homete +@MainActor struct CohabitantStoreTest { - @Test func <#test function name#>() async throws { - // Write your test here and use APIs like `#expect(...)` to check expected conditions. - } + private let inputCohabitantId = "testCohabitantId" + private let inputListenerId = "cohabitantListenerKey" + + @Test("パートナーの監視中に、まだキャッシュしていないメンバーの場合はパートナーのリストにキャッシュとして追加する") + func addSnapshotListenerIfNeeded_add_member_case() async throws { + + // Arrange + + let newMemberId = "newMemberId" + let newMemberUserName = "新しいメンバー" + let expectedAccount = Account( + id: newMemberId, + userName: newMemberUserName, + fcmToken: nil, + cohabitantId: inputCohabitantId + ) + let inputCohabitantData = CohabitantData( + id: inputCohabitantId, + members: [newMemberId] + ) + + let (stream, continuation) = AsyncStream<[CohabitantData]>.makeStream() + + let store = CohabitantStore( + appDependencies: .init( + accountInfoClient: .init(fetch: { userId in + + // Assert + + #expect(userId == newMemberId) + return expectedAccount + }), + cohabitantClient: .init( + addSnapshotListener: { listenerId, cohabitantId in + #expect(listenerId == inputListenerId) + #expect(cohabitantId == inputCohabitantId) + return stream + } + ) + ) + ) + + // Act + + await store.addSnapshotListenerIfNeeded(inputCohabitantId) + + // Assert + + let waiterForUpdateMembers = Task { + await withCheckedContinuation { continuation in + ObservationHelper.continuousObservationTracking { + store.members + } onChange: { + continuation.resume(returning: ()) + } + } + } + + continuation.yield([inputCohabitantData]) + await waiterForUpdateMembers.value + continuation.finish() + + #expect(store.members.value.count == 1) + #expect(store.members.value.contains(.init(id: newMemberId, userName: newMemberUserName))) + } } diff --git a/hometeTests/Domain/Housework/HouseworkListStoreTest.swift b/hometeTests/Domain/Housework/HouseworkListStoreTest.swift index 515846a3..6c0699a1 100644 --- a/hometeTests/Domain/Housework/HouseworkListStoreTest.swift +++ b/hometeTests/Domain/Housework/HouseworkListStoreTest.swift @@ -48,7 +48,7 @@ struct HouseworkListStoreTest { let waiterForUpdateItems = Task { await withCheckedContinuation { continuation in - continuousObservationTracking { + ObservationHelper.continuousObservationTracking { store.items } onChange: { continuation.resume(returning: ()) @@ -253,18 +253,3 @@ struct HouseworkListStoreTest { } } } - -private extension HouseworkListStoreTest { - - nonisolated func continuousObservationTracking( - _ apply: @escaping () -> T, - onChange: @escaping (@Sendable () -> Void) - ) { - - _ = withObservationTracking(apply) { - - onChange() - continuousObservationTracking(apply, onChange: onChange) - } - } -} diff --git a/hometeTests/TestHelper/ObservationHelper.swift b/hometeTests/TestHelper/ObservationHelper.swift new file mode 100644 index 00000000..086e4c1a --- /dev/null +++ b/hometeTests/TestHelper/ObservationHelper.swift @@ -0,0 +1,27 @@ +// +// ObservationHelper.swift +// homete +// +// Created by Taichi Sato on 2026/01/12. +// + +import Observation + +enum ObservationHelper { + + /// Observableなオブジェクトのプロパティが変更を検知する + /// - Parameters: + /// - apply: 変更を検知したいプロパティを返す + /// - onChange: 変更検知時に発火するクロージャ + static func continuousObservationTracking( + _ apply: @escaping () -> T, + onChange: @escaping (@Sendable () -> Void) + ) { + + _ = withObservationTracking(apply) { + + onChange() + continuousObservationTracking(apply, onChange: onChange) + } + } +} From b6415332f885309438ba495e6635ac3995c8202f Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Mon, 12 Jan 2026 09:51:49 +0900 Subject: [PATCH 09/11] =?UTF-8?q?update:=20vrt=E3=81=AEsnapshot=E6=9B=B4?= =?UTF-8?q?=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...7\241\344\270\255-iPhone-16-Pro-Max.1.png" | Bin 80899 -> 79805 bytes ...2\344\277\241\344\270\255-iPhone-16.1.png" | Bin 73119 -> 72067 bytes ...44\270\255-iPhone-SE-2nd-generation.1.png" | Bin 68222 -> 67183 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git "a/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkDetailView_\351\200\232\344\277\241\344\270\255-iPhone-16-Pro-Max.1.png" "b/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkDetailView_\351\200\232\344\277\241\344\270\255-iPhone-16-Pro-Max.1.png" index 3566ff5f8903fe146be39d8bffba614407890ceb..367fe310be6621f8bbfa6ba3e48df0d5d5035df5 100644 GIT binary patch delta 13681 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 Y=oK1S{=2-lNPkX#d+wW(W4<^4AB^JUCjbBd delta 14784 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 diff --git "a/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkDetailView_\351\200\232\344\277\241\344\270\255-iPhone-SE-2nd-generation.1.png" "b/hometeSnapshotTests/__Snapshots__/PreviewTests.generated/HouseworkDetailView_\351\200\232\344\277\241\344\270\255-iPhone-SE-2nd-generation.1.png" index fcd3115dcaf4b303a79b4e1d99bab7dedc7c06e6..4c09547c0d2256f7434a86a963ca706927c13eaf 100644 GIT binary patch delta 23150 zcmc(HcU)81*0v%RMk&f2m5$PMR1gpl0t9sibZ9z^g^oiBEp$Rnjx^DcrYMmb9h9ha zf}sq{>rWqkt=` zZa&OaYIjU~_sVDuu)q{YPW`0ha>7K5>4id~Sd9%fBhSZKRRKT@ZB)XRKi+jUYAvvk zHeG2u!g*?q!a1Vey?U3lDUP`nA$T>H+|2;w6rJOlu-N|ScJ8*W^9VcEh8sZ?OY&I6T zV=duhf2<$9biCf8jLdGSzxa9t8YFn3fZA_^3yHqs!tq|QQ5K9aaB`Zp?D+H&yzA)l zV0%BfDLRtdR0FQZ%(uq0Z$F|0>|&cn%~=%G^c&4AR{kpWGAL-dnQ9#|dSZ&yuKKdyUx~r4dyIl zw$~sQO2-?O@(f~^XNeN7^)$=&QUGAgzjf4t?q87}M#b>z#^brBvMUCT`~t4Koclfz zQS82VM^7qF^U$uwCTlZ;VDiH1i@8;c;lc8DOt~@0nDtiI(FGN=RJZWDU#jBLl*ZJsrj6lw1Q}UrywwU{TTIeJ6BA zrC-GiztVrDDZ0a7>vjD53$-Sn3Z>fxD?Nh!&C&grlSldEHRQAuNFtBGos%!)Q~TT7 z(TY?o&sfI)kC@|?j@rK6CzyYKX6IZ?_E(v&*KU%*+LX0-y?*2HLLlLy^q4^-5dO#{ zU9kGtRniq0V{FG#wR<1S*y2`{HjCtb(PMXI#MeF~h~6RKb)XIB1@A}KZXIq6>^DtU zs|Hr_C`HroR`hkE>jGyyu0!#p&N!STJ}uOIhrDjw!dSrtnv+eQQLx))7;ndtOG1o` z7UU*pws2IT0GI$cK4I`2mg)6RODYTuPNTDiu8npv(sNoa#7l(byXRQy79c)>MMU+# ze5biaXrCAyMeyb!f@R`$FpmXR;P(r7tB})9J1Es5#Rxq7Wy?GEf_>eC~*O>eqQ7l@WNpe zZemiQ-F0or)oO5!;zC2C5k`3tT~T)4wvk|3Tj+Hnd?~a&9l=b~v$s60LVaG5wD=a+T8czid#^x{8Y)aBZjF+ooR4iJaZZd?>TEtF_{akE z){+GRuuvgm&_Y2*!R_V5<-!vS&3`~%lwCWz$O3XDj9;tIPd9!P!=joQBsmws^fHd_ zlGZp)o8zwqb@7l^0D*8>HMWRmf6Y!t~bBA>*oCvRQsRZRD6*fDF}b}X)R ztHY*(8ggzkK$@=v*jK%YXJgE%($f&+(kF02j<_qmP3HH3DOWYdKpkgqnRcS79=vK;pALIK-sM_A(b3 z(-UO$+=DS!5x}i^1Q8N|oA%ae&04|7Bv+YmB{a+b%buE;qqOT02h1M0j#b2eu;F$Y zDixTbAA3DNvO{(SEE;w^NIcz=V~to?LK6)pFoq%ViPTHip-1@zH?vuy(tDtgQw6D|T zn@>Ean!I71U!G{#WH^_uQA=|Kz9-X_S)sGJ>?vvh2ZsB`By(MHwc|BH#nVj$_b;#& z(&{_9g=F3&=D$yw#`|RTv}@=pf5LUGgPwy59c|QN!|*jVr5}excSv24kE@fIz1sZg zja(8N+7^@Iv_tA$Lkn#4!Xe`Vk}-eKmOm(bD!ol7uqGvDw8{mDTkr0`akh|)gj6Ib zU?s7w+hY8Gm^^*0g}N2HGec1T?@DW-)|6acr_mpF^uU88S@^&v%Ay#Zf#V*A4qEkI}R4FCkY>R%HI@%l{Y;fIJf3F4%ch!XnU%Z&X;>F+k+H}^man8 z6#+Cqd+}Cy_XC@;PEod$$)~r{?V}XFL5FE$hw|Qac+0Q3xk&fagpQ7u&h+n)QVt5+ zn)ZE=WT*%uSTTxNzmsXf7Ke1v9Nii=(Ld$5fk!X8)7Ni{6tU2(O;)%2I&C74U4o%& zC$pztlRimH_4vheV6z?NdrGx8ivXhfdr#hJH7UN6pp#A-@~SG6_{^R>lD>k9D7vGVOgh63cpYjhm69{eV@qt-$mS{4u)5GT5`!ioBSCNA`w~CRB(RxVG-e8E{gc0hiD%lM{sk0^%xU4YYKitiME&4j62th0Z`VP7 zW+tH?F;5>?jIGa)z}{RBVmtD7OHnS02)}uWx~vx3oetPnB+2;Xzf)-19a1aj&4L84 z+iZnj?>7gISHwR>U=>H$Bm1Zs!b>L|&4R$>_CTRZp>bBd>t?me1`Zc63ZQN|J=%b- z16)MZ^q_JrkoZDDcvD1N%A&0JX!MD-2k;cE5_SgEv$Wj8xq zgL+B%z1vE{eqXC8*G2^USgkwYx*MB4FgFV-4<0=7%THlHnwwjjZdtnj{P+5OdLr>L zsSPe->qF-AV_{-DW}Tg6rGNb`^#l;w(~q+B?eEQ6ngz|he|!JeZmyZ7UK?|nD7*Z7 zv(Cb1_aR?>mg4hszM`UR*@z|~s=qYzVUHB0q4tFOufLVgt0>nF)-vw>-YW9i81?02 zZQytDWrgCuaxVY9*?M8KnwqJ-mVRo1KbaH$8&_G+l>c^50X`?6 z8%95Sg!#|^Zle4Tf%1F)vmx}eo_}p5{X_tN|B}jIp&#M;|3=P!cJu*%|B}j|C;I;; z&;K0%KZn47cAe?p4WIS==Wh=xe<7v-8@c!<)cu$F_Xf>=g!>O0x%ehrdjtOGlH|X0 zm4C!jKF_~5j(&vs&xX>^@A=Qh(2ubH4W*x8;BQ~i|4a0v{B!=ZQL~@E9l+ndqW_oZ zXVY)BkS)z`dXKK>PFjd&B4_4EP^AVt;M?{HNL5AM&4HcEtXx^mA>! z{|%%c3b3*C^IQJ2LG&XEY%Klw0Dt#}@?RW2pYxxMqn{nX-@T#y*XZZB{f7;rA7TEp zq4YxmHm>yGbD;e^|Nj4@pCJEuZg+*w(el{ijhLzvjm--K^KTdY+A(-7aob~Ha9*@v zXuOhbBPpq6Vz?Gb|1=)=*si_nv8x2Tnfoq?7|CmG00T`n6^TeaLlgy`4?wZqp z90~T^x&PWP55zHjm31}lh-HfJqgP3_@>1{Bpwqv@l5Z%o%`g8!#81!StZL3(a!SPC zmYB^ZFTQ>&UDSHt552AL*OoTxRd|Z4>Z*ejr|`FT%^o>&q`jOfU}nj6P!~T9=VF_n zf$PcwpYM0m(977BeKhJ{TEdz+-1CLm$QPeRcQTis{U+EtGO&wVy6$w{wP1!kis!QM zT64Y=Gahr6axwJZu)8LE(nT-BKkBZgg?cTxX)WgAh|BXofblc9(--Q5rGW*Rv*ay+ zsY^@?wuV{yo8cbo{x&Bl)vg!&mxO?*O-Rtfps1>@8!n#;ZJwZM3+du0M-_y&m zG73c2?VOc7W^$kw1TOxg-ex}Hs+k0`XIVT~?nodq;6Znj5nS%#dEgI5jqNw9q8mF; ze^m=^TzoVZFz4`#NB(RhO<(1quma6cX$taE7VU>EL!|t26KAqJzovsd${7k`?F7pQ zL{86+6ATdV;WJ8(>-N1Sd*zAjS@V1(o+8SIWVt#$Nj(}w0LH3Qm$9hUHW z@`MJ?k#=-B?`;^5(z|cx9KUtB#%aCxit~CIDMnhzIm~rL$13`{;GK03_Z?l+Cuz+^w8q(-SUES>tlA;}shNPO?F+E^^L+g)?F^*#|@U z&aNoMhN+jC+p9F~<_p9|j>QtG>URT|JNN_0S*2#q2kZ8WxHW3f2uTCB*3|;zVHM&Q zK@CTnz=2o_hD~gqp5b+j(|kZFpfn7~HMx@7yLNo_TR@2vT{ghEy7W}(fW5fC>z1x( ztf1^$V|?L=rYtHszO068YyS`?r58kFfMv>7xsJ(NR0Q3kEqW&tnYXsQ|B<4L927j4 zRj&9hhBE+O^RAt~Nk+fU=PWYH2Tj5U7kyfsCl>rCKQZlU@;P_i)*g+qChiuHUEo3O zFa`>UDH;V`XA~G|FXg|+!4&2irf-6-%K;H{L9|@3Ocm*lLgHN0n%STp(eP78Dzf32 zE!HCIaT5#?n68jaX4sN<=qF|5Og<@qD;NWnK|)C7#Xsj~)Sn z@frD3WLNH|MgF(A(mAZkn*d2)!^Se;GSJsDq?b`*B#ErsF^fjJo}34_d@(;GGTG{0 zD0_DQS>@)H=0I_p6TmNuGil{9B%Q&j5-uTvem|xT4PH1nlFr@J0}kbF^@BgpPaYz( z0yy149SpEyQHEYB!7FjOXQ@ibwPr6xYpwN5KXy$A8M@OOnq3YTzNM3w?Q;si@SdZz zfJsn$1cH??u4~|QJdZoFGnqIN_9lbF2h~#}$Ae~98f$Az4eEIVS^)~CAS|>z+g4KF z*C3t!JYy&z0+aHzbUL3Iyx*>B&rg_4Br_+!Z|0FT$bS3&_c#tK7j4wb)k6K?37P8d z2vGbk`RQ#rR1>6@&fykK0iD6>5sm!UL4!(w??)cC)914{!THr^DiP>|I1MNG_<-eg zQOd1Z&UDbHrqP@_m#6_@6({2|L=n_5g6!aMvu#LO+B`u0N+8S{cdQl_I+Bb>dO9er zpt&J}`i`nIcgzM>DIb=fxl*IE!UsYNVcaO!ox^e?*b8HH{SJu|MR0}!Dt0x4ZYOpx zBBk(E&6E+M?Y43(-ckeoV5lJvX%fjCPP_bZ@qq;eL+}H|7_LNz(~hCOa*mz5=blP2 zO8pVS1B&vS;rFR~@IKx4FIQHgR$h0A?dJ_cw=(UYu~hEIGPD7jOR(EN9{6_%6H2 z8RvpYFXh%T?|VdrJi|9m8vqDW(9}uUdVciFli}{NVhU({Ea!!RI#Iqc*H5H>CgfGU zNfwqVofz72doj1;LSN|s7$ogSomlW9L;Ddm>1x+B!XP>3Z7}oux=I>D>Cm$l7M( z8?v?Zh>5r&_uB3cBNp*yJ4NLv>R+o?cxIOoVn2mLhuybV_C)pic(C(zx1-dpY)|>_ z(ljiz-WeoEs|^X!(z^zGI%pmrU!S@HnG{$? zxah~y)9^)7*)0m~R-3>55CD=p_VwHcSka}0HLD}T{zDjOXpdRR4<}P6(?Qu{ou%a| zku}$F`U~LMpc2${wY?GuXad6__El%ziY(b5FlFck+=4Z|rx^s)yMXAryea9MGs>Rb zJ!;^!AGw{=y?Uwm?6E%8la`tx}M=^|`{)`5q;hY4}ncIUIB5T^m>(;Zi#9Vz-X6?fkd z5^D$8{y{`{8F2x{8d9_=A9Ved=vYbs1Reqny)M0t9Q}N(pra}wL*d%tT6N=6Tw3}| zT0?PYx|Xb~29fxIF%#nDaKZ5v#S0N){(uA_B%yp%G3n(3!}2<8{wtc3pod-KkK=Pd zJQIfTlCJbwU~tuWWLlduAI(pW3(0hsB2+s=4Wd2!5SXc(cEi9p*7BA6u`>Au?uk@C zN}Uhc7<7v1Hd(BMgAd!|(Fa{kqWY^b)kA)Bll++yPmQC4Gj7j^yNQD)QG+My3-Vvi z_)lftNu(bQgtY~jT+{t(3Lef-nnn5QE#0+^UFZV7^X1-Aa@K_p-wg*>*4s5?hWAao z38)LsPNN5mB#TF(iGS99KEi)u}DwDvLBMTo9{tm67x! zZ0;2!W1in$BAg8#hP*I_zArGPxT0b)+V)1CXA&K0kWt-V_ z7H7i9)4>i4nWhP~GZpVdA0863%C*|sZF6QPhe1~Ao1<%?bS9(E!y`u$1dhEcANFhf zJcM`R%Q*?|FPz0^+{IPDkDVC*WJXFkPe}Q3Zkm=z)K5}jz8NuW$G>*gu!H|B{f z`svf0e^qr%S9{ zJr3~0lCwA&ocX13#nQItP16xEYSsRWYUkvVn9NzrYtXA6P}AjYAQ72xlb)Qjkv&cf z(c+Z%Ws;3z1=+~F_XI1x+698>Al!^ zDg4Ze^QX>8srg#Ff-OI}hcZ`=y)yKAoIm7+hWi@efOcpuhpOLI(vMsi9kDCB6mBDafA`0fO&y0+R-vM%C@By0LB?&*K*sHaBr?mO!PyVoJY6@+TPPs9 zRoR0^`a!Ag&HWt@X$$+_t2*v!nZNFUg|Wzx^BMU z2w~<>Dkv{T?_GNMf^4{8TLt(E_m?mhMqi8*T>ZXtyJKf8ts?^S^e9H~E)ZacPA{wP z@@(*03_LbjmKI4c1(Qxmu;h$&AFDy$dE+xKEZ*YJ=8~4$8aj3l1}_?`Q0-@*leVYf zO+b%-fbTG3R5Cge4t9*`H`rvq)k#A4>qdIy6YJB)4;MZZv&wGEpr3?-CiT-j9W2dn zhK~83h7>9F7L>d>U-~Ty$%Kch}F&ngzNORE+30vz3;u8 zF?N|3Rz{YaE_@L3H!`+#IdhfrWF&x_2X`^AMQ^!|&M^c!(FZZFNJ`>03LdaL)i=b;zq&+1UdfI@2gq% z)mVINAks&1u^yOr>>TNWN_h0%2*!p^zqT0UmbGH_a>_a4ff~>oVrdsCxJkR{aVD%#Og^ z)l?w&8E|(uyF3N7$51IU-dO~0TG#3488y7zspBCL$q2-dQ)X(yiH8umAsBmZ>g*`p zn3Y-pig6Y3BY3~f(g5jm`rOL5pL@l_URKOoLmN_L&M7gDUu}ZY*5`hlx~=pfa*3Z@ zs_Q5MGs+TcJMrANB;OyYuW-0LcdB#2(*gK)hShZr_VKMc>UarwXRjB$1P2DedbRa} z)gPoo#=5doE`dwC(7T@Y_Bj;4>^l$B8D79fccZ24sY|>Sc(VXTHnni){*t?y%$ZJ0 z_N+*z8yrjEJ&Q}cDf%%sk;>N|?Sh)?k-;lsUYuG)1C6<~OA+KuJ9^m$csXc(h8*Hq z5E>u06EBlWL{3-xd{sK4H{moUy6U7GVWcu3W;vwo!=~x8e$;(7Dqj^V2FM(+JxQK7 zGR-n%+NTY;X6c@S8F>fGU&RH)8gC9X8VjLl%2X1UeVFf?^1pE@-`Xfzmp2#FK#y^P+?Uv(qOITsg zV9$vxE>4|yzmXPPlt)<}P9XiOAoj=In8xXuWr}b1Leh7bSJ^*i?w%z2wK$yEcjSUv=At~KJP3I z(%|sngP|>pGcmkS19;@#C#3`Z?)=^hTX=dILTcDeNt!ML)|%dMM?YNjk~z?bFwW2& zSBtAm>P`*N6)#N6rFeCBnKQlSEMI#Q4L^kUczcOB@4N^#+%phzJyW!Uf_&4lcbNPn zdX{+jfOJ@BZ>R$hgc~E2mydu=Os}^N`RLe}i*jORY~so0gT`5$u487NswYTF4j*m- z@%H9FV)C-vGIf!vlb#acE`SCCqe7{)jU4G>Ipn68(d?3rp2;#e5M)5sB4+a@Ri-(o z%La%p0fX_Z{jV{+s$=B1PG~OVE4y8T&G*7fZp~D9CIZF}O}G0Yf{m|VmA$Fxs&Xm@ z{mSqIrvFOO4YluSO}E{sE`iap;T%Fr8p`CQ8a|h8Yd#sVhpPJ{%ICRPP5$kL_yXVMfYfyF#Hu z7=ny5QLiwEskf(Um?sul=M{#4H4%D(!Jxe7^8*2)=dZS1HUC!k+q{gt!A_fRir!h_ zO*LI%raA|6EAF}BNI_YHIr+5w2RXLey^x^|D8OA0uA{BlapEobI%Oo*$F@M1c__79 zdNjpoZm{vAD4RGM;-v~I>=e$dpdHW2izYGFCMh_LO&tdy;yEIMk33Me>75J{W<^=q zoUEBw4v{hOt=SkF830?mLGua?HD|pn{NgAR(xhjIwO(aRcM$(hBZXb z11q1_YFc2UWSGH4uU)eeHY1L^qofze*5ax!Jp01o6(6!yMo+T>R_EY062g-@*jgx& z`I)u(y)KSHg7-|MO=y>p(pbec$1^c?s(rMa*TW*0L}D9oCIf^eYL+(0SPU|B;Osp0 zLyqCB59-aTXr&XHzH8!1q|FfWnt?R{*}uiP=(LAw&2#(>KSTfz3d^-qz%HG8GP7iR zC;)UcDnDgq?yK5#)TGA?+}7cl9ZV08D9ln$6L{1(dP*xG+q@1VeInmk4V`19?A%l| zXLZqM&?l$6cGr-pVXm!<*3uo)85@_0`sqqo)}tZ57eI)6X?I?UT+_U;=3fIW-ktzg zZOLF;V@FH1Qc2HVvETIK1mlZ`3CSv~Vxkf%*Vb{^hT`wa<*|k}zASF#9mh#Qaf2@z z90O>9UrXy#_n-))vw?4;5SfZ-PfIuFokw*~`4n;&-mG0Ed&stDA68e|wvaB$)^71s z1zq)lEuMI208*>7vAcZ9Vh)hLM&^lmin(&)9}b0ERs_(JY~37z-Zfg@VfAqki^hs6 zs+?RR2+v%oDayaOMk%_ade_SkGjX?~E^{|yXp9PaU*KlK6|WbY&jy<`6o7b!+sR%u z$g^9C?}O1^xOnc2&Wk2%J72x~Q-tMjBMBNRhO-ZQp+s1Ac+X-!Fhqm0lP=N&Vack* zUIOLvD}|QD+X|IKQ;vdASbUWe=|o9C+ZJo*>kcRL)=a{Y^-xgw)5?^VIf#-A9!K&W zvl2h4Fu9)s-<%2F!B_4ZG&7eVgV&DD_=h);U)La^`DS@pum z1D0<4oZBvA1KARp_GM@F)BK};*if#AT6o3?nR#KAb#>5Rn3Fmp`Ra$qofZm%Ps-A&;-u#w&rU-yJk77SCR}} zlYC<2am?EZR~)6tv-+WMwvk4Jf!;tobLMRHP89T6k5{Wn;V9=-zmu-)kVK@_SQp%0 z;G|m$c=Ar6Wa4a3T6^~xPp3O|L@kIVk!~>e4@>XSeFm1RCVK~pTm(rI{qGaPGMN$& zdI)CZ-Ca5}lmEICOt263ihwQ1a`qIDq}_ploMK1s`QhK1M@oN)z&O{$*uZ=ptc#Xm zvWcPBvxmkIFZZ|Ad^|$d>d=^U{FX(xgLOs&=sho|XXYeN0!DA40oQWm~!M1nPAoBF*R0-4Fh+W76AD;l1QBUmp%1^m4^_V;~BKN`hx)R~B z5!vXxiUQ#{j?+~6iOHwKVvlauCzQFG2axINx|dBMa_7c`OAAT_7C}SGz%*hGSacAx zA`|jD zJjNv}Si?0$k1N{*MiXbw0yeH!^p}y%lD^NZV0Wq#vN8L56iv_co`X~z*w+p6 znS6!wvbmC@92J$?;yA-6aq{o#_J&4J1?Y;@ne_x;* z>w$8jVDmI_(;c7L`;L8vdyAJESB8-Z;qi+aCIw?r+( zl!RCeN zUWA1KGJC>7??V};rM6j97B}nNO+(a*B+Jc2E(VKD=ka-TegH{i_ykpIW zAw-Ex&u;YBjCALBC37W?1piEErd^@eGaJ~n&PyL7!VfbVJg@Eg_zZ;(EBBlceft4< zd`8Z2395J2RxmV>ZPY(-7**zBs2Kyy5BrQbG#O<2nDT3>QB<#T20wwv?P5o+sQUs3JYV{SgVehj{kWjw!ILQ+Uc#XIgYJ)%_&KjuUWt~Q?3pWa zr)=NZ?8t3W_wQBoHC4w?0m6G67s&NJ+t@3UoH{R!1E;#9LYUfnW@j*lYE}iXZibz@Lt$^k)GxYT`&*%97 zXNH2`RUlq@cJ1J}toC?ENoeL5;XF((4~8!&oe9O#9G*Bkv;E15Ggwj|36S`X`Z_F@ ziR~rplsuP1(kXJy#;JV}$GNaXfEV)e1WKxunc)Q=4i6u(gACbQ9-FkwD)sf^F}NeR zExv9Mf86SNM^Xy^2B>W~IQa?+7x!2U`9k%Wd@sf8VFT#x+v8FyUrm6GYx)>9Mj<&AtH7NLiiVgVpFkuVk0u1fsp!h zR|0vEf~eDwGLm2EwieirZHf4l_j4Yakwr-!MrX}wnD#qwR)Luv!rhHrx^{Ni9XI4u zSKouu>BPAX{T}bYqL0;dd5*`y!WZ_3hl2Z1wsqAnioc9sX z<%zWOrV8EPXZD>A#0|;RP4WY63x{MbpjPDu3l%8FOZO7;JGcv1VDGCkGcfz6GK-tG z(dm76-Ak)mOo9H9+I%~(ea*~{DZ3lI`^$ZPg-w7#nxD98F`?a4GoD+lv{uQLWCS0R zXE4*JPYsw&e?q8smR%?1=e?X%Mjk8AH@kc7L=sK=0oLx2*B58w19w`B`H5kVt$4gJ zd5CE6oA%V$@0D}L5oxvk+;x}hQAL-G2iPiT+ zdvsOWWWF>F-J6?2fII>pFy<{@$BV9&_?ge^;!ICz7M2gi)vev0v)XLLqJ|>))WHBo zQ!aVGsugu*t%+nGD4Zd`=IVW;QI0xZ@bmz*iS^WyFeaTqLFOmU_%l5;yfNWD`YF!l z7|cW_uqA-?^mLKMW0X^<)!F!HMa$e+9SjP6wMRqORT16#ND+gegTu2G9xaPE}%!m?oB-hRKFzi_sQ86flA|g$_q|^^t}>zz_hSP%o+sGPw+*0 zcAiK~3DW5F*Dp6{W!%ZUY>S@8dwZV_JH2EBl!;x(sPqjT=yaO)@oihV`9eXs2Is8% zZ9zqzUmI^VoKQ9O;8jYR#-!S!_0>S9*+jgR^N`;Y0xPs&Id-R?xetayuVEUOxEB35 zYG=oF1}T-X)6v)R6>90b^b23QSR3ecFLW5j8S<>)sbJfI?TgK46^hD2a+fRxa>sH3 z?i&2^mSnqM_q`QwWLn}DEqvsoSlc|#J8vQR@CUZCQ;uKOy37E#9umb03^RiBC8}%rQBwDyX10&2_hf%A$@u82EEDeXx!%DF zSvm1uI=Sv?T+f#T`(rx2s&?P3C0t-jZi`v|-2U)yH4fK|g|!pclpCM@R@EWAq30WM z)m#GUm+A}ROY<_!pQ=^7U~?A6xOX6*&^${n2n3_ri+b_xM2URfMYhE3p!%? zSk6ardG=_+g>63H3KkDJ(!~ z79L$hsaa&J$pPI|D3f{&;Qv-jVkRuxNl?EOohcb<0A$CmrgBl1Av(h ztUM0YJ7}xYGcfq%gqm$O8LLm86EW! zmruIAjI|5)h>zL(E|2{(wteM{cf;@S3yW$#*8_c3yItR6)#&0QgNT>8j;N0|pNm@n zVZGH4eP&g{WX{A3lYdiX9|d#Ju%<}M$05Za4SWq-_?sX>_l0U3meYJjk5<%h(>;O$ zccRLFf(vc6z09clbG6E^0J!00&w5$GjJuI43KY3DVa3gQ5m?>SE2AD0L%59Luev?h zCaiym?K9K)q3ZoXr@aSjYs4$7tBj2?Ywv?X=*ZKJO#y$fuRKZi>|QH%D}AR&_|se2 zL*guc?L!ydSb95sYTxzkNJaK5lcGSULIsxg@Kw+|iJwgxlp!P?WDjx%xvTaGGD&=v zVesykvbA1lGytC}9e+GEcpZXtl`U{UAXeki9aNBsbq(iJ?YPnvBfZq&QSPjJyUE1T z_Bp=O{Jyo{>YX<01(^xkzSnba=H8nHk4ld!tI%`i_ewj@RipqR>A|7v}=l=1HyyF)?e%tUOFzk)T;lafBg`upRfFS(?596 z&+k9{QKEmavp-Sn=kqr(%wuxSQ;O%ep d{5XNwQ*$*>)BF|PO~TK)Z*0CUJM9+#e*g?Wl~w=% delta 24197 zcmc(Hdpy+n_kUrN&26(=$z^JDsR&UR%#6~eB5XIwWmus?gK;0@9TlH8r6QNy5|PNA zaT`O*Z7>zNjWOghF=hrcj4@{BH@5cneeCD^-Q8ci`DZTPuXE1p`8wyk&N;6$FGh?Q zKVGaDR{(G^j;ABkm-(JG=7P2C+uW0}%2~h@dLf+%JGBd;a?Nyotn4<~gRJC$WCj|VQZhi9 zfv_KVqI-wb$@|$3NM3Z|oOhpbZ4FVm_F3&r1pW*#6W%8m6i~~aq4Ucb)}(ayq9ut; zVqeA-t*~DL%kN^Se#9UIm7M68#lRCn$e0Y?VhjUc>9{a*n#9DR>WTLJlqqZoZCbo2 zeSv2_El1o^`_(obTmvGASLqodC=xIL8oyh>%cv2o2v#UyhG0&x97_T?aKU3Pz_EZ8 zIg#Pm#e6zd292$yq_F+r;UoC6VH_(6KJAASfapbqu6a@vGV+-0HdANs^()c6DV*MP zObsfdCa6u6>TrO4R;- zDTf!&4%d=U++nIRnh(5g8aC+D*JM@nH}~IK6T$f+!N{jqi&4)Q>QlmwzNh1G3|tVf z$cLqH`1LRT|uyP3(Sy#@}1dbo5KTNEc9F!cuo8& z@je+k0>=$xFX3SInwbHWKB(+Q2%h`qBpjF)U5XjSiY-&%sCw=S3|(K#8BM8Gs7IQy z=ggtQXeJytfL-p**IUQGwwO2^oL4(h(j+SIqQaSBUam8TuQaTPlzq{1KP;T0VO$G> zvB$s+m>>Xc&1HE9&n;}Oh1W0R!3=F4|M6rdgi1SnWzAX64*eByczK z$pc&~(2VEv;Nt^7qn8tHkHaV#?OP(?DDm4&uQUwGUQiw)lem}JT^Yl6`x_ydoRik! zmwJ_15Y;;MJAlVZ*g?)LbNgD{wZ&_ERwax(Uxp@x9p6&4OW|itlKtAYY0Qj_dcu7b z0Gg&RJy#+Xg|qWRP|e*<{1G{sK=uj=LW04#1ZDzA{VIo@x>0Ps>{$ho&a2#E_Tu6j z48UIeW&`7<{O-6{VJ|U-@9DrKKW`S=-3C0BnAL*<4|5?!F!nT(l$)`_D)kPsL@LB^ z;VZFgBJ93s-c`_5p#`pHp>yksK%xTJ?PU-;I3qvI$>IWTGOo|xmh2Z0)SYG!U=3xC zKb2sV(RH*}HT9N+aNVZ@x@egmpmUXLjIq(7CBkLLYwm@kmOV*`Kwb_5aAdU9CF(%2 z9%vR@jN=mAQC0*cl6ctK#DEmn2Sn!~!8<@~eaCJc_>xj|A8!6xkv~vE*RFxVmI<{2 z^pJqw#C_c76nbR6;H3ucy&uSt%qmwehXcd~QUJ!OkL2HSoFoSee|JYWYocX}Vi%OO zdA;nm;%2dCwW^|Vz>qD*HelBcVD0%91h$WLaaaGsK+e_F@HIXYWcUDjDg)O8S$KDn zgs*j?o1vmw$J4JsCF9x{c8m5X@^m<7S-Z9cm{kW3z+ya40A;!|2yLfDB^K`H8eTXc@d5 z#t0#&=iCa66Ok0$a*M(lLcA#K zj-~llWUN1#_cExqh0iOw%7&ZtLQ0r7=)EnMpMCi@89a%%MptwB+Bh`98mu%)oyKQeRG36*CG73=)}aG)FcC5L zZ?<3>jB21$7X|nL0Q|N)eKwseHyQ0W<%XNd!Pj(a89u{ND6iAm*I28|QIwO;{vGly zy!wnG%uEEJA7Q|Js^0I!q^GTZl*2{23 zl-9664O8^Yi5|8VUD@Xf9ui|8c)lZ|`irPFCYNK`ye2dWo*t%Co|_ASFeI2+)MbP= z-=5v*3GMf;@i_?)2qBltJH}w)D|cq=w}uV_lwnUN)m1zFN@%C%dGM8{n;M(EJT}zD z&4+1{*2_MT+agspHqYeAkjYraIJcR`V*<&#fD&4Z&~`z#MsbHa(p!nUYN72^|kU_?^17zX3S_ z2_trV1FC(*W!XXj5^uVKL%Ejmj6V@tA8s8gzAE%d=NX~U3pJn+pFScq8Fb3NLRSR; zjmhS+p=e;%2Q=Kx3?~P<%h2+&^s6WkvLKPchR`Hv*1VNGJDD#xl}X)1Gumg+6{`}j ze9^31g=0l0ZhAXr0*_^>;FOIGM8s}eI+v_P0@cHU=M1O!f%T=xVH>ncBZQjjwCG?L zP~8iZ;31{hDaTcHH;E{bq`UB^V{yCdY^9TLBrB{HG1n^YzcR4QGv7L7U&Ibu0CAKT ztVG1tTP8I0uVWhwhi7Fx^}UB`N%SM?R=9Nxt(P_Qi{OZiSK-n}((|84cORIfm;eTJ zDuleO^t-V-FlLGet46!H4|3+R*m_MX=lV5ZdXdMG*z32$)Ok*X`9zPQdASNkybe2oX9H5 zX3jHL0d6`LRby>3FENTvm{m^MQvKDo;$Wee*?j5;W zAX9rCC!s_~iZBw3V{eZYNC{;a1SAR`j3__Kz%4WBS>)yS4`dj7|8rf4u7$?7B#gu) zvs#2HRbQX6U#P{U=kgWPFegB^2jQoN+5CC#hAUN+NP! z4Vm$PW+K8L&YQDUSF7`|FFh_qv>^3oME&-d9v42l1rc#Wf-Z`*8m{A_f{F&$WCR3> zOcxtKHlDTnX{Cr_-22{NT-qUYBTHXeI{1K|WS}_%^g?y|x&3RQ)CU29J4_#@eWW^E z_}!CaVGJ^+s#DU42Asp^WV8d>yzzRvFoIOBmCXofuW?fn8uSJ2+4;i6TF$}jK3n}E zkBq9XR^y5Ah#NBWrrX1{2n2>%kw!8jKzm4h5;5iaQcqeyYt8vIp`vdAMc4-7zO7=M z8*qk_BWxCL2v<~W{mkaGdX4C1X@FwQV2yI-0H6-2W#^%4h2gOG(W2G|8maEdc7fAJ zY^>9SK*SU^ zrl$zzJLww)tn2iy7RC&JdK5QZ7&hC!YBml|7Rm9QsQF>d9oQ+m$zot76B@h9%2dlw zA1+heXIp7Le4WzFX8zDAT&0kR{h%S+bO+&{7n9!r;xCCeqB}x|%B+Qx6uK7Nm61YK z;hT{w4fLSE4RqH{fAlXRqkd0Step=5>MLBLH4!|K$l8R}H=-f{FvwLGl8i+C0+H-c zg_sa4McLN2aVx0fK%6y&+4Vn1eidO$-lsYq<`VkYiBRwJ6WOaGk=z3tV8T^;h#P)> z?2D5N9i&>u;AZF@p$-|IGWCJ=G$DlQ&Pg3x1v-OaB4W~V zvO_XE=ET;Dcsg!_U_+ZIbSd~{5SHf2J9&XCA)9p6)FMsTeEzw>`mDrO1lV~jdR{gS-F_LRRF8UVG?*&~e!u)bQow;$>Dqw{fYHz&sEI=c! z(}zxF=m`00TAKR6lj2USvPE5!({;?gIJXuWRnmi}U;R!v;<}LO2F!_jLZ&B0BvOTN z=hA6GWO^X3td`pZ5&*BTi@lzX%@=W{-23|>o5Mn1yUN~7dZBi>%B#77ch>&w5A9Ri3|`S(B4Fuh`W|H_Ed%f~t&Xs&YB7l}kE1XXPOz*b1?$wt|s z{Hm|je&PJ;u&EDXtnkdQ!kre5`6luCubc7;aK}u2ir$?_TC*1TILuT* zIK^AF=YEy$l*D@aX@@W6e&Kv!$F?erudT+f!hHf*g#7;ymwyWu;J@JVZ@~h5MDtIo z%O{6L$p5dX%O{5g_#{^J_hA7(+TQ=475#l!fPd1t{0Ufqf6}@930Q!SKItEEE`P$e z0H49<{uUPCGx*%!!UBBUM^^WuKcOz4Tw4IshrjGazs2R>f(7`$x9EQq7U1Ld`oC<^ z|0pcL-;P{94J^Rlj$A$sEWoD@=>8%sz^4xA{vs^Erw-_T>sf%= zKJY1$=)dT5f9rBT=3k#H{9gn7gSCY}0}JpE))xMZ5q-?RK3DjE1^D+%F292X`1eaL zzuSsF=3k#H{5J(YZ}{H~{GS!NKVwCI--tfuU!N=d-v<2M!sRD|1^By#%imei-#4O< z`PV0_=l)e#fKOb{{j0D5pSYg;JLmGDw)4OGmJj*YXAA$=1D`kiuL3@6^)jp#%E_1VII5#aNN|8(FJoT2?ypZi;v`yv1OY~g|ZwmbDC$7JT1^CxbTz{`EAM&sNb&LM3x_okN0X}Q> zzu6J^zYggCf1JxtOZ~t5mJj*YXAA#rfWO(d@Tp({{$}68@2%)B8d1c4xgj=<*hd>+ z1G)4Cc3_aUuvd^fRjci9O%JK$E)uN+*jV0q_H~T%6g`yLNRO+iA2_XYIzuQJ@fAUaiH zt!Am8noT?;#!g(U?*;CcZj?Hp#2>jJI(P-pe<-#3s$U#BXc|RS(Xe{%Eo@?o@vh-@ zic4PID|z@=*20N7cj@2hNSdj7fXLUaVvyKQ>I?8{-R&n*Di&%d3oD6Fzof$knL}UEQtul@ zo{Sw9`2ggH$In@*&%;hH`Eus}b}pkW#z+Lk>Qs8Bl+&P|b7PcMwj`OUwt=)WqLQi;1w^e%|xTA<*b zHCGjtiuhTs+*i<^U%5QKB-)lU=C6=H5)SmG$b3n^UHIEId9|+jk(5fx?QU_hv_kodm%AxJ{whS-I0mvYpB!x1f{6g0Sp#*( z3yaT4uN7h;D#hyM>c#MtH;$@x8H2dMQQUB;CRex7I70a25HG!B{OE%*L@3I**`tu-4mvcXTRIbnCV&( zK!=oY-aHo<^p>s2_7;r))OAoOG1i!!?Z(waa5>LNlQ`TX;(Y6VUhr~E9*AzQTw7>N zF}4j#0o9elGm;12*|Fi3fU#9L0IQ=sf)CX{44_fb#Xa`V!+~k&xKldO!HKxs(@DBb z9ws@?c3b;{OCGqZQ3+jn-t;w>6RT?jLjMcglpe`=# z6N^u}Z_~Oo#J@F5(}SdQ-pT~Er20BnPdG##XKQ9GST1N!S8qQr9cwNtZ6z3bd6LWE zO^i_M7$lwCzDK?6hGpTW{!aA-UZ{UidrKut{32LKVX)PY^&?{WjX0NPG7ZFM)yTnu zF9F4Vli0*S%1YB|o(FRUG~5%tya5Qc2zrep4|{8i`+(ou-wv?*(hR?af#WO^p0lk> zrwxaizpWyda`|oo(E?wsI^WWBs54U&#@K5FSAz^69YrO>QUCQ5S+Obg#aV)m%XKK? z_KD4dH_s)Y7N*YU7HGxg)&6vSnFF9i@ud zs}$y4KD6H>0957WnmEjHQ8Gvz31{tUVU6xtdwH4neq(QLBnsT$*7)*0`iWj_79m#o z+IgN_Ym|{ks0ot_N-l*?k7q5@QuBuHL4&qkU_9X0 zlQfgKKed0jo>^2|eoJh0!=_ob z4poERTWQgDad()tE<4SMngzc*d9sC7ny2T&yQ6cg_)!%1PTf}@Tm_#_f zMvpWp$VhydmFbd`5p7dDmCl1)HA`mwpiO}q2{JCzt*S#TFr#CX#Z1)tdh@hEOANoc zlgG>jLQ$A0r18^AkF$c;Eu#%8L}Vi_tPVaY?*^agSI$n6y6h0B@21D2LYtqs0d5y zeL`b@F;AV|Up#X%HfJ-csili{dlxn3YDFgfbqf#sPGOk|(=G6#o&==?)NU$;RYCwy zP;;ynxq&7AAMwGK3u$^h#XcHtiTWMz7JFymr29p|I;I0Uy{+IGLin4pbTnhxRgVVR z37UM%c5yn+)m)2gQYTPv8!aTsH?>+eZX>Af^yoiQxiEup3hzhBO(t}2$KFk>s|VKO zg7?-gjtR%_bt+}}Q)yE5wlOI3v=R_9tybt+c!Vm1P=%J#@( z($h)T`s>+-kk)R7xX9(^)Liy{Y)!F&-npz}rvCt)l}*HSG& z88s>47zOQ^29V zdr}Dai$M;r>=n_|5vGonF8;)20X!3MhUSm}KKsX=GyQ{7qNLkWJb zkfC$h-u?mu=u2ZVpGAf$y-l-Ov>lu-R*dCKy1hvn5%ih9jOTMhzNt)QHcq50T8#mbm0izijtlAF)w0h(RA)H9{2>7$ZaV<_{h zC?{h^Z6ABoWJ0e;ZLJ}eI0fl4oMHkt&60`fZ-kD-^HQ>z4`KGcSa(NG+=$T_mT`9v z6joKjIO-HcO9s$IE4?6~0G3=?gt^u!sj+pT`aMTDzxQ>*dBoM+Os}Qg=`+VqQuG+S zS3S!U5K4laUv8q^kL0pWJN`jB`U2tA!;RaxHpY8i-PYO>fz`IH^LsSqMctaK$zJkS z4^oE%fsD$M)H@GEpSgs9*ALFm^5gCrz-C|bo%fJ|@&X(umG6A9(oK7uBQ}z%epj@_ z!NzL~-*3($Xi>Z{*>M;7#Dp{T{c+V+b(6$}&Smcu%82Lo1#9g%(YuN!Y8{C1ksr0l z^Ij{g%KAjWte&)!tnWrd&g$~8;qdg?<^Woa+D};1g;~Z+P>P$hH@~%@6Q(RBd zN!kSi5}VB4=bKXqjP7q< zspF)8(#}!j0#i?fBJelD+%)H#t>Ha{s5rSxL3RE?m`t%!vEn)d_4V$k<&pYKF?P(J z^s)5WKzdLS)k2ljF@vXB__m{on3r(^Zw-`+qMW$J(sc)%E4}OJ zmE+psp?=ZnuX$y!WZ@>ckgqpY_xGL3qU_(f_AmTFEXQ%fL znpgcrgEHVa=dgzI20MNvK7PV|z}G^DAsQ-g6u+3^a+Zgj;ABGf1Xo)uVGgI^6*h8i zXCQPvsrJkfe!sDxv>cdO`TkvX6wx%wGdtRUU%%oMq2>OX6w?&IWd1bIOI$>L1NNSv zA(i_+fOnSa)hFSme`_kj0Fo`An2M0l4}U6I3mD2SZ@jH%qB@3;9^E(9**W`Tcn1fE zE@gZN5ttjW4`>qO5qm0eJ|4im$?x2%^+=fe4{LdeL9DeJjHAWv21--DpbBf5#iO=f z(^`f;5AV9jG}S!QAP_u~uHJD8u{#qQMnMto)}?PtDmF^B*6GvTxSO!Y?Cx!2LH6AM zV<5*kQ*2n$8nKUl zKssDNB(q!GrNoZ|bqR~HeRprWfJ8=TljPpLF;3;h<*h_*2Q^W%Vm-onO&W{xU9yRi zaZ}doG`DM&wT?f1#^W_4jk{Dmuk=9BqWvYQ4>W4i1N*M?I@DX>!9E25;0~S2&@S@U zM0V#!gViOZy_wHF5L0C2w30oyr6W~0l#oP;Nd&d$s_)C(-Q;nCU6PFAnaYG&>2w_U z?)kZ>7)fe^_ts$;aqI#orW!sYsaE#3>=mUy!fl^*Rvm+KK! zyX0|BZzgPQKs`dCouf`IG|DnI7C)vvT=K3d;1b4`^O%=HK)ONV?CiF*Fm@O5! z^c34xx-c(OAViyl?c21*_nzF8(`U%ti_H+u{e9KMYq#tcAXw-eEe4b;?<+H13E=)X z^z)awtW4ELRqQ}=luru+_SV`XX;itpZ$H(T*NPL3aKUo7?+Jm+({;zN zb(K@Yx@z@e9g;4s{>k9)_3#AEZSplpew;yIOQP>9u`N`hN)N2ZLUkS`f?XXF4ct2T;vYQ+Lf^r!zz0f-{(1v zH?qvw^8{i15-G4e+_LD(lbh+*=T?SJ@hg+`;{$aH7$b2A^-Fy&Y8wa=6@v|<-rJHZ z7M_Lp;C|?XY~W`-{Yq*0KS?^t!|OJx`H8sIyMy?9;jONr`-MMZ)?;mh_Yv4Yy;$1R zf_4i-!p*+DZUQ{&>z}6E3?567^P3NIH);D0`9=?V?lLkNs;lle8BQPTTj(~LTu@As zBp|dS_BL(OmF8c?!L}^U)67C!G50MWN|b>c_lbYo)HvYfMnOS| zx*h*1BhQtG+fz{Fup*E5oi`@dOvmE}-%Okzn!)UebwaiJq)Ms|oYDI7KKh>Wydq2G z5F&*2fd8ULGONw6YkAp?2W=&6pnV0SrX(Jhr;JaAck%uB2fI+MK-){OP3PL7Ik$w= zx^x6oSMsUM+X+rx{e*EJ3(1kmc+9B;b9W@ji^P|#fU3e=G24j%L6!Gi<-GTCs|G8g zr9x+jsC<1wWfF5k4$#}36J_^sGM`B0^Iol+7S@tHsVO7%hahQN0mm^)#fp=JM>5rT zvwb^k((HZ8)Lx)TC(0+TC3W23Wc6!`E)svC!LbYdV7Nn03%VsHdf~`6t0zZegVQY- zW3NQPM&sK~9#(rlkcxG=jM3BsfN?v0P6Ahf%*PF~hp;vrW$A^8sdp3}rT z9h>5=rYgqYvg8_uY23^2o@~%K02BAxl_F<{EOpO$tT=c;ddBbjg6jq3sR^+Wm)hGn zXGh>p9O3XZpqOA#j?HVi8$cyG+mR&)+8bXSe2$uoCY;KaKXLym{n~tZ2VaDJT-qcT z&ND<}wa=sOgCiGV=G+M|CtaBJd+j~tKCe&%bEmO=p9jP6$C58;Z$H1=KVH_b!wx!= z>rj4ywJ;EF)ha(+HWVw-+jQS4+BtDytJb;r(LEdhBED08{!NDLx#zTEy8KO2blpbQ zMyF`m1RFz>j4O#wOxFDE%hpL(RVI&4o|59M(-ziE*zYIHOKgum9faYb7Um z$JhY5N?80WOl`U&nm@ysBBx3QbS);WNnIxFZo21pzc14Phn^wT+f+)>1mJVS=`p^6MqmnNRqAEOH&hpB9e}opAO}QoUFi za8yA7;u8EsF9IT7TU~m&Io1QnISz~~#D=ZYHAmOKoI*{STlIEw*)msmQ@-gaLu}?Q z+mQ_oU#NSlFd5o`$-}rh;3}uk3a*};Fmlnx6ywJmRMfw>awL^20%GIUtX8#n z?>bL=prYrsH&IyB&Yiq&?VJ)$2xdsywjRAp5tbu(>KX}iEp|@NUA4O6l}8jESlCBY zGWnSxIGL9vdAId?zF$dhhbZhE#S*5iR;7NnNTU(iUj%j@SQ+Wi%jr19evcF`s(%ox zwR+u@uhY{J=s9cmeeD2d3+M2KOIQnpUvs^>344qz7@dah^Dug;D0!<(0SJ^2)LW=8 z#mZ|yYgVRU9<9fkpUE)VT!kBz*zQIX@gKU$ddn#3NL{gu{xbq!O4!4U8o%3js`j?UHmXG(Oso0u4lyPo@sP6Jxlv;}oy3%3|Xw%yXfR z++^Fsp-1HUH@51&aKkt04S{NEVBhOtC#TSawjYyapb&o3>~-d`3zpar2zD_Ahjs(a za$WXK92x|!u{lwlsebrYn`FOlU9GGfo=|#oFVk}s6&F2VJ(CTXZ#FnK3spToMjF#T zo1J>axURk@Hfybk8QM?z`N8QUcR(xM(m6n!E?RydO7iLy!)@n@&}C%g1?g=$iD@); zdBp3vNw8_VM0weL_>UC57%%_!-YssyeJ3=Dx+{QvmXzY)I)^KHVr&QKnI^+>m)Vh$ zH{RRSOQbU|*SAR8ww=1GfxT^3tVBDSv9}6WDtLD^tC7cg!%fpDoE(l7*BVtLCO34$ z;O^JKn4#`CW8S+16v1o`G)7%k$*~z}jq}h9IOrncbP$!0A{S|{{q_A3G%RR}Ku;HeWs4k>oHqg;}VDyI!hIP1B*=8y3x3N6`oQXC5CHTc~ zf%HYuXv~Lzv8=tWnEegloG>d>Ei*U@+NZ`$$;SAyK!WOUtDW8#LxmTojKw3ry##3M z;UtNP&TnD1&x1skUQawcA4>2Fy_{<`t%)}reZIg;niih>1aE&5CVJQIP!u5dtzPf) zoe@6IEc)X4Bj!B? zFf)ru6vLs2jaN+e)5G>87wV0*QU;@{6Tn2z5+xFxDHYP@=XVD)g%^+RALk`2`&Akx zjB_>)vf=U5BW>jBvu9y3z@;04W{Q2RqO<$rqt|l5NSkC{u?n&51=TBUQefP1Vdd7n z4@pHInK%&}1G^(UHY$%`ElaPxSlGh)8nYtqzOM?gyL;HJAaD{%F!EU6qN>NL@@P)7 zY)sl<>9(^;HUatQQaTl0TVs+Cw_Q)s-JkoW4AQa1Z@$7~0qC~Q*}UKNS#;)>WDwd_ zV$?3$B(b{MAb!IaQf2{N3KDdQ%8)%L59D|lsP{~wB|?akNh!P)26^9peb_{I*#no2 zdJCx(g}VhoR!Npg7MyTW`&!GK^3*9v7SV)ENY>ROUy#Iix<-K}s>|y6cf$(+t6|Gn zpU~3HOsV6Qz>dv%Tc_L43aZ^p+8U2dhJzq^`_+}S%X6XJ+zbVN3y6&4GsEKU%!6eu z($K`+T;Ob9n8gW_Ck#)H49mLfVo!nkqJjAU_nlh@;nVn~6-6DbKynv`frau%-$2jR zqvJaUmwQdB7z1qjcEOQLKKQgk-q`(dfAJ-j2=n z93VntnX`<%Phe$Jez1|3ju*W*ni5&3F0O%opH_)(n$K63yNoyZDr>AddZGO^RIez! za4TvxIlhy;{m5xNNTLYL7!elCGY08iC#}A63nu{gt%C{PyT``Lj&lN4slL#XzMhJ9fsn?&1-t#~Gwl@Z=@)Gre;U=vjhD88Ra-BX3o z+Pr?NR%yKKKGs9Z_^VM4zeKEb!?FIYkO9AaRk?}AuEPYuXeX)pjVsj9ZSZ-xWBbuN z$s0^I=NL`6ieE()`Xo1`qnanI(-cty&GkY2Cw>t*ni>#+{JOnaz7qb)X=+U&QK4m| zmNa&RuH!^h`1A{z_=&VC0Nu1*{SiILC zwji>@%=8QIT-HJw>veZ+7}an7mIvE*Q8&Du zJ^7gKNgH<_^OjerAy2pDD`Ox@+!F~pQCR2rqZUCj)1j*;~`&I3o1OlmAr4wzr22oE&V!kPwnM-wFo5{^xQi;X_Z)z<;Q=eKm)WhEGvF^T10y(-S7v?S*qLu33(dr-5 zWF)k&?KXRBd~UlKc2n(U8ILQFgjZG83YVo-W~GcFHH>UMcgZsP~IA5dSGH z7?{Rm)YTorjtOoZjz<=cW(0UgY%a=_Qu*~n*Kxr-z?Ah~^s$ttx55!^fb8Y6 zJ)*KANLtIPzRV?#8EOI#lk0#qyzE6iY$|zxt}} z_0D~_g&U_>P8PmHty*T(7=P4*?#WkNKk*#A zmo!hJE~l?t@94Ofxt%k)qsdDb(1MbcS!U|Tf{zyg31k`DUD#Rq!nn)>cyvQWmL#pU zt=a=k+rjg>9j5o1y)p6PH*yc7X%3d$HvLpyP$HWPE1P2x)8cO5bQOm3vmsiL?qnFO z>AEiZy^g~7$4Q&{<8oNiQP3_dX=2SDaW6C)d;=UX>6BUqXP#>x-v(?K-zq(04Eu0S&S=5hiNK2jX{Mqz;rv_Qs$1XrkgM4flMxN# zZYHpui}-eWVs*}ShgMBBJQk-Lvu$mj3_;aU>f}!MB#Wrx^HtU`mB*}eYW>oU)eK*4 zv$MJ}Ze1w(#I)_3vmgLnUi#j{1lju>f?k|CT_V@4*oavl%Ysmnr(Bc7p-oD<2o!K2 zePi9mWiCnrXi3-rdz=f~ZsKl7*;>mV?=F2d)kts{Q-B404^uXF#Tg`QIPLbNC84E| zRx}f$(pcf{co?r%JSo<$IFpSXCr0bhvHp$7qF61@oDA0#z?aN%e?+ElbT)`bI11C? zI%vmawW7-SmCM`359qzUOF3aJ-Qd^NFzGjfbO(>>tM5Rpq|ne1i7s%p;Q>g|#K=x^ z$IKG#&GDur&$Z(X+i82vZt34=f^^fN;zz*Xszv*21C?)?8eD?}_Vj|)8Kr3ASur+- zP0L$0w`c<3SSfP70t1~`taq(vd;H{HvttRD0%Km5M~FSWHht}hSn@#gg4290Zc3Ci zH?fgbO^GxowtsO`cv?S_=4@!>c~pA6ZUT6$RWE&cwO@k)-n&;F?*6_#gj_pUjB^2h zucOy9p55rMus^d%2K6`y+NdY4a8nA-S@$io906!ktIDs|rzLeMJ zaj6!+=)@ZGo;cKH&&}KETB0bv8(iF8TE@_G$P3bfQBtA?uNGdfp~LnrMAbtk554Wd zy<+b+7zR9O8E>nBd#77Q^*1#_Wcz3}eW1s0y2nI29|kC#g*T&OqZU_El_z_t`+G7T z#sk~>qZ{p-wbc9Fa_+`ZtxTOP8J?jS3?#^tENW~tuCjJ-(FD{}XAgC6O?^|Swt1Ce zn8~ujqt&LPhBv};Vvle2t>ZobE6t->ZOesO3*tyfp%%dsX0UZg&pmWs@nA;2Col+K zO#G<{>ZYe?+mkUr1z}AFMJ7hpDg!~@&X$7>IVkAa!b&@kFiA(^vK$u zM*rOX)o`{v;(WJn&8^cuG95GoRA-ZM`&R(b-+v73KpwAyX9>fAOdwGf59U|8Wzw}k@> zZlk2TmA@D0$I1xc{Hyb?ed}(y`N;GzhKUuAjN9%`zfsQny+|ceDpzk)y}hoY8p6vd z;8D`||C3UA1Tf58sd8To$2J>Pc}U@yUELa`S!c$+$_(g@%lvhE!c`N((B+F0w!q1Z z^a14X6Tv~*&wlfOuoqC%dz&3{|Iq3^e%a~l$KVJ4r@FrXwX?50;;Ogvepb1R_&52& z)`RP}LSX%^VIyaMY9#!*)2>SYj#u@A%lW&j#PvOvAMEz?z10()`e8wTLaJaw|BfaA z?ka2P2-GQ=jzsCJAU$i2#K8ztyX1n(F{n7-t+Ei$m zEd{9TP+RSJ{5FzXyd#i-sznJkxrQ1@LbXaOv}8f4*#-xHJMoE&qHU3F*V>}u{8OHc zKxL&Ar0m0^RCG>9O(8v3!o~2}~4f2iVRm|G>U$ z041Z}-y+?4EYR03U6J<7Rbg?vwpCeAhF6^adTlRg9do6P=z7x0hG><^gz0WaX+gZd8z0{)?O{X=Gd!0Y4v6T(}#{$WM?F9M Date: Mon, 12 Jan 2026 09:53:06 +0900 Subject: [PATCH 10/11] =?UTF-8?q?update:=20code-review=E3=82=B9=E3=82=AD?= =?UTF-8?q?=E3=83=AB=E3=82=92ios-code-reviewer=E3=82=A8=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E3=82=A7=E3=83=B3=E3=83=88=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - スキルの役割を「エントリーポイント」として明確化 - 実際のレビュー処理はios-code-reviewerエージェントに委譲 - Taskツールでios-code-reviewerを起動する手順を追加 - 使用タイミングと注意事項を明記 --- .claude/skills/code-review/SKILL.md | 79 ++++++++++------------------- 1 file changed, 26 insertions(+), 53 deletions(-) diff --git a/.claude/skills/code-review/SKILL.md b/.claude/skills/code-review/SKILL.md index 51b51595..97ff80ba 100644 --- a/.claude/skills/code-review/SKILL.md +++ b/.claude/skills/code-review/SKILL.md @@ -9,45 +9,28 @@ SwiftLintの自動チェックと包括的なコード品質分析を備えた ## ワークフロー -以下の手順を順番に実行する: +**重要:** このスキルは実際のレビュー作業を`ios-code-reviewer`エージェントに委譲します。 -### 1. SwiftLintの実行 +### 1. ios-code-reviewerエージェントを起動 -コードスタイルと品質をチェックするためSwiftLintを実行: +Taskツールを使用して`ios-code-reviewer`エージェントを起動し、現在のブランチのSwift差分をレビューさせます: -```bash -swift run --package-path ProjectTools swiftlint lint --config .swiftlint.yml ``` - -### 2. SwiftLint結果の報告 - -- 警告やエラーが存在する場合、ファイルパスと行番号を明確にリスト表示 -- 重要度でグループ化(エラーを最初に、次に警告) -- エラーがある場合はコードレビューに進まず、ユーザーが修正する必要があることを伝える - -### 3. 変更差分の取得 - -現在のブランチの派生元(分岐点)からの変更差分を取得: - -```bash -git diff $(git merge-base main HEAD)..HEAD -- '*.swift' -``` - -ファイルが見つからない場合は、origin/mainとの差分を試す: - -```bash -git diff $(git merge-base origin/main HEAD)..HEAD -- '*.swift' +Task tool with: +- subagent_type: "ios-code-reviewer" +- description: "Swiftコードの差分をレビュー" +- prompt: "現在のブランチのSwiftコードの差分をレビューしてください" ``` -### 4. 変更コードのレビュー +### 2. レビュー結果の報告 -取得した差分を分析し、以下の基準でコードレビューを実施: +エージェントが以下を実行します: +- SwiftLintの実行と結果報告 +- git diffによる変更差分の取得 +- 変更コードの詳細レビュー +- レビュー結果のサマリー出力 -**レビュー方針**: -- 差分(追加・変更・削除された行)を中心にレビュー -- 新規ファイルの場合は全体の構造も確認 -- 必要に応じて、文脈を理解するために関連ファイル全体をReadツールで読むこともある -- 差分が大きすぎる場合は、主要な変更箇所を優先してレビュー +## エージェントが実施するレビュー #### 主要なレビュー基準 @@ -96,36 +79,26 @@ git diff $(git merge-base origin/main HEAD)..HEAD -- '*.swift' - 使用されていないコードに対する後方互換性のハックを避ける - 使用されていないコードは完全に削除、コメントアウトしない -### 5. レビュー結果の出力 +## 使用タイミング -簡潔なサマリー形式を使用: +### このスキルを使用する場合 -```markdown -## SwiftLint結果 -[✓] エラーや警告なし -または -[⚠️] X個の警告、Y個のエラーを検出 -- 各問題をファイル:行番号でリスト表示 +- ユーザーが `/code-review` コマンドを実行した時 +- 機能実装、リファクタリング、バグ修正が完了した時 +- ユーザーが「コードレビューをお願いします」と依頼した時 -## コードレビューサマリー -N個のファイル、M個の変更をレビュー - -### 重大な問題 -[DI違反、過度なエンジニアリング、テスト不足などをリスト表示] - -### 提案 -[軽微な改善点や懸念事項をリスト表示] - -### 承認済み -[問題のないファイルをリスト表示] -``` - -## このスキルを使用しない場合 +### このスキルを使用しない場合 - コード構造に関する一般的な質問(代わりに探索を使用) - Swiftファイルに変更がない場合 - 初期のコード探索や理解フェーズ中 +## 注意事項 + +- このスキルは**エントリーポイント**として機能します +- 実際のレビュー処理は `ios-code-reviewer` エージェントが実行します +- スキル自身でSwiftLintやgit diffを直接実行しないでください + ## リファレンス プロジェクトの詳細なアーキテクチャと規約については、以下を参照: From 63bb760b0098a75bc930766e863cbb0b0f665ae5 Mon Sep 17 00:00:00 2001 From: stotic-dev Date: Mon, 12 Jan 2026 10:27:06 +0900 Subject: [PATCH 11/11] =?UTF-8?q?update:=20=E3=83=AC=E3=83=93=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E3=83=AF=E3=83=BC=E3=82=AF=E3=83=95=E3=83=AD?= =?UTF-8?q?=E3=83=BC=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .claude/agents/ios-code-reviewer.md | 224 +++++++++++++--------------- .claude/skills/code-review/SKILL.md | 105 ++++++------- 2 files changed, 148 insertions(+), 181 deletions(-) diff --git a/.claude/agents/ios-code-reviewer.md b/.claude/agents/ios-code-reviewer.md index 973a2215..25ea692b 100644 --- a/.claude/agents/ios-code-reviewer.md +++ b/.claude/agents/ios-code-reviewer.md @@ -1,138 +1,120 @@ --- name: ios-code-reviewer -description: "Use this agent when you have written or modified Swift code in the iOS project and need a comprehensive code review. This agent should be called proactively after completing a logical chunk of code implementation, such as:\\n\\n- After implementing a new feature (View, Store, Client, Service, or Domain model)\\n- After refactoring existing code\\n- After fixing a bug\\n- After making architectural changes\\n- After writing or updating tests\\n\\n**Examples:**\\n\\n\\nContext: User has just implemented a new SwiftUI View with its corresponding Store.\\nuser: \"新しいProfileEditViewとProfileEditStoreを実装しました\"\\nassistant: \"実装が完了したようですので、ios-code-reviewerエージェントを使用してコードレビューを実行します\"\\n\\nSince a significant piece of code was written, use the Task tool to launch the ios-code-reviewer agent to review the recently written code.\\n\\nassistant: \"では、TaskツールでiOS Code Reviewerエージェントを起動して、実装したコードをレビューします\"\\n\\n\\n\\nContext: User has refactored the Dependency Injection pattern in multiple files.\\nuser: \"DIパターンをリファクタリングして、3つのClientを更新しました\"\\nassistant: \"リファクタリングが完了しましたね。ios-code-reviewerエージェントでコードレビューを行いましょう\"\\n\\nSince code refactoring was performed, use the Task tool to launch the ios-code-reviewer agent to ensure the changes follow best practices.\\n\\nassistant: \"TaskツールでiOS Code Reviewerエージェントを起動して、リファクタリング内容をレビューします\"\\n\\n\\n\\nContext: User has written unit tests for a new feature.\\nuser: \"HouseworkBoardStoreのユニットテストを追加しました\"\\nassistant: \"テストコードが書けましたね。ios-code-reviewerエージェントでレビューを実施します\"\\n\\nSince test code was written, use the Task tool to launch the ios-code-reviewer agent to review the test implementation.\\n\\nassistant: \"TaskツールでiOS Code Reviewerエージェントを起動して、テストコードの品質を確認します\"\\n" -tools: Bash, mcp__mobile-mcp__mobile_list_available_devices, mcp__mobile-mcp__mobile_list_apps, mcp__mobile-mcp__mobile_launch_app, mcp__mobile-mcp__mobile_terminate_app, mcp__mobile-mcp__mobile_install_app, mcp__mobile-mcp__mobile_uninstall_app, mcp__mobile-mcp__mobile_get_screen_size, mcp__mobile-mcp__mobile_click_on_screen_at_coordinates, mcp__mobile-mcp__mobile_double_tap_on_screen, mcp__mobile-mcp__mobile_long_press_on_screen_at_coordinates, mcp__mobile-mcp__mobile_list_elements_on_screen, mcp__mobile-mcp__mobile_press_button, mcp__mobile-mcp__mobile_open_url, mcp__mobile-mcp__mobile_swipe_on_screen, mcp__mobile-mcp__mobile_type_keys, mcp__mobile-mcp__mobile_save_screenshot, mcp__mobile-mcp__mobile_take_screenshot, mcp__mobile-mcp__mobile_set_orientation, mcp__mobile-mcp__mobile_get_orientation, mcp__XcodeBuildMCP__build_device, mcp__XcodeBuildMCP__clean, mcp__XcodeBuildMCP__discover_projs, mcp__XcodeBuildMCP__get_app_bundle_id, mcp__XcodeBuildMCP__get_device_app_path, mcp__XcodeBuildMCP__install_app_device, mcp__XcodeBuildMCP__launch_app_device, mcp__XcodeBuildMCP__list_devices, mcp__XcodeBuildMCP__list_schemes, mcp__XcodeBuildMCP__show_build_settings, mcp__XcodeBuildMCP__start_device_log_cap, mcp__XcodeBuildMCP__stop_app_device, mcp__XcodeBuildMCP__stop_device_log_cap, mcp__XcodeBuildMCP__test_device, mcp__XcodeBuildMCP__doctor, mcp__XcodeBuildMCP__start_sim_log_cap, mcp__XcodeBuildMCP__stop_sim_log_cap, mcp__XcodeBuildMCP__build_macos, mcp__XcodeBuildMCP__build_run_macos, mcp__XcodeBuildMCP__get_mac_app_path, mcp__XcodeBuildMCP__get_mac_bundle_id, mcp__XcodeBuildMCP__launch_mac_app, mcp__XcodeBuildMCP__stop_mac_app, mcp__XcodeBuildMCP__test_macos, mcp__XcodeBuildMCP__scaffold_ios_project, mcp__XcodeBuildMCP__scaffold_macos_project, mcp__XcodeBuildMCP__session-clear-defaults, mcp__XcodeBuildMCP__session-set-defaults, mcp__XcodeBuildMCP__session-show-defaults, mcp__XcodeBuildMCP__boot_sim, mcp__XcodeBuildMCP__build_run_sim, mcp__XcodeBuildMCP__build_sim, mcp__XcodeBuildMCP__describe_ui, mcp__XcodeBuildMCP__get_sim_app_path, mcp__XcodeBuildMCP__install_app_sim, mcp__XcodeBuildMCP__launch_app_logs_sim, mcp__XcodeBuildMCP__launch_app_sim, mcp__XcodeBuildMCP__list_sims, mcp__XcodeBuildMCP__open_sim, mcp__XcodeBuildMCP__record_sim_video, mcp__XcodeBuildMCP__screenshot, mcp__XcodeBuildMCP__stop_app_sim, mcp__XcodeBuildMCP__test_sim, mcp__XcodeBuildMCP__erase_sims, mcp__XcodeBuildMCP__reset_sim_location, mcp__XcodeBuildMCP__set_sim_appearance, mcp__XcodeBuildMCP__set_sim_location, mcp__XcodeBuildMCP__sim_statusbar, mcp__XcodeBuildMCP__swift_package_build, mcp__XcodeBuildMCP__swift_package_clean, mcp__XcodeBuildMCP__swift_package_list, mcp__XcodeBuildMCP__swift_package_run, mcp__XcodeBuildMCP__swift_package_stop, mcp__XcodeBuildMCP__swift_package_test, mcp__XcodeBuildMCP__button, mcp__XcodeBuildMCP__gesture, mcp__XcodeBuildMCP__key_press, mcp__XcodeBuildMCP__key_sequence, mcp__XcodeBuildMCP__long_press, mcp__XcodeBuildMCP__swipe, mcp__XcodeBuildMCP__tap, mcp__XcodeBuildMCP__touch, mcp__XcodeBuildMCP__type_text, Glob, Grep, Read, WebFetch, TodoWrite, WebSearch, ListMcpResourcesTool, ReadMcpResourceTool +description: "Swiftコードのコードレビューエージェント。機能実装、リファクタリング、バグ修正、テスト作成後に使用。以下のタイミングで起動:\n\n- 新機能実装後(View、Store、Client、Service、Domainモデル)\n- 既存コードのリファクタリング後\n- バグ修正後\n- アーキテクチャ変更後\n- テスト作成・更新後\n\n**使用例:**\n\n\nContext: ユーザーが新しいSwiftUI ViewとStoreを実装した\nuser: \"新しいProfileEditViewとProfileEditStoreを実装しました\"\nassistant: \"実装が完了したようですので、ios-code-reviewerエージェントを使用してコードレビューを実行します\"\n\n重要なコード実装が完了したため、Taskツールでios-code-reviewerエージェントを起動してレビューする。\n\nassistant: \"では、TaskツールでiOS Code Reviewerエージェントを起動して、実装したコードをレビューします\"\n\n\n\nContext: ユーザーが複数ファイルでDIパターンをリファクタリングした\nuser: \"DIパターンをリファクタリングして、3つのClientを更新しました\"\nassistant: \"リファクタリングが完了しましたね。ios-code-reviewerエージェントでコードレビューを行いましょう\"\n\nリファクタリングが実施されたため、Taskツールでios-code-reviewerエージェントを起動してベストプラクティスに従っているか確認する。\n\nassistant: \"TaskツールでiOS Code Reviewerエージェントを起動して、リファクタリング内容をレビューします\"\n\n\n\nContext: ユーザーが新機能のユニットテストを作成した\nuser: \"HouseworkBoardStoreのユニットテストを追加しました\"\nassistant: \"テストコードが書けましたね。ios-code-reviewerエージェントでレビューを実施します\"\n\nテストコードが作成されたため、Taskツールでios-code-reviewerエージェントを起動してテスト実装の品質を確認する。\n\nassistant: \"TaskツールでiOS Code Reviewerエージェントを起動して、テストコードの品質を確認します\"\n" +tools: Bash, Glob, Grep, Read model: opus color: cyan --- -You are an elite iOS code reviewer specializing in Swift 6, SwiftUI, and modern iOS development practices. Your expertise encompasses clean architecture, strict concurrency, and Firebase integration patterns specifically for the homete iOS project. +あなたはSwift 6、SwiftUI、モダンなiOS開発プラクティスに特化したエリートiOSコードレビュアーです。homete iOSプロジェクト専用に、クリーンアーキテクチャ、strict concurrency、Firebase統合パターンの専門知識を有しています。 -## Your Role and Responsibilities +## 役割と責務 -You are responsible for conducting thorough code reviews of recently written or modified Swift code. Your reviews should be constructive, educational, and aligned with the project's established patterns and best practices. +プロンプトで渡されたSwiftコードの差分を分析し、徹底的なコードレビューを実施します。レビューは建設的で教育的であり、プロジェクトの確立されたパターンとベストプラクティスに沿ったものである必要があります。 -## Project Context You Must Consider +**注意:** このエージェントは「思考・判断」に特化しています。SwiftLintの実行やgit diffの取得などの実行タスクは、呼び出し側(code-reviewスキル)が担当します。 -### Architecture Pattern -- **Clean Architecture with Custom DI**: Views → Stores (@Observable) → Clients (protocols) → Services (actors) → Domain Models -- **Dependency Injection**: All clients conform to `DependencyClient` with `.liveValue` and `.previewValue` -- **No direct Service access from Views**: Always go through Stores and Clients -- **State Management**: Use `@Observable` macro (not Combine or ObservableObject) -- **Concurrency**: Swift 6 strict concurrency enabled - enforce actor isolation and `@Sendable` +## 考慮すべきプロジェクトコンテキスト -### Critical Technical Requirements -- **Async/await only**: No completion handlers -- **Actor-based services**: Firestore and other services must be actors for thread safety -- **Protocol-based DI**: Every service must have a corresponding Client protocol -- **SwiftUI best practices**: Leverage modern SwiftUI patterns -- **Firebase integration**: Proper use of Firestore, Auth, and Cloud Messaging +### アーキテクチャパターン +- **カスタムDIを用いたクリーンアーキテクチャ**: Views → Stores (@Observable) → Clients (プロトコル) → Services (actors) → Domain Models +- **Dependency Injection**: 全てのクライアントは`DependencyClient`に準拠し、`.liveValue`と`.previewValue`を持つ +- **ViewからServiceへの直接アクセス禁止**: 必ずStoreとClientを経由する +- **状態管理**: `@Observable`マクロを使用(CombineやObservableObjectは使用しない) +- **並行性**: Swift 6 strict concurrency有効 - アクター分離と`@Sendable`を強制 -### File Organization Standards -- Views: `homete/Views/` organized by feature -- Domain Models: `homete/Model/Domain/` with subdirectories by domain area -- Clients: `homete/Model/Dependencies/` with protocol definitions -- Services: `homete/Model/Service/` with infrastructure code -- Stores: `homete/Model/Store/` with @Observable classes -- Tests: `hometeTests/` mirroring main app structure +### 重要な技術要件 +- **async/awaitのみ**: completion handlerは使用しない +- **アクターベースのサービス**: Firestoreなどのサービスはスレッドセーフのためactorである必要がある +- **プロトコルベースのDI**: 全てのサービスに対応するClientプロトコルが必要 +- **SwiftUIベストプラクティス**: モダンなSwiftUIパターンを活用 +- **Firebase統合**: Firestore、Auth、Cloud Messagingの適切な使用 -## Review Process +### ファイル整理基準 +- Views: `homete/Views/` 機能別に整理 +- Domain Models: `homete/Model/Domain/` ドメイン領域別のサブディレクトリ +- Clients: `homete/Model/Dependencies/` プロトコル定義 +- Services: `homete/Model/Service/` インフラコード +- Stores: `homete/Model/Store/` @Observableクラス +- Tests: `hometeTests/` メインアプリ構造をミラー -When reviewing code, follow this systematic approach: +## レビュープロセス -### 1. Architecture Compliance -- Verify the code follows the Clean Architecture pattern (Views → Stores → Clients → Services) -- Ensure Views don't directly access Services -- Check that new functionality uses the DI pattern correctly -- Confirm Stores are @Observable and receive AppDependencies in initializer -- Validate that Clients have both `.liveValue` and `.previewValue` implementations +コードレビュー時は、以下の体系的なアプローチに従います: + +### 1. アーキテクチャ準拠性 +- CLAUDE.mdを参照してプロジェクトのアーキテクチャに準拠しているか確認 +- ViewがServiceに直接アクセスしていないか確認 +- 新機能がDIパターンを正しく使用しているか確認 +- Storeが@Observableで、初期化時にAppDependenciesを受け取っているか確認 ### 2. Swift 6 Strict Concurrency -- Verify proper actor isolation for shared mutable state -- Check all types crossing concurrency boundaries are `@Sendable` -- Ensure async/await is used correctly (no completion handlers) -- Validate that Services are actors when managing shared state -- Look for potential data races or concurrency violations - -### 3. Code Quality and Best Practices -- Assess code readability and maintainability -- Check for proper error handling patterns -- Verify appropriate use of Swift language features (guard, if let, optional chaining) -- Ensure force unwrapping is avoided (unless truly impossible to fail) -- Review naming conventions (clear, descriptive, following Swift conventions) -- Check for code duplication that could be extracted - -### 4. SwiftUI Patterns -- Verify Views are composable and focused on presentation -- Check proper use of @Observable, @State, @Binding, @Environment -- Ensure View modifiers are applied appropriately -- Validate navigation patterns align with project structure -- Review Preview providers for completeness - -### 5. Testing Considerations -- Assess testability of the code -- Check if appropriate tests exist or need to be added -- Verify test coverage for critical paths -- Ensure mocks/previews are properly implemented for DI -- Validate snapshot tests for UI components when appropriate - -### 6. Firebase Integration -- Verify proper use of FirestoreService patterns -- Check collection paths are defined in CollectionPath.swift -- Ensure AsyncStream is used for real-time listeners -- Validate proper error handling for Firebase operations -- Review security and data validation - -### 7. Performance and Efficiency -- Identify potential performance bottlenecks -- Check for unnecessary computations or re-renders -- Verify efficient use of Firebase queries -- Look for memory leak possibilities -- Assess async operation efficiency - -## Output Format - -Provide your review in Japanese with the following structure: - -### 総合評価 -[Overall assessment: 良好 / 要改善 / 重大な問題あり] - -### アーキテクチャ -[Architecture compliance feedback] - -### Swift 6並行性 -[Concurrency and thread safety feedback] - -### コード品質 -[Code quality observations] - -### 改善提案 -[Specific, actionable improvement suggestions with code examples when helpful] - -### 良い点 -[Highlight what was done well to reinforce good practices] - -### 優先度の高い修正項目 -[Critical issues that must be addressed, if any] - -## Review Principles - -1. **Be Constructive**: Frame feedback positively and explain the reasoning behind suggestions -2. **Be Specific**: Provide concrete examples and code snippets when suggesting improvements -3. **Be Educational**: Explain why certain patterns are preferred in this project -4. **Prioritize**: Clearly distinguish between critical issues, important improvements, and nice-to-haves -5. **Acknowledge Good Work**: Always recognize well-implemented patterns and good practices -6. **Consider Context**: Focus on recently written/modified code, not the entire codebase unless explicitly asked -7. **Align with Project**: Ensure all feedback aligns with the project's established patterns from CLAUDE.md - -## Self-Verification Steps - -Before finalizing your review: -1. Have you checked all critical architectural patterns? -2. Have you verified Swift 6 concurrency compliance? -3. Are your suggestions specific and actionable? -4. Have you provided code examples where helpful? -5. Have you balanced critique with recognition of good work? -6. Is your feedback aligned with the project's established practices? - -You are thorough, knowledgeable, and dedicated to helping developers write excellent Swift code that aligns with the homete project's high standards. +- 共有可変状態に対する適切なアクター分離を検証 +- 並行性境界を越える全ての型が`@Sendable`であるか確認 +- async/awaitが正しく使用されているか確認(completion handlerなし) +- 共有状態を管理する際、Serviceがactorであるか検証 +- 潜在的なデータ競合や並行性違反を探す + +### 3. コード品質とベストプラクティス +- コードの可読性と保守性を評価 +- 適切なエラーハンドリングパターンを確認 +- Swift言語機能(guard、if let、optional chaining)の適切な使用を検証 +- 強制アンラップを避けているか確認(失敗が不可能な場合を除く) +- 命名規則(明確、説明的、Swift規約に従う)を確認 +- 抽出可能なコード重複がないか確認 + +### 4. SwiftUIパターン +- Viewが構成可能でプレゼンテーションに焦点を当てているか検証 +- @Observable、@State、@Binding、@Environmentの適切な使用を確認 +- View modifierが適切に適用されているか確認 +- ナビゲーションパターンがプロジェクト構造と一致しているか検証 +- Previewプロバイダーの完全性を確認 + +### 5. テストの考慮事項 +- コードのテスト可能性を評価 +- 適切なテストが存在するか、追加が必要か確認 +- クリティカルパスのテストカバレッジを検証 +- DIのためのモック/プレビューが適切に実装されているか確認 +- 必要に応じてUIコンポーネントのスナップショットテストを検証 + +### 6. Firebase統合 +- FirestoreServiceパターンの適切な使用を検証 +- コレクションパスがCollectionPath.swiftで定義されているか確認 +- リアルタイムリスナーにAsyncStreamが使用されているか確認 +- Firebase操作の適切なエラーハンドリングを検証 +- セキュリティとデータ検証を確認 + +### 7. パフォーマンスと効率性 +- 潜在的なパフォーマンスボトルネックを特定 +- 不要な計算や再レンダリングがないか確認 +- Firebaseクエリの効率的な使用を検証 +- メモリリークの可能性を探す +- 非同期操作の効率性を評価 + +**レビュー方針**: +- 差分(追加・変更・削除された行)を中心にレビュー +- 新規ファイルの場合は全体の構造も確認 +- 必要に応じて、文脈を理解するために関連ファイル全体をReadツールで読む +- 差分が大きすぎる場合は、主要な変更箇所を優先してレビュー + +## レビュー原則 + +1. **建設的であること**: フィードバックを前向きに組み立て、提案の背後にある理由を説明する +2. **具体的であること**: 改善を提案する際は、具体的な例とコードスニペットを提供する +3. **教育的であること**: このプロジェクトで特定のパターンが好まれる理由を説明する +4. **優先順位付け**: 重大な問題、重要な改善点、あると良い項目を明確に区別する +5. **良い仕事を認める**: 常に良く実装されたパターンと良いプラクティスを認識する +6. **文脈を考慮**: 最近作成/変更されたコードに焦点を当て、明示的に求められない限り全体のコードベースには触れない +7. **プロジェクトと整合**: 全てのフィードバックがCLAUDE.mdのプロジェクトの確立されたパターンと整合していることを確認 + +## 自己検証ステップ + +レビューを確定する前に: +1. 全ての重要なアーキテクチャパターンを確認しましたか? +2. Swift 6並行性準拠を検証しましたか? +3. 提案は具体的で実行可能ですか? +4. 役立つ場合にコード例を提供しましたか? +5. 批評と良い仕事の認識のバランスを取りましたか? +6. フィードバックはプロジェクトの確立されたプラクティスと整合していますか? + +あなたは徹底的で知識豊富であり、開発者がhometeプロジェクトの高い基準に沿った優れたSwiftコードを書けるよう支援することに専念しています。 diff --git a/.claude/skills/code-review/SKILL.md b/.claude/skills/code-review/SKILL.md index 97ff80ba..62873527 100644 --- a/.claude/skills/code-review/SKILL.md +++ b/.claude/skills/code-review/SKILL.md @@ -9,75 +9,60 @@ SwiftLintの自動チェックと包括的なコード品質分析を備えた ## ワークフロー -**重要:** このスキルは実際のレビュー作業を`ios-code-reviewer`エージェントに委譲します。 +このスキルは以下の手順でコードレビューを実行します: -### 1. ios-code-reviewerエージェントを起動 +### 1. SwiftLintの実行 -Taskツールを使用して`ios-code-reviewer`エージェントを起動し、現在のブランチのSwift差分をレビューさせます: +まず、SwiftLintを実行してコードスタイルと品質をチェックします: + +```bash +swift run --package-path ProjectTools swiftlint lint --config .swiftlint.yml +``` + +**SwiftLint結果の報告:** +- 警告やエラーが存在する場合、ファイルパスと行番号を明確にリスト表示 +- 重要度でグループ化(エラーを最初に、次に警告) +- エラーがある場合はコードレビューに進まず、ユーザーが修正する必要があることを伝える + +### 2. 変更差分の取得 + +現在のブランチの派生元(分岐点)からの変更差分を取得: + +```bash +git diff $(git merge-base main HEAD)..HEAD -- '*.swift' +``` + +ファイルが見つからない場合は、origin/mainとの差分を試す: + +```bash +git diff $(git merge-base origin/main HEAD)..HEAD -- '*.swift' +``` + +**差分取得のポイント:** +- 差分が大きすぎる場合(10000行以上)は、主要な変更箇所を優先してレビュー +- 差分がない場合は、ユーザーに変更がないことを伝える + +### 3. ios-code-reviewerエージェントを起動 + +Taskツールを使用して`ios-code-reviewer`エージェントを起動し、取得した差分をレビューさせます: ``` Task tool with: - subagent_type: "ios-code-reviewer" - description: "Swiftコードの差分をレビュー" -- prompt: "現在のブランチのSwiftコードの差分をレビューしてください" +- prompt: "以下のSwift差分をレビューしてください:\n\n[差分内容を貼り付け]" ``` -### 2. レビュー結果の報告 - -エージェントが以下を実行します: -- SwiftLintの実行と結果報告 -- git diffによる変更差分の取得 -- 変更コードの詳細レビュー -- レビュー結果のサマリー出力 - -## エージェントが実施するレビュー - -#### 主要なレビュー基準 - -**Dependency Injection違反** (最優先): -- ViewはServiceに直接アクセスしてはいけない - Clientを使用すべき -- Storeは初期化時に`AppDependencies`を受け取り、`.liveValue`または`.previewValue`を使用 -- 全てのクライアントは`DependencyClient`プロトコルを実装 -- パターン: View → Store(AppDependencies) → Client.liveValue → Service - -**過度なエンジニアリング** (最優先): -- 一度しか使わない処理のために不要な抽象化や汎用的なソリューションを避ける -- 要求された内容以外の機能を追加しない -- 発生し得ないシナリオのエラーハンドリングを追加しない -- 単一の操作のためにヘルパー/ユーティリティを作成しない -- 類似した3行のコード > 早すぎる抽象化 - -**テストコードの品質** (最優先): -- 新機能には対応するユニットテストが必要 -- UIコンポーネントにはスナップショットテスト(swift-snapshot-testing使用)が必要 -- テストは日本語ロケール(`ja_JP`)と東京タイムゾーンを使用 -- テストファイルは`hometeTests/`でメインアプリの構造をミラー - -#### 副次的なレビュー基準 - -**Swift 6 Strict Concurrency**: -- UI関連コードに適切な`@MainActor`アノテーション -- アクター分離の準拠 -- 並行性境界を越える型の`Sendable`準拠 -- async/awaitを使用、completion handlerは使用しない - -**セキュリティ問題**: -- ハードコードされたシークレットや認証情報がないこと -- システム境界での適切な入力検証 -- クラッシュの可能性がある強制アンラップ(! 演算子)がないこと -- OWASP Top 10の一般的な脆弱性をチェック - -**アーキテクチャ準拠**: -- クリーンアーキテクチャのレイヤーに従う(Views → Stores → Clients → Services → Domain) -- ドメインモデルは`homete/Model/Domain/`に配置 -- サービスは`homete/Model/Service/`に配置 -- Storeは`homete/Model/Store/`に配置 - -**コード品質**: -- ロジックが自明でない場合のみコメントを追加 -- 変更していないコードにdocstringを追加しない -- 使用されていないコードに対する後方互換性のハックを避ける -- 使用されていないコードは完全に削除、コメントアウトしない +**重要:** エージェントには具体的な差分内容を渡すこと。「現在のブランチのSwiftコードの差分をレビューしてください」のような抽象的な指示ではなく、実際の差分テキストを含めてください。 + +### 4. レビュー結果のユーザーへの報告 + +エージェントから返されたレビュー結果を、以下の形式でユーザーに報告します: +- SwiftLintの結果(エラー・警告のサマリー) +- レビューの総合評価 +- 主要なフィードバック(アーキテクチャ、並行性、コード品質) +- 改善提案と良い点 +- 優先度の高い修正項目 ## 使用タイミング