@su-record/vibe 2.4.72 → 2.4.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +216 -215
- package/README.md +4 -4
- package/agents/research/best-practices-agent.md +13 -13
- package/agents/research/codebase-patterns-agent.md +33 -33
- package/agents/research/framework-docs-agent.md +23 -23
- package/agents/research/security-advisory-agent.md +29 -29
- package/agents/review/architecture-reviewer.md +31 -31
- package/agents/review/complexity-reviewer.md +21 -21
- package/agents/review/data-integrity-reviewer.md +29 -29
- package/agents/review/git-history-reviewer.md +24 -24
- package/agents/review/performance-reviewer.md +29 -29
- package/agents/review/python-reviewer.md +53 -53
- package/agents/review/rails-reviewer.md +40 -40
- package/agents/review/react-reviewer.md +40 -40
- package/agents/review/security-reviewer.md +29 -29
- package/agents/review/simplicity-reviewer.md +24 -24
- package/agents/review/test-coverage-reviewer.md +31 -31
- package/agents/review/typescript-reviewer.md +41 -41
- package/commands/vibe.analyze.md +103 -7
- package/commands/vibe.reason.md +106 -0
- package/commands/vibe.review.md +123 -38
- package/commands/vibe.run.md +286 -223
- package/commands/vibe.spec.md +425 -186
- package/commands/vibe.utils.md +104 -3
- package/commands/vibe.verify.md +179 -86
- package/dist/cli/detect.js +40 -40
- package/dist/cli/detect.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/llm.js +5 -5
- package/dist/cli/llm.js.map +1 -1
- package/dist/cli/setup.js +3 -3
- package/dist/cli/setup.js.map +1 -1
- package/dist/lib/ContextCompressor.js +1 -1
- package/dist/lib/ContextCompressor.js.map +1 -1
- package/dist/lib/MemoryManager.d.ts +13 -155
- package/dist/lib/MemoryManager.d.ts.map +1 -1
- package/dist/lib/MemoryManager.js +52 -617
- package/dist/lib/MemoryManager.js.map +1 -1
- package/dist/lib/gemini-api.js +12 -12
- package/dist/lib/gemini-api.js.map +1 -1
- package/dist/lib/gemini-oauth.js +22 -22
- package/dist/lib/gemini-oauth.js.map +1 -1
- package/dist/lib/gemini-storage.js +3 -3
- package/dist/lib/gemini-storage.js.map +1 -1
- package/dist/lib/gpt-api.js +11 -11
- package/dist/lib/gpt-api.js.map +1 -1
- package/dist/lib/gpt-oauth.js +28 -28
- package/dist/lib/gpt-oauth.js.map +1 -1
- package/dist/lib/gpt-storage.js +3 -3
- package/dist/lib/gpt-storage.js.map +1 -1
- package/dist/lib/memory/KnowledgeGraph.d.ts +34 -0
- package/dist/lib/memory/KnowledgeGraph.d.ts.map +1 -0
- package/dist/lib/memory/KnowledgeGraph.js +216 -0
- package/dist/lib/memory/KnowledgeGraph.js.map +1 -0
- package/dist/lib/memory/KnowledgeGraph.test.d.ts +2 -0
- package/dist/lib/memory/KnowledgeGraph.test.d.ts.map +1 -0
- package/dist/lib/memory/KnowledgeGraph.test.js +189 -0
- package/dist/lib/memory/KnowledgeGraph.test.js.map +1 -0
- package/dist/lib/memory/MemorySearch.d.ts +25 -0
- package/dist/lib/memory/MemorySearch.d.ts.map +1 -0
- package/dist/lib/memory/MemorySearch.js +85 -0
- package/dist/lib/memory/MemorySearch.js.map +1 -0
- package/dist/lib/memory/MemorySearch.test.d.ts +2 -0
- package/dist/lib/memory/MemorySearch.test.d.ts.map +1 -0
- package/dist/lib/memory/MemorySearch.test.js +149 -0
- package/dist/lib/memory/MemorySearch.test.js.map +1 -0
- package/dist/lib/memory/MemoryStorage.d.ts +77 -0
- package/dist/lib/memory/MemoryStorage.d.ts.map +1 -0
- package/dist/lib/memory/MemoryStorage.js +278 -0
- package/dist/lib/memory/MemoryStorage.js.map +1 -0
- package/dist/lib/memory/MemoryStorage.test.d.ts +2 -0
- package/dist/lib/memory/MemoryStorage.test.d.ts.map +1 -0
- package/dist/lib/memory/MemoryStorage.test.js +198 -0
- package/dist/lib/memory/MemoryStorage.test.js.map +1 -0
- package/dist/lib/memory/index.d.ts +4 -0
- package/dist/lib/memory/index.d.ts.map +1 -0
- package/dist/lib/memory/index.js +8 -0
- package/dist/lib/memory/index.js.map +1 -0
- package/dist/orchestrator/orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/orchestrator.js +4 -6
- package/dist/orchestrator/orchestrator.js.map +1 -1
- package/dist/tools/convention/analyzeComplexity.d.ts +3 -1
- package/dist/tools/convention/analyzeComplexity.d.ts.map +1 -1
- package/dist/tools/convention/analyzeComplexity.js +102 -4
- package/dist/tools/convention/analyzeComplexity.js.map +1 -1
- package/dist/tools/convention/analyzeComplexity.test.d.ts +2 -0
- package/dist/tools/convention/analyzeComplexity.test.d.ts.map +1 -0
- package/dist/tools/convention/analyzeComplexity.test.js +207 -0
- package/dist/tools/convention/analyzeComplexity.test.js.map +1 -0
- package/dist/tools/convention/applyQualityRules.js +1 -1
- package/dist/tools/convention/applyQualityRules.js.map +1 -1
- package/dist/tools/convention/checkCouplingCohesion.js +2 -2
- package/dist/tools/convention/checkCouplingCohesion.js.map +1 -1
- package/dist/tools/convention/suggestImprovements.js +1 -1
- package/dist/tools/convention/suggestImprovements.js.map +1 -1
- package/dist/tools/convention/validateCodeQuality.d.ts +3 -1
- package/dist/tools/convention/validateCodeQuality.d.ts.map +1 -1
- package/dist/tools/convention/validateCodeQuality.js +145 -2
- package/dist/tools/convention/validateCodeQuality.js.map +1 -1
- package/dist/tools/convention/validateCodeQuality.test.d.ts +2 -0
- package/dist/tools/convention/validateCodeQuality.test.d.ts.map +1 -0
- package/dist/tools/convention/validateCodeQuality.test.js +230 -0
- package/dist/tools/convention/validateCodeQuality.test.js.map +1 -0
- package/dist/tools/memory/autoSaveContext.js +1 -1
- package/dist/tools/memory/autoSaveContext.js.map +1 -1
- package/dist/tools/memory/createMemoryTimeline.js +27 -27
- package/dist/tools/memory/createMemoryTimeline.js.map +1 -1
- package/dist/tools/memory/deleteMemory.js +1 -1
- package/dist/tools/memory/deleteMemory.js.map +1 -1
- package/dist/tools/memory/getMemoryGraph.js +24 -24
- package/dist/tools/memory/getMemoryGraph.js.map +1 -1
- package/dist/tools/memory/getSessionContext.js +36 -36
- package/dist/tools/memory/getSessionContext.js.map +1 -1
- package/dist/tools/memory/linkMemories.js +21 -21
- package/dist/tools/memory/linkMemories.js.map +1 -1
- package/dist/tools/memory/prioritizeMemory.js +1 -1
- package/dist/tools/memory/prioritizeMemory.js.map +1 -1
- package/dist/tools/memory/restoreSessionContext.js +1 -1
- package/dist/tools/memory/restoreSessionContext.js.map +1 -1
- package/dist/tools/memory/searchMemories.js +1 -1
- package/dist/tools/memory/searchMemories.js.map +1 -1
- package/dist/tools/memory/searchMemoriesAdvanced.js +42 -42
- package/dist/tools/memory/searchMemoriesAdvanced.js.map +1 -1
- package/dist/tools/memory/startSession.js +2 -2
- package/dist/tools/memory/startSession.js.map +1 -1
- package/dist/tools/memory/updateMemory.js +1 -1
- package/dist/tools/memory/updateMemory.js.map +1 -1
- package/dist/tools/semantic/analyzeDependencyGraph.js +38 -38
- package/dist/tools/semantic/analyzeDependencyGraph.js.map +1 -1
- package/dist/tools/semantic/findReferences.js +1 -1
- package/dist/tools/semantic/findReferences.js.map +1 -1
- package/dist/tools/semantic/findSymbol.js +1 -1
- package/dist/tools/semantic/findSymbol.js.map +1 -1
- package/dist/tools/time/getCurrentTime.js +1 -1
- package/dist/tools/time/getCurrentTime.js.map +1 -1
- package/dist/tools/ui/previewUiAscii.js +2 -2
- package/dist/tools/ui/previewUiAscii.js.map +1 -1
- package/hooks/hooks.json +11 -2
- package/hooks/scripts/llm-orchestrate.js +1 -1
- package/hooks/scripts/utils.js +31 -6
- package/languages/csharp-unity.md +82 -83
- package/languages/dart-flutter.md +89 -88
- package/languages/go.md +76 -75
- package/languages/java-spring.md +85 -84
- package/languages/kotlin-android.md +64 -63
- package/languages/python-django.md +83 -82
- package/languages/python-fastapi.md +82 -81
- package/languages/rust.md +75 -74
- package/languages/swift-ios.md +73 -72
- package/languages/typescript-electron.md +70 -71
- package/languages/typescript-nextjs.md +93 -92
- package/languages/typescript-node.md +64 -63
- package/languages/typescript-nuxt.md +113 -112
- package/languages/typescript-react-native.md +82 -81
- package/languages/typescript-react.md +76 -75
- package/languages/typescript-tauri.md +74 -75
- package/languages/typescript-vue.md +73 -72
- package/package.json +1 -1
- package/skills/git-worktree.md +25 -25
- package/skills/multi-llm-orchestration.md +4 -6
- package/skills/priority-todos.md +39 -39
- package/skills/vibe-capabilities.md +2 -2
- package/vibe/config.json +2 -2
package/languages/swift-ios.md
CHANGED
|
@@ -1,32 +1,33 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Swift + iOS Quality Rules
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Core Principles (inherited from core)
|
|
4
4
|
|
|
5
5
|
```markdown
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
# Core Principles (inherited from core)
|
|
7
|
+
Single Responsibility (SRP)
|
|
8
|
+
No Duplication (DRY)
|
|
9
|
+
Reusability
|
|
10
|
+
Low Complexity
|
|
11
|
+
Function <= 30 lines
|
|
12
|
+
Nesting <= 3 levels
|
|
13
|
+
Cyclomatic complexity <= 10
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
## Swift/iOS
|
|
16
|
+
## Swift/iOS Specific Rules
|
|
16
17
|
|
|
17
|
-
### 1. SwiftUI
|
|
18
|
+
### 1. SwiftUI Basic Structure
|
|
18
19
|
|
|
19
20
|
```swift
|
|
20
|
-
//
|
|
21
|
+
// Good: View structure
|
|
21
22
|
import SwiftUI
|
|
22
23
|
|
|
23
24
|
struct UserProfileView: View {
|
|
24
|
-
// 1.
|
|
25
|
+
// 1. State and bindings
|
|
25
26
|
@StateObject private var viewModel: UserProfileViewModel
|
|
26
27
|
@State private var isEditing = false
|
|
27
28
|
@Binding var selectedUser: User?
|
|
28
29
|
|
|
29
|
-
// 2.
|
|
30
|
+
// 2. Environment variables
|
|
30
31
|
@Environment(\.dismiss) private var dismiss
|
|
31
32
|
@EnvironmentObject private var authManager: AuthManager
|
|
32
33
|
|
|
@@ -34,14 +35,14 @@ struct UserProfileView: View {
|
|
|
34
35
|
var body: some View {
|
|
35
36
|
NavigationStack {
|
|
36
37
|
content
|
|
37
|
-
.navigationTitle("
|
|
38
|
+
.navigationTitle("Profile")
|
|
38
39
|
.toolbar { toolbarContent }
|
|
39
40
|
.sheet(isPresented: $isEditing) { editSheet }
|
|
40
41
|
}
|
|
41
42
|
.task { await viewModel.loadUser() }
|
|
42
43
|
}
|
|
43
44
|
|
|
44
|
-
// 4.
|
|
45
|
+
// 4. Separate view components
|
|
45
46
|
@ViewBuilder
|
|
46
47
|
private var content: some View {
|
|
47
48
|
if viewModel.isLoading {
|
|
@@ -55,9 +56,9 @@ struct UserProfileView: View {
|
|
|
55
56
|
|
|
56
57
|
private func userContent(_ user: User) -> some View {
|
|
57
58
|
List {
|
|
58
|
-
Section("
|
|
59
|
-
LabeledContent("
|
|
60
|
-
LabeledContent("
|
|
59
|
+
Section("Basic Info") {
|
|
60
|
+
LabeledContent("Name", value: user.name)
|
|
61
|
+
LabeledContent("Email", value: user.email)
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
64
|
}
|
|
@@ -65,7 +66,7 @@ struct UserProfileView: View {
|
|
|
65
66
|
@ToolbarContentBuilder
|
|
66
67
|
private var toolbarContent: some ToolbarContent {
|
|
67
68
|
ToolbarItem(placement: .topBarTrailing) {
|
|
68
|
-
Button("
|
|
69
|
+
Button("Edit") { isEditing = true }
|
|
69
70
|
}
|
|
70
71
|
}
|
|
71
72
|
}
|
|
@@ -74,18 +75,18 @@ struct UserProfileView: View {
|
|
|
74
75
|
### 2. ViewModel (MVVM)
|
|
75
76
|
|
|
76
77
|
```swift
|
|
77
|
-
//
|
|
78
|
+
// Good: ViewModel with @Observable (iOS 17+)
|
|
78
79
|
import Foundation
|
|
79
80
|
import Observation
|
|
80
81
|
|
|
81
82
|
@Observable
|
|
82
83
|
final class UserProfileViewModel {
|
|
83
|
-
//
|
|
84
|
+
// State
|
|
84
85
|
private(set) var user: User?
|
|
85
86
|
private(set) var isLoading = false
|
|
86
87
|
private(set) var error: AppError?
|
|
87
88
|
|
|
88
|
-
//
|
|
89
|
+
// Dependencies
|
|
89
90
|
private let userRepository: UserRepository
|
|
90
91
|
private let userId: String
|
|
91
92
|
|
|
@@ -117,7 +118,7 @@ final class UserProfileViewModel {
|
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
|
|
120
|
-
//
|
|
121
|
+
// Good: ViewModel with ObservableObject (iOS 13+)
|
|
121
122
|
import Combine
|
|
122
123
|
|
|
123
124
|
final class UserListViewModel: ObservableObject {
|
|
@@ -161,10 +162,10 @@ final class UserListViewModel: ObservableObject {
|
|
|
161
162
|
}
|
|
162
163
|
```
|
|
163
164
|
|
|
164
|
-
### 3. Repository
|
|
165
|
+
### 3. Repository Pattern
|
|
165
166
|
|
|
166
167
|
```swift
|
|
167
|
-
//
|
|
168
|
+
// Good: Protocol definition
|
|
168
169
|
protocol UserRepository {
|
|
169
170
|
func fetchUsers() async throws -> [User]
|
|
170
171
|
func fetchUser(id: String) async throws -> User
|
|
@@ -173,7 +174,7 @@ protocol UserRepository {
|
|
|
173
174
|
func deleteUser(id: String) async throws
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
//
|
|
177
|
+
// Good: Implementation
|
|
177
178
|
final class DefaultUserRepository: UserRepository {
|
|
178
179
|
private let apiClient: APIClient
|
|
179
180
|
private let cache: CacheManager
|
|
@@ -184,18 +185,18 @@ final class DefaultUserRepository: UserRepository {
|
|
|
184
185
|
}
|
|
185
186
|
|
|
186
187
|
func fetchUser(id: String) async throws -> User {
|
|
187
|
-
//
|
|
188
|
+
// Check cache
|
|
188
189
|
if let cached: User = cache.get(key: "user_\(id)") {
|
|
189
190
|
return cached
|
|
190
191
|
}
|
|
191
192
|
|
|
192
|
-
// API
|
|
193
|
+
// API call
|
|
193
194
|
let user: User = try await apiClient.request(
|
|
194
195
|
endpoint: .user(id: id),
|
|
195
196
|
method: .get
|
|
196
197
|
)
|
|
197
198
|
|
|
198
|
-
//
|
|
199
|
+
// Save to cache
|
|
199
200
|
cache.set(key: "user_\(id)", value: user, ttl: 300)
|
|
200
201
|
|
|
201
202
|
return user
|
|
@@ -210,10 +211,10 @@ final class DefaultUserRepository: UserRepository {
|
|
|
210
211
|
}
|
|
211
212
|
```
|
|
212
213
|
|
|
213
|
-
### 4.
|
|
214
|
+
### 4. Error Handling
|
|
214
215
|
|
|
215
216
|
```swift
|
|
216
|
-
//
|
|
217
|
+
// Good: Custom error definition
|
|
217
218
|
enum AppError: LocalizedError {
|
|
218
219
|
case networkError(underlying: Error)
|
|
219
220
|
case decodingError(underlying: Error)
|
|
@@ -225,17 +226,17 @@ enum AppError: LocalizedError {
|
|
|
225
226
|
var errorDescription: String? {
|
|
226
227
|
switch self {
|
|
227
228
|
case .networkError:
|
|
228
|
-
return "
|
|
229
|
+
return "Please check your network connection"
|
|
229
230
|
case .decodingError:
|
|
230
|
-
return "
|
|
231
|
+
return "Unable to process data"
|
|
231
232
|
case .notFound(let resource, let id):
|
|
232
|
-
return "\(resource)
|
|
233
|
+
return "\(resource) not found (ID: \(id))"
|
|
233
234
|
case .unauthorized:
|
|
234
|
-
return "
|
|
235
|
+
return "Login required"
|
|
235
236
|
case .serverError(let message):
|
|
236
|
-
return "
|
|
237
|
+
return "Server error: \(message)"
|
|
237
238
|
case .unknown:
|
|
238
|
-
return "
|
|
239
|
+
return "An unknown error occurred"
|
|
239
240
|
}
|
|
240
241
|
}
|
|
241
242
|
|
|
@@ -256,7 +257,7 @@ enum AppError: LocalizedError {
|
|
|
256
257
|
}
|
|
257
258
|
}
|
|
258
259
|
|
|
259
|
-
//
|
|
260
|
+
// Good: Result type usage
|
|
260
261
|
func loadData() async -> Result<User, AppError> {
|
|
261
262
|
do {
|
|
262
263
|
let user = try await repository.fetchUser(id: userId)
|
|
@@ -267,10 +268,10 @@ func loadData() async -> Result<User, AppError> {
|
|
|
267
268
|
}
|
|
268
269
|
```
|
|
269
270
|
|
|
270
|
-
### 5.
|
|
271
|
+
### 5. Networking (async/await)
|
|
271
272
|
|
|
272
273
|
```swift
|
|
273
|
-
//
|
|
274
|
+
// Good: API Client
|
|
274
275
|
final class APIClient {
|
|
275
276
|
static let shared = APIClient()
|
|
276
277
|
|
|
@@ -295,7 +296,7 @@ final class APIClient {
|
|
|
295
296
|
request.httpMethod = method.rawValue
|
|
296
297
|
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
|
297
298
|
|
|
298
|
-
//
|
|
299
|
+
// Auth token
|
|
299
300
|
if let token = AuthManager.shared.accessToken {
|
|
300
301
|
request.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
|
|
301
302
|
}
|
|
@@ -324,7 +325,7 @@ final class APIClient {
|
|
|
324
325
|
}
|
|
325
326
|
}
|
|
326
327
|
|
|
327
|
-
//
|
|
328
|
+
// Good: Endpoint definition
|
|
328
329
|
enum Endpoint {
|
|
329
330
|
case users
|
|
330
331
|
case user(id: String)
|
|
@@ -353,10 +354,10 @@ enum Endpoint {
|
|
|
353
354
|
}
|
|
354
355
|
```
|
|
355
356
|
|
|
356
|
-
### 6.
|
|
357
|
+
### 6. Dependency Injection
|
|
357
358
|
|
|
358
359
|
```swift
|
|
359
|
-
//
|
|
360
|
+
// Good: DI via Environment (SwiftUI)
|
|
360
361
|
private struct UserRepositoryKey: EnvironmentKey {
|
|
361
362
|
static let defaultValue: UserRepository = DefaultUserRepository()
|
|
362
363
|
}
|
|
@@ -368,7 +369,7 @@ extension EnvironmentValues {
|
|
|
368
369
|
}
|
|
369
370
|
}
|
|
370
371
|
|
|
371
|
-
//
|
|
372
|
+
// Usage
|
|
372
373
|
struct ContentView: View {
|
|
373
374
|
@Environment(\.userRepository) private var userRepository
|
|
374
375
|
|
|
@@ -377,7 +378,7 @@ struct ContentView: View {
|
|
|
377
378
|
}
|
|
378
379
|
}
|
|
379
380
|
|
|
380
|
-
//
|
|
381
|
+
// Good: Container pattern
|
|
381
382
|
final class DIContainer {
|
|
382
383
|
static let shared = DIContainer()
|
|
383
384
|
|
|
@@ -393,13 +394,13 @@ final class DIContainer {
|
|
|
393
394
|
}
|
|
394
395
|
```
|
|
395
396
|
|
|
396
|
-
### 7.
|
|
397
|
+
### 7. Testing
|
|
397
398
|
|
|
398
399
|
```swift
|
|
399
400
|
import XCTest
|
|
400
401
|
@testable import MyApp
|
|
401
402
|
|
|
402
|
-
//
|
|
403
|
+
// Good: Mock Repository
|
|
403
404
|
final class MockUserRepository: UserRepository {
|
|
404
405
|
var fetchUsersResult: Result<[User], Error> = .success([])
|
|
405
406
|
var fetchUserResult: Result<User, Error> = .failure(AppError.notFound(resource: "User", id: ""))
|
|
@@ -412,10 +413,10 @@ final class MockUserRepository: UserRepository {
|
|
|
412
413
|
try fetchUserResult.get()
|
|
413
414
|
}
|
|
414
415
|
|
|
415
|
-
// ...
|
|
416
|
+
// ... other methods
|
|
416
417
|
}
|
|
417
418
|
|
|
418
|
-
//
|
|
419
|
+
// Good: ViewModel test
|
|
419
420
|
final class UserListViewModelTests: XCTestCase {
|
|
420
421
|
var sut: UserListViewModel!
|
|
421
422
|
var mockRepository: MockUserRepository!
|
|
@@ -432,11 +433,11 @@ final class UserListViewModelTests: XCTestCase {
|
|
|
432
433
|
super.tearDown()
|
|
433
434
|
}
|
|
434
435
|
|
|
435
|
-
func
|
|
436
|
+
func test_loadUsers_onSuccess_updatesUsers() async {
|
|
436
437
|
// Given
|
|
437
438
|
let expectedUsers = [
|
|
438
|
-
User(id: "1", name: "
|
|
439
|
-
User(id: "2", name: "
|
|
439
|
+
User(id: "1", name: "Test1", email: "test1@example.com"),
|
|
440
|
+
User(id: "2", name: "Test2", email: "test2@example.com")
|
|
440
441
|
]
|
|
441
442
|
mockRepository.fetchUsersResult = .success(expectedUsers)
|
|
442
443
|
|
|
@@ -448,30 +449,30 @@ final class UserListViewModelTests: XCTestCase {
|
|
|
448
449
|
XCTAssertFalse(sut.isLoading)
|
|
449
450
|
}
|
|
450
451
|
|
|
451
|
-
func
|
|
452
|
+
func test_filteredUsers_withSearchText_filtersCorrectly() {
|
|
452
453
|
// Given
|
|
453
454
|
sut.users = [
|
|
454
|
-
User(id: "1", name: "
|
|
455
|
-
User(id: "2", name: "
|
|
455
|
+
User(id: "1", name: "John Doe", email: "john@example.com"),
|
|
456
|
+
User(id: "2", name: "Jane Smith", email: "jane@example.com")
|
|
456
457
|
]
|
|
457
458
|
|
|
458
459
|
// When
|
|
459
|
-
sut.searchText = "
|
|
460
|
+
sut.searchText = "John"
|
|
460
461
|
|
|
461
462
|
// Then
|
|
462
463
|
XCTAssertEqual(sut.filteredUsers.count, 1)
|
|
463
|
-
XCTAssertEqual(sut.filteredUsers.first?.name, "
|
|
464
|
+
XCTAssertEqual(sut.filteredUsers.first?.name, "John Doe")
|
|
464
465
|
}
|
|
465
466
|
}
|
|
466
467
|
```
|
|
467
468
|
|
|
468
|
-
##
|
|
469
|
+
## File Structure
|
|
469
470
|
|
|
470
|
-
```
|
|
471
|
+
```text
|
|
471
472
|
Project/
|
|
472
473
|
├── App/
|
|
473
|
-
│ ├── ProjectApp.swift #
|
|
474
|
-
│ └── DIContainer.swift #
|
|
474
|
+
│ ├── ProjectApp.swift # App entry point
|
|
475
|
+
│ └── DIContainer.swift # Dependency container
|
|
475
476
|
├── Features/
|
|
476
477
|
│ ├── Auth/
|
|
477
478
|
│ │ ├── Views/
|
|
@@ -504,13 +505,13 @@ Project/
|
|
|
504
505
|
└── UITests/
|
|
505
506
|
```
|
|
506
507
|
|
|
507
|
-
##
|
|
508
|
+
## Checklist
|
|
508
509
|
|
|
509
|
-
- [ ] @Observable
|
|
510
|
-
- [ ] MVVM
|
|
511
|
-
- [ ] async/await
|
|
512
|
-
- [ ]
|
|
513
|
-
- [ ]
|
|
514
|
-
- [ ]
|
|
515
|
-
- [ ]
|
|
516
|
-
- [ ]
|
|
510
|
+
- [ ] Use @Observable or @ObservableObject
|
|
511
|
+
- [ ] Follow MVVM pattern
|
|
512
|
+
- [ ] Handle async with async/await
|
|
513
|
+
- [ ] Abstract dependencies with Protocol
|
|
514
|
+
- [ ] Ensure UI updates with @MainActor
|
|
515
|
+
- [ ] Define error messages with LocalizedError
|
|
516
|
+
- [ ] Separate conditional views with @ViewBuilder
|
|
517
|
+
- [ ] Testable structure (Mock injection)
|
|
@@ -1,45 +1,44 @@
|
|
|
1
|
-
#
|
|
1
|
+
# TypeScript + Electron Quality Rules
|
|
2
2
|
|
|
3
|
-
##
|
|
3
|
+
## Core Principles (inherited from core)
|
|
4
4
|
|
|
5
5
|
```markdown
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
6
|
+
# Core Principles (inherited from core)
|
|
7
|
+
Single Responsibility (SRP)
|
|
8
|
+
No Duplication (DRY)
|
|
9
|
+
Reusability
|
|
10
|
+
Low Complexity
|
|
11
|
+
Function <= 30 lines
|
|
12
|
+
Nesting <= 3 levels
|
|
13
|
+
Cyclomatic complexity <= 10
|
|
13
14
|
```
|
|
14
15
|
|
|
15
|
-
## Electron
|
|
16
|
+
## Electron Architecture Understanding
|
|
16
17
|
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
│ - window.electronAPI 사용 │
|
|
31
|
-
└─────────────────────────────────────────────┘
|
|
18
|
+
```text
|
|
19
|
+
Main Process (Node.js)
|
|
20
|
+
- App lifecycle management
|
|
21
|
+
- System APIs (file, network)
|
|
22
|
+
- BrowserWindow creation/management
|
|
23
|
+
|
|
24
|
+
Preload Script (Isolated Context)
|
|
25
|
+
- Expose APIs via contextBridge
|
|
26
|
+
- Main <-> Renderer bridge
|
|
27
|
+
|
|
28
|
+
Renderer Process (Chromium)
|
|
29
|
+
- UI rendering (React/Vue/etc)
|
|
30
|
+
- Use window.electronAPI
|
|
32
31
|
```
|
|
33
32
|
|
|
34
|
-
## TypeScript/Electron
|
|
33
|
+
## TypeScript/Electron Specific Rules
|
|
35
34
|
|
|
36
|
-
### 1.
|
|
35
|
+
### 1. Process Separation Required
|
|
37
36
|
|
|
38
37
|
```typescript
|
|
39
|
-
//
|
|
40
|
-
// nodeIntegration: true
|
|
38
|
+
// Bad: Direct Node.js usage in Renderer (security vulnerability)
|
|
39
|
+
// nodeIntegration: true is prohibited!
|
|
41
40
|
|
|
42
|
-
//
|
|
41
|
+
// Good: Main Process (main.ts)
|
|
43
42
|
import { app, BrowserWindow, ipcMain } from 'electron';
|
|
44
43
|
import path from 'path';
|
|
45
44
|
|
|
@@ -49,9 +48,9 @@ function createWindow(): BrowserWindow {
|
|
|
49
48
|
height: 600,
|
|
50
49
|
webPreferences: {
|
|
51
50
|
preload: path.join(__dirname, 'preload.js'),
|
|
52
|
-
contextIsolation: true, //
|
|
53
|
-
nodeIntegration: false, //
|
|
54
|
-
sandbox: true //
|
|
51
|
+
contextIsolation: true, // Required!
|
|
52
|
+
nodeIntegration: false, // Required!
|
|
53
|
+
sandbox: true // Recommended
|
|
55
54
|
}
|
|
56
55
|
});
|
|
57
56
|
|
|
@@ -62,13 +61,13 @@ function createWindow(): BrowserWindow {
|
|
|
62
61
|
app.whenReady().then(createWindow);
|
|
63
62
|
```
|
|
64
63
|
|
|
65
|
-
### 2. Preload Script
|
|
64
|
+
### 2. Preload Script Pattern
|
|
66
65
|
|
|
67
66
|
```typescript
|
|
68
67
|
// preload.ts
|
|
69
68
|
import { contextBridge, ipcRenderer } from 'electron';
|
|
70
69
|
|
|
71
|
-
//
|
|
70
|
+
// Good: Type definition
|
|
72
71
|
interface ElectronAPI {
|
|
73
72
|
readFile: (path: string) => Promise<string>;
|
|
74
73
|
writeFile: (path: string, content: string) => Promise<void>;
|
|
@@ -76,7 +75,7 @@ interface ElectronAPI {
|
|
|
76
75
|
platform: NodeJS.Platform;
|
|
77
76
|
}
|
|
78
77
|
|
|
79
|
-
//
|
|
78
|
+
// Good: Safely expose API
|
|
80
79
|
contextBridge.exposeInMainWorld('electronAPI', {
|
|
81
80
|
readFile: (path: string) => ipcRenderer.invoke('read-file', path),
|
|
82
81
|
writeFile: (path: string, content: string) =>
|
|
@@ -89,7 +88,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|
|
89
88
|
platform: process.platform
|
|
90
89
|
} satisfies ElectronAPI);
|
|
91
90
|
|
|
92
|
-
//
|
|
91
|
+
// Good: Type declaration (for use in renderer)
|
|
93
92
|
declare global {
|
|
94
93
|
interface Window {
|
|
95
94
|
electronAPI: ElectronAPI;
|
|
@@ -97,7 +96,7 @@ declare global {
|
|
|
97
96
|
}
|
|
98
97
|
```
|
|
99
98
|
|
|
100
|
-
### 3. IPC
|
|
99
|
+
### 3. IPC Communication Type Safety
|
|
101
100
|
|
|
102
101
|
```typescript
|
|
103
102
|
// shared/ipc-types.ts
|
|
@@ -120,7 +119,7 @@ export interface AppInfo {
|
|
|
120
119
|
import { ipcMain } from 'electron';
|
|
121
120
|
import fs from 'fs/promises';
|
|
122
121
|
|
|
123
|
-
//
|
|
122
|
+
// Good: Type-safe handler
|
|
124
123
|
ipcMain.handle('read-file', async (_event, path: string): Promise<string> => {
|
|
125
124
|
return fs.readFile(path, 'utf-8');
|
|
126
125
|
});
|
|
@@ -141,12 +140,12 @@ ipcMain.handle('get-app-info', async (): Promise<AppInfo> => {
|
|
|
141
140
|
});
|
|
142
141
|
```
|
|
143
142
|
|
|
144
|
-
### 4.
|
|
143
|
+
### 4. IPC Usage in Renderer
|
|
145
144
|
|
|
146
145
|
```typescript
|
|
147
146
|
// renderer/hooks/useElectron.ts
|
|
148
147
|
|
|
149
|
-
//
|
|
148
|
+
// Good: Custom Hook
|
|
150
149
|
function useFileReader() {
|
|
151
150
|
const [content, setContent] = useState<string | null>(null);
|
|
152
151
|
const [loading, setLoading] = useState(false);
|
|
@@ -171,7 +170,7 @@ function useFileReader() {
|
|
|
171
170
|
return { content, loading, error, readFile };
|
|
172
171
|
}
|
|
173
172
|
|
|
174
|
-
//
|
|
173
|
+
// Good: Event subscription Hook
|
|
175
174
|
function useFileWatcher(onChanged: (path: string) => void) {
|
|
176
175
|
useEffect(() => {
|
|
177
176
|
const unsubscribe = window.electronAPI.onFileChanged(onChanged);
|
|
@@ -180,13 +179,13 @@ function useFileWatcher(onChanged: (path: string) => void) {
|
|
|
180
179
|
}
|
|
181
180
|
```
|
|
182
181
|
|
|
183
|
-
### 5.
|
|
182
|
+
### 5. Window Management
|
|
184
183
|
|
|
185
184
|
```typescript
|
|
186
185
|
// main.ts
|
|
187
186
|
import { BrowserWindow, screen } from 'electron';
|
|
188
187
|
|
|
189
|
-
//
|
|
188
|
+
// Good: Save/restore window state
|
|
190
189
|
interface WindowState {
|
|
191
190
|
x?: number;
|
|
192
191
|
y?: number;
|
|
@@ -214,7 +213,7 @@ function createWindowWithState(): BrowserWindow {
|
|
|
214
213
|
win.maximize();
|
|
215
214
|
}
|
|
216
215
|
|
|
217
|
-
//
|
|
216
|
+
// Save state on change
|
|
218
217
|
win.on('close', () => {
|
|
219
218
|
saveWindowState({
|
|
220
219
|
...win.getBounds(),
|
|
@@ -225,7 +224,7 @@ function createWindowWithState(): BrowserWindow {
|
|
|
225
224
|
return win;
|
|
226
225
|
}
|
|
227
226
|
|
|
228
|
-
//
|
|
227
|
+
// Good: Multiple window management
|
|
229
228
|
const windows = new Map<string, BrowserWindow>();
|
|
230
229
|
|
|
231
230
|
function getOrCreateWindow(id: string): BrowserWindow {
|
|
@@ -242,12 +241,12 @@ function getOrCreateWindow(id: string): BrowserWindow {
|
|
|
242
241
|
}
|
|
243
242
|
```
|
|
244
243
|
|
|
245
|
-
### 6.
|
|
244
|
+
### 6. Menu Configuration
|
|
246
245
|
|
|
247
246
|
```typescript
|
|
248
247
|
import { Menu, MenuItemConstructorOptions } from 'electron';
|
|
249
248
|
|
|
250
|
-
//
|
|
249
|
+
// Good: Platform-specific menu
|
|
251
250
|
function createMenu(): Menu {
|
|
252
251
|
const isMac = process.platform === 'darwin';
|
|
253
252
|
|
|
@@ -283,18 +282,18 @@ function createMenu(): Menu {
|
|
|
283
282
|
}
|
|
284
283
|
```
|
|
285
284
|
|
|
286
|
-
### 7.
|
|
285
|
+
### 7. Auto Update
|
|
287
286
|
|
|
288
287
|
```typescript
|
|
289
288
|
import { autoUpdater } from 'electron-updater';
|
|
290
289
|
|
|
291
|
-
//
|
|
290
|
+
// Good: Auto update setup
|
|
292
291
|
function setupAutoUpdater(): void {
|
|
293
292
|
autoUpdater.autoDownload = false;
|
|
294
293
|
autoUpdater.autoInstallOnAppQuit = true;
|
|
295
294
|
|
|
296
295
|
autoUpdater.on('update-available', (info) => {
|
|
297
|
-
//
|
|
296
|
+
// Notify user
|
|
298
297
|
dialog.showMessageBox({
|
|
299
298
|
type: 'info',
|
|
300
299
|
title: 'Update Available',
|
|
@@ -320,15 +319,15 @@ function setupAutoUpdater(): void {
|
|
|
320
319
|
});
|
|
321
320
|
});
|
|
322
321
|
|
|
323
|
-
//
|
|
322
|
+
// Check for updates on app start
|
|
324
323
|
autoUpdater.checkForUpdates();
|
|
325
324
|
}
|
|
326
325
|
```
|
|
327
326
|
|
|
328
|
-
### 8.
|
|
327
|
+
### 8. Security Checklist
|
|
329
328
|
|
|
330
329
|
```typescript
|
|
331
|
-
//
|
|
330
|
+
// Good: Validate security settings
|
|
332
331
|
function validateSecuritySettings(win: BrowserWindow): void {
|
|
333
332
|
const webPrefs = win.webContents.getWebPreferences();
|
|
334
333
|
|
|
@@ -343,9 +342,9 @@ function validateSecuritySettings(win: BrowserWindow): void {
|
|
|
343
342
|
}
|
|
344
343
|
}
|
|
345
344
|
|
|
346
|
-
//
|
|
345
|
+
// Good: Handle external links
|
|
347
346
|
win.webContents.setWindowOpenHandler(({ url }) => {
|
|
348
|
-
//
|
|
347
|
+
// Open external URLs in system browser
|
|
349
348
|
if (url.startsWith('https://')) {
|
|
350
349
|
shell.openExternal(url);
|
|
351
350
|
}
|
|
@@ -353,9 +352,9 @@ win.webContents.setWindowOpenHandler(({ url }) => {
|
|
|
353
352
|
});
|
|
354
353
|
```
|
|
355
354
|
|
|
356
|
-
##
|
|
355
|
+
## Recommended Folder Structure
|
|
357
356
|
|
|
358
|
-
```
|
|
357
|
+
```text
|
|
359
358
|
my-electron-app/
|
|
360
359
|
├── src/
|
|
361
360
|
│ ├── main/ # Main Process
|
|
@@ -368,13 +367,13 @@ my-electron-app/
|
|
|
368
367
|
│ │ ├── components/
|
|
369
368
|
│ │ ├── hooks/
|
|
370
369
|
│ │ └── App.tsx
|
|
371
|
-
│ └── shared/ #
|
|
370
|
+
│ └── shared/ # Shared types
|
|
372
371
|
│ └── ipc-types.ts
|
|
373
372
|
├── electron-builder.yml
|
|
374
373
|
└── package.json
|
|
375
374
|
```
|
|
376
375
|
|
|
377
|
-
##
|
|
376
|
+
## Build Configuration (electron-builder)
|
|
378
377
|
|
|
379
378
|
```yaml
|
|
380
379
|
# electron-builder.yml
|
|
@@ -394,14 +393,14 @@ linux:
|
|
|
394
393
|
target: [AppImage, deb]
|
|
395
394
|
```
|
|
396
395
|
|
|
397
|
-
##
|
|
398
|
-
|
|
399
|
-
- [ ] `contextIsolation: true`
|
|
400
|
-
- [ ] `nodeIntegration: false`
|
|
401
|
-
- [ ]
|
|
402
|
-
- [ ] IPC
|
|
403
|
-
- [ ]
|
|
404
|
-
- [ ]
|
|
405
|
-
- [ ]
|
|
406
|
-
- [ ]
|
|
407
|
-
- [ ] CSP
|
|
396
|
+
## Checklist
|
|
397
|
+
|
|
398
|
+
- [ ] `contextIsolation: true` configured
|
|
399
|
+
- [ ] `nodeIntegration: false` configured
|
|
400
|
+
- [ ] Expose APIs only through preload script
|
|
401
|
+
- [ ] Define IPC channel types
|
|
402
|
+
- [ ] Handle external links (setWindowOpenHandler)
|
|
403
|
+
- [ ] Save/restore window state
|
|
404
|
+
- [ ] Auto update setup
|
|
405
|
+
- [ ] Platform-specific menu configuration
|
|
406
|
+
- [ ] CSP header configured
|