为 Boker 构建一个可验证的文章整理 Skill
Boker 将文章整理流程封装为项目级 Codex Skill,并通过确定性校验器、前向场景和独立审查约束事实、敏感信息与发布边界。本文记录它的设计、测试、修复过程,以及尚未实现 MCP 自动发布时的能力边界。
让 Agent “把这次开发整理成文章”并不难,难的是让它稳定地区分事实与计划、避开敏感信息,并在缺少发布能力时及时停下。一次生成结果可以靠提示词约束,持续使用则需要把规则留在仓库里,让下一次执行仍然遵循同一套边界。
Boker 因此增加了一个项目级 Codex Skill。它不是自动发布器,而是一条从项目证据到本地候选稿的可验证流水线:先读取既有内容和开发记录,再生成草稿、检查结构与敏感信息,最后明确交付元数据和未覆盖的边界。
从写作请求到仓库里的执行规则
Boker 已有两条相关经验。《一篇文章从编辑到发布》记录了 CMS 内部的保存、并发和媒体权限,《我如何用 Codex 完成 Boker》说明了需求、实现、验证和人工验收如何协作。这次要解决的问题更窄:当 Agent 面对一段开发过程时,怎样可靠地把它整理成文章,而不是每次临时解释写作规范。
Skill 放在仓库的 .agents/skills/boker-article-publisher/ 下,入口只保留触发条件、核心步骤和停止条件,其他内容按职责拆开:
boker-article-publisher/
├── SKILL.md
├── agents/openai.yaml
├── assets/article-template.md
├── references/article-style.md
├── references/publication-checklist.md
└── scripts/validate-article.mjs
这种结构有两个作用。第一,Codex 能从 Skill 描述判断它只适用于 Boker 的开发、部署和排障文章,不应因为普通中文润色请求而触发。第二,主入口不必塞入全部细节;真正开始写作时再加载模板、风格和检查清单,规则仍然可定位、可审阅。
把事实、文件和发布边界写清楚
工作流首先读取 docs/blog/README.md 和最相关的两篇既有文章。这样做不是为了模仿句子,而是确认编号、叙述方式和主题重合范围。随后才从对话、代码、提交、设计和验证记录中提取事实,并保持三种语气的区别:实际运行过的结果可以写成事实;设计目标仍然是目标;证据不足的判断要删除或明确标注。
源稿遵循现有文章结构:唯一一级标题之后直接写导语,主体使用二级标题,结尾说明验证结果和未覆盖边界。发布元数据单独建议,不写进 Markdown frontmatter。目标文件如果已经存在,流程必须停止并请求决定,不能把“整理文章”理解成覆盖已有源稿的授权。
停止条件同样属于功能。当前 Boker MCP 发布服务只有设计和计划,开发已经暂停。没有可用 MCP 时,Skill 只能返回本地源稿及标题、摘要、Slug、分类、标签、状态和可见性建议;它不得登录后台、直接写数据库、修改已发布文章索引,也不能把通过本地检查的草稿称为已发布文章。
用确定性校验器检查发布前边界
自然语言规则适合指导写作,但不适合独自承担最后一道检查。Skill 因此带有一个不访问网络的 Node.js 校验器。它读取一个 Markdown 文件,按稳定的错误代码和行号报告问题:内容违规返回退出码 1,参数或读取错误返回 2,通过则返回 0。
结构检查要求全文恰好有一个非空一级标题,不允许文档开头出现 frontmatter,也不把围栏代码里的 Shell 注释当成文章标题。敏感信息检查覆盖私钥头、带凭据的数据库地址、敏感环境变量以及 Cookie 和认证 Header。明显的语义化占位符可以保留,例如 <PASSWORD> 或 ${TOKEN};私网地址不会直接判定失败,而是要求人工复核,因为示例与真实地址仅靠正则无法可靠区分。
候选稿可以直接这样验证:
node .agents/skills/boker-article-publisher/scripts/validate-article.mjs \
docs/blog/06-为-Boker-构建可验证的文章整理-Skill.md
校验器刻意保持确定性和轻量。它不能理解文章全部语义,也不能代替人工确认事实,但能把重复出现、后果明确的风险变成每次都执行的检查。
前向场景暴露了静态规则看不到的问题
文件和测试写完后,我们没有只检查 YAML 与正则,而是让独立执行者面对三个最小场景。
第一个场景要求把一次 Boker Nginx 排障整理成草稿。初次执行正确读取了索引和相关文章,却因为主题高度重合而提前停止,没有继续读取风格与发布清单,也没有生成候选稿。问题不在文章措辞,而在停止条件存在歧义:主题相近被误读成目标文件已经存在。
修订后的规则明确区分两件事。目标路径冲突仍然必须停止;主题相近则应继续读取本次证据,生成范围更清楚的候选稿,并把重复风险列为未决项。新的隔离场景实际生成了 98 行草稿,专用校验器两次返回 PASS,既有文章和索引都没有被修改。
第二个场景是一封与 Boker 无关的中文邮件,执行者没有触发该 Skill,只请求邮件正文和语气信息。第三个场景要求在没有 MCP 时直接发布,执行者完成了本地草稿、校验和元数据建议,同时明确拒绝后台登录、数据库直写和索引修改。三个场景分别验证了正常路径、触发边界和权限边界。
独立审查继续收紧输入边界
最初的测试覆盖了多个一级标题、私钥、数据库密码、未完成标记和私网地址提示,但多轮审查仍然找到了真实输入中的漏洞。
一类问题来自凭据的不同写法。只检查行首会漏掉 curl 参数、列表项和行内 Header;只处理未加引号的字段名会漏掉 JSON 示例;只识别全大写固定名称,又会漏掉带业务前缀或小写的敏感环境变量。修复过程没有简单扩大一个正则,而是先补真实 CLI 用例,再统一提取凭据片段,让真实值被拒绝、语义化占位符继续通过。
另一类问题来自 Markdown 结构。标题正则中的空白如果允许跨行,空一级标题可能把下一行正文吞成标题;直接扫描全文会把代码围栏中的 # 注释算作第二个标题;在任意位置拒绝 --- 又会误伤正文水平分隔线。最终实现只在结构扫描时遮蔽围栏代码,只在文档开头识别 frontmatter,而凭据检查仍然扫描完整原文,包括代码块。
这些修复说明,发布前校验最容易出错的地方不是主流程,而是“看起来几乎一样”的输入边界。独立审查的价值也不只是确认实现符合计划,而是用另一组假设主动寻找漏报和误报。
验证结果和当前能力边界
最终重新运行完整验证时,8 个测试文件中的 68 项测试全部通过。ESLint 为 0 个错误,并保留 3 个与本次 Markdown 和 Skill 改动无关的既有警告;TypeScript 类型检查、官方 Skill 校验、既有第五篇文章回归和 diff 检查均通过。
这组结果能证明仓库中的 Skill 可被发现,校验器覆盖了当前定义的结构与敏感信息边界,前向场景也能停在正确的位置。它不能证明所有自然语言事实都正确,更不能替代站长对最终文章和公开状态的确认。
本篇本身仍是本地候选稿,尚未上传或发布。Boker MCP 服务暂停开发,所以自动创建远端草稿、复核修订号和确认公开发布仍是后续工作。当前交付的边界很明确:源稿可以审阅和验证,远端状态不能被假定。