@hungvv/super-ads-skills 1.0.0 → 1.1.0

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.
Files changed (55) hide show
  1. package/README.md +71 -13
  2. package/dist/cli.js +8 -2
  3. package/package.json +1 -1
  4. package/skills/README.md +4 -0
  5. package/skills/adspro-to-superads/SKILL.md +157 -732
  6. package/skills/adspro-to-superads/references/global-invariant-check.md +49 -0
  7. package/skills/adspro-to-superads/references/migration-contract.md +57 -0
  8. package/skills/adspro-to-superads/references/phase-0-inventory.md +39 -0
  9. package/skills/adspro-to-superads/references/phase-1-2-gradle-manifest.md +85 -0
  10. package/skills/adspro-to-superads/references/phase-3-config-migration.md +159 -0
  11. package/skills/adspro-to-superads/references/phase-4-wrapper-observability.md +70 -0
  12. package/skills/adspro-to-superads/references/phase-5-7-callsites-layout-cleanup.md +73 -0
  13. package/skills/adspro-to-superads/references/principles-and-authority.md +73 -0
  14. package/skills/adspro-to-superads/references/user-questions.md +53 -0
  15. package/skills/adspro-to-superads/snippets/ads-api-quick.md +20 -0
  16. package/skills/adspro-to-superads/templates/migration-inventory.md +42 -0
  17. package/skills/super-ads-ab-test/SKILL.md +91 -23
  18. package/skills/super-ads-adb-test/SKILL.md +85 -36
  19. package/skills/super-ads-adb-test/{references → examples}/scenarios-sample.md +5 -5
  20. package/skills/super-ads-add-placement/SKILL.md +145 -50
  21. package/skills/super-ads-add-placement/references/call-site-api.md +33 -0
  22. package/skills/super-ads-add-placement/snippets/app-open.md +25 -0
  23. package/skills/super-ads-add-placement/snippets/fullscreen-nav.md +14 -0
  24. package/skills/super-ads-add-placement/snippets/inline-banner-native.md +19 -0
  25. package/skills/super-ads-add-placement/snippets/preload-event.md +13 -0
  26. package/skills/super-ads-add-placement/snippets/reward.md +17 -0
  27. package/skills/super-ads-config/SKILL.md +94 -36
  28. package/skills/super-ads-debug/SKILL.md +87 -37
  29. package/skills/super-ads-debug/references/logging-setup.md +2 -0
  30. package/skills/super-ads-debug/scripts/filter-logads.sh +21 -0
  31. package/skills/super-ads-debug-override/SKILL.md +154 -81
  32. package/skills/super-ads-debug-override/snippets/arm-override.md +9 -0
  33. package/skills/super-ads-debug-override/snippets/check-module.md +8 -0
  34. package/skills/super-ads-debug-override/snippets/clear-override.md +9 -0
  35. package/skills/super-ads-debug-override/snippets/confirm-active.md +16 -0
  36. package/skills/super-ads-guide/SKILL.md +129 -0
  37. package/skills/super-ads-guide/references/compatibility.md +26 -0
  38. package/skills/super-ads-guide/references/sdk-version-check.md +50 -0
  39. package/skills/super-ads-integrate/SKILL.md +145 -47
  40. package/skills/super-ads-integrate/references/application-setup.md +2 -21
  41. package/skills/super-ads-integrate/references/gradle-setup.md +1 -13
  42. package/skills/super-ads-integrate/snippets/consent-request.md +12 -0
  43. package/skills/super-ads-integrate/templates/application-oncreate.md +17 -0
  44. package/skills/super-ads-integrate/templates/gradle-dependencies.md +17 -0
  45. package/skills/super-ads-release-check/SKILL.md +85 -32
  46. package/skills/super-ads-show-rate/SKILL.md +87 -36
  47. package/skills/super-ads-show-rate/references/collect-snippet.md +2 -36
  48. package/skills/super-ads-show-rate/scripts/show-rate.sh +25 -0
  49. package/skills/super-ads-show-rate/snippets/show-events-logger.md +26 -0
  50. package/skills/super-ads-upgrade/SKILL.md +137 -0
  51. package/skills/super-ads-upgrade/references/upgrade-hops.md +26 -0
  52. package/skills/super-ads-upgrade/templates/changelog-template.md +39 -0
  53. package/skills/super-ads-add-placement/references/call-site-snippets.md +0 -90
  54. /package/skills/super-ads-ab-test/{references → templates}/firebase-handoff-template.md +0 -0
  55. /package/skills/super-ads-config/{references → examples}/patch-examples.md +0 -0
@@ -1,9 +1,10 @@
1
1
  ---
2
2
  schema: 1
3
3
  name: adspro-to-superads
4
- description: Use when migrating an Android app from AdsPro (com.android.fullhd.adssdk:AdsPro, AdsSDK, Admob*) to SuperAds (com.android.fullhd.ads:SuperAds, Ads.show). Deterministic procedure — JSON-first, wrapper-first, minimal-diff.
5
- version: 1.2.0
4
+ description: Use when migrating an Android app from AdsPro (AdsSDK, Admob*) to SuperAds (Ads.show) - JSON-first, wrapper-first
5
+ version: 1.3.0
6
6
  group: Ads
7
+ superAds: ">=4.0.0 <5.0.0"
7
8
  ---
8
9
 
9
10
  # AdsPro → SuperAds
@@ -11,94 +12,61 @@ group: Ads
11
12
  Đây là **deterministic migration procedure**, không phải tài liệu giới thiệu SuperAds. Mọi chỗ
12
13
  agent có thể "sáng tạo" đều bị khoá bằng contract, invariant hoặc stop condition.
13
14
 
14
- Đọc `references/` khi đến phase tương ứng — đừng đoán API.
15
-
16
- | Phase | File |
17
- |---|---|
18
- | 0 + 3 | [references/migration-pitfalls.md](references/migration-pitfalls.md) — bẫy SDK + cách **đo app đang migrate** (không copy số liệu app khác) |
19
- | 3 — config | [references/config-template.md](references/config-template.md) |
20
- | 4 — wrapper | [references/wrapper-template.md](references/wrapper-template.md) |
21
- | 3 + 5 — map API | [references/api-mapping.md](references/api-mapping.md) |
22
- | fail runtime | [references/troubleshooting.md](references/troubleshooting.md) |
23
- | sau Phase 7 | [references/verification.md](references/verification.md) |
24
-
25
15
  ## When to use
26
16
 
27
17
  Dùng skill này khi app đang phụ thuộc `com.android.fullhd.adssdk:AdsPro` (entry `AdsSDK` +
28
18
  `Admob*`) và cần chuyển sang SuperAds (`object Ads`, config 3 tầng `units / adItems / placements`).
29
19
 
30
- ## When NOT to use
20
+ ## 🚫 When NOT to use
31
21
 
32
22
  - App đã dùng SuperAds — đây không phải skill thêm placement mới (dùng `super-ads-add-placement`).
33
23
  - Sửa bug SuperAds core, thêm network plugin, hay đổi schema JSON của SDK.
34
24
  - App không có AdsPro (không có `AdsSDK.init`, không có `AdmobInter` / `AdmobNative`).
35
25
 
36
- ## Authority Order
37
-
38
- Khi các nguồn xung đột, thứ tự thẩm quyền:
39
-
40
- 1. Source SuperAds hiện tại (`super-ads/src/main/.../api/`, `domain/config/key/AdConfigKey.kt`, `core/flow/`)
41
- 2. Migration production của SlideShow — **cho API và JSON**, không cho chiến lược diff
42
- 3. Source AdsPro
43
- 4. Template / example trong skill này
44
- 5. Suy luận của agent
45
-
46
- Không dùng example/template trong skill để suy ra API nếu source thực tế khác. **Không được tự
47
- suy luận nếu source chưa chứng minh.** Không copy `showEvery`, số native chain, countdown, RC
48
- key, hay `mode.navigate` từ SlideShow / app đã migrate khác — đo app hiện tại.
49
-
50
- ## Ba nguyên tắc
26
+ ## 📋 Prerequisites
51
27
 
52
- Thứ tự khi xung đột: **JSON-first > wrapper-first > minimal-diff**. Giữ diff nhỏ không bao giờ
53
- là lý do để giữ behavior trong Kotlin.
28
+ - Định tuyến skill và kiểm version SDK: [super-ads-guide](../super-ads-guide/SKILL.md).
29
+ - Skill này nhắm SuperAds `>=4.0.0 <5.0.0`. Nếu app đang dùng SuperAds thấp hơn, **dừng** và khuyến nghị
30
+ (không ép) nâng cấp bằng [super-ads-upgrade](../super-ads-upgrade/SKILL.md) trước khi tiếp tục.
31
+ - App phụ thuộc AdsPro (`com.android.fullhd.adssdk:AdsPro`), repo git để tạo checkpoint `migration/*`
32
+ (rollback phải biết điểm trở về).
33
+ - Truy cập repo SuperAds artifacts `http://repo.volio.vn/repository/maven-s3/` (chi tiết trong
34
+ [phase-1-2-gradle-manifest.md](references/phase-1-2-gradle-manifest.md)).
35
+ - Đã đọc [migration-pitfalls.md](references/migration-pitfalls.md): đo **app này**, không copy số liệu app khác.
54
36
 
55
- ### 1. JSON-first
37
+ ## ⚡ Quick Reference
56
38
 
57
- Một dòng behavior mà JSON diễn đạt được thì **không được tồn tại trong Kotlin**. Ba ví dụ điển
58
- hình (bảng đầy đủ behavior → key JSON, theo từng tầng `adItems`/`placements`/`wrapper`: xem
59
- [references/config-template.md](references/config-template.md) §"Key theo tầng" và
60
- [references/api-mapping.md](references/api-mapping.md)):
61
-
62
- | Behavior | Kotlin cũ (AdsPro) | JSON mới |
63
- |---|---|---|
64
- | preload theo thời điểm | `AdmobX.load(space)` rải rác | `preload.eventActive` |
65
- | gate "chỉ show khi đã load" | `getStatusBySpace == LOADED` | `mode.show: SHOW_LOADED` |
66
- | auto reload chu kỳ | timer trong Fragment | `reload.auto.placementID` + `show.timeAutoReloadMs` |
39
+ Đọc file tương ứng khi đến phase đó — đừng đoán API.
67
40
 
68
- Code smell sau migration — còn thấy là chưa xong: `Handler`/`delay` quanh ads, biến `countShowAd`,
69
- `System.currentTimeMillis()` so khoảng cách ad, `Ads.preload*` thủ công, `if (status == LOADED)`
70
- trước show, `R.layout.*` trong **thân wrapper**, timer reload trong Fragment.
71
-
72
- ### 2. Wrapper-first
73
-
74
- **Giữ vỏ, bỏ ruột.** Hàm gọi ad gốc của app được giữ **nguyên chữ ký** và trở thành lớp wrapper
75
- mỏng delegate xuống `Ads.show`. Behavior bên trong bị bỏ hết vì đã sang JSON. Màn hình **không bị
76
- sửa** nếu chữ ký giữ được.
77
-
78
- Giữ chữ ký ≠ port behavior. Điều bị cấm là mang *logic* cũ sang, không phải mang *tên hàm* sang.
79
-
80
- Một số tham số trở thành **inert** (`idLayoutAds`, `layoutLoadingRes`, `forceLoadNew`, …). Rủi
81
- ro: sau này có người đổi `R.layout.x` ở call site và không có gì xảy ra. Xử lý:
82
-
83
- - Mỗi tham số inert ghi vào bảng "Inert parameters" trong `migration-inventory.md`.
84
- - Trong thân wrapper có **một** dòng comment nêu tham số đó giờ do JSON quyết định.
85
- - Dọn tham số inert là **commit riêng sau migration**, không nằm trong phạm vi này.
86
-
87
- ### 3. Minimal-diff
88
-
89
- - Không tạo file utils mới nếu đã có nhà. File `AdsUtils.kt` (hoặc tương đương) cũ được **giữ lại
90
- làm nhà cho wrapper** — moi ruột, giữ path và tên. Giữ path nghĩa là ~40 màn hình không đổi
91
- một dòng `import`.
92
- - Một file wrapper duy nhất. Không tách `NativeAdsHelper` / `InterAdsHelper`.
93
- - Không sửa màn hình nếu wrapper giữ được chữ ký. Mỗi lần sửa màn hình phải có lý do thuộc đúng
94
- danh sách Phase 5b.
95
- - Code mới (`EventScreen`, hook base fragment) ưu tiên thêm vào file đã có trong package base.
96
- - Mục tiêu diff: `≈ 1 file config JSON + 1 file wrapper + 1 file base + n màn hình có lý do 5b`.
97
- Không phải 40 màn hình.
98
-
99
- SlideShow (commit `4e36e791`) tạo `AdsSP.kt` mới và comment bỏ 650 dòng `AdsUtils.kt`, sửa ~158
100
- file. Skill dùng SlideShow làm authority cho **API và JSON**, nhưng **không** copy chiến lược
101
- diff của nó.
41
+ | Cần gì | Mở file |
42
+ |---|---|
43
+ | Bẫy SDK + cách đo app đang migrate (phase 0 + 3) | [references/migration-pitfalls.md](references/migration-pitfalls.md) |
44
+ | Map API AdsPro → SuperAds, bảng `adsType` → `format` (phase 3 + 5) | [references/api-mapping.md](references/api-mapping.md) |
45
+ | Mẫu config JSON, key theo tầng, referential integrity (phase 3) | [references/config-template.md](references/config-template.md) |
46
+ | Mẫu wrapper `AdsUtils.kt` + `EventScreen` (phase 4) | [references/wrapper-template.md](references/wrapper-template.md) |
47
+ | Authority order, ba nguyên tắc, diff budget | [references/principles-and-authority.md](references/principles-and-authority.md) |
48
+ | Contract MUST / MUST NOT, Stop Conditions | [references/migration-contract.md](references/migration-contract.md) |
49
+ | Câu hỏi nhóm A-D phải hỏi user | [references/user-questions.md](references/user-questions.md) |
50
+ | Phase 0: checkpoint, inventory, verify | [references/phase-0-inventory.md](references/phase-0-inventory.md) |
51
+ | Bảng A-D, Decisions, Inert, Exclusions (mẫu) | [templates/migration-inventory.md](templates/migration-inventory.md) |
52
+ | Phase 1-2: Gradle, Manifest | [references/phase-1-2-gradle-manifest.md](references/phase-1-2-gradle-manifest.md) |
53
+ | Phase 3: 3a-3g config | [references/phase-3-config-migration.md](references/phase-3-config-migration.md) |
54
+ | Phase 4: wrapper, EventScreen, observability | [references/phase-4-wrapper-observability.md](references/phase-4-wrapper-observability.md) |
55
+ | Phase 5-7: call site, layout, cleanup | [references/phase-5-7-callsites-layout-cleanup.md](references/phase-5-7-callsites-layout-cleanup.md) |
56
+ | Global Invariant Check + 13 chuỗi `adType` | [references/global-invariant-check.md](references/global-invariant-check.md) |
57
+ | Runtime fail | [references/troubleshooting.md](references/troubleshooting.md) |
58
+ | Verify sau Phase 7 | [references/verification.md](references/verification.md) |
59
+ | API `Ads.*` tra nhanh | [snippets/ads-api-quick.md](snippets/ads-api-quick.md) |
60
+
61
+ ## 🏗️ Recommended Architecture
62
+
63
+ Thứ tự khi xung đột: **JSON-first > wrapper-first > minimal-diff**. Giữ diff nhỏ không bao giờ là lý do
64
+ để giữ behavior trong Kotlin. Chi tiết và authority order:
65
+ [principles-and-authority.md](references/principles-and-authority.md).
66
+
67
+ - **JSON-first** — behavior mà JSON diễn đạt được thì không được tồn tại trong Kotlin.
68
+ - **Wrapper-first** — giữ vỏ, bỏ ruột: hàm gọi ad gốc giữ nguyên chữ ký, delegate xuống `Ads.show`.
69
+ - **Minimal-diff** — mục tiêu `≈ 1 file config JSON + 1 file wrapper + 1 file base + n màn hình có lý do 5b`.
102
70
 
103
71
  ```text
104
72
  AdsPro: 40 màn --space + R.layout + timing--> AdsUtils (650 dòng) --> Admob*.load/show
@@ -107,666 +75,69 @@ SuperAds: 40 màn KHÔNG ĐỔI --chữ ký cũ--> AdsUtils moi ruột --> A
107
75
  JSON (format, layout, mode, preload, reload, chain)
108
76
  ```
109
77
 
110
- ## Migration Contract
111
-
112
- MUST:
113
-
114
- - Placement name giữ **nguyên văn** spaceName cũ, kể cả sai chính tả
115
- (`Native_Trennding`, `Native_Litst_Face_Filter`, `Native_WellcomeBack`).
116
- **MUST NOT CHANGE.** Dashboard doanh thu, Firebase RC và analytics đang query đúng chuỗi đó.
117
- - `adsType` preserve đúng format semantics theo bảng trong `references/api-mapping.md`.
118
- - Mediation adapter `com.google.ads.mediation:*` hiện có **giữ nguyên**.
119
- - App open setup **đúng một lần** trong wrapper `init`.
120
- - Firebase RC key ads giữ nguyên chuỗi.
121
- - Mỗi thời điểm preload cũ ứng **đúng một** màn/thời điểm trong `preload.eventActive` (một chuỗi,
122
- hoặc cặp `_fo` + `_fn` của **cùng** thời điểm nếu code cũ preload vô điều kiện — xem 3b).
123
- - Consent (CMP) **trước** mọi preload / show / app open: wrapper chỉ dispatch event và mở app open
124
- sau callback `showCmp` (`MIGRATION.md:126-127` — core không tự chặn request khi chưa có consent).
125
- - `mode.navigate` theo **từng** placement, đọc callback AdsPro của **app này**. Default SDK `NAVIGATE_ON_DISMISS` — không copy template/SlideShow.
126
- - Reload → `reload.*` trên `placements`. Layout + unit là của **placement đích** (`ReloadCoordinator`). Bảng D khi có click reload.
127
- - File JSON migrate **trùng** `Ads.init` / `configProvider` của app này.
128
- - Nếu có native-sau-inter: `firstShowAt` / `showEvery` / `adItems.native[]` đo từ counter + số dialog **production của app này** (công thức SDK `ChainAdActivity`), không copy app khác / không đếm space JSON.
129
- - Hàm gọi ad gốc **giữ nguyên chữ ký**; wrapper ở đúng file cũ.
130
- - Nối đủ ba mối observability trước khi verify: logger sink, tracker collector, config provider.
131
-
132
- MUST NOT:
133
-
134
- - Đổi / chuẩn hoá / sửa chính tả placement name.
135
- - Tạo abstraction mới, hay tách wrapper thành nhiều file helper.
136
- - Port *logic* của `AdsUtils.kt` cũ (chữ ký thì giữ).
137
- - Xoá mediation adapter vì "SuperAds đã có sẵn".
138
- - Gọi `Ads.preload*` thủ công thay `preload.eventActive`.
139
- - Tự viết timer / listener reload ở app — `ReloadCoordinator` trong core đã làm.
140
- - Sửa màn hình khi không có lý do thuộc danh sách Phase 5b.
141
- - Dọn tham số inert trong cùng commit migration.
142
- - Tạo `LegacyAdCallbackAdapter` hay bất kỳ lớp ép 14 callback AdsPro thành 7 callback SuperAds.
143
- - Gán `mode.navigate` hàng loạt từ template / default SDK.
144
- - Gộp nhiều layout click khác nhau vào một adItem reload (P2) — hoặc tách bừa khi AdsPro đã tách space.
145
- - `adItems.native[]` / `showEvery` copy từ template hoặc từ app đã migrate khác.
146
- - Reload đích = space zero call site chỉ vì tên có `Reload`.
147
- - Sửa JSON A trong khi init / RC đọc JSON B.
148
-
149
- ## Stop Conditions
150
-
151
- Agent **dừng và hỏi user** (theo catalog bên dưới), không đoán, khi:
152
-
153
- - Gặp API AdsPro không có trong bảng mapping.
154
- - Placement không có format mapping xác định.
155
- - Thiếu module provider SuperAds cho network trong config.
156
- - Callback semantics không thể bảo toàn.
157
- - Config có placement trùng tên.
158
- - Không xác định được hành vi của RC key ads.
159
- - Call site `load()` không xác định được ứng với screen event nào.
160
- - App chưa có `screenName()` hoặc registry tracking screen.
161
- - AdItem `NATIVE_COLLAPSIBLE` không đủ cặp layout collapsed + expanded.
162
- - Không xác định được code cũ nav từ callback show hay dismiss.
163
- - Click reload: cùng space AdsPro nhưng **khác** layout / unit — không biết tách bao nhiêu target (P2).
164
- - Flag RC / AB / counter ads không diễn đạt được bằng một JSON tĩnh (P4).
165
- - Reload cũ không xác định được trigger là click, dismiss hay timer.
166
- - Wrapper không thể giữ chữ ký cũ (hàm cũ trả về giá trị SuperAds không cung cấp).
167
-
168
- ## Câu hỏi phải hỏi user
169
-
170
- Mỗi câu hỏi phải có **phương án khuyến nghị** và nêu **hệ quả**. Agent không được tự chọn thay
171
- user ở nhóm A và B. Gộp câu hỏi cùng nhóm vào **một** lần hỏi.
172
-
173
- - Nhóm B: hỏi khi lập inventory Phase 0.
174
- - Nhóm A và D: hỏi **trước** Phase 3.
175
- - Nhóm C: hỏi **một lần** đầu Phase 4.
176
-
177
- Mọi câu trả lời ghi vào mục "Decisions" trong `migration-inventory.md`.
178
-
179
- ### Nhóm A — tính năng không chuyển được (loại C)
180
-
181
- | Tình huống | Câu hỏi | Phương án gợi ý |
182
- |---|---|---|
183
- | App dùng `AdmobNativePip` | SuperAds không có native PIP. Xử lý placement này thế nào? | (1) Bỏ PIP, chuyển thành `NATIVE` thường ở cùng vị trí — khuyến nghị; (2) Giữ AdsPro riêng cho PIP — không khuyến nghị, hai SDK cùng tồn tại; (3) Tạm tắt placement |
184
- | App dùng `AdmobNativeCollapsibleHalfSize` | Không có half-size. Dùng gì thay? | (1) `NATIVE_COLLAPSIBLE` với layout expanded nhỏ hơn — khuyến nghị; (2) Bỏ, dùng `NATIVE` |
185
- | Space có `adsType` không nằm trong bảng map | `adsType` này không có format tương ứng. Ý định sản phẩm là gì? | Liệt kê 2–3 format gần nhất kèm khác biệt hành vi |
186
-
187
- ### Nhóm B — behavior không suy ra được từ code
188
-
189
- | Tình huống | Câu hỏi | Phương án gợi ý |
190
- |---|---|---|
191
- | Code cũ nav ở cả hai callback, hoặc nav ngoài callback | Placement này nav khi ad *hiện* hay khi ad *đóng*? | Mặc định SDK là `NAVIGATE_ON_DISMISS`; nêu hệ quả UX từng lựa chọn |
192
- | `load()` gọi trong `onCreate` của Activity, không thuộc màn nào | Preload này nên gắn với event màn nào? | Đề xuất màn gần nhất trong flow + phương án preload ngay sau init |
193
- | Không rõ preload áp cho phiên nào | Preload cho người mở lần đầu, người quay lại, hay cả hai? | Khuyến nghị **cả hai** (`_fo` + `_fn`) nếu code cũ preload vô điều kiện |
194
- | Reload cũ vừa có timer vừa có click | Giữ cả `reload.auto` và `reload.click`, hay chỉ một? | Chain chỉ nhận click |
195
- | Space trong JSON nhưng zero call site (vd `Banner_chung`) | Giữ placement này hay bỏ? | (1) Giữ, ghi vào Documented exclusions — khuyến nghị (an toàn cho RC); (2) Bỏ khỏi config |
196
- | Hai space khác nhau nhưng cùng ý nghĩa | Gộp hay giữ tách? | Khuyến nghị **giữ tách** để bảo toàn analytics |
197
- | Event cũ mang `previous_screen`/`next_screen` trong bundle | Event khai trong JSON không mang được cặp screen. Bỏ cặp screen, hay giữ tracking app-side? | (1) Bỏ, dùng `adItemId` — khuyến nghị; (2) Collect `TrackerEvent.Impression` rồi tự ghép screen từ state app |
198
- | Tên event analytics cũ rải rác trong Kotlin | Tên nào đưa vào `tracking.event*`, tên nào giữ app-side? | Event AB/theo placement vào JSON; event bespoke cần ngữ cảnh app thì giữ trên flow |
199
-
200
- ### Nhóm C — quyết định kiến trúc (đầu Phase 4)
201
-
202
- | Câu hỏi | Phương án gợi ý |
203
- |---|---|
204
- | Nhà cho wrapper: moi ruột `AdsUtils.kt` cũ, hay tạo `AdsSP.kt` mới? | (1) Moi ruột file cũ — **khuyến nghị**, giữ path nên ~40 màn không đổi import; (2) File mới kiểu SlideShow — sạch tên nhưng phải sửa import ở mọi màn |
205
- | Nhà cho `EventScreen`: thêm vào file base có sẵn hay tạo file mới? | (1) Thêm vào file base sẵn có; (2) File mới `EventScreen.kt` nếu package base không có nhà hợp lý |
206
- | Có dọn tham số inert trong migration không? | Khuyến nghị **không** — tách commit riêng sau khi verify xong |
207
- | Config: một file JSON asset, hay thêm nhánh Remote Config ngay? | Khuyến nghị asset trước, RC sau khi verify |
208
-
209
- ### Nhóm D — hạ tầng (hỏi ngay khi phát hiện)
210
-
211
- | Tình huống | Câu hỏi |
212
- |---|---|
213
- | Config có network nhưng thiếu module | Config có `"network": "CloudX"` nhưng chưa có `SuperAdsCloudX`. Thêm module hay bỏ network khỏi config? |
214
- | Thiếu `io.cloudx.APP_KEY` / `applovin.sdk.key` | Cung cấp key, hay bỏ network đó? |
215
- | App chưa có `screenName()` / registry tracking | Không có gì dựng chuỗi event preload. Wire tracking screen trước, hay preload theo cách khác? |
216
- | App có RC key timing ads (tên tuỳ app, vd `keyTimeBetweenAdInter`) | JSON chỉ có `show.timeBetweenMs` cố định. Ops còn chỉnh nóng qua RC không? |
217
- | Có native-sau-inter | Production: mấy native/lần inter? RC/AB nào (nếu có) tắt native phụ? Counter `% N` ra pos nào? Điền JSON theo đo, không theo template |
218
- | Click reload cùng space, khác layout | Tách twin SuperAds theo Bảng D, chung unit nếu AdsPro chung `ids[]` |
219
- | `minimumFetchIntervalInSeconds` | SlideShow đặt `0` (fetch mọi lần). Giữ `0` hay đặt giá trị an toàn hơn cho production? |
220
- | App đã có AppsFlyer / Adjust / Meta | Giữ nguyên kênh nào? `SuperAdsTracking` là **mặc định nên có** (Phase 1) — thiếu nó là mất `ad_impression` / `ad_click` Firebase (`MIGRATION.md:452`). Chỉ bỏ khi app tự bắn doanh thu Firebase ở chỗ khác và user xác nhận |
221
-
222
- ## Checkpoint git
223
-
224
- Trước Phase 1, tạo checkpoint và **luôn biết mình đang ở checkpoint nào**. Không bắt buộc commit
225
- tự động — nhưng rollback phải biết điểm trở về.
226
-
227
- | Checkpoint | Khi nào |
228
- |---|---|
229
- | `migration/00-inventory` | Sau khi `migration-inventory.md` đầy đủ |
230
- | `migration/01-gradle` | Sau Phase 1 |
231
- | `migration/02-manifest` | Sau Phase 2 |
232
- | `migration/03-config` | Sau Phase 3 |
233
- | `migration/04-wrapper` | Sau Phase 4 |
234
- | `migration/05-call-sites` | Sau Phase 5 |
235
- | `migration/06-layout` | Sau Phase 6 |
236
- | `migration/07-cleanup` | Sau Phase 7, trước Global Invariant Check |
237
-
238
- ---
239
-
240
- ## Phase 0 — Inventory matrix (artifact bắt buộc)
241
-
242
- Tạo file `migration-inventory.md` trong repo **app** trước khi sửa dòng code nào. Không có file
243
- này thì không được sang Phase 1.
244
-
245
- Nguồn: `rg -l "com.android.fullhd.adssdk"`, `rg -n "\.load\("`, mọi `spaceName` trong
246
- `assets/*.json`, layout native XML + view id, RC key ads.
247
-
248
- ### Bảng A — placement identity + behavior
249
-
250
- | spaceName | adsType | ids[] | layout collapsed | layout expanded | show API cũ | nav từ callback nào | reload trigger cũ | space reload cũ | RC key | placement | format | mode.show | mode.navigate | status |
251
- |---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
252
- | Native_Home | native | 1 | native_collap_small | - | show | - | click | Native_Reload | config_ads_22_4 | Native_Home | NATIVE | LOAD_AND_SHOW | - | TODO |
253
- | Interstitial_Chung | interstitial | 1 | - | - | show + gate LOADED | onAdDismissed | - | - | config_ads_22_4 | Interstitial_Chung | INTER | SHOW_LOADED | NAVIGATE_ON_DISMISS | TODO |
254
-
255
- Cột `placement` = nguyên văn `spaceName`. **MUST NOT CHANGE.**
256
-
257
- ### Bảng B — preload moment (một hàng mỗi call site `load()`)
258
-
259
- | File:line | space | màn đang đứng | screenName() | thời điểm | chuỗi event | status |
260
- |---|---|---|---|---|---|---|
261
- | LanguageFragment.kt:45 | Native_Onboarding | Language | open_screen_1 | onViewReady | create_open_screen_1_fo | TODO |
262
-
263
- Không gom nhiều `load()` của cùng space thành một hàng. Mỗi thời điểm là một hàng.
264
-
265
- ### Bảng C — cặp reload
266
-
267
- | placement nguồn | trigger | placement đích | chu kỳ (ms) | key JSON | status |
268
- |---|---|---|---|---|---|
269
- | Native_Home | click | Native_Reload | - | reload.click.placementID | TODO |
270
- | Native_Record_Video | auto | Native_Record_Video_ReLoad_15s | 15000 | reload.auto.placementID + show.timeAutoReloadMs | TODO |
271
-
272
- ### Bảng D — click reload theo layout (khi app có click reload)
273
-
274
- Bảng C gom trigger. Bảng D vì SDK bind layout trên **adItem đích**. Hàng mẫu chỉ hiểu cột —
275
- điền space/layout/unit **của app này**. Tách twin **chỉ khi** layout hoặc unit lúc click khác nhau.
276
-
277
- | nguồn | layout lúc show | unit AdsPro lúc click | SuperAds reload placement | unit | layout đích | self-click? |
278
- |---|---|---|---|---|---|---|
279
- | *(space)* | *(xml)* | *(ids click)* | *(space reload cũ hoặc twin mới)* | *(khớp AdsPro)* | *(= layout show)* | *(click lại cùng space?)* |
280
-
281
- Space reload **zero call site** → Exclusions, không làm đích vì tên.
282
-
283
- ### Các mục khác trong cùng file
284
-
285
- - **Decisions** — câu trả lời nhóm A/B/C/D.
286
- - **Inert parameters** — tham số giữ chữ ký nhưng không còn tác dụng.
287
- - **Documented exclusions** — space được giữ / bỏ có lý do (vd `Banner_chung` zero call site).
288
-
289
- ### Verify Phase 0
290
-
291
- - [ ] Mọi hàng có `status`
292
- - [ ] Không hàng nào trống `format`
293
- - [ ] Số hàng Bảng B = số call site `load()` đếm được
294
- - [ ] Mọi space reload **có call site** xuất hiện ở Bảng C
295
- - [ ] Nếu có click reload: Bảng D; layout/unit đích khớp AdsPro lúc click
296
- - [ ] Đã đọc [migration-pitfalls.md](references/migration-pitfalls.md) — đo **app này**, không copy số liệu app khác
297
- - [ ] Nhóm câu hỏi B đã hỏi và ghi vào Decisions
298
- - [ ] Checkpoint `migration/00-inventory`
299
-
300
- ---
301
-
302
- ## Phase 1 — Gradle
303
-
304
- Bỏ:
305
-
306
- ```gradle
307
- implementation 'com.android.fullhd.adssdk:AdsPro:...'
308
- ```
309
-
310
- Thêm (phiên bản hiện tại của core và mọi plugin: `4.0.0` — `super-ads/build.gradle:6`,
311
- `MIGRATION.md:7-15`; **đọc lại source / maven hiện tại, không hardcode từ skill nếu source khác**):
312
-
313
- ```gradle
314
- def superAds = '4.0.0' // core + MỌI plugin cùng một version (SPI đổi package ở 4.0.0 — trộn 3.x là NoClassDefFoundError lúc chạy)
315
- implementation "com.android.fullhd.ads:SuperAds:$superAds"
316
- // Bộ nền: LUÔN khai (kể cả config chưa có unit InHouse)
317
- implementation "com.android.fullhd.ads:SuperAdsInHouse:$superAds"
318
- implementation "com.android.fullhd.ads:SuperAdsDebug:$superAds" // có cả ở release
319
- debugImplementation "com.android.fullhd.ads:SuperAdsDebugOverride:$superAds" // chỉ debug, không lọt release
320
- implementation "com.android.fullhd.ads:SuperAdsAdmob:$superAds" // BẮT BUỘC nếu config có network Admob (kéo theo SuperAdsUmp)
321
- implementation "com.android.fullhd.ads:SuperAdsTracking:$superAds" // ad_impression / ad_click Firebase — thiếu là mất doanh thu
322
- implementation 'com.android.fullhd.layout:SuperAdsLayout:1.0.0-alpha27'
323
-
324
- // Chỉ thêm khi config có network tương ứng:
325
- implementation "com.android.fullhd.ads:SuperAdsCloudX:$superAds"
326
- implementation "com.android.fullhd.ads:SuperAdsApplovin:$superAds"
327
- ```
328
-
329
- Wrapper (Phase 4) viết theo API 4.0.0 (vd `com.android.fullhd.super_ads.api.lifecycle.AppLifecycleMonitor`,
330
- `MIGRATION.md:26`). Pin bản 3.x trở xuống thì wrapper không compile.
331
-
332
- `SuperAdsTracking` tự cài qua `TrackingInstaller` (ContentProvider), không gọi code. Thiếu module ⇒
333
- **mất `ad_impression` (doanh thu) và `ad_click`** dù config bật (`MIGRATION.md:452`). Cờ bật/tắt là
334
- key phẳng trong `global`: `"global": { "tracking.revenue": true }` (mặc định `true`,
335
- `SA/domain/config/model/AdsConfig.kt:85`).
336
-
337
- **Thiếu `SuperAdsAdmob` = zero ads im lặng, không crash.** `ConfigRegistryCheck` log
338
- `ERROR_AND_CRASH` lúc init — chỉ thấy được nếu đã nối logger sink (Phase 4).
339
-
340
- Giữ nguyên mọi `com.google.ads.mediation:*` hiện có. CloudX adapter (`io.cloudx:adapter-*`) chỉ
341
- thêm khi user chọn dùng CloudX (nhóm D).
342
-
343
- Repo SuperAds artifacts: `http://repo.volio.vn/repository/maven-s3/` (`allowInsecureProtocol = true`).
344
-
345
- Kiểm version **trước khi khai** (mỗi artifact sẽ dùng, `<A>` = `SuperAds`, `SuperAdsAdmob`, `SuperAdsUmp`, `SuperAdsTracking`…):
346
-
347
- ```bash
348
- curl -sL https://repo.volio.vn/repository/maven-s3/com/android/fullhd/ads/<A>/maven-metadata.xml | grep -o "<version>[^<]*</version>"
349
- ```
350
-
351
- Trạng thái đã kiểm ngày 2026-10-27: repo có `4.0.0` của cả 11 artifact SDK (`SuperAds`, `SuperAdsAdmob`, `SuperAdsAdmobNextGen`, `SuperAdsApplovin`, `SuperAdsArbiter`, `SuperAdsCloudX`, `SuperAdsInHouse`, `SuperAdsTracking`, `SuperAdsUmp`, `SuperAdsDebug`, `SuperAdsDebugOverride`); `SuperAdsContract:1.0.0-alpha` đã có. Trạng thái đổi theo thời gian: luôn chạy lệnh trên, đừng tin con số này. Version cần dùng chưa có trên repo ⇒ Stop, hỏi user: chờ nhóm SDK publish (`scripts/publish-modules.sh --remote`, script đã gồm `ump` và `admob-next-gen`), hay dùng tạm `mavenLocal` / project dependency. Không tự hạ xuống bản cũ: wrapper và config theo API 4.0.0.
352
-
353
- ### Verify Phase 1
354
-
355
- - [ ] `./gradlew :app:dependencies --configuration releaseRuntimeClasspath` thấy `SuperAds`,
356
- `SuperAdsAdmob`, `SuperAdsTracking` cùng `4.0.0`, và **không còn** `AdsPro`
357
- - [ ] Mediation adapter cũ vẫn còn
358
- - [ ] Checkpoint `migration/01-gradle`
359
-
360
- ---
361
-
362
- ## Phase 2 — Manifest
363
-
364
- Giữ:
365
-
366
- ```xml
367
- <meta-data
368
- android:name="com.google.android.gms.ads.APPLICATION_ID"
369
- android:value="@string/admob_app_id" />
370
- ```
371
-
372
- Thêm **chỉ khi** config có network tương ứng:
373
-
374
- ```xml
375
- <meta-data android:name="io.cloudx.APP_KEY" android:value="..." />
376
- <meta-data android:name="applovin.sdk.key" android:value="..." />
377
- ```
378
-
379
- Không khai activity / provider ads thủ công — mỗi module SuperAds tự register bằng
380
- `ContentProvider` (`AdmobInstaller`, `InHouseInstaller`, `CloudXInstaller`, `AppLovinInstaller`).
381
-
382
- ### Verify Phase 2
383
-
384
- - [ ] AdMob APPLICATION_ID còn
385
- - [ ] Key CloudX / AppLovin khớp quyết định nhóm D
386
- - [ ] Checkpoint `migration/02-manifest`
387
-
388
- ---
389
-
390
- ## Phase 3 — Config migration
391
-
392
- Đọc [references/config-template.md](references/config-template.md),
393
- [references/api-mapping.md](references/api-mapping.md) và
394
- [references/migration-pitfalls.md](references/migration-pitfalls.md) **trước** khi viết JSON. Hỏi
395
- nhóm A và D nếu chưa hỏi.
396
-
397
- Bảy bảng con 3a–3g + Bảng D. Cập nhật cột `status` của Bảng A/B/C/D khi xong từng hàng.
398
-
399
- ### 3a — Identity
400
-
401
- - 1 `spaceName` → 1 `placement` **giữ nguyên tên**.
402
- - `ids[]` (waterfall high-floor) → nhiều `units` entry, **giữ đúng thứ tự** trong `adItem.units[]`.
403
- - `adsType` → `format` theo bảng 15 giá trị. Không map được → stop, hỏi nhóm A.
404
-
405
- ### 3b — Preload: từ `load()` sang event
406
-
407
- ```text
408
- BaseFragment.onCreate -> EventScreen.pushEventCreateScreen(screenName())
409
- BaseFragment.onDestroy -> EventScreen.pushEventDestroyScreen(screenName())
410
- -> emit "create_{screenName}_{fo|fn}" / "destroy_{screenName}_{fo|fn}"
411
- -> setUpPreloadTrigger() collect -> Ads.waitAdSDKReady { Ads.dispatchEvent(activity, event) }
412
- -> khớp "preload.eventActive" của adItem -> preload
413
- ```
414
-
415
- SlideShow gọi `pushEventCreateScreen` trong `onCreate`, `pushEventDestroyScreen` trong
416
- `onDestroy` — không phải `onViewCreated` / `onDestroyView`. Khớp source app đang migrate, không
417
- đoán lifecycle.
418
-
419
- - Chuỗi event là **convention của app**, không phải hằng số SDK. JSON khớp **từng ký tự**; lệch
420
- một ký tự là **im lặng không preload**, không log lỗi.
421
- - `_fo` / `_fn` (first-open / quay lại) là **hai chuỗi khác nhau**. Code cũ preload vô điều kiện
422
- thì khai **cả hai**, như SlideShow: `["create_open_screen_5_fo", "create_open_screen_5_fn"]`.
423
- - Chỉ màn **đã hook** `pushEventCreateScreen` mới emit (Activity thường sót). `isFo` nếu
424
- `lazy` thì lock lần đầu. Event = màn **đang đứng** lúc `load()`, không đoán màn kế.
425
- - Key thuộc tầng **`adItems`**; đặt lên `placements` bị bỏ qua.
426
- - `preload.enabled` default **true** (`AdItemConfig.isPreloadEnabled()`). Lật false trên adItem
427
- `SHOW_LOADED` giết âm thầm placement vì cache luôn rỗng.
428
- - Key phụ: `preload.max`, `preload.delayLoadFistMs` (đúng chính tả source — `Fist` không phải
429
- `First`), `preload.delayLoadAfterShowMs`, `preload.autoPreloadAfterDismiss`.
430
- - Mỗi hàng Bảng B → một chuỗi thêm vào `preload.eventActive` của adItem tương ứng.
431
- - **Không** thay `load()` bằng `Ads.preload.preloadNow` (API thủ công, đã `@Deprecated` —
432
- `SA/api/AdsFeatures.kt:90`).
433
-
434
- Phase 3b và Phase 4 khớp nhau **qua chuỗi event** — coupling duy nhất được phép.
435
-
436
- ### 3c — Layout
437
-
438
- - Native / banner thường: **một** key `ui.layoutAdName.{network}`.
439
- - Collapsible (`NATIVE_COLLAPSIBLE`): **hai** key —
440
- `ui.layoutAdName.{network}` = **collapsed / small**,
441
- `ui.layoutAdNameExpand.{network}` = **expanded / large**.
442
- - Từ AdsPro: `AdmobNativeCollapsible.show(..., layoutRes, collapsibleLayoutRes, ...)` →
443
- `layoutRes` vào key thường, `collapsibleLayoutRes` vào key Expand.
444
- **Đảo hai cái thì ad vẫn fill, vẫn có impression, build vẫn pass, chỉ hiện sai cỡ.**
445
- - Suffix network **chữ thường, phân biệt hoa thường**: `.admob`, `.cloudx`, `.applovin`, `.meta`.
446
- AdItem trộn network phải khai key cho **từng** network, thiếu là `RenderError.LayoutNotFound`.
447
- - Giá trị là **tên file XML không đuôi**, resolve trong res app hoặc `SuperAdsLayout`.
448
- - Loading: `ui.loading.layoutName` + `ui.loading.enable`.
449
- - `NativeConfig(resNativeMeta)` (layout riêng khi mediation AdMob trả về Meta) → **loại B**:
450
- `ui.layoutAdName.meta` (+ `ui.layoutAdNameExpand.meta` cho collapsible). Plugin AdMob chọn layout
451
- meta khi response là Meta mediation và file XML tồn tại, ngược lại dùng layout thường
452
- (`SA/presentation/render/NativeRenderer.kt:97`, `SA/presentation/render/CollapsibleNativeRenderer.kt:80`;
453
- `super-ads-admob/src/main/java/com/android/fullhd/super_ads/plugin/admob/nativead/NativeLayoutResolver.kt:40`). `meta` chỉ là **hậu tố key**, không phải
454
- network — không khai unit `"network": "Meta"` (`SA/domain/config/key/AdConfigKey.kt:42`).
455
- - **Một** `ui.layoutAdName` / adItem / network. AdsPro xoay `index % N` layout → P5, hỏi product.
456
- - Native full chain: XML app **hoặc** catalog SuperAdsLayout — product chọn, ghi Decisions. Không tự coi lệch XML là bug, cũng không mặc định catalog.
457
-
458
- ### 3d — Chuyển màn: theo behavior thật của app
459
-
460
- - `mode.show`: `LOAD_AND_SHOW` (default) ứng `loadAndShow(...)`; `SHOW_LOADED` ứng `show(...)`
461
- **có gate `getStatusBySpace == LOADED`**. Vì vậy `getStatusBySpace` làm gate là loại **B**.
462
- - `mode.navigate`: chỉ `NAVIGATE_ON_SHOW` / `NAVIGATE_ON_DISMISS`. Default
463
- `NAVIGATE_ON_DISMISS` (`AdItemConfig.getModeNavigate()`; giá trị lạ bị ép về default).
464
- Xác định **bắt buộc đọc từ app**: nav trong `onAdShowedFullScreenContent` → `NAVIGATE_ON_SHOW`;
465
- trong `onAdDismissedFullScreenContent` → `NAVIGATE_ON_DISMISS`. Không suy ra được thì hỏi nhóm B.
466
- - Sau migration app **vẫn sở hữu nav đi đâu** qua `NavAdCallback`, chỉ không còn sở hữu *thời điểm*.
467
- - Không áp một `mode.navigate` chung. Splash / leftover / in-app / chain đo riêng (P1).
468
- - Placement chain: một giá trị không cover hai path AdsPro (nav lúc show vs lúc đóng native). Ghi Decisions nếu lệch.
469
-
470
- ### 3e — Reload: đưa hết vào `placements`
471
-
472
- SDK tự chạy — `ReloadCoordinator.Plan.from(placement, surface)` trong core đọc config. App
473
- **không viết một dòng reload nào**. Wrapper **không** có hàm reload. SlideShow từng viết
474
- `showAdViewWithReload` rồi comment bỏ toàn bộ — đó là bằng chứng, không phải code cần copy.
475
-
476
- | Trigger | Key | Timing kèm theo |
477
- |---|---|---|
478
- | click vào ad | `reload.click.placementID` | - |
479
- | đóng ad (collapsible dismiss) | `reload.dismiss.placementID` | `show.timeDelayShowAfterDismissMs` |
480
- | chu kỳ tự động | `reload.auto.placementID` | `show.timeAutoReloadMs` |
481
-
482
- Mẫu chuẩn: cặp placement sinh đôi `X` / `X_Reload`, khớp họ space `*_Reload` của app cũ.
78
+ ## Workflow
79
+
80
+ Mỗi phase kết thúc bằng mục "Verify Phase N" và một checkpoint `migration/NN-*`
81
+ (bảng checkpoint: [phase-0-inventory.md](references/phase-0-inventory.md)).
82
+
83
+ 1. **Chốt contract.** Đọc [migration-contract.md](references/migration-contract.md) (MUST / MUST NOT,
84
+ Stop Conditions). Gặp stop condition thì dừng và hỏi user, không đoán.
85
+ 2. **Hỏi user theo nhóm.** [user-questions.md](references/user-questions.md): nhóm B khi lập inventory
86
+ Phase 0; nhóm A và D trước Phase 3; nhóm C một lần đầu Phase 4. Mọi câu trả lời ghi vào "Decisions".
87
+ 3. **Phase 0 — Inventory.** Tạo `migration-inventory.md` trong repo **app** (Bảng A/B/C/D) từ
88
+ [templates/migration-inventory.md](templates/migration-inventory.md) trước khi sửa dòng code nào.
89
+ Quy trình: [phase-0-inventory.md](references/phase-0-inventory.md).
90
+ 4. **Phase 1 — Gradle.** Bỏ `AdsPro`, thêm core + mọi plugin cùng một version `4.0.0`, giữ mediation
91
+ adapter. Kiểm version trên maven trước khi khai.
92
+ [phase-1-2-gradle-manifest.md](references/phase-1-2-gradle-manifest.md).
93
+ 5. **Phase 2 — Manifest.** Giữ AdMob `APPLICATION_ID`; chỉ thêm key CloudX / AppLovin khi config có
94
+ network đó; không khai activity / provider ads thủ công.
95
+ [phase-1-2-gradle-manifest.md](references/phase-1-2-gradle-manifest.md).
96
+ 6. **Phase 3 — Config JSON** (3a identity, 3b preload, 3c layout, 3d chuyển màn, 3e reload, 3f analytics,
97
+ 3g native sau inter). Đọc [config-template.md](references/config-template.md),
98
+ [api-mapping.md](references/api-mapping.md), [migration-pitfalls.md](references/migration-pitfalls.md)
99
+ trước khi viết JSON. Chi tiết: [phase-3-config-migration.md](references/phase-3-config-migration.md).
100
+ 7. **Phase 4 — Wrapper + EventScreen + observability.** Mặc định moi ruột `AdsUtils.kt` cũ, giữ path và
101
+ chữ ký; nối đủ logger sink, tracker collector, config provider; consent trước preload / app open.
102
+ Mẫu: [wrapper-template.md](references/wrapper-template.md); chi tiết:
103
+ [phase-4-wrapper-observability.md](references/phase-4-wrapper-observability.md).
104
+ 8. **Phase 5 — Call sites.** 5a: không sửa màn hình nếu wrapper giữ được chữ ký. 5b: chỉ sửa màn hình khi
105
+ có lý do trong danh sách đóng. [phase-5-7-callsites-layout-cleanup.md](references/phase-5-7-callsites-layout-cleanup.md).
106
+ 9. **Phase 6 — Layout.** Id badge `ivAdMark` → `tvAdMark`, bỏ `R.layout.*` khỏi thân wrapper, mọi
107
+ `ui.layoutAdName*` có file XML thật. Cùng file ở bước 8.
108
+ 10. **Phase 7 — Cleanup.** Xoá dialog ads tự viết và timer đã bị JSON thay thế; không xoá wrapper, không
109
+ dọn tham số inert (commit riêng). Cùng file ở bước 8.
110
+ 11. **Global Invariant Check** sau Phase 7, **trước** build:
111
+ [global-invariant-check.md](references/global-invariant-check.md).
112
+ 12. **Verify runtime** theo [verification.md](references/verification.md); gặp lỗi xem
113
+ [troubleshooting.md](references/troubleshooting.md).
114
+
115
+ ## 🔧 API Reference
116
+
117
+ Ba ví dụ điển hình "JSON-first" (bảng đầy đủ behavior → key JSON theo tầng `adItems` / `placements` /
118
+ `wrapper`: [config-template.md](references/config-template.md) §"Key theo tầng" và
119
+ [api-mapping.md](references/api-mapping.md)):
483
120
 
484
- Layout + unit thuộc **đích**. Gộp / tách theo Bảng D của **app này** (P2) — không hardcode một twin hay N twin. Click lại cùng space AdsPro → self-click trên twin. Chain: `reload.click` trên placement chain.
485
-
486
- `ConfigRegistryCheck` log issue lúc init (key vẫn bị bỏ qua, không crash):
487
-
488
- - `show.timeAutoReloadMs` mà **thiếu** `reload.auto.placementID` ⇒ auto-reload **không chạy**.
489
- - Placement gốc INTER / REWARD / APP_OPEN mang `reload.*` ⇒ bỏ qua (không có container để thay tại
490
- chỗ, `SA/config/validate/ConfigRegistryCheck.kt:182`).
491
- - Placement đích không tồn tại / tắt / priority rỗng / **khác loại bề mặt** với gốc ⇒ bỏ qua
492
- (`SA/config/validate/ConfigRegistryCheck.kt:202`).
493
- - Placement **chain** không nhận `reload.auto` (tự advance slot, timer tranh render).
494
- - Placement **chain** không nhận `reload.dismiss` (đóng slot = sang bước kế). Chain chỉ reload bằng click.
495
-
496
- Ngoài `ConfigRegistryCheck`:
497
-
498
- - Chu kỳ auto bị `ReloadCoordinator` kẹp sàn `MIN_AUTO_RELOAD_MS = 5_000L`
499
- (`SA/show/ReloadCoordinator.kt:23`, `SA/show/ReloadCoordinator.kt:82`).
500
- - Key `reload.placementID` (thiếu `.click` / `.dismiss` / `.auto`) **không tồn tại** trong
501
- `AdConfigKey.Reload` — có trong config demo nhưng **vô hiệu**, SDK không báo gì. Không được dùng.
502
-
503
- ### 3f — Analytics: tên event vào JSON
504
-
505
- | Key (tầng `adItems`) | Bắn khi | Tương ứng AdsPro |
121
+ | Behavior | Kotlin cũ (AdsPro) | JSON mới |
506
122
  |---|---|---|
507
- | `tracking.eventImpress` | impression | `onAdImpression` + `logPairValue*` |
508
- | `tracking.eventLoaded` | load xong | `onAdLoaded` |
509
- | `tracking.eventPaid` | paid event | `onAdPaidValueListener` |
510
- | `tracking.mode` | `ALWAYS` / `ONCE_PER_PROCESS` / `ONCE_PER_LIFETIME` | `Set` chống trùng tự viết |
511
-
512
- Giá trị là **mảng** tên event. Event bắn ra `TrackerEvent.TrackingEvent` trên `Ads.tracker.events`;
513
- app forward sang Firebase / Meta ở **một** chỗ trong wrapper.
514
-
515
- Doanh thu tự động: artifact `com.android.fullhd.ads:SuperAdsTracking` (Phase 1; tự cài khi có
516
- dependency, không gọi code; bật/tắt bằng key phẳng `tracking.enable` / `tracking.revenue` /
517
- `tracking.perf` / `tracking.arbiter` trong object `global`) và
518
- `Ads.tracking.enableAppsflyer(app, appsflyerId)` — thay `pushAppsflyerRevenue` thủ công.
519
-
520
- Event JSON **không mang** được cặp `previous_screen` / `next_screen`. Hỏi nhóm B.
521
-
522
- `global.version` là nhãn để soi config nào đang chạy (Toast `UpdateJson`). Bump version mỗi lần
523
- sửa JSON để Toast chứng minh file đang chạy (O1).
524
-
525
- ### 3g — Native sau inter (`INTERSTITIAL_NATIVE_CHAIN`)
526
-
527
- Chỉ khi **app này** có dialog / flow native sau inter. Công thức SDK:
528
- `nextPos >= firstShowAt && (nextPos - firstShowAt) % showEvery == 0`.
529
- Điền số từ counter AdsPro của app — template `1`/`2`/`5000` / 1–2 native **không** phải chuẩn sản phẩm.
530
-
531
- - `adItems.native[]` = số native production (kèm RC/AB **tên key của app này**), không = số space JSON.
532
- - Countdown = timer dialog app này.
533
- - `reload.click` trên placement chain. Xoá dialog Kotlin song song (Phase 5b).
534
- - adItem chain `SHOW_LOADED` cần cache: `preload.enabled` (mặc định true) **không** được tắt và phải
535
- có `preload.eventActive`. Tắt preload thì không preload, không vào event map, nhánh `SHOW_LOADED`
536
- luôn `ModeShowedButNotHaveAdCached` ⇒ placement không bao giờ show
537
- (`SA/engine/load/Preloader.kt:225`, `SA/config/repository/AdsConfigRepositoryImpl.kt:180`, `SA/engine/load/PriorityLoader.kt:163`).
538
- Không preload được thì dùng `LOAD_AND_SHOW`.
539
-
540
- `forceLoadNewAdIfShowed` **không** suy ra `mode.show`. Đọc `show` vs `loadAndShow` + gate `LOADED`.
541
-
542
- ### Verify Phase 3
543
-
544
- - [ ] Checklist referential integrity trong `config-template.md` pass
545
- - [ ] Cột `status` Bảng A/B/C cập nhật; nếu có click reload thì Bảng D layout/unit khớp AdsPro
546
- - [ ] `Ads.init` / RC khớp file JSON vừa viết (P0)
547
- - [ ] Nếu có chain: `adItems.native` + gate khớp đo Phase 0 của **app này**
548
- - [ ] Checkpoint `migration/03-config`
549
-
550
- ---
551
-
552
- ## Phase 4 — Wrapper + EventScreen + observability
553
-
554
- Hỏi nhóm C trước. Mặc định khuyến nghị: **moi ruột file `AdsUtils.kt` cũ**, giữ path, giữ chữ ký
555
- mọi hàm public, thay ruột bằng delegate xuống `Ads.show`.
556
-
557
- Đọc [references/wrapper-template.md](references/wrapper-template.md).
558
-
559
- Bề mặt cần có: `init / setPremium / isPremium / showCmp / setUpAppOpen / setUpPreloadTrigger /
560
- dispatcherEvent` + 4 extension `showAdView`, `showAdFull`, `showAdFullInApp`, `showAdRewardFull`
561
- (extension có thể private nội bộ file nếu màn hình vẫn gọi chữ ký cũ).
562
-
563
- `EventScreen`: `MutableSharedFlow` replay 0, extraBufferCapacity 64. Hook base fragment:
564
- `onCreate` → `pushEventCreateScreen(screenName())`, `onDestroy` →
565
- `pushEventDestroyScreen(screenName())`. Collector của `EventScreen` phải subscribe **trước** màn đầu
566
- emit (replay 0 ⇒ emit khi chưa có subscriber là mất): launch bằng `Dispatchers.Main.immediate`.
567
-
568
- **Consent trước preload / app open.** Core không tự chặn request khi chưa có consent
569
- (`MIGRATION.md:126-127`). Wrapper giữ cờ `consentDone`: `showCmp` set cờ trong callback; preload
570
- trigger chỉ `Ads.dispatchEvent` khi cờ bật (event màn đầu nhận trong lúc chờ CMP được giữ lại, phát
571
- sau callback); `setUpAppOpen` bỏ qua khi cờ tắt. Xem wrapper-template.
572
-
573
- Ba mối observability — **bắt buộc trước khi verify**, không phải tuỳ chọn:
574
-
575
- **(a) Logger sink** — `Ads.logger.init(LogConfig(enabled = true, minLevel = LogLevel.FLOW),
576
- debug = isDebug, externalSink)` (`LogLevel` không có `VERBOSE`; `FLOW` là mức thấp nhất,
577
- `SA/domain/model/log/LogLevel.kt:6`). Logcat chỉ ở debug; `ERROR` / `ERROR_AND_CRASH` thì debug Toast (post lên
578
- main — sink chạy đồng bộ trên luồng đang log, thường là IO; Toast ngoài main thread ném exception và
579
- bị SDK nuốt trong `runCatching`, `SA/infra/logger/SdkAdLogger.kt:50`, nên Toast không hiện), release
580
- `Firebase.crashlytics.recordException(Exception(event.toJsonString()))`. Đây là đường duy nhất
581
- thấy issue `ConfigRegistryCheck`.
582
-
583
- **(b) Tracker collector** — collect `Ads.tracker.events` một lần trên `Dispatchers.Main`:
584
-
585
- | Event | Việc làm |
586
- |---|---|
587
- | `TrackingEvent` | forward `event.eventName` sang Firebase; thêm hậu tố `_oke` / `_fail` theo `FirebaseRemoteConfig.getValue(key).source == VALUE_SOURCE_REMOTE` |
588
- | `StartRequest` / `Impression` | event bespoke theo `event.data.adItemId` (`logOnce` cho impression) |
589
- | `UpdateJson` | DEBUG: Toast `"Load version ${event.version}"` |
590
- | `Click` | DEBUG: Toast `unitId.takeLast(4)` + `adItemId` |
591
- | `FailedToLoad` / `FailedToShowFull` | DEBUG: Toast kèm `error.code` |
592
-
593
- `global.tracking.item_events` mặc định bật (không khai = `true`, từ 3.0.6) ⇒ `SuperAdsTracking` tự forward `TrackingEvent` (kèm `ab_variant`): **bỏ** dòng forward `TrackingEvent` ở trên (hoặc khai `false`), nếu không event bị gửi đôi. Dự án chạy Firebase A/B bằng RC: nạp lại config bằng `Ads.loadFromJson(json, firebaseActivated = true)` **chỉ** sau `activate()` (`loadFromJson(json)` và config provider không push activation event) — xem `MIGRATION.md`, mục A/B test bằng remote config.
594
-
595
- **(c) Config provider** — overload `Ads.init(application, configProvider = { ... }, isDebug)`:
596
-
597
- ```text
598
- assets = readAssetString(file, timeout 2000)
599
- remote = getRemoteConfigString(key, fallbackToOldValue = true, timeout 3500)
600
- remote hợp lệ (parse được JSON object có "placements") ? remote : assets
601
- ```
602
-
603
- JSON RC hỏng mà vẫn trả về ⇒ SDK parse ra `null` ⇒ init **FAILED**, mất sạch ad, không có bản dự
604
- phòng (`SA/runtime/SdkRuntime.kt:226`, `SA/runtime/SdkRuntime.kt:275`). Vì vậy provider phải kiểm
605
- JSON trước khi trả. Muốn có bản dự phòng: `Ads.init(application, fileNameAsset)` chạy asset ngay, rồi nạp
606
- giá trị RC sau `fetchAndActivate()` bằng `Ads.loadFromJson(json, firebaseActivated = true)` (`SA/api/Ads.kt:197`);
607
- JSON hỏng bị từ chối, giữ bản đang chạy — hỏi nhóm C. (`initRemote` mới mở lại, chỉ app mẫu dùng; chưa kiểm trên thiết bị.)
608
-
609
- `minimumFetchIntervalInSeconds = 0` như SlideShow là rất hung — hỏi nhóm D. RC key phụ
610
- `keyTimeBetweenAdInter` không có tương đương.
611
-
612
- Cấm thêm vào wrapper: state timing, cache riêng, retry, adapter callback SDK, preload thủ công,
613
- driver reload, tách file helper theo loại ad.
614
-
615
- ### Verify Phase 4
616
-
617
- - [ ] Chữ ký public của wrapper cũ không đổi
618
- - [ ] EventScreen emit chuỗi khớp `preload.eventActive` đã viết ở Phase 3b
619
- - [ ] Ba mối observability đã nối
620
- - [ ] Không event preload / app open nào chạy trước callback `showCmp`
621
- - [ ] Checkpoint `migration/04-wrapper`
622
-
623
- ---
624
-
625
- ## Phase 5 — Call sites, chia hai nhóm
626
-
627
- ### 5a — Không sửa màn hình
628
-
629
- Mọi lời gọi mà wrapper giữ được chữ ký thì **màn hình không đổi một ký tự**:
630
- `showAdsNative(...)`, `showAdsInterGeneral(...)`, `showNativeCollapsible(...)`,
631
- `showInterAdsSplash(...)`, `showInterSplashLate(...)`.
632
-
633
- ### 5b — Danh sách đóng: chỉ những lý do này mới được sửa màn hình
634
-
635
- | Lý do | Hành động |
636
- |---|---|
637
- | `AdmobX.load(space)` gọi trực tiếp trong màn | Xoá call site (đã ở `preload.eventActive`) |
638
- | `getStatusBySpace(...) == LOADED` làm gate trước show | Xoá gate (đã ở `mode.show: SHOW_LOADED`) |
639
- | Gọi lại `showAdsNative("X_Reload", ...)` trong `onAdClicked` | Xoá (đã ở `reload.click.placementID`) |
640
- | Timer reload trong Fragment | Xoá (đã ở `reload.auto.placementID` + `show.timeAutoReloadMs`) |
641
- | `AdmobOpenResume.loadAndAutoShowIfAvailable(...)` rải rác | Xoá, dồn về `setUpAppOpen(...)` một lần trong init |
642
- | Dialog native-sau-inter tự viết | Xoá, dùng `INTERSTITIAL_NATIVE_CHAIN` + `chain.native.showEvery` |
643
- | Pseudo-reward bằng native dialog | Đổi sang placement `REWARD` / `NATIVE_FULL_ACTIVITY` thật |
644
- | Màn gọi trực tiếp `Admob*` không qua wrapper | Đổi sang chữ ký wrapper |
645
- | Base fragment | Thêm hook `EventScreen` |
646
- | Override `onAdPaidValueListener` / `logPairValue*` trong màn hoặc dialog | Xoá, gom về tracker collector (tên event ở `tracking.event*`) |
647
-
648
- Không có lý do trong bảng này thì **không sửa file đó**.
649
-
650
- ### Verify Phase 5
651
-
652
- - [ ] `rg "com.android.fullhd.adssdk"` rỗng
653
- - [ ] Không còn `.load(` trên code ads
654
- - [ ] Không còn lời gọi tay tới placement `*_Reload`
655
- - [ ] Mọi file màn hình bị sửa truy được về đúng một dòng bảng 5b
656
- - [ ] Ba bảng inventory `status = DONE`
657
- - [ ] Checkpoint `migration/05-call-sites`
658
-
659
- ---
660
-
661
- ## Phase 6 — Layout
662
-
663
- Layout AdMob chuẩn giữ nguyên. Sửa tay:
664
-
665
- - Id badge `ivAdMark` (AdsPro) → `tvAdMark` (SuperAds `ProtectedAssets`).
666
- - `iv_blur` chỉ còn ý nghĩa với AdMob.
667
-
668
- Xoá `R.layout.*` khỏi **thân wrapper** (call site vẫn truyền vào — tham số inert). Đối chiếu
669
- ngược: mỗi giá trị `ui.layoutAdName*` có file XML tồn tại thật (app res hoặc SuperAdsLayout).
670
-
671
- View id bắt buộc: `ad_media`, `ad_app_icon`, `ad_headline`, `ad_body`, `ad_call_to_action`,
672
- `ad_choices_container`, `tvAdMark`. Optional: `ad_price`, `ad_store`, `ad_stars`, `ad_advertiser`.
673
-
674
- ### Verify Phase 6
675
-
676
- - [ ] Không layout collapsible nào đảo collapsed / expanded
677
- - [ ] Mọi `ui.layoutAdName*` resolve được
678
- - [ ] Checkpoint `migration/06-layout`
679
-
680
- ---
681
-
682
- ## Phase 7 — Cleanup
683
-
684
- Xoá dialog ads tự viết và timer đã bị JSON thay thế. Xoá import chết.
685
-
686
- **Không** xoá file wrapper. **Không** dọn tham số inert (commit riêng).
687
-
688
- Cách SlideShow comment lại 650 dòng `AdsUtils.kt` là điều **không** nên copy — hoặc moi ruột,
689
- hoặc xoá, không để code chết.
690
-
691
- ### Verify Phase 7
692
-
693
- - [ ] Không còn dialog ads tự viết đã bị `INTERSTITIAL_NATIVE_CHAIN` thay
694
- - [ ] Không còn file utils ads mới ngoài quyết định nhóm C
695
- - [ ] Checkpoint `migration/07-cleanup`
696
-
697
- ---
698
-
699
- ## Global Invariant Check
700
-
701
- Chạy sau Phase 7, **trước** build. Đọc [references/verification.md](references/verification.md).
702
-
703
- ```text
704
- COUNT(unique AdsPro spaceName) <= COUNT(SuperAds placements)
705
- AdsPro spaceName \ SuperAds placements = rỗng
706
- SuperAds placements \ AdsPro spaceName ⊆ Bảng D reload extras ∪ Documented exclusions
707
- COUNT(AdsPro ids toàn bộ) == COUNT(SuperAds units toàn bộ)
708
- (gộp unit trùng network+id chỉ khi product yêu cầu; DUPLICATE_UNIT_ID = warning)
709
-
710
- COUNT(call site load() cũ) == COUNT(hàng Bảng B đã map)
711
- mọi chuỗi preload.eventActive ⊆ tập EventScreen app này thực sự emit
712
-
713
- mọi adItem NATIVE_COLLAPSIBLE có đủ 2 key layout, và collapsed != expanded
714
- mọi ui.layoutAdName* trỏ tới file XML tồn tại
715
- mọi network trong adItem.units có key layout tương ứng
716
-
717
- mọi reload.*.placementID trỏ tới placement tồn tại
718
- mọi placement có show.timeAutoReloadMs đều có reload.auto.placementID
719
- không placement chain nào mang reload.auto hoặc reload.dismiss
720
- mọi space reload AdsPro **có call site** là đích của đúng một reload.*.placementID
721
- zero-call-site reload → Documented exclusions
722
- nếu Bảng D: layout đích == layout show; unit == unit AdsPro lúc click
723
- không gộp ≥2 layout nguồn khác nhau vào một adItem reload
724
-
725
- file Ads.init / configProvider == file JSON đang migrate
726
- nếu có chain: len(native) + firstShowAt/showEvery khớp đo app này
727
- mọi INTER fullscreen: mode.navigate từ callback app, không default SDK
728
-
729
- mọi file màn hình bị sửa có lý do thuộc bảng 5b
730
- mọi tham số inert đã ghi vào bảng Inert parameters
731
- không có file utils ads mới nào được tạo ngoài quyết định nhóm C
732
-
733
- ba mối observability đã nối: logger sink, tracker collector, config provider
734
- không dispatchEvent / app open nào trước callback consent
735
- mọi tên event analytics cũ đều có nhà mới (tracking.event* trong JSON, hoặc collector app-side)
736
- không tên event analytics nào bị mất mà không ghi vào Decisions
737
- ```
738
-
739
- Ngoại lệ chỉ hợp lệ khi ghi vào "Documented exclusions".
740
-
741
- Sau invariant: build → Toast `UpdateJson` version (bước đầu tiên) → `ConfigRegistryCheck` sạch →
742
- fill từng placement → semantic matrix → 13 chuỗi `adType` analytics không đổi.
743
-
744
- 13 chuỗi khoá bởi `AdProfileMappingTest`: `INTER`, `NATIVE`, `NATIVE_COLLAPSIBLE`,
745
- `NATIVE_FULL_ACTIVITY`, `APP_OPEN`, `BANNER`, `BANNER320x250`, `BANNER_COLLAPSIBLE_TOP`,
746
- `BANNER_COLLAPSIBLE_BOTTOM`, `REWARD`, `REWARD_INTER`, `NATIVE_CHAIN`,
747
- `INTERSTITIAL_NATIVE_CHAIN`. Đổi chuỗi ⇒ dashboard doanh thu trắng, không có gì fail.
748
-
749
- ## Quick Reference
750
-
751
- ```kotlin
752
- // Init
753
- Ads.init(application, configProvider = { getConfig(...) }, isDebug = isDebug)
754
- Ads.isPremium = purchased
755
- // Consent TRƯỚC preload/show/app open (core không tự chặn): Activity đầu
756
- Ads.gdpr.requestAndShowIfRequired(activity, isDebug, timeoutMs = /* timeout AdsPro của app */) { onConsentDone(); next() }
123
+ | preload theo thời điểm | `AdmobX.load(space)` rải rác | `preload.eventActive` |
124
+ | gate "chỉ show khi đã load" | `getStatusBySpace == LOADED` | `mode.show: SHOW_LOADED` |
125
+ | auto reload chu kỳ | timer trong Fragment | `reload.auto.placementID` + `show.timeAutoReloadMs` |
757
126
 
758
- // Inline (native / banner)
759
- Ads.show(placementId, activity, AdTarget.AdView(container, lifecycle), callback)
127
+ Gọi `Ads.*` tra nhanh: [snippets/ads-api-quick.md](snippets/ads-api-quick.md).
760
128
 
761
- // Fullscreen / reward
762
- Ads.show(placementId, activity, callback = NavAdCallback { ... })
763
- Ads.show(placementId, activity, callback = RewardAdCallback(true) { ... })
129
+ ## ✅ Best Practices
764
130
 
765
- // Preload — KHÔNG gọi thủ công. EventScreen + preload.eventActive.
766
- Ads.dispatchEvent(activity, "create_open_screen_5_fo")
767
- ```
131
+ - Placement name giữ **nguyên văn** `spaceName` cũ, kể cả sai chính tả.
132
+ - Đo behavior từ **app đang migrate** (`showEvery`, số native, countdown, RC key, `mode.navigate`), không
133
+ copy từ SlideShow / app khác.
134
+ - Mỗi tham số inert ghi vào bảng "Inert parameters" kèm một dòng comment trong thân wrapper.
135
+ - Bump `global.version` mỗi lần sửa JSON để Toast `UpdateJson` chứng minh file đang chạy.
136
+ - Code smell còn thấy sau migration là chưa xong: `Handler` / `delay` quanh ads, `countShowAd`,
137
+ `Ads.preload*` thủ công, `if (status == LOADED)` trước show, `R.layout.*` trong thân wrapper, timer reload
138
+ trong Fragment.
768
139
 
769
- ## Anti-patterns
140
+ ## ❌ Anti-patterns
770
141
 
771
142
  - Mang nguyên `AdsUtils.kt` 650 dòng sang SuperAds.
772
143
  - Tạo `LegacyAdCallbackAdapter`.
@@ -782,6 +153,38 @@ Ads.dispatchEvent(activity, "create_open_screen_5_fo")
782
153
  - Comment 650 dòng rồi để chết trong repo.
783
154
  - Sửa 40 màn hình trong khi chữ ký wrapper giữ được.
784
155
 
156
+ ## ⚠️ Common Issues
157
+
158
+ Lỗi runtime (không preload, layout sai cỡ, zero ads, init FAILED…): xem
159
+ [troubleshooting.md](references/troubleshooting.md) và bẫy SDK trong
160
+ [migration-pitfalls.md](references/migration-pitfalls.md).
161
+
162
+ ## Files created
163
+
164
+ Trong repo **app** được migrate:
165
+
166
+ - `migration-inventory.md` (Bảng A/B/C/D + Decisions + Inert parameters + Documented exclusions).
167
+ - File JSON config SuperAds (assets).
168
+ - `EventScreen` — ưu tiên thêm vào file base sẵn có; chỉ tạo file mới nếu package base không có nhà hợp lý
169
+ (quyết định nhóm C).
170
+
171
+ Skill này không tạo file helper ads mới ngoài quyết định nhóm C.
172
+
173
+ ## Files modified
174
+
175
+ - Gradle (Phase 1) và `AndroidManifest.xml` (Phase 2).
176
+ - File wrapper cũ (`AdsUtils.kt` hoặc tương đương) — moi ruột, giữ path và chữ ký.
177
+ - Base fragment (hook `EventScreen`).
178
+ - Màn hình / dialog **chỉ** khi có lý do thuộc bảng 5b.
179
+ - Layout native (id badge `tvAdMark`) theo Phase 6.
180
+
181
+ ## Verification
182
+
183
+ Chạy Global Invariant Check ([global-invariant-check.md](references/global-invariant-check.md)) trước
184
+ build. Sau đó: build → Toast `UpdateJson` version (bước đầu tiên) → `ConfigRegistryCheck` sạch → fill từng
185
+ placement → semantic matrix → 13 chuỗi `adType` analytics không đổi. Chi tiết:
186
+ [verification.md](references/verification.md). Báo output thật; bước bỏ qua thì ghi là bỏ qua.
187
+
785
188
  ## Checklist
786
189
 
787
190
  - [ ] `migration-inventory.md` tồn tại với Bảng A/B/C/D + Decisions + Inert + Exclusions
@@ -792,13 +195,35 @@ Ads.dispatchEvent(activity, "create_open_screen_5_fo")
792
195
  - [ ] Diff budget: số màn hình bị sửa giải thích được bằng bảng 5b
793
196
  - [ ] Báo cáo file đã sửa, tóm tắt, và lỗi / vấn đề còn lại
794
197
 
198
+ ## 📈 Performance Notes
199
+
200
+ > **Not applicable for this skill.**
201
+
202
+ ## 🔒 Security Notes
203
+
204
+ > **Not applicable for this skill.**
205
+
206
+ ## 🔄 Migration Guide
207
+
208
+ Skill này chính là quy trình migration AdsPro → SuperAds (các phase ở mục Workflow). Nâng cấp SuperAds
209
+ phiên bản thấp lên `>=4.0.0` không thuộc skill này: dùng [super-ads-upgrade](../super-ads-upgrade/SKILL.md).
210
+
795
211
  ## Output
796
212
 
797
213
  Báo cáo: file đã sửa, tóm tắt từng phase, Decisions đã hỏi, diff budget (config / wrapper / base /
798
214
  màn 5b), kết quả invariant + runtime, và lỗi còn lại.
799
215
 
800
- ## References
216
+ ## Examples
217
+
218
+ - Hàng mẫu Bảng A/B/C/D: [templates/migration-inventory.md](templates/migration-inventory.md).
219
+ - Gọi `Ads.*` mẫu: [snippets/ads-api-quick.md](snippets/ads-api-quick.md).
220
+ - Wrapper và config đầy đủ: [wrapper-template.md](references/wrapper-template.md),
221
+ [config-template.md](references/config-template.md).
222
+
223
+ ## 📚 References
801
224
 
225
+ - [super-ads-guide](../super-ads-guide/SKILL.md), [super-ads-upgrade](../super-ads-upgrade/SKILL.md)
226
+ - [super-ads-add-placement](../super-ads-add-placement/SKILL.md)
802
227
  - [references/migration-pitfalls.md](references/migration-pitfalls.md)
803
228
  - [references/api-mapping.md](references/api-mapping.md)
804
229
  - [references/config-template.md](references/config-template.md)
@@ -806,7 +231,7 @@ màn 5b), kết quả invariant + runtime, và lỗi còn lại.
806
231
  - [references/troubleshooting.md](references/troubleshooting.md)
807
232
  - [references/verification.md](references/verification.md)
808
233
 
809
- ## Tags
234
+ ## 🏷️ Tags
810
235
 
811
236
  - android
812
237
  - kotlin