|
|
[TITLE]
taste-skill 两种加载路径对比:npx init 与 skills install 的失效边界
[/TITLE]
[CATEGORY]
tech
[/CATEGORY]
[KEYWORDS]
华强北,MaxClaw,科技数码,AI,热点
[/KEYWORDS]
[FID]
41
[/FID]
[CONTENT]# taste-skill 两种加载路径对比:npx init 与 skills install 的失效边界
taste-skill 装完不生效,多数人第一反应是文件坏了。其实文件好得很。同一份 SKILL.md,走 `npx init` 写进项目,和走 `openclaw skills install` 由 CLI 托管,进的根本是两套可见性规则。最后加载不加载,看的不是文件在不在,是 OpenClaw 的发现和注入这两关放不放行。下面把两条路径摊开说,顺带讲清每种失败卡在哪一层。
## 一、两条路径到底差在哪
包里有 7 个变体:default、soft-calm、editorial、strict-gsap、redesign、complete-output、image-gen。每个变体一份独立 `SKILL.md`,实测体积 5049、5014、5794、6521、5526、6145、6912 字节,也就是 5.0–6.9 KB。frontmatter 里的 `name` 是 `taste-default` 这种扁平写法,不是目录名;`description` 一律是 "Core anti-slop design rules for UI output."。
npm registry 上,这包 1.0.0 版发布于 2026-06-28,也是唯一的 latest 版本。没有运行时依赖,只注册了一个 `bin/cli.js` 入口。NPM 官方下载接口显示 2026-10-23 到 10-29 这一周下了 140 次。这个量级,说白了就是个人作者维护的小工具,不是那种周下载几万、天天有人开 issue 的热门包。真出问题,基本只能自己啃源码。
| 维度 | npx taste-skill init | openclaw skills install |
|---|---|---|
| 写入位置 | 当前项目 `skills/taste/<变体>/`,另附 `templates/taste/*.txt` | 工作区 `skills/<slug>/` |
| 安装粒度 | 一次写入 7 个变体 + 7 个模板 | 一次只安装一个目录 |
| 命名来源 | 目录结构 + frontmatter | frontmatter `name` 优先,`--as` 可覆盖 |
| 来源追踪 | 无,纯文件拷贝 | 写入 `.openclaw/source-origin.json`(origin type / spec / installedAt) |
| 后续更新 | 重跑 init 覆盖 | 仅 ClawHub 来源可 `update`,本地路径装完就断链 |
| 可见性限制 | 与其他本地 skill 相同 | 与其他本地 skill 相同 |
`init` 那段代码短得可怜。`copyDirRecursive` 把 `package/skills/taste/<变体>` 递归拷到 `cwd/skills/taste/`,顺手把 `templates/` 下面 7 个 txt 铺到 `templates/taste/`。输出里有句 "Hermes users: skills are ready",说明作者起初是给 Hermes Agent 写的,OpenClaw 属于顺带兼容。
`skills install` 走的是 `installExtractedSkillRoot`:解析 slug、做目录安全校验、落盘写 origin 文件,还专门调 `removeClawHubInstallMetadata` 把旧的 `.clawhub` 标记清掉。
关键就一句话——两条路径只决定文件怎么写进去,不决定文件怎么被读出来。一旦超过发现上限,你写得再规范也白搭。
## 二、四个真实失败点
**1. 包根目录没有 SKILL.md。**
直接 `openclaw skills install ./package`,返回 `archive is missing SKILL.md`,退出码是 1 不是 0。源码里 `DEFAULT_SKILL_ARCHIVE_ROOT_MARKERS = ["SKILL.md"]`,只认解压根目录。taste-skill 的结构是 `skills/taste/<变体>/SKILL.md`,根目录躺着 README、package.json 和 bin。所以只能一个目录一个目录装,7 个变体就循环 7 次。
这不算 bug。只是"一条命令装完"的期待,碰上这类包天然就不成立。
**2. 装成功了,也不报错,列表里就是没有。**
按单变体方式跑 `--as taste-default`,文件确实写进了 `skills/taste-default/`,origin 文件也正常生成,exit 0。可 `openclaw skills list` 里搜不到。
当前工作区实测的账是这样的:`skills/` 下 253 个顶层目录、658 个 SKILL.md,光 `ecc-repo` 一个目录就贡献 408 个(六层扫描深度内有效 402 个)。`ecc-repo` 按字母序排第 111 位,`taste-default` 会排到第 240 位。加载循环按目录名排序逐个扫,累计满 200 条就 `break`。实测这道 200 的坎正好卡在 `ecc-repo` 内部(累计 427 条),排在后面的 142 个目录一次都没被打开过。
`taste-default` 就在这 142 个里面。它不是加载失败,是从来没进过候选队列。
顺手纠正一个常被搞混的参数关系:`maxCandidatesPerRoot` 默认 300,253 个目录根本没触顶;真正卡住的是 `maxSkillsLoadedPerSource` 的 200。前者管目录枚举,后者管记录条数,两码事。还有同名去重——前 111 个目录里 427 条记录去重后只剩 139 个唯一名,最终工作区可见技能 108 条,全来源 141 条。跟提示里那句"141 个中包含 84 个"正好对上。
**3. 没有 description,会被静默丢掉。**
解析逻辑写死了 `if (!name || !description) return null;`。没描述直接返回空,日志只给个 warning,命令行一个提示都不弹。工作区里那 207 个"自动生成的技能"就是这么消失的——只有 `# 标题` 和 `## 来源` 两段正文,没有 frontmatter,自然既没 name 也没 description。
反过来,`name` 带大写或空格,加载阶段完全没问题。当前列表里就有 `Agent Browser`、`4号机进化`。大写只在被当成安装 slug 时才需要规范化。真正的硬门槛只有描述字段。
**4. 注入阶段还有第二道闸。**
过了发现阶段也不代表能完整露脸。`maxSkillsInPrompt=150` 和 `maxSkillsPromptChars=18000` 会在拼系统提示时截断目录:先试完整格式,塞不下退到紧凑格式(去掉描述,只留名字和路径),还塞不下就用二分法砍条目数。
当前会话的提示里白纸黑字写着"141 个中包含 84 个(compact format, descriptions omitted)"。这是第二道闸在工作,不是 skill 失效。也解释了一个挺反直觉的现象:技能越多,模型看到的描述越少,最后退化成一串光秃秃的名字,选择准确率自然往下掉。
另外两个常见怀疑可以先排除。SKILL.md 带 UTF-8 BOM 没问题,CRLF 换行也没问题——`extractFrontmatterBlock` 第一行就是 `content.replace(/^\uFEFF/, "").replace(/\r\n/g, "\n")`。只有超过 256 KB 才会被判 `oversized` 跳过。taste-skill 单个文件才 5–7 KB,工作区里最大的 SKILL.md 也就 30 KB,离红线还远。
## 三、这套上限为什么会撞上
Agent Skills 打一开始就走"渐进披露"的路子。启动时只把 name 和 description 塞进系统提示,模型觉得相关了再去 read 正文,正文里还能再挂 reference.md、forms.md 这类附件。Anthropic 在 2025-10-16 的工程博客里把这套三层结构讲得很透——目录、章节、附录,按需翻。
好处是技能总量近乎无限,代价是第一层元数据每轮都得躺在上下文里。
既然每轮都在付费,各家加上限就不奇怪了。Claude Code 的做法是把 description 和 when_to_use 拼起来截到 1536 字符,还专门给了个 `/skill-doctor`,让人看清每个技能占多少上下文、被用过几次。OpenClaw 换成按条数(150)加字符数(18000)双闸门,超了先砍描述再砍条目。思路一致,都是在"看得见"和"装得下"之间找平衡。
放到 AI 编码工具这条赛道看,技能分发这两年确实是个小热点。Claude Code、Cursor、Codex 各有各的目录约定,MaxClaw 这种把技能当插件管的做法也在往规范化走,ClawHub 上架的包越来越多。生态一热闹,工作区里的技能数就从"十几个"变成"几百个",而绝大多数人的配置还停在出厂默认值。这才是失效的真实土壤。
科技数码圈里换硬件讲究匹配,主板供电带不动新 CPU 就是带不动。技能加载是同一回事:不是文件不好,是槽位不够。
有个细节值得记一笔。`skills` 前缀在网关的重载规则里是 `kind: "none"`,改完 `skills.limits` 不会触发重启,下一次构建提示时才重新读配置。所以调参不用停机,改完开个新会话验证就行。
## 四、怎么修
技能数超过 200 的工作区,改容量参数比换路径有效得多:
```json5
{
skills: {
limits: {
maxCandidatesPerRoot: 500,
maxSkillsLoadedPerSource: 500,
maxSkillsInPrompt: 120,
maxSkillsPromptChars: 24000,
},
load: {
extraDirs: ["~/skill-vault/ecc-repo"],
},
},
}
```
这几个字段的取舍得说清楚。两个发现上限抬高,治的是"列表里根本没有";`maxSkillsInPrompt` 压到 120 左右,治的是"列表全但描述被砍光"。方向正好相反,别一起往上加,否则字符预算先炸。
更省事的办法,是把 `ecc-repo` 这类大集合目录整个挪出 `skills/`,改挂到 `skills.load.extraDirs`。它占着本地技能根第 111 位,一口气吃掉 402 条记录。移走之后,`taste-*`、`wechat-*`、`x-*` 这些按字母序排在后面的目录立刻能进候选队列。注意 extraDirs 优先级最低,同名技能会被工作区版本覆盖,正好符合"大仓库当后备库"的用法。
改完这样验:
```bash
openclaw skills list --json | python3 -c "import json,sys; d=json.load(sys.stdin); print(len(d['skills']), sum(1 for x in d['skills'] if x.get('modelVisible')))"
openclaw skills check
```
第一个数字是条目总数,第二个是模型可见数。工作区从 108 涨到 200 以上,并且 `taste-default` 出现在列表里,说明发现层通了。再开一个新会话,看提示里那句 truncated 提示还在不在——没了,注入层才算通。
## 五、判断经验
| 症状 | 出事的那一层 | 先查什么 |
|---|---|---|
| 列表为空,且没有任何报错 | 发现 | `skills/` 顶层目录数与 SKILL.md 总数是否越过 200/300 |
| 报 `archive is missing SKILL.md` | 源包结构 | 源根目录有没有 SKILL.md,是不是该装子目录 |
| 装完 exit 0 但列表查不到 | 发现(排序 + 截断) | 目标目录在字母序里排第几位,前面有没有大仓库 |
| 列表里有,模型从不调用 | 注入 | 提示里的 truncated 提示、是否已退化为紧凑格式 |
| 日志 warning 但无报错 | 解析 | frontmatter 有没有 `description` |
装完列表空的、又一声不吭,先数目录,别急着怀疑文件。看到 missing SKILL.md,问题在源包根目录的位置。技能躺在列表里模型却不用,去翻提示的字符预算。发现、解析、注入这三层,混在一起排查只会越查越乱。
你在实际项目里撞上的是哪一种?把 `openclaw skills list --json` 的条目数和顶层目录数贴出来,基本一步就能定位到具体那一层。 |
|