---
name: generate-tracking-plan
description: 交互式生成 AE 埋点方案并上传。触发词：埋点方案、埋点模板、tracking plan、AE 方案生成、create tracking plan、トラッキングプラン、트래킹 플랜。走 anchor → draft → refine → token → upload 五阶段，产物是 AE 后台里真实创建好的埋点方案。
---

# generate-tracking-plan

> **会话语言**：本 skill 文档为中文，但**所有与用户的对话输出必须使用用户输入的语言**。
> 英文用户输入 → 英文回复；日文用户输入 → 日文回复。不确定时默认英文。
> 包括 Section 标题、Phase 名称、模板提示文案、示例文字、选项列表等全部输出，不得照搬文档中的中文原文。

## 何时触发

用户提到"埋点方案 / 埋点模板 / tracking plan / AE 方案 / 帮我建埋点"等意图时触发。
应用类型覆盖 H5 / Web / iOS / Android / 小程序 / Unity。进入本 skill 后严格按
Phase 0 → 1 → 2 → 3 → 4 推进，不得跳步。

> Phase 1 / 3 / 4 通过 `ae-tracking` CLI 执行（`ae-tracking plan draft`
> / `ae-tracking auth` / `ae-tracking plan upload` / `ae-tracking plan delete`）。
> **所有 CLI 命令必须加 `AE_LANG=<用户语言>` 前缀**（如 `AE_LANG=en ae-tracking plan draft ...`），
> 确保 CLI 输出消息和生成的 xlsx 表头与用户语言一致。
> 
> **语言规则**：draft.json 中所有面向用户的字段（`display_name`、`event_desc`、`event_tag`、
> 属性 `display_name`、属性 `desc` 等）必须使用**用户输入的语言**生成。从中文模板/PRD
> 提取的内容在写入 draft.json 时需翻译为用户语言。仅 `event_name`、`prop_name` 等
> 标识符字段保持英文 snake_case（canonical 格式）。
> 本 skill 只关心命令行为，不关心内部实现。

---

## Phase 0 — Anchor（每个问题单独一条消息）

> **第一步：语言初始化**
> 
> 根据用户输入语言确定 `<user_lang>`：中文→`zh`，英文→`en`，日文→`ja`，韩语→`ko`。其他语言默认 `en`。
> 后续所有 CLI 命令必须加 `AE_LANG=<user_lang>` 前缀，确保 CLI 输出消息和生成的 xlsx 表头与用户语言一致。

依次收集以下 **5 项**，**不得一次问完**：

### 第 1 项 — 应用场景

问：**"应用业务场景是什么？一句话概括，比如：电商网站，用户可以浏览商品和下单购买"**

用户回答后，记录到 `meta.scenario`，并生成 `meta.plan_name`（方案名称）。

### 第 2 项 — 素材来源 + 业务维度（合并）

问：**"选择素材来源，最多选 2 项："**

用户可多选（最多 2 项），选项如下：

1. **产品文档**（本地路径、图片文件或文件夹）— 从产品文档抽取事件和属性，支持 md/pdf/docx/URL/图片（png/jpg/jpeg/webp）
2. **详细描述**（对话形式）— 描述应用业务流程、核心功能、用户行为、盈利模式等
3. **工程代码**（工程本地路径）— 分析源码提取事件和属性
4. **预置模板** (内置多个行业和游戏品类模板) — 选择内置模板（运行 `AE_LANG=<user_lang> ae-tracking plan list-templates` 查看可用模板）

根据用户选择判定素材来源并记录到 `meta.source_type`：

| 选择 | source_type | 处理方式 |
|---|---|---|
| 仅产品文档 | `prd` | 读产品文档（文字/图片），抽取事件和属性 |
| 仅详细描述 | `chat` | 根据描述在 Draft 阶段构造事件 |
| 仅工程代码 | `codebase` | 扫描源码，从业务逻辑提取事件和属性 |
| 仅预置模板 | `template` | 提供内置模板选择 |
| 任意两项组合 | 用 `_` 连接两项 type | 第一项为 baseline，第二项补充（优先级：template → codebase → prd → chat） |

**追问**（按选择顺序逐一追问）：
- 产品文档 → 问 **"产品文档路径是什么？可以提供一个或多个路径，用逗号或换行分隔（本地路径、URL、图片文件或文件夹）"**
- 详细描述 → 如果描述过于简单，追问核心功能、用户行为、业务流程、盈利模式
- 工程代码 → 问 **"工程目录路径是什么？"**，然后扫描源码提取业务逻辑
- 预置模板 → 展示匹配的模板内容让用户确认

**工程代码分析流程（source_type 含 `codebase` 时）**：

1. 用户提供工程目录路径
2. 扫描目录结构，识别技术栈（引擎/框架/语言）
3. 读取核心业务模块（游戏逻辑、场景管理、UI 交互、状态/数据模型、网络/支付等）
4. 从代码中提取：
   - **事件**：玩家交互动作（点击/滑动/触发）、场景切换、游戏状态变化（开始/暂停/结束）、业务流程节点（购买/升级/解锁）
   - **事件属性**：动作参数（子弹类型/敌人等级/道具 ID）、状态值（分数/血量/金币）、上下文（关卡 ID/难度/模式）
   - **用户属性**：持久状态（等级/经验/VIP/累计充值）
5. 将提取结果映射为 AE 命名规则（`snake_case` 事件名 + 中文 `display_name`）
6. 与用户确认提取结果，补充遗漏

**业务维度确认**：

在素材来源确认后，**必须**根据 source_type 处理业务维度信息，用户明确确认后才能进入下一步。

| source_type | 处理方式 |
|---|---|
| `template` | 直接展示模板继承的业务维度，跳过详细推断 |
| `prd` / `codebase` / `chat` | 推断业务维度 → 追问缺失 → 事件注入预览 |

**若 source_type = template**：

展示模板预设的业务维度信息：

```
业务维度（继承自模板：<模板名>）

收入模型：<revenue_model>
核心循环：<core_loop>
功能入口：<functional_entries>
货币体系：<currency_system>

确认使用此业务维度？ok / 修改
```

- 用户 `ok` → 进入下一步
- 用户说"修改" → 切换到 prd/chat 流程，由用户补充

**若 source_type 为 prd / codebase / chat**：

1. **推断展示**：将推断结果格式化为摘要，使用 `business-dimension-mapping.md` 作为映射基准
2. **追问缺失**：只追问缺失或推断为"简单"的信息
3. **事件注入预览**：给出建议注入的事件模块，用户 ok 后进入下一步

platform 校验：使用 `business-dimension-mapping.md` 第五章判定规则，检查注入事件的 platform 是否合理。

**模板匹配**（prd / codebase / chat 场景，可选）：

业务维度确认后，根据应用类型自动检测是否有匹配的模板：
```bash
AE_LANG=<user_lang> ae-tracking plan list-templates
```

有匹配模板时展示给用户确认。用户确认后模板作为 baseline 参与 Phase 1 事件合并。

**业务维度信息记录到 `meta.business_dimension`**：
```json
"business_dimension": {
  "revenue_model": "<模型>",
  "core_loop": "<描述>",
  "functional_entries": ["<入口列表>"],
  "currency_system": { ... },
  "ad_scenes": [],
  "iap_items": []
}
```

### 第 3 项 — SDK 集成配置（客户端 + 服务端合并）

问：**"客户端平台是什么？（可多选，如 Android + iOS）是否集成服务端 SDK？"**

**客户端集成**（可多选）：

| 选项 | 客户端 SDK 类型 |
|---|---|
| H5/Web应用 | JavaScript SDK |
| 手机游戏 - Android原生 | Android SDK |
| 手机游戏 - iOS原生 | iOS SDK |
| 手机游戏 - Unity | Unity SDK |
| 手机游戏 - CocosCreator | CocosCreator SDK |
| 手机游戏 - Cocos2d-x | Cocos2d-x SDK |
| 手机游戏 - LayaAir | LayaAir SDK |
| 手机游戏 - Egret | Egret SDK |
| 手机游戏 - Unreal | Unreal SDK |
| 手机APP - Android原生 | Android SDK |
| 手机APP - iOS原生 | iOS SDK |
| 手机APP - React Native | React Native SDK |
| 手机APP - Flutter | Flutter SDK |
| 手机APP - uni-app | uni-app SDK |
| 手机APP - OpenHarmony | OpenHarmony SDK |
| 小游戏 | 小游戏 SDK（统一 SDK，支持微信/QQ/抖音/百度等平台） |
| 小程序 | 小程序 SDK（统一 SDK，支持微信/抖音/支付宝/百度等平台） |
| PC游戏 - Unreal | Unreal SDK |
| PC游戏 - Unity | Unity SDK |
| PC应用 - C++ | C++ SDK |
| PC应用 - C# | C# SDK |
| PC应用 - MacOS原生 | MacOS SDK |
| PC应用 - OpenHarmony | OpenHarmony SDK |
| 不集成客户端 SDK | 无（`sdk_integration_mode: "server_only"`） |

**开发语言**（仅 Android / iOS SDK 需要）：

| SDK 类型 | 支持的开发语言 |
|---|---|
| Android SDK | Java / Kotlin（可多选） |
| iOS SDK | Objective-C / Swift（可多选） |
| 其他 SDK | 固定语言，无需选择 |

追问：选择 Android SDK → 问 **"开发语言是什么？Java / Kotlin / 两者都支持"**
追问：选择 iOS SDK → 问 **"开发语言是什么？Objective-C / Swift / 两者都支持"**

记录到 `client_platform_languages`：
```json
"client_platform_languages": {
  "android": ["java", "kotlin"],
  "openharmony": ["typescript"]
}
```

> ⚠️ **多平台 meta 字段规则**：当选择了 ≥2 个客户端平台时，Draft meta **必须**使用 `client_platforms`（数组）+ `client_platform_languages`（字典），**不得**只用 `client_sdk_type`（单值）+ `client_language`（单值）导致只记录一个平台。单平台场景才用 `client_sdk_type` + `client_language`。

**服务端集成**：

| 选项 | 服务端 SDK 类型 | sdk_integration_mode |
|---|---|---|
| Java | Java SDK | `both` |
| Python | Python SDK | `both` |
| Go | Go SDK | `both` |
| Node.js | Node SDK | `both` |
| PHP | PHP SDK | `both` |
| C# / .NET | C# SDK | `both` |
| C++ | C++ SDK | `both` |
| Erlang | Erlang SDK | `both` |
| Lua | Lua SDK | `both` |
| Ruby | Ruby SDK | `both` |
| 其他 | 追问具体语言，查询 wiki 是否有对应 SDK | `both` |
| 不集成服务端 SDK | 无 | `client_only` 或 `none` |

**SDK 集成模式自动判定**：

| 客户端集成 | 服务端集成 | sdk_integration_mode |
|---|---|---|
| 需要 | 需要 | `both` |
| 需要 | 不需要 | `client_only` |
| 不需要 | 需要 | `server_only` |
| 不需要 | 不需要 | `none`（RESTful / LogBus / DataX 等方式上报） |

`none` 模式说明：适用于历史数据导入、批量数据同步、第三方系统对接等场景，Refine 阶段不注入 SDK 自动采集事件。

### 第 4 项 — 用户体系（访客ID + 账号ID合并）

问：**"访客 ID 生成策略是什么？"**

选项：
- `auto` — SDK 自动生成（默认，适用于大多数场景）
- `device_id` — 使用设备 ID（iOS IDFV / Android AndroidID）
- `custom` — 自定义访客 ID（需在 SDK 初始化后立刻调用 `identify()`）

追问：选 `custom` → 问 **"访客 ID 取什么值？例如：设备 ID / UUID / 游客临时 ID"**

---

问：**"账号 ID 来源是什么？"**

选项：
- `user_account` — 用户账号 ID（登录后的唯一标识）
- `role_id` — 角色 ID（游戏类专用，一个账号可能有多个角色）
- `none` — 无账号体系（纯游客模式）

追问：
- 选 `user_account` → 问 **"账号 ID 具体取什么值？例如：user_id（用户 ID）、phone（手机号）、email（邮箱）"**
- 选 `role_id` → 说明 **"角色 ID 适合游戏类应用，一个账号可创建多个角色，可更细粒度分析每个角色的行为"**

记录到 `meta.user_identity`：

---

## Phase 1 — Draft

### 1.1 构造 canonical draft

Draft 的概念结构（**内部 json，用户不直接看**）：

```
Draft
├── meta:
│   ├── app_type:                   应用类型
│   ├── sdk_integration_mode:       SDK 集成模式：client_only / server_only / both
│   ├── client_platforms?:          客户端 SDK 类型列表（多平台时必填，如 ["android","ios"]）
│   ├── client_sdk_type?:           客户端主 SDK 类型（单平台时填写，向后兼容）
│   ├── client_platform_languages?: 每平台对应语言（多平台时必填，如 {"android":["kotlin"],"ios":["swift"]}）
│   ├── client_language?:           客户端开发语言（单平台时填写，向后兼容）
│   ├── server_language?:           服务端开发语言（仅 server_only 或 both）
│   ├── project_id?:                AE 项目 ID（Phase 3 补充）
│   ├── host?:                      AE web 地址（Phase 3 补充）
│   ├── plan_name:                  方案名称
│   ├── lang:                        xlsx 输出语言（zh/en/ja/ko），根据用户当前语言设置，控制生成 xlsx 的表头/Ssheet 名
│   ├── scenario:                   业务场景描述
│   ├── source_type:                素材来源类型
│   ├── user_identity:              用户体系配置
│   │   ├── account_id_source:      账号 ID 来源：user_account / role_id / none
│   │   ├── account_id_field?:      账号 ID 具体字段名（仅 user_account 时填写，如 user_id / phone / email）
│   │   ├── distinct_id_strategy:   访客 ID 策略：auto / device_id / custom
│   │   └── distinct_id_custom_value?: 自定义访客 ID 取值
│   └── business_dimension:         业务维度配置（Phase 1.3 注入）
│       ├── revenue_model:          收入模型：IAA / IAP / mixed / subscription / commission
│       ├── core_loop:              核心玩法循环描述
│       ├── functional_entries:     功能入口列表
│       ├── currency_system:        货币体系
│       ├── ad_scenes:              广告场景（仅 IAA / 混合）
│       └── iap_items:              内购物品（仅 IAP / 混合）
├── events:                         一组事件，每个事件含 platform 字段
├── event_properties:               全局事件属性池（去重后）
├── common_event_properties:        公共事件属性（每个事件自动带）
└── user_properties:               用户属性
```

**SDK 集成模式字段**：

```json
// 多平台示例（Android + iOS）
{
  "sdk_integration_mode": "both",       // client_only / server_only / both
  "client_platforms": ["android", "ios"],   // 多平台时必填
  "client_platform_languages": {            // 多平台时必填，每平台对应语言
    "android": ["kotlin"],
    "ios": ["swift"]
  },
  "server_language": "java"                // 仅 server_only 或 both 时填写
}

// 单平台示例（向后兼容）
{
  "sdk_integration_mode": "client_only",
  "client_sdk_type": "android",            // 单平台时填写
  "client_language": "kotlin"              // 单平台时填写
}
```

**事件平台标记（`events[].platform`）**：

```json
{
  "event_name": "order_create",
  "display_name": "订单创建",
  "platform": "server",    // client / server / both
  "prop_names": ["order_id", "order_amount", "payment_method"],
  "source": "prd"
}
```

- `platform: "client"` — 客户端上报（用户行为事件）
- `platform: "server"` — 服务端上报（业务数据事件）
- `platform: "both"` — 两端同时上报（需同步时间戳）

**用户体系字段（`meta.user_identity`）**：

```json
{
  "account_id_source": "user_account", // 账号 ID 来源：user_account / role_id / none
  "account_id_field": "user_id",       // 账号 ID 具体字段名（仅 user_account 时填写）
  "distinct_id_strategy": "auto",      // 访客 ID 策略：auto / device_id / custom
  "distinct_id_custom_value": null     // 自定义访客 ID 取值说明（仅 strategy=custom 时填写）
}
```

**属性类型**（枚举）：`string` / `number` / `bool` / `datetime` /
`object`（单对象，含子属性）/ `array_row`（对象数组，支持 `父名.子名` 嵌套）/ `array_string`（字符串数组）

**命名规则**：事件名 / 属性名必须 `snake_case`；中文放 `display_name` 字段。

### 1.2 合并素材

按优先级从低到高：**template → codebase → prd → chat → autotrack**

- **template**：用户选的行业模板（见下方「模板查找约定」）作为 baseline，每项标记 `source: "template"`
  - 模板优先使用 `.md` 蒸馏文件（`tracking-plan-template/*.md`），回退到 `.xlsx`
  - 导入命令：`AE_LANG=<user_lang> ae-tracking code import-template --template <path> --out .ae-tracking/draft.json`
  - ⚠️ **模板导入后必须立即执行校验**（见 Phase 1.6），模板内容不一定完全正确
  - ⚠️ **模板内容为中文，导入后必须翻译**：读取 draft.json，将 `display_name`、`event_desc`、`event_tag`、属性 `display_name`/`desc` 翻译为用户语言后写回。仅 `event_name`、属性 `name`、`type` 等标识符保持原样。
  - ⚠️ **event_tag 也需要翻译**：`业务事件`→用户语言（如 EN: `Business Event`）、`系统事件`→用户语言（如 EN: `System Event`，但无需重复翻译，autotrack 事件由 CLI 自动处理）
- **codebase**：扫描工程目录源码，从业务逻辑提取事件/属性，**同名事件合并 prop_names，不覆盖既有字段**；新项 `source: "codebase"`
- **prd**：读取用户提供的所有产品文档（md / pdf / docx / URL / 图片），每个文件分别抽取事件和属性，**同名事件合并 prop_names，不覆盖既有字段**；图片文件通过多模态解读分析 UI 元素和交互流程；所有新项 `source: "prd"`
  - **prd 路径为文件夹时**：递归扫描目录下所有文件，规则如下：
    - md/pdf/docx → 读取文本内容，抽取事件/属性
    - png/jpg/jpeg/webp → 多模态解读，分析 UI 元素和交互流程
    - 子文件夹 → 递归扫描
    - 其他文件 → 跳过
  - **prd 路径为 URL 时**：直接 fetch URL 内容，按上述同类规则处理
- **chat**：anchor 阶段的业务场景 + refine 阶段的编辑指令，`source: "chat"`
- **autotrack**：根据 `meta.client_sdk_type` 自动注入 SDK 自动采集事件（仅 `client_only` 或 `both` 模式），`source: "autotrack"`

### 1.3 注入业务维度事件

根据 Phase 0 收集的 `meta.business_dimension`，按以下规则注入对应事件。

**收入模型 → 必选事件**：

| 收入模型 | 注入事件 | 说明 |
|---|---|---|
| `IAA` | `ad_show`、`ad_click`、`ad_reward_get` | 广告展示/点击/奖励领取 |
| `IAP` | `payment`、`payment_fail` | 支付成功/失败 |
| `mixed` | IAA 事件 + IAP 事件都包含 | |
| `subscription` | `subscription_start`、`subscription_renew`、`subscription_cancel` | 订阅开始/续费/取消 |
| `commission` | `order_create`、`order_paid`、`commission_settled` | 订单创建/支付/佣金结算 |

**核心玩法循环 → 事件序列**：

根据用户描述的 `core_loop`，解析节点动作，映射为事件：

例："玩家反复刷关卡获得金币，用金币抽卡获取角色"
→ 关卡模块：`stage_start`（关卡开始）、`stage_complete`（关卡完成）、`stage_fail`（关卡失败）
→ 资源获取：`token_get`（金币获取，prop: `token_type=diamond`、`token_amount`）
→ 抽卡模块：`gacha_draw`（抽卡）、`pool_type`（卡池类型）、`draw_count`（抽取次数）
→ 角色获取：`hero_get`（获取角色）、`hero_id`

**功能入口 → 模块事件组**：

| 功能入口 | 事件示例 | 说明 |
|---|---|---|
| 关卡 | `stage_start`、`stage_complete`、`stage_fail`、`stage_id` | 关卡开始/完成/失败 |
| 抽卡 | `gacha_draw`、`pool_type`、`draw_count`、`hero_get` | 抽卡/卡池/次数/获取 |
| 商城 | `shop_open`、`shop_buy`、`token_balance` | 商城打开/购买/余额 |
| 公会 | `guild_join`、`guild_donate`、`guild_boss_start` | 公会加入/捐献/BOSS战 |
| 排行榜 | `rank_view`、`rank_refresh`、`rank_click` | 排行查看/刷新/点击 |
| 任务 | `task_accept`、`task_complete`、`task_reward_claim` | 任务接受/完成/奖励领取 |
| 成就 | `achieve_unlock`、`achieve_reward_claim` | 成就解锁/奖励领取 |
| 签到 | `daily_sign`、`sign_reward_claim` | 每日签到/奖励领取 |

**货币体系 → 属性设计**：

| 货币类型 | 事件 | 属性 |
|---|---|---|
| 硬货币（钻石）获取 | `token_get` | `token_type=diamond`、`token_amount`、`token_balance`、`source` |
| 软货币（金币）获取 | `token_get` | `token_type=gold`、`token_amount`、`token_balance`、`source` |
| 硬货币消耗 | `token_consume` | `token_type`、`token_amount`、`token_balance`、`consume_type` |
| 软货币消耗 | `token_consume` | `token_type`、`token_amount`、`token_balance`、`consume_type` |

**注入规则**：
1. 业务维度事件标记 `source: "business_dimension"`
2. 与素材来源事件合并时，**同名事件保留素材来源版本，不覆盖**
3. 收入模型为 `none`（无收入）时，跳过收入相关事件注入

### 1.4 自动注入 SDK 自动采集事件

根据 Phase 0 收集的 SDK 集成模式，决定是否注入自动采集事件：

**注入条件**：
- `sdk_integration_mode === "client_only"` → 注入自动采集事件
- `sdk_integration_mode === "both"` → 注入自动采集事件（仅客户端）
- `sdk_integration_mode === "server_only"` → **不注入**（服务端 SDK 无自动采集）
- `sdk_integration_mode === "none"` → **不注入**（无 SDK，通过其他方式上报）

**注入规则**：
1. **仅注入 recommended 事件**，optional 事件不自动注入（Refine 阶段提示用户可选开启）
2. 自动采集事件放在 `events` 数组末尾，标记 `source: "autotrack"`
3. 自动采集事件的 `event_tag` 设为 `"系统事件"`
4. 自动采集事件只携带预置属性（`prop_names` 为空或仅含预置属性名）
5. 预置属性以 `#` 开头，不加入 `event_properties` 池（由 SDK 自动采集）
6. **去重**：模板可能已包含同名自动采集事件（如 `ta_app_start` 标记 `source: "template"`），CLI 按事件名全局去重，不会重复注入

**SDK 类型 → recommended 事件**（详见 `references/autotrack-events.md`）：

| SDK 类型 | recommended（自动注入） | optional（Refine 提示） |
|---|---|---|
| Android / iOS | `ta_app_install`, `ta_app_start`, `ta_app_end` | `ta_app_view`, `ta_app_click`, `ta_app_crash` |
| JavaScript | `ta_page_show`, `ta_page_hide` | `ta_pageview` |
| 微信小程序 | `ta_mp_launch`, `ta_mp_show`, `ta_mp_hide`, `ta_mp_view`, `ta_mp_share` | `ta_page_leave`, `ta_add_favorite`, `ta_mp_click` |
| 微信小游戏 | `ta_mp_launch`, `ta_mp_show`, `ta_mp_hide` | — |
| Unity | `ta_app_install`, `ta_app_start`, `ta_app_end` | `ta_scene_loaded`, `ta_scene_unloaded` |
| Unity 微信小游戏 | `ta_mg_launch`, `ta_mg_show`, `ta_mg_hide` | `ta_scene_loaded`, `ta_scene_unloaded` |
| 其他游戏引擎 | `ta_app_install`, `ta_app_start`, `ta_app_end` | — |

**自动采集事件的作用说明**：
- 自动采集事件是 SDK 内置的自动上报机制，**不需要手动定义同名事件**
- 例如：JavaScript SDK 的 `ta_page_show` 自动采集页面浏览，不需要定义 `home_page_show`、`about_page_show` 等手动页面浏览事件
- 如果模板已包含自动采集事件（如行业模板中的 `ta_app_install`），CLI 不会重复注入
- 这些事件在 `generate-tracking-code` 阶段需要在 SDK 初始化时**开启对应开关**才会生效

**Draft JSON 示例**：

```json
{
  "event_name": "ta_app_install",
  "display_name": "APP 安装",
  "event_desc": "APP 首次安装时上报，升级不触发，删除重装会触发",
  "event_tag": "系统事件",
  "platform": "client",
  "prop_names": [],
  "source": "autotrack"
}
```

**注意**：自动采集事件 `platform` 固定为 `"client"`，因为只有客户端 SDK 有自动采集能力。

### 1.5 持久化 + 生成 xlsx

**必须先写入 draft.json，再运行 draft 命令。不得跳过此步骤直接执行 ae-tracking plan draft。**

- 保存 draft 到 `.ae-tracking/draft.json`
- ⚠️ **写入 `meta.lang`**：根据用户当前语言设置 `draft.meta.lang`（`zh`/`en`/`ja`/`ko`），确保生成的 xlsx 表头 / Sheet 名 / 类型值与 AE 后台语言一致
- **写入后必须验证文件已更新**（检查文件修改时间或内容），确认是新内容后再生成 xlsx
- 生成上传用 xlsx：

```bash
AE_LANG=<user_lang> ae-tracking plan draft --in .ae-tracking/draft.json --out .ae-tracking/draft.xlsx
```

### 1.6 规则校验（必须执行）

生成 xlsx 时自动执行校验，确保符合 AE 埋点方案规则：

```bash
AE_LANG=<user_lang> ae-tracking plan draft --in .ae-tracking/draft.json --out .ae-tracking/draft.xlsx --fix
```

**校验规则**：

| 规则 | 说明 | 自动修复 |
|---|---|---|
| 显示名唯一 | 同一属性池内 `display_name` 不能重复 | ✅ 添加区分前缀 |
| 对象组一致性 | 同一个 `array_row` 在不同事件中子属性必须一致 | ✅ 补充缺失子属性 |
| snake_case | 事件名/属性名必须符合 `^[a-z][a-z0-9_]*$` | ❌ 需手动修改 |
| 属性名唯一 | 属性名不能重复 | ❌ 需手动修改 |
| 事件名唯一 | 事件名不能重复 | ✅ 移除后续重复事件 |

**校验流程**：

1. CLI 自动校验 draft.json
2. 发现可修复问题 → 自动修复 → 更新 draft.json → 生成 xlsx
3. 发现不可修复问题 → 报错，需手动修改 draft.json
4. 无问题 → 进入 Phase 1.7

**注意**：不同属性池的显示名可以相同（如 `vip_level` 可以同时作为事件属性和用户属性）。

### 1.7 展示 Summary

向用户 markdown 表格展示：事件数 / 事件属性池大小 / 公共属性数 / 用户属性数；告知产物路径。

展示后告诉用户：

```
Phase 1 完成。
接下来进入 Phase 2 — Refine 确认方案。
```

### 📁 模板查找约定

运行以下命令动态发现可用模板：

```bash
AE_LANG=<user_lang> ae-tracking plan list-templates
```

**语言规则**：模板文件名是中文，向用户展示时**必须翻译为用户当前语言**，**不要**附带中文原名（如只展示 "Card Game v1"，不要 "Card Game (卡牌游戏) v1"）。

按以下顺序搜索目录：

1. `./tracking-plan-template/` —— 本项目根（开发期）
2. `~/.ae-tracking/templates/` —— `ae-tracking` CLI 安装后的用户目录（未来默认）

每个模板优先使用 `.md` 蒸馏文件（若同名 `.md` 存在则返回 md 路径，否则返回 xlsx 路径）。
导入时自动识别格式：

```bash
AE_LANG=<user_lang> ae-tracking code import-template --template <path> --out .ae-tracking/draft.json
```

---

## Phase 2 — Refine（分 5 段循环）

按顺序，每段一轮对话：

1. **sdk_config（SDK配置 + 用户体系，合并）** — 展示 SDK 集成模式、平台/语言、访客ID策略、账号ID来源、对应SDK调用
2. **business（业务维度 + 业务事件，合并）** — 展示收入模型、核心循环、功能入口、注入事件模块（按 platform 分组）
3. **autotrack（自动采集事件）** — 单独表格展示 SDK 自动采集事件，提示开启方式
4. **common_props（公共属性）** — 展示所有公共属性，提示使用场景和注意事项
5. **props（事件属性 + 用户属性）** — 按事件分组展示事件属性池 + 用户属性表

每段流程：

1. 展示当前 draft 对应部分
2. 问 **"这段对吗？`ok` 进入下一段，或描述要改什么"**
3. 用户给自然语言指令 → 更新 `.ae-tracking/draft.json` → 重跑：
   ```bash
   AE_LANG=<user_lang> ae-tracking plan draft --in .ae-tracking/draft.json --out .ae-tracking/draft.xlsx
   ```
4. 用户 `ok` 进下一段

允许用户随时说 **"回到第 N 段"** 跳回任意段（段号为 1-5）。

---

### sdk_config（Phase 2 第 1 段）

展示 SDK 集成配置 + 用户体系：

```
SDK 集成配置：
- 集成模式：<client_only / server_only / both / none>
- 客户端：<平台>（<语言>）
- 服务端：<语言>

用户体系配置：
- 账号 ID 来源：<user_account / role_id / none>
- 账号 ID 字段：<具体字段>
- 访客 ID 策略：<auto / device_id / custom>

```

用户 ok / 修改。

---

### business（Phase 2 第 2 段）

展示业务维度 + 业务事件：

```
业务维度：
- 收入模型：<IAA / IAP / mixed / subscription / commission>
- 核心循环：<描述>
- 功能入口：<列表>
- 货币体系：<硬货币/软货币>

已注入事件模块（按 platform 分组）：

客户端事件（platform=client）：
- <event>：<display_name> — <event_desc>
- ...

服务端事件（platform=server）：
- <event>：<display_name> — <event_desc>
- ...

两端事件（platform=both）：
- <event>：<display_name> — <event_desc>
```

**冲突检测**：若业务维度与素材来源矛盾，醒目提示用户确认。

用户 ok / 修改。

---

### autotrack（Phase 2 第 3 段）

仅 `client_only` 或 `both` 时展示。

展示 SDK 自动采集事件列表，提示：
- 这些事件由 SDK 自动采集，**代码埋点时无需调用**，只需在 SDK 初始化时开启对应开关
- 如需修改 `display_name` 或 `event_desc` 可调整，事件名不可改

用户 ok / 修改。

---

### common_props（Phase 2 第 4 段）

展示所有公共属性（`name` / `display_name` / `type` / `desc`）。

提示：
- 公共属性是每个事件都会携带的属性，适合**业务全局维度**
- 常见公共属性（根据应用类型）：
  - H5/Web：`channel`（来源渠道）、`referrer_domain`（来源域名）
  - 手机 APP：`channel`、`app_version`、`platform`
  - 游戏：`channel`、`vip_level`、`server_id`
  - 小程序：`channel`、`scene`
- **禁止**将 SDK 预置属性（`#` 开头）设为公共属性

用户 ok / 修改。

---

### props（Phase 2 第 5 段）

展示事件属性池（按事件分组）+ 用户属性表：

- 事件属性：`name` / `display_name` / `type` / `desc`，按 `event_tag` 分组
- 用户属性：`name` / `display_name` / `type` / `update_type` / `desc`

**用户属性更新方式**：
| 更新方式 | 说明 | 适用场景 |
|---|---|---|
| `user_setOnce` | 仅首次设置生效，后续更新忽略 | 首次注册时间、首次付费时间、首次渠道 |
| `user_set` | 覆盖更新，每次都取最新值 | 当前等级、当前余额、昵称、VIP 状态 |
| `user_add` | 数值累加，适用于累计值 | 累计充值金额、累计登录天数、累计看广告次数 |

用户 ok → **立即执行归档命令**，然后进入 Phase 3。

---

### Refine 阶段启发式建议

- **公共属性 vs 事件属性**：属性出现在 3+ 事件上 → 升为公共属性；公共属性适合业务全局维度（`channel`、`vip_level`、`ab_test_group`）
- **禁止**将 SDK 预置属性（`#` 开头）设为公共属性
- **事件必须有属性**：每个事件至少带一个有语义的可描述属性
- **嵌套属性**：`array_row` 父属性下挂 `父.子` 子属性，描述一组对象

---

## Phase 2 完成后 — 交付前自检

**⚠️ 上传 AE 前，必须完成以下自检。任何一项有问题都需修复后再上传。**

### 命名规范检查

- [ ] 事件名/属性名是否以字母开头
- [ ] 事件名/属性名是否只有字母、数字、下划线（无中文、无空格）
- [ ] 是否存在重复事件名或同义事件并存（如 `order_create` 和 `create_order`）
- [ ] 是否存在同一属性名对应多个显示名或多个类型

### 收入模型必选事件检查

| 收入模型 | 必须包含的事件 | 检查 |
|---|---|---|
| IAA | `ad_show`、`ad_click`、`ad_reward_get` | [ ] |
| IAP | `payment`、`payment_fail` | [ ] |
| `mixed` | IAA + IAP 事件都包含 | [ ] |
| `subscription` | `subscription_start`、`subscription_renew`、`subscription_cancel` | [ ] |
| commission | `order_create`、`order_paid`、`commission_settled` | [ ] |

### 成对事件一致性检查

**开始/结束成对事件**（如 `battle_start`/`battle_end`）必须携带**同名同结构**的对象组：
- 开始事件上报的对象组子属性，结束事件必须同名、同类型
- 检查：关卡（`stage_start`/`stage_complete`）、战斗（`battle_start`/`battle_end`）、抽卡（`gacha_draw`/`hero_get`）等成对事件

### 单位说明检查

- [ ] 金额字段是否写明单位（分/美分）
- [ ] 时长字段是否写明单位（秒/毫秒）
- [ ] 余额字段是否说明是充值后余额还是操作前余额

### 枚举值检查

- [ ] 枚举值是否在事件说明中写出（少于等于 10 个值）
- [ ] 超过 10 个值的枚举是否提示挂维表
- [ ] 空值兜底策略是否说明（如"未知渠道填 `unknown`"）

### 对象组使用检查

满足以下**三条件**才使用对象组（`array_row`），否则用对象或普通属性：

1. 一个事件属性中有多个同类实体需记录
2. 每个实体需记录 ≥2 个属性
3. 实体数量不固定

### 对象使用检查

满足以下条件使用对象（`object`），与对象组区分：

- **一对一**关系用 `object`：如用户当前的"装备"详情、玩家"当前出战英雄"（只有一个）
- **一对多**关系用 `array_row`：如"背包物品"列表、"阵容英雄"列表（多个）

简单判断：**数据是 `{...}` 用 `object`，是 `[{...}]` 用 `array_row`**。

---

## Phase 2 完成后 — 归档

所有 5 段 Refine 确认完毕后（用户 `ok` 通过第 5 段后），**立即归档，不得跳过或延后**：

```bash
AE_LANG=<user_lang> ae-tracking plan archive --draft .ae-tracking/draft.json --xlsx .ae-tracking/draft.xlsx --name "<plan_name>"
```

| 参数 | 说明 | 示例 |
|---|---|---|
| `--draft` | draft.json 路径（必填） | `.ae-tracking/draft.json` |
| `--xlsx` | draft.xlsx 路径（必填） | `.ae-tracking/draft.xlsx` |
| `--name` | 方案名称，用作产物文件名前缀（必填） | `"AI SaaS 官网埋点方案"` |

产物落盘到 `plans/<日期>-<plan_name>.xlsx`，同时 `draft.json` 写入 `meta.archived_at`。

归档完成后，告诉用户：

```
✅ 归档完成
文件路径：plans/<日期>-<plan_name>.xlsx

接下来进入 Phase 3 — 上传 AE：
1. 登录 AE 获取 token
2. 获取 AE 项目 ID
3. 上传方案到 AE
```

**⚠️ 强制规则**：归档完成后才能进入 Phase 3。在 Phase 3.1 第一句问话之前，必须确认 `meta.archived_at` 已存在。

---

## Phase 3 — Token & projectId

Phase 3 开始时告诉用户：

```
## Phase 3 — 上传准备
接下来需要：
1. AE web 地址
2. 登录 AE 获取 token
3. AE 项目 ID
```

### 3.1 获取 AE web 地址

❌ **禁止在此处提供未经确认的示例 URL**，直接询问用户即可。

问用户：**"AE web 地址是什么？"**

用户回答后，保存到 `.ae-tracking/draft.json` 的 `meta.host` 字段。

根据 AE web 地址引导获取和保存 token：

```bash
ae-tracking auth login --host <host>
ae-tracking auth status
```

常见错误提示与自助恢复：

| 错误 | 原因 | 提示用户做什么 |
|---|---|---|
| `Chrome JS-from-AppleEvents disabled` | Chrome 禁用了 AppleScript 执行 JS | Chrome 菜单 View → Developer → Allow JavaScript from Apple Events，重试 |
| `not_mac` | 非 macOS | 手动设置 `export AE_TOKEN=<uuid>`（从 DevTools → Application → Local Storage → `ACCESS_TOKEN` 取，去掉首尾引号）|
| `NO_TAB_FOUND` | 没在 Chrome 登录 AE | 先在 Chrome 打开并登录 TE，重试 |

Token 缓存 20 小时，同 host 下次免取。

### 3.2 获取 AE 项目ID 并更新 draft

问用户：**"AE 项目ID 是什么？请前往 AE 系统「项目管理」→「接入配置」→ 复制「项目ID」，或者查看 AE 系统 URL 参数 currentProjectId=<id>"**

用户提供 projectId 后，更新到 `.ae-tracking/draft.json` 的 `meta.project_id` 字段。

---

## Phase 4 — Upload

Phase 4 开始时告诉用户：

```
## Phase 4 — 上传到 AE
正在检查项目现有方案...
```

### 4.1 检查项目现有方案

上传前先检查项目是否已有埋点方案：

```bash
AE_LANG=<user_lang> ae-tracking plan fetch --project <projectId> --host <host> > .ae-tracking/existing-plan.json
```

**判断结果**：
- 文件为空或命令报错 `404` → 项目无方案，直接上传
- 文件有内容 → 项目已有方案，展示 summary + **冲突检测**

**已有方案时展示**：
```
项目已有埋点方案：
- 事件数：XX
- 事件属性数：YY
- 公共属性数：ZZ
- 用户属性数：WW

请选择上传方式：
1. 追加（保留现有方案，添加新事件/属性）
2. 替换（删除现有方案，上传新方案）

追加 / 替换？
```

---

### 4.2 追加模式的冲突检测

用户选择"追加"时，**必须检测冲突**（AE merge-by-name 有静默丢弃风险）：

**读取两份文件**：
- 既有方案：`.ae-tracking/existing-plan.json`
- 新方案：`.ae-tracking/draft.json`

**检测两类冲突**：

#### 冲突类型 A：同名属性类型不一致（严重）

AE 规定：同名属性必须类型一致，否则上报数据会被抛弃。

**检测逻辑**：

对比 `event_properties` / `common_event_properties` / `user_properties` 三个属性池：
- 遍历新方案每个属性，在既有方案中查找同名属性
- 若同名但类型不一致 → 记录为 `property_type_conflict`（严重错误）

**冲突展示**：
```
⚠️ 严重冲突：同名属性类型不一致

以下属性在既有方案和新方案中类型不同，
追加上传会导致上报数据被抛弃：

| 属性名 | 既有类型 | 新方案类型 |
|---|---|---|
| order_amount | number | string |
| vip_level | string | number |

建议：
1. 修改新方案的属性类型，与既有方案一致
2. 或选择「替换」模式重新定义类型

继续追加（不修复） / 修改 draft / 改用替换？
```

#### 冲突类型 B：同名事件（提醒）

AE merge-by-name：同名事件不覆盖，新名事件新增。

**检测逻辑**：

遍历新方案每个事件，在既有方案中查找同名事件：
- 若同名事件已存在 → 记录为 `event_exists`（提醒，非错误）

**冲突展示**（仅有提醒时）：
```
⚠️ 提醒：以下事件已存在于项目中（追加模式不会覆盖）

- user_login（既有：2 属性，新方案：3 属性）
- order_create（既有：5 属性，新方案：5 属性）

追加后：
- 既有事件的属性关联不会改变
- 既有事件的新属性不会被添加
- 新事件会正常添加

是否继续追加？yes / no
```

---

### 4.3 上传流程

根据冲突检测结果决定：

| 检测结果 | 处理方式 |
|---|---|
| 无冲突 | 直接上传 |
| 仅有提醒（类型 B） | 用户确认后上传 |
| 有严重冲突（类型 A） | 用户修复或改用替换 |

上传命令：
```bash
AE_LANG=<user_lang> ae-tracking plan upload --project <projectId> --xlsx .ae-tracking/draft.xlsx --draft .ae-tracking/draft.json [--replace]
```

- **加 `--replace`**：先删除项目现有方案再上传（用户选择"替换"时，或严重冲突改用替换）
- **不加 `--replace`**：AE merge-by-name 合并（无冲突或仅有提醒，用户确认追加）

如果 CLI 返回 exit code 13（语言不匹配），代表 AE 项目语言与 xlsx 语言不一致。此时**不得静默重试**，必须提示用户：
> "AE 项目语言为 {aeLang}，但当前 xlsx 为 {xlsxLang}。是否切换 AE 语言？"

用户确认后，加 `--switch-lang` 重新上传：
```bash
AE_LANG=<user_lang> ae-tracking plan upload --project <projectId> --xlsx .ae-tracking/draft.xlsx --draft .ae-tracking/draft.json --switch-lang [--replace]
```

成功上传后，提示用户去 AE 后台确认，**提供完整 URL**（埋点方案页面 URL 格式为 `https://<host>/#/data/plan`）。

---

### 4.4 上传失败处理与自动修复

上传时使用 `--auto-fix` 选项（默认开启），CLI 会自动检测错误并修复：

```bash
AE_LANG=<user_lang> ae-tracking plan upload --project <projectId> --xlsx .ae-tracking/draft.xlsx --draft .ae-tracking/draft.json [--replace]
```

#### AE API 错误类型

| 错误类型 | 错误描述 | 自动修复 |
|---|---|---|
| `event_prop_display_duplicate` | 事件属性显示名重复 | ✅ 添加区分前缀 |
| `complex_event_property_should_has_same_child_property` | 对象组子属性不一致 | ✅ 补充缺失子属性 |
| `event_name_duplicate` | 事件名重复 | ✅ 移除后续重复事件 |
| `property_type_conflict` | 属性类型不一致（追加模式） | ❌ 改用替换模式 |

#### 自动修复流程

CLI 自动执行：
1. 上传 xlsx 到 TE
2. 检查 `eventErrorMap` 是否有错误
3. 有可修复错误 → 自动修复 draft.json → 重新生成 xlsx → 重新上传
4. 循环最多 3 次，超过后提示用户手动介入

**示例输出**：
```
🔧 Auto-fixing upload errors (attempt 1/3)...
Fixed: 修复对象组子属性不一致, 修复显示名重复
[plan-upload] regenerated: .ae-tracking/draft.xlsx
```

---

### 4.5 成功后处理

成功上传后，提示用户去 AE 后台确认，**提供完整 URL**（埋点方案页面 URL 格式为 `https://<host>/#/data/plan`）

**询问是否生成埋点代码**：

埋点方案已上传成功，接下来可以生成埋点代码。

是否需要生成埋点代码？

- **是** → 引导用户通过 `generate-tracking-code` skill 生成代码
- **否** → 告知方案归档位置：`plans/<日期>-<plan_name>.xlsx`，后续随时可继续

## 禁止

- 一次问完所有 anchor 问题
- 跳过 Phase 3 直接 upload
- 手动拼 xlsx 绕过 `.ae-tracking/draft.json`
- 在 skill 里直接 `fetch` AE API —— 所有 AE 通信必须通过项目的脚本 / 未来的 CLI

---

## 内部参考（贡献者用，不是 skill 使用路径）

- `skills/generate-tracking-plan/references/te-api.md` — AE 后端三个端点的抓包文档
- `skills/generate-tracking-plan/references/xlsx-schema.md` — xlsx 格式契约（writer 规则 + reader 兼容）
- `skills/generate-tracking-plan/references/autotrack-events.md` — SDK 自动采集事件定义（各平台事件列表 + SDK 类型映射）
- `skills/generate-tracking-plan/references/business-dimension-mapping.md` — 业务维度 → 事件/属性映射表（收入模型/功能入口/货币体系 → 注入事件）
- `src/plan/types.ts` — Draft 的 TypeScript 类型定义