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 (logEvent / setUserId / setUserProperty / 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 track() and remove source SDK |
| 双写 / 新增 | Add / Dual-write | Keep source SDK and add AE track() 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. session_start) |
| 公共属性 | 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 references/providers/ 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++ | firebase/analytics, firebase.analytics(), FirebaseAnalytics, FIRAnalytics, Analytics.logEvent (iOS Swift), FirebaseAnalytics.instance |
| Amplitude | references/providers/amplitude.md | Web legacy (amplitude-js), Browser 2.x, Node, Python, Go, Java, Android, iOS, React Native, Flutter, Unity | amplitude-js, @amplitude/analytics-browser, @amplitude/analytics-node, amplitude.init, amplitude.getInstance(), amplitude.logEvent, amplitude.track, Identify, server imports (from amplitude import, analytics-go, com.amplitude:java-sdk) |
| 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) | sensorsdata.min.js, sensors.init, sensors.track, SensorsDataAPI, SensorsAnalyticsSDK, profileSet, setProfile (legacy), server sa-sdk-* (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 | mixpanel-browser, mixpanel.init, mixpanel.track, mixpanel.identify, mixpanel.people, MixpanelAPI, mixpanel.NewApiClient (Go), Mixpanel::Tracker (Ruby), Mixpanel::getInstance (PHP), mp.track / people_set (Python) |
| Google Analytics 4 | references/providers/ga4.md | Web (gtag.js / dataLayer), Measurement Protocol (server) | gtag('event', gtag('config', gtag('set', dataLayer (GA4 only when gtag(/G- also present), G-XXXXXXX, Measurement Protocol mp/collect |
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 (
package.json,build.gradle,Podfile,pubspec.yaml,*.csproj, ...) 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 (
getInstance().logEvent) vs SDK 2.x (amplitude.track/@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
gtag(...)andlogEvent(...)coexist in one project, they are not two providers — merge them into a single migration run: one scan, onedraft.json/mapping.json, 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:
logEvent(...)/track(...)and their params/properties. - Identity calls:
setUserId(...)/identify(...)/login(...). - User property calls:
setUserProperty(...)/setUserProperties(...)/Identifyoperations. - Super property calls (common event properties):
register(...)/registerSuperProperties(...)(Mixpanel, Sensors Data) /setDefaultEventParameters(...)(Firebase). Record them askind: super_propertywith the fullpropsmap — do not misread them as user properties (seemapping-framework.md§6). Amplitude and GA4 have no such API.
Record each site as file:line, 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 references/ir.md.
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 (trackEvent(name, props) → mixpanel.track(name, props)). A naive grep sees only the wrapper's single SDK call (event name = parameter → would be resolved:false) 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 resolved:false:
- Identify the enclosing function
F— it is an analytics wrapper only whenF'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 asresolved:falseas usual. - Grep for every caller of
F. Resolve each caller with a static event name into a normal call site:kindis inferred from the SDK callFdelegates to (event / identity / user_property),event_namefrom the caller's literal or constant argument,source_call= the caller's invocation line,file= the caller's file, andvia_wrapper=F's name. - Callers with a dynamic event name stay
resolved:false. 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 singleresolved:falsesite. - Record
Fitself in scan.json's top-levelwrappersarray (seereferences/ir.md) — it is the pivot for tracing, not a migration target.
Auto-track note: source SDK auto-collected events (e.g. Firebase session_start, first_open, screen_view) 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 —
mapping-framework.md§1;display_namein 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 (register/setDefaultEventParameters) also land indraft.json'scommon_event_propertiespool — seemapping-framework.md§6. - Map identity → AE
login(accountId)(account_id_sourceuser_account); plus visitor-id continuity (setDistinctId/identify, init-time) when forward compatibility with imported historical data is in scope — seemapping-framework.md§4. User property ops → AEuser_set/user_setOnce/user_add/user_append/user_unsetper the adapter's mapping table. Super property calls → AEsetSuperProperties({...})(static common event property), permapping-framework.md§6. - Decide each event's
platform(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
mapping-framework.md§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 inreferences/ir.md). - Write
.ae-cli/draft.jsonin the AE plan schema (same schemaae-generate-tracking-planproduces) so the plan skill can consume it directly.
- Write
Show the mapping summary table and get user ok 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.xlsxae-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
draft.jsonmeta; 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 switch (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
logEvent/trackcall with the AEtrack(...)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
track(...)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.
firebase/analytics,@amplitude/analytics-browser). - Remove dead wrappers: when every call site resolved through a wrapper is
replaced, 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. (addmode 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
login(...)/ user-property calls similarly; super-property calls →setSuperProperties({...})in place at the original call site (never hoisted — the source'sregisterposition encodes when the values become available, e.g. after login; seemapping-framework.md§6).
Mode add (dual-write):
- Keep the source call untouched; add the AE
track(...)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
track()/login()/ user-property /setSuperProperties()call must NOT be nested inside the source SDK's availability guard, early-return, or error path — e.g. afterif (!analytics) return, behindisSupported(), or inside the source'stry/catch. If the source call sits behind such a guard, lift the AE call out so it runs unconditionally. - Give each SDK its own
try/catcharound its write: a source-SDK failure (unavailable,isSupported() === false, 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
except ValueErrorfor 400 validation, Javacatch (IllegalArgumentException)) is not the independence guard — other failures (unconfigured API key →RuntimeError, 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
try/catchtoo, 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
add/add_after/add_beforeentry in mixed runs too.
- The AE
- Keep the source SDK dependency; only add the AE SDK dependency.
Async call sites — a source call that is awaited (React Native / Flutter await analytics().logEvent(...), some await mixpanel.track(...)) returns a Promise, but the AE client track() / login() / user-property / setSuperProperties() calls are synchronous:
switch: drop theawaiton the replaced call — awaiting a synchronous AE call is a lint warning and changes nothing. If the source call sat inside atry/catchwhosecatchhandled the source SDK's Promise rejection (network retry, offline fallback, error reporting), that rejection branch dies with the source SDK: remove the now-deadcatchlogic; if thetryblock then holds only the AE call, collapse thetry/catchinto the bare AE call (AE sync failures then follow the code skill's Code Style — loud inswitch). Never wrap the AE call in a made-upasync/awaitto keep the shapes matching.add: keep the source call'sawaitand itstry/catchexactly as-is, and place the AE call outside that block, un-awaited, in its owntry/catchper 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 append / unset (ae_call = user_append / user_unset): these two are not in the plan's update_type enum (only user_set / user_setOnce / user_add are), so they do not enter draft.json — in the draft their update_type is written user_set (placeholder). At code-insertion time, emit the AE SDK call directly per the mapping's ae_call (user_append → userAppend, user_unset → userUnset), reading the real method signature from the wiki main doc (never guess).
⚠️ add mode overrides the code skill's "Do NOT add try/catch" Code Style. That rule is a single-SDK rule (greenfield insert / switch 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 try/catch" rule above applies to the generated AE code too:
- The AE init must be guarded the same way (its own
try/catch/ 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-
undefinedreference and throws on every call (e.g.var ta = window.TA;in a wrapper) — a missing AE SDK must be a per-call no-op, not a crash of business logic or the source write. switchmode 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:
ae-cli auth status/ confirm active host; debug device add/select.- Trigger the migrated events and query
ae-cli tracking debug-data list. - In
addmode, verify AE data alongside the still-running source SDK; inswitchmode, confirm the source SDK is fully removed and AE data flows.
Modes
| Mode | Source SDK kept? | AE call placement | Dependency change |
|---|---|---|---|
switch | removed | replaces source call | remove source dep, add AE dep |
add | kept | added alongside | add AE dep only |
Ask the user once in Phase 2 which mode to use. Default to add (dual-write): present add as the recommended option in the confirmation prompt. Treat switch 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: mode is the run-level default, but each mapping entry carries its own action (replace vs add_after/add_before), and the per-entry action is authoritative. A switch run may still keep selected entries as add_after for a staged roll-out (e.g. cut over identity + sign-up now, keep purchase 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 replace). See references/ir.md §mapping.json and mapping-framework.md §8.
⚠️
switchis 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 ifswitchwas already chosen in Phase 2.
Prohibitions
- Guessing AE SDK imports/package names — always read the wiki main doc via
ae-generate-tracking-code(references/sdk-index.md). - Modifying event/property names away from the AE plan (
snake_case, registered names). - Migrating source SDK auto-track events as manual
track()calls — map them to AE auto-track switches or drop them per the adapter. - Nesting the AE
track()/login()/ user-property calls inside the source SDK's availability guard, early-return, ortry/catchinaddmode, 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 owntry/catch, 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
user_append/user_unsetintodraft.json'supdate_type(the plan enum isuser_set/user_setOnce/user_addonly) — append/unset stay inmapping.jsonae_calland 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 (
switchmode) without explicitly warning the user and getting their confirmation immediately before deletion.
Internal Reference
references/ir.md— normalized IR schema (scan.json/mapping.json).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.