lint 报了一屏:哪些必须修,哪些可以先放着
给 agency-agents 提一个新 agent,或者只是改了一个已有 agent 的正文,仓库里等着你的第一道关是 scripts/lint-agents.sh。这个脚本一次性会吐出好几类条目,而且 ERROR 和 WARN 是混在同一份输出里的。第一次面对它的人通常会做两件事之一:要么全部埋头改完,要么看一眼觉得”都是警告”就提交了。这两种做法都不太对——因为这个脚本里恰好有一条 WARN,后果比另外两条 WARN 严重得多。
下面按排查的顺序来:先说现象,再说怎么确认自己碰到的是哪一类,然后是源码给出的处置,处置完怎么验证,最后是哪些情况根本不该往 lint 上赖。
一、现象:一次校验出来的条目分不出轻重
lint-agents.sh 脚本头部自述了它要校验的三件事:frontmatter 必须存在且包含必填字段(这一类是 ERROR)、推荐章节只给警告(WARN)、文件必须有实质内容。用法是:
./scripts/lint-agents.sh [file ...]
给了文件就只查这些文件,不给参数则扫描所有 agent 目录。仓库里带 frontmatter 的 agent markdown 有 255 个,分布在 17 个 division(数字为 2026-08-09 的仓库快照)——不带参数扫全库,条目自然不会少。
问题就出在这里:条目多,级别混排,而两个级别的实际含义完全不同。ERROR 是会让检查失败的,WARN 不会。所以”哪些必须修”这个问题,第一步不是逐条看内容,而是先按级别把这一屏切成两半。
二、怎么确认:把五道检查按级别对号入座
脚本一共五道检查(加上一道前置的行尾检查),级别在源码里是写死的,不随文件内容变化。你只要知道自己被哪一道拦住,就知道它是不是阻塞项:
| 检查 | 级别 | 说的是什么 |
|---|---|---|
| 拒绝 CRLF 行尾 | ERROR | 仓库标准是 LF,见 .gitattributes |
| frontmatter 分隔符存在 | ERROR | 取首尾两个 --- 之间的内容 |
| 必填字段齐全 | ERROR | name / description / color |
| 推荐章节存在 | WARN | Identity、Core Mission、Critical Rules,大小写不敏感匹配 |
| 正文有实质内容 | WARN | 正文词数小于 50 就警告 |
| 章节标题能映射到两个目标文件 | WARN | 见下一节 |
可执行的判定动作有三个。
第一,缩小范围只查你改的那个文件。 直接把路径当参数传进去:
./scripts/lint-agents.sh engineering/engineering-ai-engineer.md
全库扫描适合做审计,定位单个文件的问题时它只会淹没你。
第二,如果报的是 frontmatter 相关的 ERROR,先怀疑行尾而不是内容。 源码注释专门解释了这个坑:行尾多一个 \r,会让后面的 frontmatter 检查报出令人困惑的”missing frontmatter ---“——明明你打开文件,第一行就是 ---。所以判定动作是:先看这个文件的行尾是不是 CRLF,而不是反复检查那三个字段的拼写。这一条在 Windows 上尤其容易中招,编辑器或 Git 配置换掉行尾,文件看起来一点毛病没有。
第三,必填字段就照着源码那一行核。 lint-agents.sh 里写的是:
REQUIRED_FRONTMATTER=("name" "description" "color")
只有这三个是必填。仓库样例里还会出现 emoji 和 vibe——vibe 是一句话人设标语,被 app 一类的目录工具消费——但它们不在这个数组里,缺了不报 ERROR。有人看着样例照抄,以为五个字段都得齐,其实没有必要。
三、处置:三条 ERROR 全修,三条 WARN 分开对待
三条 ERROR 没有商量余地,它们决定检查是否通过:行尾统一成 LF;文件开头和 frontmatter 结尾各有一行 ---;name、description、color 三个键齐全。
三条 WARN 就要分开看了。
“推荐章节存在”这条,是排版建议。 它匹配的是 Identity、Core Mission、Critical Rules 三类标题,大小写不敏感。缺了不会让检查失败。如果你写的 agent 结构上确实没有这几节,先放着不修是可以接受的。
“正文词数小于 50”这条,当成信号看。 它警告的是文件基本没内容。一个正文只有几十个词的 agent,提示词本身大概率也没写完。它不阻塞,但它在提醒你这个文件还没写好。
“章节标题能映射到两个目标文件”这条,别当成排版建议。 这是本文真正想说的那一条。classify_header_target() 会把每个 ## 级标题分流到两个目标:命中 identity、learning + memory、communication、style、critical rule、rules you must follow 这几个关键词的标题去 SOUL.md,其余全部去 AGENTS.md。lint 要求两边都至少有一个标题,否则各报一条 WARN,措辞是 no section headers map to SOUL.md in convert.sh 和 ... to AGENTS.md in convert.sh。
注意警告文案里那半句 in convert.sh。这两个文件名对应的是 convert.sh 里 OpenClaw 格式的工作区结构:openclaw-workspace 这个 format 会给一个 agent 生成一个目录,正文按 ## 标题关键词拆成 SOUL.md 和 AGENTS.md 两个文件,默认桶是 agents。换句话说,你给章节起的标题名,实际上是在给转换产物做路由。这是一处看起来只是排版、实际上有下游后果的设计——如果所有标题都落到同一个桶,转换出来的另一个文件里就没有内容可写。
所以处置口径是:如果你的目标工具链里包含 OpenClaw,这条 WARN 应当按硬伤处理;如果完全用不到那条转换路径,它才退回成建议。判断依据不在 lint 里,在你打算装到哪几个工具。
四、处置后怎么验证
先重跑单文件 lint。 还是那条带路径的命令,确认原来报的条目消失。这是最直接的一步。
再跑原创性检查。 scripts/check-agent-originality.sh 与 lint 是两把不同的尺子:它把候选 agent 和整个已有花名册做对比,用实体中性化后的 8 词 shingle 重叠率打分,把 vietnam/chinese/douyin/tiktok/korean 这类专有名词中性化掉,让”换个国家名的换皮 agent”藏不住。两个默认阈值是 ORIGINALITY_FAIL=40(达到或超过这个百分比视为重复,退出码 1)和 ORIGINALITY_WARN=20(达到或超过给警告,不失败),都可以用环境变量覆盖。源码注释给出的库内校准结论是:现有 agent 库里同一对之间最差相似度约 1.5%,中位数 0%——所以双位数就已经是强异常,默认阈值留了很宽的误判余地。这个脚本依赖 python3,缺了直接退出码 2;给文件参数就只查这些文件(CI 用于变更文件),不给参数则全库两两互查。
如果你动的不只是一个 agent 文件,还要跑对应的一致性脚本。 仓库把”配置漂移”当成明确的敌人,几个脚本各守一段:
| 脚本 | 守什么 |
|---|---|
check-divisions.sh | division 列表要与磁盘目录、convert.sh 和 lint-agents.sh 里的 AGENT_DIRS、lint-agents.yml 的路径过滤器一致 |
check-tools.sh | tools.json 要与 install.sh 的 ALL_TOOLS、convert.sh 的转换器集合一致,条目缺 id/label/kebab/format/installKind/dest 就失败 |
check-runbooks.sh | runbook 里引用的每个 slug 必须能解析到真实存在的 agent 文件 |
新建 division 的正确顺序,divisions.json 的 _note 里写得很清楚:建目录 → 在 divisions.json 加条目 → 跑 scripts/check-divisions.sh → 按它的报错去更新它指向的地方。顺序反过来做,你就是在跟脚本的报错捉迷藏。
以上脚本的职责划分来自仓库源码与 divisions.json 的 _note,我们没有逐项运行过,以仓库最新内容与各脚本源码里的用法说明为准。
五、什么情况说明不是 lint 的问题
这一节比前面几节更值钱,因为它能省掉你在错误方向上的时间。
lint 全绿,但 Cursor 里”像没装”。 这跟校验没关系。convert.sh 生成的 .mdc 文件里,alwaysApply: false 和 globs: "" 是写死的,也就是说转换出来的 Cursor 规则默认不会自动生效,需要在 Cursor 里按需引用。往 lint 上找原因是找不到的。
装完在目标工具里翻不到。 先看落点。install.sh 的注释里,opencode、cursor、aider、windsurf 这四个写的是当前目录,其余多为家目录(例如 Claude Code 是 ~/.claude/agents/)。你在哪个目录下执行,决定了是给这个项目装还是给整个账户装。这是安装 scope 的问题,不是文件格式的问题。
Aider 或 Windsurf 里没法只装某一个 agent。 答案在 tools.json 的 installKind:这两个是 roster,所有 agent 会合并成一个文件(分别是 CONVENTIONS.md 和 .windsurfrules),产物形态本来就不是一 agent 一文件。Hermes 更特殊,它是 plugin,是一个构建出来的产物,不能按 agent 渲染成字符串,只能走 CLI。这些都与 lint 无关。
agent 装上了,但提示词效果不理想。 这条要说得直白些:lint 检查的是结构与重复度,不评估提示词写得好不好用。它不会告诉你这个人设是不是有效,也不会告诉你这段职责描述模型听不听得懂。别指望校验脚本承担质量评审的职责。
被校验的文件根本不是 agent。 integrations/ 是 convert.sh 写出的转换产物,strategy/ 放的是没有 agent frontmatter 的 playbook 与 runbook,examples/、scripts/ 同理——这四个目录在 check-divisions.sh 里通过 NON_DIVISION_DIRS 被排除。如果你手动指定路径去 lint 这些目录下的文件,报出来的 frontmatter 缺失是意料之中的,不是 bug。
在 Windows 上脚本压根跑不起来。 install.sh 的注释列出的平台是 Linux、macOS(需要 bash 3.2+)、Windows 的 Git Bash / WSL。在 PowerShell 或 cmd 里直接执行 .sh,那是运行环境的问题,跟脚本内容无关。
最后补一句节奏上的建议:真要往 ~/.claude/agents/ 这类共享目录写东西之前,install.sh 有个 --dry-run,只打印计划不落盘。lint 管的是单个文件写得对不对,--dry-run 管的是这一批文件会落到哪儿——两件事都确认过,再动真格。
延伸阅读
本文依据 agency-agents 官方仓库(github.com/msitarzewski/agency-agents)的 README、
tools.json、divisions.json、scripts/ 下的安装与校验脚本整理,核对日 2026-08-09。
本文内容为仓库源码与文档口径,我们没有安装或运行过其中任何 agent。
目录、脚本与安装路径随上游更新而变动,请以仓库最新内容与 ./scripts/install.sh --help 的实际输出为准。