AGENTS.md · git:20261015.3d89302 · 2026-10-15 · sha256 e5031e8b697795c6

AGENTS.md git:20261015.3d89302B

Immutable. This exact content never changes and is served at /api/v1/blob/e5031e8b697795c6.

# AGENTS.md

Leon (eatmoreduck) 的个人单仓多 skill 仓库:本地优先的 Agent Skills,把录音变成文字、把 X Space 蒸馏成中文干货笔记。面向用户的文档、commit message、对话一律用中文。

## 结构与依赖(改代码前必看)

```
skills/
├── yaya-xdl/      原子能力:Space 链接 → audio.m4a(xdl.py,下载即归档)
├── yaya-asr/      原子能力:任意音频 → 带说话人转写(scripts/asr.py)
├── yaya-diarize/  原子能力:音频 → 说话人时间线 turns.json(diarize.py)
└── yaya-xspace/   编排层:Space 链接 → 调 xdl 下载 → 调 asr 转写 → LLM 蒸馏(distill.py)
```

- **依赖单向**:`yaya-xspace → yaya-xdl + yaya-asr`、`yaya-asr → yaya-diarize`;兄弟 skill 一律经 `run.sh` 子进程调用、靠相邻目录寻址。xdl/asr/diarize 是原子能力,互不依赖、不认识业务概念。不许反向依赖、不许复制实现。
- **数据位置**(Leon 拍板,无例外):产物/音频归档/缓存一律放 skill 自己的 `out/`(distill.py/xdl.py 各自的 `OUT_ROOT` 常量)——skill 数据跟 skill 走,删 skill 目录=删数据,`git pull` 不动未跟踪的 out/。**没有 YAYA_HOME 之类的搬移开关**——数据不许放 skill 外。`skills/*/out/` 已 .gitignore **整目录忽略**(私人 Space 笔记+音频绝不能进公开仓库);`demo/` 是手工挑选、随仓库公开的样例产出,别和 out/ 混。不用 /tmp 存要留的东西(重启即清,违背下载即归档)。音频默认保留(下载即归档),`--no-keep-audio` 才删。
- **asr 内部流水线并行**:转写(MLX/GPU)开始时以后台进程拉起 yaya-diarize(CPU 分离),`transcribe` 返回后 `wait()`。实测 GPU/CPU 零争抢;**但两个 MLX 进程绝不能并发**(会崩/拖死)——别把并行改成"再起一个转写进程"。
- **新增 skill 必须登记** `.claude-plugin/marketplace.json` 的 `plugins[0].skills[]`——`source: "./"` 且显式列出时只加载列出的目录,不会自动扫描 `skills/`。步骤见根 README「如何新增一个 skill」。

## 验证命令(无测试套件,按此组合验证)

```bash
skills/yaya-asr/.venv/bin/python -m py_compile skills/yaya-asr/scripts/asr.py skills/yaya-xspace/distill.py skills/yaya-xdl/xdl.py skills/yaya-diarize/diarize.py
bash -n skills/yaya-asr/run.sh && bash -n skills/yaya-xspace/run.sh && bash -n skills/yaya-xdl/run.sh && bash -n skills/yaya-diarize/run.sh
# 端到端:跑一场已有数据的 Space(数据在 skills/yaya-xspace/out/ 下,缓存命中时只花一次 LLM 调用)
bash skills/yaya-xspace/run.sh "https://x.com/i/spaces/1oKMvNAwRdNGQ"
```

Python 环境在 `skills/yaya-asr/.venv`(uv 管理,3.13),别用系统 python3(3.9)跑;模型在 `skills/yaya-asr/models/`(已 gitignore)。

## 硬约束(踩过坑的,别回退)

1. **文件操作必须带后缀过滤**。转写产物与下载音频同 stem(`audio.labeled.json` / `audio.m4a` …),裸 `glob("audio.*")` 曾把产物当音频/误删产物,下载侧和清理侧各咬过一次。`_audio_files()` 现居 `yaya-xdl/xdl.py`;`distill.py` 的清理用同款后缀过滤。下载一律走临时名 + 原子 rename(`audio.dl.*` → `audio.m4a`)。
2. **缓存必须带源文件指纹**(`_fp()`:size+mtime_ns)+ 参数绑定;写入走 `_atomic_write_json`(tmp+replace);读取容错自愈。旧格式缓存(无指纹)刻意兼容放行——别"顺手"删掉兼容分支,会让存量缓存全部重算(一场 114 分钟 ≈ 50 分钟)。
3. **`--speakers` 语义 = 强制聚成 N 类(确切人数),不是上限**(sherpa `num_clusters>0` 时 threshold 被忽略)。默认 12 是为 X Space 人多场景刻意保留的,别改成 -1(阈值自动聚类在长音频会过分割出几十个说话人)。文档必须如实写"不是上限"。分离缓存归 `yaya-diarize` 所有(文件名 `asr.diarization.json` 与历史版本一致,asr 通过共享 workdir 复用旧缓存)。
4. **幻觉过滤只删整段**(命中模式后剩余 <6 字才删);`HALLUCINATION_PATTERNS` 里不许出现 `.*` 尾部——真实发言提到"明镜/字幕志愿者"等词时不能连坐删除。
5. **输出路径用字符串拼接**(`_out()`),不用 `with_suffix`(stem 含点时错位)。
6. **蒸馏 LLM 必须显式关思考**(`thinking: {"type":"disabled"}`,glm-5.x 默认开会吃光 token 返回空);必须检查 `finish_reason=="length"` 并在 distilled.md 顶部打截断警告。Key=`ZAI_CODING_CN_API_KEY`。
7. **多段 Space 陷阱**:主持人断线重连会新开 Space(新 ID),只拿到一段会得出"某内容不存在"的错误结论。转写稿出现重连信号(开场静音、"网络问题""你俩继续""刚才讲到哪")要主动问用户是否有另一段。
8. **分享图(card.py)**:蒸馏后自动把 distilled.md 渲染成 distilled.png(**五种风格 `--style` 可选、默认 `swiss`**——swiss 右上角装饰必须保持**红色声波弧**(三道同心弧+源点),白底实心红圆会被联想成"日之丸"(Leon 点名要避的坑),别改回圆点或方块;Chrome headless 两遍截图;`hand` 风格的字体在 `fonts/` 随 skill 走)。五条铁律:①出图失败**只警告**,绝不能当流程失败(Chrome 缺失是常态);②HTML+字体副本只写 `.audio.asr`,输出目录只留 PNG;③解析 distilled.md 的列表必须同时兼容 `- ` 与 `1. `——同一条提示词两次蒸馏的输出格式都会变;④**说话人署名绝不显示 A/B/C/D 字母**——`_header_name()` 兼容 `A / Punk`、`A(Punk,…)`、`Punk(A)` 等多种格式并有全局映射兜底,动它之前先想清楚 LLM 还会换什么格式;⑤**每套风格模板必须自带高度测量 `<script>`**(把 scrollHeight 写进 `<title>`)——漏了会静默回退到固定高度把长图截断(踩过一次,四张全断在 2600px)。
9. **对外文本产物不得出现说话人字母标签**:`audio.dialog.txt` / `audio.dialog.srt` / `distilled.md` 用真名(由 distilled.md 解析出 字母→真名 映射,映射存 `speaker_names.json`);`labeled.json` / `turns.json` 保持原始字母(机器接口,不许改)。改名统一走 `distill.py apply_speaker_names()`(主流程在写完 distilled.md 后、出图之前调用;`--transcript-only` 无蒸馏稿不调用)。**必须有幂等性**:重复运行不破坏(标题改完后映射从 `speaker_names.json` 续用),任何异常只警告不中断主流程;解析不出名字的字母保留原字母。正文替换只认高置信结构(括号组指代、名词表 `—— A/C/D/F ——` 单元格、`A主张` 式句首归属),**金句引文原话绝不许改**。

## 已知但刻意不做的

- **性能优化未应用**(Leon 拍板挂起):分离声纹 `num_threads=4` 可 2× 加速、转写∥分离可并行(GPU/CPU 零争抢,实测 41.5→22 分钟)。别"顺手"加上,等他发话。
- **git 零 commit**:Leon 自己控提交节奏,未经要求不 commit 不 push。

## 文案与措辞

- 用词是**"蒸馏"不是"总结"**:契约是收率——砍寒暄/口头语/重复,但金句原话、关键名词、每人主张、枚举清单一项不许丢。提示词里写"总结"LLM 会往简短优化,这是错的。
- README/文档里的判断句(「下载即归档」「蒸馏不是缩写」)是作者刻意立的,润色可顺字句,不可磨平成营销腔;润色文案用 Leon 本机 `gemini --skip-trust -p "..."`。
- 技术文档里 `--speakers`、产物文件名等事实以 `AGENTS.md` 本节和代码为准,润色不得漂移。

## 敏感区先读

改 `asr.py` 的转写/分离/缓存逻辑前先读 `skills/yaya-asr/SKILL.md`(对外契约:输出文件清单是稳定接口,`<name>.labeled.json` 供下游程序消费,格式不许变)。改 `distill.py` 的提示词前先读 `skills/yaya-xspace/SKILL.md` 与根 README 的设计原则。

**文档分工**(Leon 定的):README(中英双版)只做产品介绍、别塞技术细节;架构概要、缓存/并行设计、性能实测放 `TECHNICAL.md`,README 里只留一行链接。

**README 中英联动(硬规则)**:改 `README.md`(中文)必须同步改 `README.en.md`(英文),反之亦然——新增、修改、删除逐条对应,不允许只落一版。提交前自查:`git diff` 里 README.md 与 README.en.md 的改动点应能一一对应;SKILL.md 的 `description`/frontmatter 同理(它是 Agent 的路由入口)。