--- name: maa-pipeline-graph description: 生成 MaaFramework Pipeline 项目的可交互状态机图谱。当用户想"看 Pipeline 节点关系"、"画状态机图"、"画 Mermaid 图"、"看 node 间关系"、"找孤立节点 / orphan node"、"看 Python 怎么驱动 Pipeline"、"看 interface.json 入口"、"调试跨文件 next 引用"、"重构前理清架构" 时使用。**任何涉及 MaaFramework 节点关系可视化的需求都用这个 skill**,即便用户没明确说"画图"。 --- # Pipeline 状态机图谱生成 ## 项目初始化接力 画图前先查目标根目录的 `basic_info.md`。存在且包含第 0 节时,读取“0. Maa Skills 接力协议”和第 2/3/6 节,把 task entry、Pipeline 文件清单、JSON 边与 Python 外部入口作为图谱种子;随后仍须扫描当前 `interface.json`、全部目标 Pipeline 文件,以及 `agent/**/*.py` 的 `run_task()` / `run_recognition()`,不能把缓存当完整图数据库。文件缺失或没有第 0 节时直接扫描当前项目;不得自动调用 `$maa-project-init`,只有用户明确要求初始化或刷新时才调用。源码比文档新时以源码为准并报告缓存过期,不自动刷新或覆盖已有非空文档。 ## TL;DR 把"声明式 JSON 节点 + 命令式 Python 调度"的项目(Pipeline 节点 + `context.run_task()` / `context.override_pipeline()` 调用 + option/task 入口)整理成状态机图或关系表。 **先发现真实工具,不要假设脚本存在**。历史审查发现 MaaGumballs 计划中提到的 `tools/pipeline_to_mermaid.py` 在目标提交并不存在;因此本 skill 不能默认承诺一键运行该脚本。 ```powershell rg --no-ignore --files -g '!.git/**' -g '!.venv/**' | Where-Object { [IO.Path]::GetFileName($_) -match 'pipeline.*(mermaid|graph)|(mermaid|graph).*pipeline|migrate_pipeline' } ``` - 如果仓库已有图谱脚本,先读脚本参数再运行。 - 如果没有脚本,只做分析、临时 Mermaid 草图或工具设计建议,不要声称生成了持久化图谱工具。 - 任何 HTML/图谱产物都应输出到已忽略目录,或先确认 `.gitignore`。 ## When to use - **理解陌生项目**:新接手一个 MaaFramework 项目,想一眼看清节点、调用、入口 - **重构前盘点**:大改某个 Pipeline 文件前,先看图,免得改完不知道影响了哪些边 - **调试"孤儿"节点**:某节点在 Pipeline 里没 `next` 指向它,想知道是 bug 还是被 Python 调用 - **加新节点后**:新加了一个 Pipeline 节点或 `run_task` 调用,刷新图看新边 - **Code review**:看 PR 时,先看图理解结构再看代码,效率翻倍 - **写文档 / 培训新人**:把图当 on-boarding 资料 ## What it can produce 如果项目已有图谱脚本,通常会产出这些文件;如果项目没有脚本,本 skill 只负责设计这些产物或生成临时草图,不默认创建工具: | 文件 | 用途 | 语法 | |------|------|------| | `index.html` | 主目录(卡片导航 + 全局统计) | HTML 卡片 | | `pipeline_overview.html` | 全局状态机,按实际文件数生成复合状态 | `stateDiagram-v2` | | `pipeline_external_entries.html` | Python → Pipeline 调用图 | `flowchart` | | `pipeline_utility_usage.html` | 工具节点反向引用图 | `flowchart` | | `pipeline_.html` × N | 每个实际 Pipeline 文件的状态机细节 | `stateDiagram-v2` | 多文件图谱应带顶部导航栏和主页;是否支持 `--open` / `--watch` 取决于真实脚本能力。 ## How to use ### 发现并运行现有工具 ```powershell rg --no-ignore --files -g '!.git/**' -g '!.venv/**' | Where-Object { [IO.Path]::GetFileName($_) -match 'pipeline.*(mermaid|graph)|(mermaid|graph).*pipeline|migrate_pipeline' } ``` 若发现真实脚本,例如 `tools/pipeline_to_mermaid.py`,先打开脚本看参数,再运行: ```powershell python tools/pipeline_to_mermaid.py --help python tools/pipeline_to_mermaid.py ``` ### 没有工具时 先不要生成大量 HTML。用临时分析脚本或手写 Mermaid 草图回答当前问题;后续确认要实现工具时再单独新增脚本,并配套 `.gitignore`、导航页和校验。 --- ## Key design decisions 本节是**踩过的坑 + 解法**。下次再写类似工具时照搬。 ### 1️⃣ 状态机 ≠ 子例程调用:用不同语义 **坑**:MaaFramework Pipeline 不是纯 FSM,有 2 种边: - `next: "X"` — 真·状态转移,`A` → `X` 后,`X` 接管 - `[JumpBack]X` — 子例程调用,执行 `X`,**自动返回 `A`**,X 不接管 **为什么重要**:如果用同一种箭头画两种边,看图的人会误以为有环。状态机会被画成"循环依赖"。 **解法**: - `flowchart` 用 `==>`(粗箭头)画 `[JumpBack]`,`-->`(细箭头)画 `next` - `stateDiagram-v2` 用显式标签 `: calls` / `: returns`(画成两条边:调用方 → 被调方 → 调用方) - 永远不要把 `[JumpBack]` 画成"返回当前节点"的回环——它本就是临时离开 ### 2️⃣ 孤儿子图 = Python 调用的入口 **坑**:Pipeline 里很多节点没 `next` 指向它们(无入边),新手以为是 bug。 **真相**:这些节点是**外部入口**,被两个地方调用: - Python 代码里的 `context.run_task("X")` - `assets/interface.json` 的 `task[].entry`(MaaPiCli 暴露给用户的入口) **解法**:扫描这两处,把调用边也画进图。 **怎么扫**: - **Python**:用 `ast` 解析 `agent/**/*.py`,在 `ast.Call` 里找 `func.attr == "run_task"`,记录 `Class.method` 调用方 - **interface.json**:读 `task[]` 数组,每个 `entry` 字段就是用户级入口 ### 3️⃣ 双遍解析:先建索引,再画边 **坑**:Pipeline 节点会跨文件 `next`,按文件名排序处理时,**后面的文件引用前面的节点,边会丢**。 **举例**:`auto_task.json` 按字母序排第一,里面有 `OpenCityTaskPanel.next = ["FindCityTask_OCR"]`,但 `FindCityTask_OCR` 定义在 `city.json`(字母序更后)。处理 `auto_task.json` 时,目标还没解析,直接 `continue` 跳过这条边。 **解法**:**两遍解析**: ``` Pass 1: 扫所有文件,建立 name → file 索引 Pass 2: 扫所有文件的 next,根据完整索引解析目标 ``` ### 4️⃣ Mermaid 语法选型:stateDiagram vs flowchart | 场景 | 选 | 为什么 | |------|-----|--------| | 真·FSM(节点 + 转移 + 初始/终止) | **`stateDiagram-v2`** | 原生支持 `[*]` 初始/终止、复合状态、转移标签 | | 调用图 / 反向引用图 | **`flowchart`** | stateDiagram 不支持"被谁调用"这种语义 | | 节点形状区分(Stadium / Circle) | **`flowchart`** | stateDiagram 形状统一,只能用 classDef 染色 | | 跨文件复杂关系 | **`stateDiagram-v2` + 复合状态** | `state X { ... }` 把每个文件折叠起来 | **口诀**:**状态机用 stateDiagram,其他用 flowchart**。 ### 5️⃣ ID 冲突:中文/emoji 节点必须加 hash 后缀 **坑**:Mermaid 节点 ID 由你写,内部是字符串。但**整个图里不能有两个同 ID 的节点**。 中文/emoji 节点名(如 `📲 启动游戏` 和 `📲 推年计划`)用 `re.sub(r'[^A-Za-z0-9_]', '_', name)` 过滤后,**都会变成 `iface________`**,Mermaid 解析失败,图直接黑屏。 **解法**: ```python def mid(file: str, name: str) -> str: h = hash((file, name)) & 0xFFFF return f"{re.sub(r'[^A-Za-z0-9_]', '_', f'{file}__{name}')[:50]}_{h:04x}" ``` **永远不要**只靠字符串规范化生成 Mermaid ID,必须带 hash 后缀保证唯一性。 ### 6️⃣ 产物必须 `.gitignore` **坑**:自动生成的 HTML 会被 git 当成"新增文件"跟踪,每次重生都会改 commit,PR diff 爆掉。 **解法**: - `docs/zh_cn/graph/` 加入 `.gitignore` - 脚本每次**全量清空再重生**(无缓存、无 diff) - 队友各跑各的,互不冲突 ### 7️⃣ 16+ 张图必须有导航栏 + index 主页 **坑**:第一次版就只输出每张图,**没导航**。用户要从一个文件跳到另一个,得手动记路径或重新打开 IDE。 **解法**(用户视角): - 每张 HTML 顶部固定深色 navbar(4 个常驻链接 + 当前页高亮橙色) - 一个 `index.html` 主目录,卡片网格展示所有图 - 卡片显示节点数、入边/出边数(让人快速判断要不要点开看) **对 skill 的启示**:**任何会生成多文件的工具,都要有导航机制**,否则用户每用一次就骂一次。 --- ## Common pitfalls ### 跨文件 `next` 解析漏边 **症状**:总边数比手动数少,某些 `next` 字段在图里看不到。 **排查**: 1. 跑脚本时打印 `next count = 67, jumpback = 32`,如果比预期少,基本是双遍解析没做 2. 临时加个 `assert all(target in all_names for target in nexts)` 找未解析的悬挂引用 3. 目标节点在另一个文件且字母序靠前 → 字母序靠后的文件 `next` 引用它会丢 ### ID 冲突导致图渲染失败 **症状**:浏览器控制台报 `SyntaxError: Duplicate id` 或图直接空白。 **排查**: 1. 节点名包含中文/emoji 2. 多个节点规范化后 ASCII 形式相同 3. **解决**:用 hash 后缀,见 5️⃣ ### 状态名包含 ASCII 非法字符 **症状**:`stateDiagram-v2` 不允许节点名带 `.` 或 `-`,会解析失败。 **排查**: - 节点名如 `CastleMarry_AgeCheck`(`_` 合法) OK - 节点名如 `BigMap-MarketStart`(`-` 非法) 必须转成 `BigMap_MarketStart` - **解决**:用 `re.sub(r'[^A-Za-z0-9_]', '_', name)` 转换 ### 把生成物 commit 进 git **症状**:`git status` 出现一堆 `pipeline_*.html` untracked。 **排查**: 1. `.gitignore` 加了 `docs/zh_cn/graph/` 吗 2. `git check-ignore -v docs/zh_cn/graph/pipeline_marry.html` 看是否被忽略 3. 如果没忽略,加完 gitignore 后 `git rm --cached docs/zh_cn/graph/*.html` 清理已跟踪文件 --- ## Implementation reference **不要假设参考实现一定在项目里。** 先用上面的忽略规则无关目录的发现命令查找真实脚本;只有脚本存在时才把它当实现参考。若脚本不存在,下列结构是未来实现图谱工具时的建议设计。 **结构速览**: ``` load_pipeline() # Pass 1: 建 name→file 索引;Pass 2: 解析 next scan_python_calls() # ast 扫 agent/**/*.py 的 context.run_task() scan_interface_tasks() # 读 interface.json 的 task[].entry build_state_overview() # stateDiagram-v2 复合状态 build_state_per_file() # 单文件 stateDiagram build_external_entries()# Python 调用 flowchart build_utility_usage() # 反向引用 flowchart build_index_html() # 主目录 wrap_html() # HTML 模板 + 导航栏 main() # --open / --watch 处理 ``` **改本 skill 时**: - **删改**:7️⃣ 经验任意一条 → 改对应章节 - **新增场景**:如果要给别的项目类型(非 MaaFramework)用,把"4️⃣ Mermaid 语法"和"3️⃣ 双遍解析"通用化即可,其他都是 MaaFramework 特化 --- ## 移植到其他项目 如果你的项目是其他"声明式 + 命令式"系统(比如 Airflow DAG + Python、Terraform resources + tfvars、Ansible playbook + roles),**核心思路一致**: 1. 把声明式部分当 FSM(节点 + 边) 2. 把命令式部分当外部触发器(扫 `context.run_task()` 类似物) 3. 用双遍解析处理跨文件引用 4. ID 加 hash 后缀防冲突 5. 产物 gitignore Mermaid 语法选择(stateDiagram vs flowchart)同样适用。