@leejungkiin/awkit 1.0.1 → 1.0.2

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.
@@ -0,0 +1,674 @@
1
+ ---
2
+ description: 🍎 Dịch ngược IPA iOS (class-dump, Hopper output) → App Swift hiện đại với SwiftUI, Clean Architecture, và Framework Scanner tự động.
3
+ skill: smali-to-swift
4
+ ---
5
+
6
+ # /reverse-ios — iOS IPA Reverse Engineering Workflow
7
+
8
+ > **Skill được dùng:** `smali-to-swift`
9
+ > **Tech Stack:** Swift + SwiftUI + async/await + URLSession + SwiftData
10
+ > **Philosophy:** "Read ObjC headers to understand WHAT & WHY → Write Swift for HOW"
11
+ > **Sibling:** `/reverse-android` (cùng pipeline pattern, khác platform)
12
+
13
+ ---
14
+
15
+ ## ⚡ QUICK START
16
+
17
+ User cung cấp một trong các input sau:
18
+ - Decrypted `.app` bundle (từ frida-ios-dump / bagbak)
19
+ - Class-dump headers output
20
+ - Hopper/IDA pseudo-code
21
+ - Nói: "Tôi muốn reverse engineer IPA này"
22
+
23
+ Workflow dẫn dắt từng bước — **không bao giờ nhảy cóc**.
24
+
25
+ ---
26
+
27
+ ## 🔵 Session Setup (Chạy 1 lần khi bắt đầu)
28
+
29
+ ### Bước 0.1: Khởi tạo session state
30
+
31
+ ```yaml
32
+ reverse_ios_session:
33
+ project_name: "[TBD - lấy từ Info.plist]"
34
+ app_bundle_dir: "[path user cung cấp]"
35
+ headers_dir: "[path to class-dump output]"
36
+ current_step: 0
37
+ framework_report_done: false
38
+ plist_analyzed: false
39
+ completed_screens: []
40
+ pending_screens: []
41
+ decisions: []
42
+ ```
43
+
44
+ ### Bước 0.2: Xác nhận input và hướng dẫn chuẩn bị
45
+
46
+ ```
47
+ 🍎 iOS Reverse Engineering bắt đầu!
48
+
49
+ Em cần biết:
50
+ 1. Decrypted .app bundle ở đâu? (vd: ~/decrypted/App.app/)
51
+ 2. Đã chạy class-dump chưa? Headers ở đâu?
52
+ 3. Tên app gốc? Bundle ID?
53
+
54
+ Nếu chưa chuẩn bị, đây là flow chuẩn bị:
55
+
56
+ # 1. Decrypt IPA (cần jailbroken device)
57
+ bagbak -o ~/decrypted/ com.example.app
58
+ # hoặc
59
+ frida-ios-dump -u com.example.app
60
+
61
+ # 2. Dump headers
62
+ class-dump -H ~/decrypted/App.app -o ~/headers/
63
+
64
+ # 3. (Optional) Disassembly — mở trong Hopper hoặc IDA
65
+ open -a Hopper ~/decrypted/App.app/App
66
+ ```
67
+
68
+ ---
69
+
70
+ ## 📦 Step 0: Framework Scanner (BẮT BUỘC — Không được bỏ qua)
71
+
72
+ > **Mục tiêu:** Nhận diện toàn bộ frameworks trước khi code.
73
+ > **Reference:** `skills/smali-to-swift/framework-patterns.md`
74
+
75
+ ### Bước 0.3: Quét IPA structure
76
+
77
+ ```bash
78
+ # 1. Embedded frameworks
79
+ ls [app_bundle]/Frameworks/
80
+
81
+ # 2. Linked libraries (Mach-O)
82
+ otool -L [app_bundle]/App | grep -v /System | grep -v /usr/lib
83
+
84
+ # 3. Class-dump header imports
85
+ grep -rh "#import <" [headers_dir]/ | sort -u
86
+ grep -rh "@import " [headers_dir]/ | sort -u
87
+ grep -rh "import " [headers_dir]/ | grep -v Foundation | grep -v UIKit | sort -u
88
+
89
+ # 4. String search for SDK identifiers
90
+ strings [app_bundle]/App | grep -i "cocoapods\|carthage\|firebase\|facebook\|google"
91
+
92
+ # 5. Assets & resources
93
+ ls [app_bundle]/*.car 2>/dev/null # Asset catalogs
94
+ ls [app_bundle]/*.momd 2>/dev/null # Core Data models
95
+ ls [app_bundle]/*.storyboardc 2>/dev/null # Storyboards
96
+ find [app_bundle] -name "*.json" -o -name "*.plist" | grep -v Info.plist | sort
97
+ ```
98
+
99
+ ### Bước 0.4: Tạo Framework Detection Report
100
+
101
+ Dùng patterns từ `framework-patterns.md`:
102
+
103
+ ```markdown
104
+ ## 📦 Framework Detection Report — [App Name]
105
+
106
+ ### ✅ Reuse (Add via SPM)
107
+ | Framework | Detected | Latest Version | Notes |
108
+ |-----------|----------|----------------|-------|
109
+ | Kingfisher | Frameworks/Kingfisher.framework | 7.12.0 | Keep |
110
+ | [...] | [...] | [...] | [...] |
111
+
112
+ ### 🔄 Replace (Legacy → Modern Swift)
113
+ | Old Framework | Detected | Modern Replacement |
114
+ |---------------|----------|-------------------|
115
+ | AFNetworking | Frameworks/AFNetworking.framework | URLSession async/await |
116
+ | SDWebImage | header imports | AsyncImage + Kingfisher |
117
+ | SnapKit | header imports | SwiftUI layout |
118
+ | [...] | [...] | [...] |
119
+
120
+ ### 🍏 Apple Frameworks Used
121
+ | Framework | Purpose | SwiftUI Equivalent |
122
+ |-----------|---------|-------------------|
123
+ | MapKit | Maps | Map (SwiftUI) |
124
+ | CoreLocation | GPS | LocationManager wrapper |
125
+ | AVFoundation | Camera | Camera view wrapper |
126
+ | CoreData | Database | SwiftData migration |
127
+
128
+ ### 📱 Native Libraries — investigate
129
+ | File | Notes |
130
+ |------|-------|
131
+ | libcustom.dylib | C library — need bridging header |
132
+
133
+ ### 🏷️ App Code (Rewrite in Swift)
134
+ | Class Prefix / Pattern | Estimated Module |
135
+ |------------------------|-----------------|
136
+ | MYAppUser*, MYAppAuth* | Auth module |
137
+ | MYAppHome*, MYAppFeed* | Home/Feed module |
138
+
139
+ ### ❓ Unknown (investigate)
140
+ | Framework | Notes |
141
+ |-----------|-------|
142
+ | CustomSDK.framework | Proprietary? |
143
+ ```
144
+
145
+ ### Bước 0.5: User approval
146
+
147
+ ```
148
+ 📦 Framework Report sẵn sàng!
149
+
150
+ Anh review:
151
+ ✅ "Reuse" list — còn thiếu gì không?
152
+ 🔄 "Replace" list — có cái nào anh muốn giữ?
153
+
154
+ Xác nhận xong → em bắt đầu Step 1.
155
+ ```
156
+
157
+ > **GATE:** Không tiếp tục khi chưa có user approval.
158
+
159
+ ---
160
+
161
+ ## 📄 Step 1: Info.plist & Entitlements Analysis + Project Bootstrap
162
+
163
+ > **Input:** `[app_bundle]/Info.plist` + entitlements
164
+
165
+ ### Bước 1.1: Đọc Info.plist
166
+
167
+ ```bash
168
+ # Read Info.plist (may be binary format)
169
+ plutil -p [app_bundle]/Info.plist
170
+
171
+ # Entitlements
172
+ codesign -d --entitlements :- [app_bundle]/App 2>/dev/null
173
+ # hoặc
174
+ jtool2 --ent [app_bundle]/App
175
+ ```
176
+
177
+ Trích xuất:
178
+ ```yaml
179
+ extract:
180
+ - bundle_id: "com.example.app"
181
+ - display_name: "My App"
182
+ - min_ios_version: "15.0"
183
+ - permissions:
184
+ camera: "NSCameraUsageDescription → [description]"
185
+ photos: "NSPhotoLibraryUsageDescription → [description]"
186
+ location: "NSLocationWhenInUseUsageDescription → [description]"
187
+ microphone: "NSMicrophoneUsageDescription → [description]"
188
+ notifications: "aps-environment → [production/development]"
189
+ tracking: "NSUserTrackingUsageDescription → [description]"
190
+ - url_schemes: ["myapp://"]
191
+ - universal_links: ["applinks:example.com"]
192
+ - capabilities:
193
+ push_notifications: true/false
194
+ apple_pay: true/false
195
+ sign_in_with_apple: true/false
196
+ app_groups: ["group.com.example.app"]
197
+ - supported_orientations: [portrait, landscape]
198
+ ```
199
+
200
+ ### Bước 1.2: Phân tích class hierarchy
201
+
202
+ Từ class-dump headers, xác định:
203
+ ```bash
204
+ # Find all ViewControllers
205
+ grep -rl "UIViewController" [headers_dir]/ | sort
206
+
207
+ # Find AppDelegate
208
+ grep -rl "UIApplicationDelegate" [headers_dir]/
209
+
210
+ # Find tab bar structure
211
+ grep -rl "UITabBarController" [headers_dir]/
212
+
213
+ # Find navigation controllers
214
+ grep -rl "UINavigationController" [headers_dir]/
215
+ ```
216
+
217
+ Mapping ViewControllers → SwiftUI Screens:
218
+ ```
219
+ SplashViewController → LaunchScreen (or SplashScreen.swift)
220
+ LoginViewController → Auth/LoginScreen.swift
221
+ MainTabBarController → TabView in ContentView.swift
222
+ HomeViewController → Screens/Home/HomeScreen.swift
223
+ ProfileViewController → Screens/Profile/ProfileScreen.swift
224
+ SettingsTableViewController → Screens/Settings/SettingsScreen.swift
225
+ DetailViewController → Screens/Detail/DetailScreen.swift
226
+ ```
227
+
228
+ ### Bước 1.3: Tạo Xcode project structure
229
+
230
+ Đề xuất structure (xem template trong SKILL.md Step 1).
231
+
232
+ ### Bước 1.4: Package.swift setup (hoặc SPM via Xcode)
233
+
234
+ ```swift
235
+ // Dependencies từ Framework Report
236
+ // Add via Xcode: File → Add Package Dependencies
237
+
238
+ // Firebase
239
+ "https://github.com/firebase/firebase-ios-sdk" // 11.0+
240
+ // Kingfisher (image loading)
241
+ "https://github.com/onevcat/Kingfisher" // 7.12+
242
+ // KeychainAccess
243
+ "https://github.com/kishikawakatsumi/KeychainAccess" // 4.2+
244
+ // Lottie
245
+ "https://github.com/airbnb/lottie-ios" // 4.4+
246
+ ```
247
+
248
+ ### ✅ Checkpoint Step 1
249
+
250
+ ```markdown
251
+ ## ✅ Step 1 Complete: Info.plist & Bootstrap
252
+
253
+ ### Extracted:
254
+ - Bundle ID: [bundle_id]
255
+ - Permissions: [count] total
256
+ - Screens to rebuild: [list from VCs]
257
+ - URL Schemes: [list]
258
+
259
+ ### Created:
260
+ - Xcode project structure proposal
261
+ - SPM dependency list
262
+
263
+ ### ⏭️ Next: Step 2 — Data Layer Reconstruction
264
+ - Cung cấp class-dump headers cho: Network/API classes, Model classes, Database/Storage
265
+ - Tìm trong headers: *Service, *Manager, *Client, *API, *Model, *Entity
266
+ ```
267
+
268
+ ---
269
+
270
+ ## 💾 Step 2: Data Layer Reconstruction
271
+
272
+ > **Input:** Class-dump headers cho network, models, storage classes
273
+ > **Reading help:** `skills/smali-to-swift/objc-reading-guide.md`
274
+
275
+ ### Bước 2.1: Models
276
+
277
+ ```objc
278
+ // From class-dump
279
+ @interface UserModel : NSObject
280
+ @property (nonatomic, copy) NSString *userId;
281
+ @property (nonatomic, copy) NSString *fullName;
282
+ @property (nonatomic, assign) NSInteger age;
283
+ @property (nullable, nonatomic, copy) NSString *avatarURL;
284
+ @end
285
+ ```
286
+
287
+ ```swift
288
+ // Swift Codable
289
+ struct User: Codable, Identifiable, Sendable {
290
+ let id: String
291
+ let fullName: String
292
+ let age: Int
293
+ let avatarURL: String?
294
+
295
+ enum CodingKeys: String, CodingKey {
296
+ case id = "user_id"
297
+ case fullName = "full_name"
298
+ case age
299
+ case avatarURL = "avatar_url"
300
+ }
301
+ }
302
+ ```
303
+
304
+ ### Bước 2.2: API Client
305
+
306
+ ```swift
307
+ // Data/Network/APIClient.swift
308
+ actor APIClient {
309
+ private let session: URLSession
310
+ private let baseURL: URL
311
+ private let decoder = JSONDecoder()
312
+
313
+ init(baseURL: URL, session: URLSession = .shared) {
314
+ self.baseURL = baseURL
315
+ self.session = session
316
+ }
317
+
318
+ func request<T: Decodable>(_ endpoint: Endpoint) async throws -> T {
319
+ var request = URLRequest(url: baseURL.appendingPathComponent(endpoint.path))
320
+ request.httpMethod = endpoint.method.rawValue
321
+ request.setValue("application/json", forHTTPHeaderField: "Content-Type")
322
+ endpoint.headers?.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) }
323
+ if let body = endpoint.body { request.httpBody = body }
324
+
325
+ let (data, response) = try await session.data(for: request)
326
+ guard let http = response as? HTTPURLResponse, (200...299).contains(http.statusCode) else {
327
+ throw APIError.invalidResponse
328
+ }
329
+ return try decoder.decode(T.self, from: data)
330
+ }
331
+ }
332
+ ```
333
+
334
+ ### Bước 2.3: SwiftData (nếu app có Core Data / SQLite)
335
+
336
+ ```swift
337
+ // Data/Local/SwiftDataModels/UserEntity.swift
338
+ import SwiftData
339
+
340
+ @Model
341
+ final class UserEntity {
342
+ @Attribute(.unique) var id: String
343
+ var name: String
344
+ var email: String
345
+ var lastUpdated: Date
346
+
347
+ init(id: String, name: String, email: String) {
348
+ self.id = id
349
+ self.name = name
350
+ self.email = email
351
+ self.lastUpdated = .now
352
+ }
353
+ }
354
+ ```
355
+
356
+ ### Bước 2.4: Repository
357
+
358
+ ```swift
359
+ // Domain/Repositories/UserRepository.swift
360
+ protocol UserRepository: Sendable {
361
+ func getUser(id: String) async throws -> User
362
+ func login(email: String, password: String) async throws -> AuthToken
363
+ }
364
+
365
+ // Data/Repositories/UserRepositoryImpl.swift
366
+ final class UserRepositoryImpl: UserRepository {
367
+ private let apiClient: APIClient
368
+ private let modelContext: ModelContext
369
+
370
+ func getUser(id: String) async throws -> User {
371
+ // offline-first: local → remote → cache
372
+ }
373
+ }
374
+ ```
375
+
376
+ ### ✅ Checkpoint Step 2
377
+
378
+ ---
379
+
380
+ ## 🧮 Step 3: Core Logic & Utils Reconstruction
381
+
382
+ > **CRITICAL:** Crypto output MUST match original
383
+ > **Input:** Hopper pseudo-code / headers cho encryption utils
384
+
385
+ ### Bước 3.1: Crypto utils → Swift
386
+
387
+ ```swift
388
+ import CryptoKit
389
+ import CommonCrypto
390
+
391
+ enum CryptoUtils {
392
+ // MD5 (legacy — use CommonCrypto)
393
+ static func md5(_ input: String) -> String {
394
+ let data = Data(input.utf8)
395
+ var digest = [UInt8](repeating: 0, count: Int(CC_MD5_DIGEST_LENGTH))
396
+ data.withUnsafeBytes { CC_MD5($0.baseAddress, CC_LONG(data.count), &digest) }
397
+ return digest.map { String(format: "%02x", $0) }.joined()
398
+ }
399
+
400
+ // SHA256 (modern — use CryptoKit)
401
+ static func sha256(_ input: String) -> String {
402
+ let hash = SHA256.hash(data: Data(input.utf8))
403
+ return hash.compactMap { String(format: "%02x", $0) }.joined()
404
+ }
405
+
406
+ // AES (match original algorithm exactly)
407
+ static func aesEncrypt(data: Data, key: Data, iv: Data) throws -> Data {
408
+ // Implement matching original parameters
409
+ }
410
+ }
411
+ ```
412
+
413
+ ### Bước 3.2: XCTest verification
414
+
415
+ ```swift
416
+ final class CryptoUtilsTests: XCTestCase {
417
+ func testMD5MatchesOriginal() {
418
+ XCTAssertEqual(CryptoUtils.md5("test"), "098f6bcd4621d373cade4e832627b4f6")
419
+ }
420
+ }
421
+ ```
422
+
423
+ ### ✅ Checkpoint Step 3
424
+
425
+ ---
426
+
427
+ ## 🎨 Step 4: UI & ViewModel Reconstruction (Per Screen — Lặp lại)
428
+
429
+ > **Input:** Storyboard analysis + VC headers + disassembly
430
+ > **Reference:** `skills/smali-to-swift/SKILL.md` → Step 4
431
+
432
+ ### Bước 4.0: Chọn màn hình theo ưu tiên
433
+
434
+ ```
435
+ 1. LaunchScreen / Splash (đơn giản nhất)
436
+ 2. Auth screens (Login, Register)
437
+ 3. Main TabView + Home
438
+ 4. Detail screens
439
+ 5. Settings / Profile
440
+ ```
441
+
442
+ ### Bước 4.1: Resource extraction (on-demand)
443
+
444
+ ```markdown
445
+ ### Resources cho [ScreenName]:
446
+ - Images: [icon_logo, bg_login, ...]
447
+ - Colors: [primaryColor, backgroundColor, ...]
448
+ - Strings: [login_title, email_placeholder, ...]
449
+ - Fonts: [Inter-Regular.ttf, ...]
450
+ ```
451
+
452
+ ### Bước 4.2: UIKit → SwiftUI
453
+
454
+ ```swift
455
+ // Presentation/Screens/Auth/LoginScreen.swift
456
+ struct LoginScreen: View {
457
+ @State private var viewModel: LoginViewModel
458
+ @Environment(\.dismiss) private var dismiss
459
+
460
+ var body: some View {
461
+ NavigationStack {
462
+ ScrollView {
463
+ VStack(spacing: 24) {
464
+ // Logo
465
+ Image(.appLogo)
466
+ .resizable()
467
+ .scaledToFit()
468
+ .frame(height: 80)
469
+
470
+ // Form fields
471
+ VStack(spacing: 16) {
472
+ TextField("Email", text: $viewModel.email)
473
+ .textContentType(.emailAddress)
474
+ .keyboardType(.emailAddress)
475
+ .autocapitalization(.none)
476
+
477
+ SecureField("Password", text: $viewModel.password)
478
+ .textContentType(.password)
479
+ }
480
+ .textFieldStyle(.roundedBorder)
481
+
482
+ // Login button
483
+ Button {
484
+ Task { await viewModel.login() }
485
+ } label: {
486
+ Text("Login")
487
+ .frame(maxWidth: .infinity)
488
+ }
489
+ .buttonStyle(.borderedProminent)
490
+ .disabled(viewModel.isLoading)
491
+ }
492
+ .padding()
493
+ }
494
+ .overlay {
495
+ if viewModel.isLoading { ProgressView() }
496
+ }
497
+ .alert("Error", isPresented: $viewModel.showError) {
498
+ Button("OK") {}
499
+ } message: {
500
+ Text(viewModel.errorMessage)
501
+ }
502
+ .navigationTitle("Login")
503
+ }
504
+ }
505
+ }
506
+ ```
507
+
508
+ ### Bước 4.3: ViewModel
509
+
510
+ ```swift
511
+ @Observable
512
+ final class LoginViewModel {
513
+ var email = ""
514
+ var password = ""
515
+ var isLoading = false
516
+ var showError = false
517
+ var errorMessage = ""
518
+
519
+ private let authRepository: AuthRepository
520
+
521
+ init(authRepository: AuthRepository) {
522
+ self.authRepository = authRepository
523
+ }
524
+
525
+ func login() async {
526
+ isLoading = true
527
+ defer { isLoading = false }
528
+
529
+ do {
530
+ try await authRepository.login(email: email, password: password)
531
+ } catch {
532
+ errorMessage = error.localizedDescription
533
+ showError = true
534
+ }
535
+ }
536
+ }
537
+ ```
538
+
539
+ ### ✅ Checkpoint Step 4 (Per Screen)
540
+
541
+ > **Loop:** Lặp Step 4 cho từng screen → khi hết → Step 5.
542
+
543
+ ---
544
+
545
+ ## 📦 Step 5: Third-party SDK & Native Library Integration
546
+
547
+ ### Bước 5.1: App entry point
548
+
549
+ ```swift
550
+ @main
551
+ struct MyApp: App {
552
+ @UIApplicationDelegateAdaptor private var appDelegate: AppDelegate
553
+
554
+ var body: some Scene {
555
+ WindowGroup {
556
+ ContentView()
557
+ }
558
+ .modelContainer(for: [UserEntity.self, /* other models */])
559
+ }
560
+ }
561
+
562
+ class AppDelegate: NSObject, UIApplicationDelegate {
563
+ func application(_ application: UIApplication,
564
+ didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
565
+ FirebaseApp.configure()
566
+ return true
567
+ }
568
+
569
+ func application(_ application: UIApplication,
570
+ didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {
571
+ Messaging.messaging().apnsToken = deviceToken
572
+ }
573
+ }
574
+ ```
575
+
576
+ ### Bước 5.2: Native C/C++ libraries (Bridging Header)
577
+
578
+ ```c
579
+ // App-Bridging-Header.h
580
+ #include "native_lib.h"
581
+ ```
582
+
583
+ ```swift
584
+ // Swift usage
585
+ let result = native_function(param1, param2)
586
+ ```
587
+
588
+ ### ✅ Checkpoint Step 5
589
+
590
+ ---
591
+
592
+ ## ✅ Step 6: Parity Check & Quality Gate
593
+
594
+ ### Bước 6.1: Test checklist từ disassembly branches
595
+
596
+ ```markdown
597
+ ### Edge cases (từ ObjC analysis):
598
+ - [ ] Login empty email → error message
599
+ - [ ] Login wrong password → error + retry limit?
600
+ - [ ] Network offline → cached data shown?
601
+ - [ ] App backgrounded during API call
602
+ - [ ] Deep link handling
603
+ - [ ] Push notification tap → correct screen
604
+ ```
605
+
606
+ ### Bước 6.2: Build & test
607
+
608
+ ```bash
609
+ # Build
610
+ xcodebuild -scheme App -destination 'generic/platform=iOS' build
611
+
612
+ # Unit tests
613
+ xcodebuild -scheme App -destination 'platform=iOS Simulator,name=iPhone 16' test
614
+
615
+ # SwiftLint
616
+ swiftlint lint
617
+ ```
618
+
619
+ ### ✅ Final Checkpoint
620
+
621
+ ```markdown
622
+ ## 🎉 iOS Reverse Engineering Complete!
623
+
624
+ ### Summary:
625
+ - Screens rebuilt: [count]
626
+ - Frameworks reused: [count]
627
+ - Frameworks replaced: [count]
628
+ - Native libs integrated: [count]
629
+
630
+ ### ⏭️ Next Steps:
631
+ 1. `/test` — Run full test suite
632
+ 2. `/deploy` — When ready for TestFlight / App Store
633
+ 3. `/code-janitor` — Clean up before merge
634
+ ```
635
+
636
+ ---
637
+
638
+ ## 🚫 WORKFLOW RULES
639
+
640
+ ```yaml
641
+ never_skip:
642
+ - Step 0 (Framework Scanner) — always first
643
+ - User approval of Framework Report
644
+ - Checkpoint after each step
645
+
646
+ never_do:
647
+ - Mass-copy assets from IPA
648
+ - Use UIKit when SwiftUI equivalent exists
649
+ - Use GCD for new async code (use async/await)
650
+ - Use ObjC in new code (Swift only, except bridging headers)
651
+ - Skip crypto parity testing
652
+
653
+ always_do:
654
+ - Document decisions in session state
655
+ - Present Framework Report before coding
656
+ - XCTest all crypto/hash functions
657
+ - Use @Observable for ViewModels (iOS 17+)
658
+ - Use NavigationStack for navigation
659
+ - Use SPM for all dependencies
660
+ ```
661
+
662
+ ---
663
+
664
+ ## 🔗 Related
665
+
666
+ - **Skill:** `smali-to-swift` (core knowledge & rules)
667
+ - **Framework DB:** `skills/smali-to-swift/framework-patterns.md`
668
+ - **ObjC Guide:** `skills/smali-to-swift/objc-reading-guide.md`
669
+ - **Sibling:** `/reverse-android` (Android counterpart)
670
+ - **After RE done:** `/test`, `/deploy`, `/code-janitor`
671
+
672
+ ---
673
+
674
+ *reverse-ios workflow v1.0.0 — iOS IPA RE Execution Flow*