技能制作避坑 TI
> 版本 1.0.0(2026-10-11) 内容来自三日实战:14 个包、320 次下载、0 安装的全过程。
> 12 类错误全部真实发生过,其中 5 类把错误内容发到了线上。
> 逐条实录见 references/pitfalls.md,机器可读版见 data/pitfalls.json。
一、这个技能解决什么问题
技能包制作有两个隐蔽的失败模式:
| 失败模式 | 特征 | 为什么隐蔽 |
|---|---|---|
| 内容坏了,但没人发现 | 乱码、结构损坏、字段被吞 | 发布流程照常成功,不报错 |
| 判断错了,但数据支持 | 归因错了、口径漏了 | 结论自洽,只有对照实验能推翻 |
普通排障文档只能防第一类。第二类要靠「先确认对象 → 再取证据 → 最后下结论」的顺序约束。
本技能给的是顺序,不是知识点。
二、操作前三问(缺一不动手)
这是最高优先级。任何制作、修改、发布动作之前,先回答这三个问题。
问一:对象对不对?
确认你操作的路径、数量,与对方指的是同一个。
> 实战:审查 P0 时审了自己工作区的旧目录,
> 实际交付物在 D:\workduddy\WB推荐的P0技能包制作\。
> 据此报出「图标不存在」「tests 占 64%」——三个结论全错。
做法:动手前报出绝对路径与文件数,请对方确认。
问二:基线从哪来?
默认以线上拉取版为基线,不是本机副本。
> 实战:同账号两台电脑,本机 leader-speech-clone-ti 与线上 13 个文件全部不同,
> 且本机含 private(私密数据·勿分发)/profiles-real.yaml(真实领导画像 18KB)。
> 拿本机版发布,整个目录会传到平台。
做法:scripts/fetch_from_platform.py 拉线上版;拉不到才用本机,并在报告里说明。
问三:版本号查了吗?
必须接口实测,不凭记忆、不用默认值。
> 实战:凭记忆用 1.0.0,实为 1.0.1。撞号即 409,或多发一版。
注意接口差异:详情接口的 latestVersion 字段返回 None(不可用),
须用 GET /skills/{slug}/files 的 version 或 tags.latest。
三、技术红线(这五条都造成过真实损害)
红线一:拉远端文件禁用 resp.text
# ✗ 错:Windows 下 apparent_encoding 会落到 GBK,中文全成乱码
text = resp.text
# ✓ 对:严格 UTF-8 解码
text = resp.content.decode("utf-8")
> 实战:henan-k12-learning-diagnosis-ti 整包拉下来全是 ä¸å°å¦...,已上线。 <!-- mojibake-sample -->
> 唯一救回来的是:发版前做乱码特征串检测(ä¸ å æŽ çš èƒ)。 <!-- mojibake-sample -->
红线二:改 frontmatter 不假设结构统一
有的包原文没有 --- 闭合 frontmatter。
用 s.split("---") 拆分会把正文首段当成 frontmatter,
产出 tags: [六爻占卜, ...解卦]--- ——平台侧真的生成了错误 tag 解卦]---。
做法:逐行扫描定位结束位置(先找 ---,找不到则找空行或 # 开头行);
写入后立即回读校验:闭合符存在、无 ]---、version/tags/name 与预期一致,任一不符就抛错。
参考实现:references/frontmatter-patch.md
红线三:展示名字段可能根本不存在
liuyao 的 frontmatter 只有 name / description / agent_created,
展示名是发布时单独传的。脚本若只做「替换」而不做「不存在则新增」,会静默失败。
红线四:包体必须纯文本
不得含 .png .jpg .zip .pdf .xlsx。平台打包会跳过二进制——放了也不生效。
图标走独立接口:先 POST /api/v1/community/skill-icons/upload 拿 iconUrl,
发布时带 iconUrl,不放包内。官方实测提示:--icon 自动路径偶发「上传成功但 dashboard 显示旧图标」。
红线五:重发必须带回线上现有 tags
改展示名时若不传 tags,已有的标签可能被清零。
> 实战:两个包共 41 个 tags(10 + 31),重发前先把线上 tags 原样取回并写入,
> 发后核验数量未变——一个没丢。
四、数据纪律
| # | 纪律 | 反例 → 正例 |
|---|---|---|
| 1 | 带数量的断言要能指到来源与时间 | ❌「11 个全没图标」→ ✅「13 个中 11 个有,18:55 逐包扫描」 |
| 2 | 命中即结论是错的 | 扫出「5 处内部文件名」→ 复查是政府公开发文字号(豫教考〔2025〕1号) |
| 3 | 区分平台自动字段与业务字段 | tags.latest 是版本标记,不计入标签统计 |
| 4 | 查字段必须看值 | tags: 后面为空也叫「无 tags」 |
| 5 | 单次性能测量不作数 | 交替顺序重复 3 次取中位数;组间差须小于组内波动即判「不显著」 |
| 6 | 只读扫描遇 HTTP 非 200 必须显式报「跳过」 | 不能让「取不到数据」静默变成「数据为零」 |
第 3 条的代价有多大
扫出「6 个包 tags 为 0」时,一度误判成「已有 1 个 latest,不缺」。
真实情况:6 个包标签数为零,涉及 175 次下载,占全部 320 次的 55%。
这批包标题堆了几十个关键词(六爻那个 59 字),但检索位是空的——
平台检索靠 tags,不靠标题。
五、方法纪律
归因要找对照组
三次诊断被自己的对照实验推翻:
| 我先断言 | 被什么推翻 |
|---|---|
| 包体太重所以没人装 | 下载最高的包体并不大,重包下载反而低 |
displayName 该用下划线 |
平台发布工具源码里驼峰出现 15 次、下划线 6 次 |
| 搜索不可见,没人找得到 | 45 次下载证明搜索有效 |
共同病根:先下结论,再补解释。「补一个解释」比「承认不知道」省事,但贵得多。
做法:定案前主动找一个能推翻自己的对照组。天然对照组最好——同账号、同时间的新旧包。
口径要覆盖完整问题域
口径 A 定义为「只补 tags」,因那 6 个包 tags 真是 0。
但 tags 本来就齐的包(10 个 / 31 个)掉出了批次,「名字长」这一类被整个漏掉。
做法:定口径时按完整问题域分类,每档必须能回答「几包、多少下载量」。
多变量同时改就废掉归因
P0 一次改了四项(名称 + 归类 + 减重 + 图标),一周后说不清哪项起效。
做法:一次只动一个变量。能归因的改动才有价值。
六、SkillHub 平台契约(读源码与实测确认,勿凭猜)
发布 payload 只有 6 个字段
name / slug / displayName / category / tags / version
没有 description。 SKILL.md 里那段长描述平台 API 层不接收。
三个高频失败
| 报错 | 真因 |
|---|---|
| 分类一直是错的 | 平台自带发布工具不传 category,需自写脚本补 |
INVALID_SUBCATEGORY |
子分类无公开枚举接口,猜 key 必错;取不到就不传 |
| 「至少需要上传一个文件」 | multipart 所有文件字段名统一用 files,且须传原始字节(非 base64) |
合法归类只有 13 个
pay-skill / office-efficiency / content-creation / dev-programming
data-analysis / design-media / ai-agent / knowledge-management
business-ops / education / professional / it-ops-security / life-service
> development-tools 不在其中,填了必失败。
check-slug 不能单判
# ✗ 错:只读 available 就下结论
# 实为 available=true 但同时带 reason=own 与「这是您自己的 Skill」
# → 包其实已存在(还是我半小时前刚发的)
# ✓ 对:reason=own + message 提示 → 包已存在
# ✓对:无 reason 无 message → 确未占用
七、发布前自查(跑脚本,别靠眼睛)
python scripts/pre_publish_check.py <技能包目录>
检查项:包体是否纯文本、frontmatter 结构是否完整、是否有 ]--- 类损坏、
是否含 U+FFFD 替换符号、是否含乱码特征串、tags 是否为空值、展示名是否超长、
归类是否在合法枚举内、版本号是否存在。
退出码 0 = 通过,1 = 有问题。发布前必须为 0。
八、做不到的事(承诺边界)
| 不做 | 理由与替代 |
|---|---|
| 不保证 downloads 会涨 | 平台 installs 普遍为 0,下载≠安装,归因需对照实验 |
| 不代替负责人决定归类 | 归类涉及目标用户判断,只提供合法枚举供选择 |
| 不主动发布 | 发布是不可逆的对外动作,须负责人明确下令 |
| 不编造平台接口 | 无公开文档的接口(如子分类枚举)如实说「取不到」,不猜 |
> 越界时的标准回应:「这属于 XX 问题,不在本技能范围内;建议先做 YY。」
九、快速自检清单(动手前逐条打勾)
- [ ] 对象确认:绝对路径与文件数已向对方核实
- [ ] 基线确认:来自线上拉取,不是本机副本
- [ ] 版本号:接口实测,非记忆值
- [ ] 拉取代码用
content.decode("utf-8") - [ ] frontmatter 兼容无闭合符,写后回读校验
- [ ] 展示名字段不存在时会新增,不是只替换
- [ ] 包体无 png/zip/pdf/xlsx
- [ ] 重发已带回线上现有 tags
- [ ] 归类在 13 个合法值内
- [ ] 带数量的断言已标来源与时间
- [ ] 命中隐私词已回原文确认
- [ ] 一次只动一个变量
- [ ]
pre_publish_check.py退出码为 0
十、变更记录
1.0.0(2026-10-11)—— 首版
12 类实战错误(5 类曾造成线上损害),归为对象/数据/技术/方法四类;
含 3 条操作前硬约束、5 条技术红线、6 条数据纪律、3 条方法纪律、
SkillHub 平台契约(13 个合法归类 + 4 个高频失败)、
可运行的发布前自查脚本、快速自检清单 13 项。
内容全部来自真实发生并已修复的错误,没有一条是假设或推测。