ae-migrate-tracking-code
Conversation language: This skill document is in English, but all output to the user MUST be in the user's input language.
English input → English reply; Chinese input → Chinese reply; Japanese input → Japanese reply.
If uncertain, default to English.
This applies to all output: section titles, phase names, prompts, code comments, etc.
⚠️ CRITICAL: Many provider adapter docs contain Chinese glossary notes. When reading them to answer an English/Japanese user, translate headings and comments to the user's language.
Do NOT copy Chinese text verbatim from this document into English/Japanese replies.
Terminology Glossary
| 中文 | English | Notes |
|---|
| 埋点迁移 | Tracking Migration | Convert existing third-party SDK tracking calls to AE SDK |
| 源 SDK | Source SDK / Provider | The third-party SDK already in the project (Firebase, Amplitude, ...) |
| 调用点 | Call Site | A source-SDK invocation ( / / / Identify) |
| 归一化中间表示 | IR (Intermediate Representation) | Provider-neutral dump of extracted call sites |
| 映射 | Mapping | Source event/property → AE event/property translation |
| 切换 | Switch | Replace source calls with AE and remove source SDK |
| 双写 / 新增 | Add / Dual-write | Keep source SDK and add AE alongside |
| 依赖标识 | Dependency Identifier | Package/module name used to remove the source SDK in switch mode |
| 自动采集事件 | Auto-track Event | Events the source SDK collects automatically (e.g. ) |
| 公共属性 | Super Property | Property attached to every event automatically. ⚠️ The correct Chinese AE term is "公共属性" or "公共事件属性". Never translate "Super Property" as "超级属性" — that is NOT a valid AE term. |
| 用户属性 | User Property | Property set on the user profile |
| 用户体系 | User Identity System | distinct_id / account_id strategy in AE |
When to Trigger
Trigger when user says: "migrate Firebase tracking to AE / replace Amplitude with ThinkingAI / my project already has Firebase/Amplitude tracking, switch it to AE SDK / add AE tracking alongside existing analytics" etc. — e.g. 把当前工程里的 xx SDK 切换到数数、把已有埋点用数数实现一遍、用数数替换 xx SDK、集成数数并参考已有的 xx SDK 埋点.
The
target AE SDK may be referred to by any of: 数数 / 数数 SDK / ThinkingAI / ThinkingAI SDK / Agentic Engine / Agentic Engine SDK / AE / AE SDK — they are one SDK. The trigger signal is "an
existing third-party source SDK + 迁移/切换/替换/实现一遍", or "集成/接入数数
并参考已有的 xx SDK 埋点". A bare 接入数数 SDK / 加数数埋点 with no source SDK is greenfield — it belongs to
ae-generate-tracking-code
, not this skill.
Scope: this skill is codebase-only — it scans existing third-party tracking calls (Phase 0/1). If there is no source code with third-party tracking (only product docs / descriptions), do NOT use this skill; route to
ae-generate-tracking-plan
+
ae-generate-tracking-code
instead. Historical data import into AE is offline data-migration work,
out of scope — this skill only switches the live client/server SDK, preserving user IDs and event/property names so new data joins the imported data.
This skill orchestrates the migration end-to-end but does not duplicate the two existing skills:
- Plan generation/upload → hand off to
ae-generate-tracking-plan
(codebase path).
- AE SDK code insertion → hand off to
ae-generate-tracking-code
(insert mode), with insertion sites supplied from the migration mapping instead of grep-discovered business triggers.
Provider Registry (extensible)
The core is provider-agnostic. Each third-party platform is one adapter file.
To support a new provider, add one adapter under and one row below — do not modify this SKILL.md.
| Provider | Adapter | Platforms covered | Detect pattern (summary) |
|---|
| Firebase Analytics | references/providers/firebase.md
| Web v8/v9, Android, iOS, Flutter, React Native, Unity, C++ | , , , , (iOS Swift), FirebaseAnalytics.instance
|
| Amplitude | references/providers/amplitude.md
| Web legacy (), Browser 2.x, Node, Python, Go, Java, Android, iOS, React Native, Flutter, Unity | , @amplitude/analytics-browser
, @amplitude/analytics-node
, , , , , , server imports (, , ) |
| Sensors Data (神策) | references/providers/sensors-data.md
| Web, Android, iOS, Harmony, macOS/tvOS, C++, React Native, Flutter, Unity, Unreal, Cocos2d-x, mini-programs (WeChat/other), QuickApp, APICloud, uni-app, Weex, Egret, LayaAir + server SDKs (Java/Python/Go/Node/PHP/Ruby/C/.NET/Lua) | , , , , , , (legacy), server (Python/Go/Node/PHP/Ruby/C/.NET/Lua) |
| Mixpanel | references/providers/mixpanel.md
| JavaScript, Node, Python, Go, Java, Ruby, PHP, Android, iOS, Swift, React Native, Flutter, Unity | , , , , , , (Go), (Ruby), (PHP), / (Python) |
| Google Analytics 4 | references/providers/ga4.md
| Web (gtag.js / dataLayer), Measurement Protocol (server) | , , , (GA4 only when / also present), , Measurement Protocol |
Adapter contract (required fields per adapter) is defined in
references/providers/README.md
.
Supported scope: only the providers/platforms listed above (and the per-platform tables in each adapter's Recognition section) are supported. Anything not listed is out of scope and not supported by this skill — do not fabricate a mapping for unlisted platforms.
Phase 0 — Detect source SDK and version
- Locate the project and its package/dependency manifest (, , , , , ...) plus source imports.
- Match against the registry above. Read the matching adapter's Recognition section to confirm the provider and pin the version variant:
- Firebase: v8 namespaced vs v9 modular (web), plus Android/iOS/Flutter native forms.
- Amplitude: legacy () vs SDK 2.x ( /
@amplitude/analytics-browser
).
- If multiple providers are present, list them and ask which to migrate first (one provider per run keeps mapping clean). Exception — GA4 and Firebase Analytics are the same data stream: GA4 is Firebase's web layer (one measurement ID is both a GA4 property and a Firebase project). If and coexist in one project, they are not two providers — merge them into a single migration run: one scan, one / , one dedup pass over the shared event / property / user-id names. Never produce two drafts for one Google data stream.
- If no registry match but tracking code exists → show the found calls, ask the user for the provider name, and (per
references/providers/README.md
) collect a minimal adapter from the user-provided docs/snippet before continuing.
Phase 1 — Extract call sites into IR
Scan the project for four call categories, using the adapter's Recognition patterns:
- Event calls: / and their params/properties.
- Identity calls: / / .
- User property calls: / / operations.
- Super property calls (common event properties): /
registerSuperProperties(...)
(Mixpanel, Sensors Data) / setDefaultEventParameters(...)
(Firebase). Record them as with the full map — do not misread them as user properties (see §6). Amplitude and GA4 have no such API.
Record each site as
, call kind, event/param values (literals, or variable names to resolve by reading the surrounding code). Write the normalized dump to
.ae-cli/migration/scan.json
following
.
Wrapper-layer resolution (reverse call-graph tracing) — real projects almost never call the source SDK directly at every business event; they wrap it in an analytics helper (
→
mixpanel.track(name, props)
). A naive grep sees only the wrapper's single SDK call (event name = parameter → would be
) and misses every real business event. So when an SDK call's event name is a
function parameter (a runtime variable flowing from an enclosing function's argument), do
not immediately record
:
- Identify the enclosing function — it is an analytics wrapper only when 's sole role is forwarding that parameter into the SDK call. If the event name comes from local logic/expressions rather than a parameter, it is NOT a wrapper — record the site as as usual.
- Grep for every caller of . Resolve each caller with a static event name into a normal call site: is inferred from the SDK call delegates to (event / identity / user_property), from the caller's literal or constant argument, = the caller's invocation line, = the caller's file, and = 's name.
- Callers with a dynamic event name stay . If no callers are found (an exported wrapper used externally, or dynamic dispatch), fall back to recording the wrapper's own SDK call as a single site.
- Record itself in scan.json's top-level array (see ) — it is the pivot for tracing, not a migration target.
Auto-track note: source SDK auto-collected events (e.g. Firebase
,
,
) are not call sites. Handle them per the adapter's
Auto-track decision table in Phase 2, not here.
Phase 2 — Map to AE (write draft.json + mapping.json)
- Read
references/mapping-framework.md
(generic algorithm) + the provider adapter (differences).
- For each extracted event call: map to an AE event (snake_case name, or verbatim when forward compatibility is required — §1; in the user's language), map each param to an AE property with a type, and apply naming constraints from the adapter (reserved prefixes, special characters, length caps).
- Apply dedup + promotion rules: same property on 3+ events → common event property (super property); preset properties never become super properties. Explicit super-property call sites ( /
setDefaultEventParameters
) also land in 's pool — see §6.
- Map identity → AE (account_id_source ); plus visitor-id continuity ( / , init-time) when forward compatibility with imported historical data is in scope — see §4. User property ops → AE / / / / per the adapter's mapping table. Super property calls → AE
setSuperProperties({...})
(static common event property), per §6.
- Decide each event's (client / server) from where the call site lives (app code → client; backend → server).
- Ask the user to confirm the migration mode (see Modes below) and whether historical source-platform data has been (or will be) imported into AE offline — drives forward-compatible naming per §1 and visitor-id continuity per §4. The import itself is offline data-migration work, out of scope; this skill only switches the live SDK — then:
- Write
.ae-cli/migration/mapping.json
(source site → AE event + mode decision, schema in ).
- Write in the AE plan schema (same schema
ae-generate-tracking-plan
produces) so the plan skill can consume it directly.
Show the mapping summary table and get user
before proceeding.
Phase 3 — Generate and upload the tracking plan
Hand off to
ae-generate-tracking-plan
using the codebase/draft already produced:
ae-cli tracking plan draft --in .ae-cli/draft.json --out .ae-cli/draft.xlsx
ae-cli tracking plan validate --in .ae-cli/draft.json --fix
- Resolve AE host/login/projectId per the plan skill, then
ae-cli tracking plan upload ...
(ask the user before uploading; append vs replace per that skill's conflict detection).
Phase 4 — Insert AE SDK code (switch or add)
Hand off to
ae-generate-tracking-code
(insert mode), but
insertion sites come from .ae-cli/migration/mapping.json
, not from grep business-trigger discovery:
- Client/server platform + language are already in meta; resolve APP_ID / SERVER_URL as that skill does.
- For each mapped AE event, the insertion point is the original call site recorded in mapping.json.
Mode (replace) —
high-risk, confirm before deleting:
- Before any deletion, explicitly list what will be removed (the source call sites and the source SDK dependency from the adapter's Dependency identifiers) and get the user's confirmation to delete.
- Replace the source / call with the AE call at the same site; remove now-unused source imports.
- Inline event names that came from a module constant: emit the literal snake_case name in the AE call (it must match the
// @tracking <event_name>
marker). Before deleting the constant definition, grep the whole project for its name — a module constant is often imported by multiple files, fed to a server-side report, or asserted in tests. Only remove it when the migrated call sites are its sole references (then it is dead code); otherwise keep it. Do not keep a constant that only feeds the migrated call.
- Remove the source SDK dependency using the adapter's Dependency identifiers (e.g. ,
@amplitude/analytics-browser
).
- Remove dead wrappers: when every call site resolved through a wrapper is d, the wrapper function is dead code — delete it (and its now-unused source import). If the wrapper's file holds nothing else, remove the file. ( mode keeps the wrapper — it still feeds the source SDK — and adds the AE call at each business call site.)
- Convert identity/user-property calls to / user-property calls similarly; super-property calls →
setSuperProperties({...})
in place at the original call site (never hoisted — the source's position encodes when the values become available, e.g. after login; see §6).
- Keep the source call untouched; add the AE call immediately adjacent (before or after, matching surrounding style).
- Keep the two SDKs independent — in both directions. Dual-write means two independent writes, for every provider / every SDK (Firebase, Amplitude, Sensors Data, Mixpanel, GA4, …), not a source-guarded AE write:
- The AE / / user-property / call must NOT be nested inside the source SDK's availability guard, early-return, or error path — e.g. after , behind , or inside the source's . If the source call sits behind such a guard, lift the AE call out so it runs unconditionally.
- Give each SDK its own around its write: a source-SDK failure (unavailable, , or a thrown error) must never suppress the AE write, and an AE failure must never suppress (or crash) the source write.
- The guard must cover the source SDK's full failure surface, not a single exception type. A pre-existing narrow catch (e.g. Python for 400 validation, Java
catch (IllegalArgumentException)
) is not the independence guard — other failures (unconfigured API key → , network/timeout) still escape and crash before the AE write. Broaden the catch to all exceptions, run the AE write, then re-raise the source exception so the original status/error semantics (400/503) are unchanged.
- Give each SDK's init its own too, so that one SDK being unloaded or disabled at load time (init throws / module missing) does not take down the other SDK. Both writes must keep working no matter which SDK is unloaded or disabled.
- This applies to every / / entry in mixed runs too.
- Keep the source SDK dependency; only add the AE SDK dependency.
Async call sites — a source call that is
ed (React Native / Flutter
await analytics().logEvent(...)
, some
await mixpanel.track(...)
) returns a Promise, but the AE client
/
/ user-property /
calls are synchronous:
- : drop the on the replaced call — awaiting a synchronous AE call is a lint warning and changes nothing. If the source call sat inside a whose handled the source SDK's Promise rejection (network retry, offline fallback, error reporting), that rejection branch dies with the source SDK: remove the now-dead logic; if the block then holds only the AE call, collapse the into the bare AE call (AE sync failures then follow the code skill's Code Style — loud in ). Never wrap the AE call in a made-up / to keep the shapes matching.
- : keep the source call's and its exactly as-is, and place the AE call outside that block, un-ed, in its own per the independence rule above.
Both modes follow the code skill's insert rules: SDK init from the wiki main doc (never guess imports), git-status pre-check, batched Edit with language-style check,
// @tracking <event_name>
comment prefix.
User-property / (
=
/
): these two are not in the plan's
enum (only
/
/
are), so they do not enter
— in the draft their
is written
(placeholder). At code-insertion time, emit the AE SDK call directly per the mapping's
(
→
,
→
), reading the real method signature from the wiki main doc (never guess).
⚠️ mode overrides the code skill's "Do NOT add try/catch" Code Style. That rule is a single-SDK rule (greenfield insert /
after cut-over): with one SDK a loud failure is the point, so init/track failures must surface rather than be swallowed. Dual-write is the opposite — the "each SDK its own
" rule above applies to the generated AE code too:
- The AE init must be guarded the same way (its own / availability check), so an AE-SDK load failure never throws at load time and never takes down the source SDK.
- Never emit a load-time SDK capture that becomes a permanently- reference and throws on every call (e.g. in a wrapper) — a missing AE SDK must be a per-call no-op, not a crash of business logic or the source write.
- mode does NOT get this override: after cut-over AE is the only SDK, so it follows the code skill's Code Style as-is (failures stay loud).
Phase 5 — Verify
Reuse the code skill's validation flow:
- / confirm active host; debug device add/select.
- Trigger the migrated events and query
ae-cli tracking debug-data list
.
- In mode, verify AE data alongside the still-running source SDK; in mode, confirm the source SDK is fully removed and AE data flows.
Modes
| Mode | Source SDK kept? | AE call placement | Dependency change |
|---|
| removed | replaces source call | remove source dep, add AE dep |
| kept | added alongside | add AE dep only |
Ask the user once in Phase 2 which mode to use.
Default to (dual-write): present as the recommended option in the confirmation prompt. Treat
as the non-default, destructive choice — offer it only when the user has explicitly expressed wanting a full cut-over, and never mark it recommended.
Staged / mixed runs:
is the run-level default, but each mapping entry carries its own
(
vs
/
), and the per-entry
is authoritative. A
run may still keep selected entries as
for a staged roll-out (e.g. cut over identity + sign-up now, keep
dual-writing while its data is validated). In that case: replaced files drop the source SDK, dual-write files keep it, and the source SDK dependency is removed
only on a full cut-over (every entry
). See
§mapping.json and
§8.
⚠️ is destructive (high-risk): it deletes source tracking calls, their imports, and the source SDK dependency. Never delete old code by default. Before deleting anything, explicitly tell the user what will be removed (call sites + dependencies) and get their confirmation; re-confirm again at Phase 4 immediately before deletion, even if
was already chosen in Phase 2.
Prohibitions
- Guessing AE SDK imports/package names — always read the wiki main doc via
ae-generate-tracking-code
().
- Modifying event/property names away from the AE plan (, registered names).
- Migrating source SDK auto-track events as manual calls — map them to AE auto-track switches or drop them per the adapter.
- Nesting the AE / / user-property calls inside the source SDK's availability guard, early-return, or in mode, or leaving either SDK's init unguarded, or guarding the source write with a narrow exception-type catch that still lets a source failure crash before the AE write — for every provider, dual-write must stay two independent writes (each SDK its own , init included, covering the full failure surface), so one SDK failing or being unloaded never takes down the other.
- Skipping the mapping summary confirmation in Phase 2.
- Writing / into 's (the plan enum is / / only) — append/unset stay in and are emitted directly at Phase 4.
- Translating "Super Property" as "超级属性" in any user-facing output.
- Writing source or AE code before the git-status pre-check (insert mode).
- Editing the provider adapters to add provider-specific logic into the core — new providers are new adapter files only.
- Deleting source tracking code or removing the source SDK dependency ( mode) without explicitly warning the user and getting their confirmation immediately before deletion.
Internal Reference
- — normalized IR schema ( / ).
references/mapping-framework.md
— provider-agnostic mapping algorithm.
references/ae-preset-properties.md
— authoritative AE preset-property list + system fields (only these names ingest).
references/providers/README.md
— adapter contract + how to add a provider.
references/providers/firebase.md
— Firebase adapter.
references/providers/amplitude.md
— Amplitude adapter.
references/providers/sensors-data.md
— Sensors Data (神策) adapter.
references/providers/mixpanel.md
— Mixpanel adapter.
references/providers/ga4.md
— Google Analytics 4 (gtag.js / Measurement Protocol) adapter.