在 Codex 中跑通第一个 Skill:给博客文章做一次发布前检查
前面几篇文章分别讲了 Skill 的作用、怎样写规则以及怎样设计测试。只看文件内容,仍然不知道它能不能在一次真实任务里留下有用的结果。
这次做一个小实验:准备一份只读的文章检查 Skill,再拿一份故意留错的测试副本交给它。固定检查覆盖文章元数据、正文一级标题和站内文章链接;效果数字和验证状态,再由 Codex 阅读上下文复核。
实际运行是在当前 Codex Desktop 会话中,显式读取规则文件后完成的。本文没有测试客户端的 Skill 列表或自动触发,后面的调用示例也要和这次已经执行的操作分开看。
先把范围缩到一次任务
第一份 Skill 不需要负责选题、写作、排版和发布。范围越大,发现问题时越难知道是规则没有写清楚,还是输入、工具和权限出了问题。
这次的输入是一份指定的 .mdx 文件,输出是一份检查报告。它只读文件,不修改文章,不提交代码,也不调用发布权限。固定检查项只有三类:
| 检查项 | 可以判断什么 | 不能判断什么 |
|---|---|---|
title、published | 标题是否为空,日期是否能解析 | 标题是否准确,发布时间是否应该调整 |
| 正文一级标题 | 是否重复了模板已经显示的文章标题 | 二级标题是否合理,文章论证是否完整 |
/posts/.../ 链接 | 本地文章目录中是否存在对应文件 | 线上页面是否可访问,锚点是否存在 |
这个边界很重要。检查器报“文件存在”,只是说明当前工作区找到了目标文件;它没有访问网站,也没有证明目标文章已经发布。
把规则放进项目 Skill
按照 Codex 的 Skill 结构,目录中至少需要一个带有 name 和 description 的 SKILL.md。项目级 Skill 可以放在仓库的 .agents/skills/ 下,目录名使用小写短横线,例如:
.agents/skills/article-preflight/├── SKILL.md├── scripts/check.mjs└── assets/ ├── before.mdx └── after.mdx下面是这份规则的核心内容。项目已经有 gray-matter 和 marked,配套的 scripts/check.mjs 用它们解析元数据、标题和链接,不需要增加依赖。其他项目使用前,需要确认依赖和内容规则是否相同。
---name: article-preflightdescription: 检查指定博客文章的元数据、正文重复主标题和站内文章链接,输出问题及未完成项。用于发布前检查,不修改或发布。---
# 文章发布前检查
输入是指定稿件路径和项目根目录。
1. 读取稿件和内容规则。在项目根目录运行: node .agents/skills/article-preflight/scripts/check.mjs <稿件路径>2. 本项目要求 title 为非空字符串,published 被 YAML 解析为有效日期。3. 正文不重复一级标题;代码块中的标题和链接不作为正文检查项。4. 本地目标存在不等于线上可访问、草稿已发布或锚点有效。5. 阅读效果数字和验证状态。缺少依据时标为待核实, 不把“建议运行”“尚未运行”写成“已经通过”。6. 分开报告通过项、问题和未完成项,注明位置、依据及建议。
只输出报告,不修改文章、不提交、不发布。只输出报告、不修改稿件,让两次检查之间的输入变化可以由人核对。不过,规则文件本身不会撤销客户端原有权限,不能把这句约定当成技术隔离。以后如果要增加自动修复或发布,还需要安排相应的权限和确认步骤。
固定项交给一个小脚本
将下面的代码放进 scripts/check.mjs。它读取指定文件,返回 JSON 报告;发现文章问题时仍正常输出,所以报告中的问题列表才是判断依据,不能只看进程是否退出成功。
import fs from "node:fs";import path from "node:path";import matter from "gray-matter";import { marked } from "marked";
const root = process.cwd();const file = process.argv[2];if (!file) throw new Error("请提供待检查的文章文件路径");const { data, content } = matter(fs.readFileSync(file, "utf8"));const problems = [];let headings = 0;let links = 0;
if (typeof data.title !== "string" || !data.title.trim()) { problems.push({ item: "title", reason: "标题不是非空字符串" });}if (!(data.published instanceof Date) || Number.isNaN(data.published.getTime())) { problems.push({ item: "published", reason: "未被 YAML 解析为有效日期" });}
marked.walkTokens(marked.lexer(content), (token) => { if (token.type === "heading" && token.depth === 1) { headings++; problems.push({ item: "正文一级标题", text: token.text }); } if (token.type !== "link" || !token.href.startsWith("/posts/")) return; links++; const url = new URL(token.href, "https://local.invalid"); const slug = decodeURIComponent(url.pathname.slice(7)).replace(/\/$/, ""); const base = path.resolve(root, "src/content/posts", slug); const posts = path.resolve(root, "src/content/posts") + path.sep; if (!base.startsWith(posts)) throw new Error("链接目标超出文章目录"); if (![".md", ".mdx"].some((ext) => fs.existsSync(base + ext))) { problems.push({ item: "本地文章链接", text: token.href }); }});
console.log(JSON.stringify({ file, metadata: { title: data.title, publishedType: data.published instanceof Date ? "Date" : typeof data.published, }, bodyH1: headings, localPostLinks: links, problems, scope: "固定项检查;不检查线上可达性、链接锚点、MDX 组件语义或事实依据",}, null, 2));这里使用 YAML 解析后的日期类型,与当前博客的内容规则一致。其他项目如果允许把日期写成字符串,应调整这一项。marked 检查的是 Markdown 部分,复杂的 MDX 组件仍交给项目编译器处理。
调用时把对象说清楚
在支持 Skill 选择的 Codex 客户端中,可以显式提及 $article-preflight。初次测试时,建议先明确调用,再考虑描述能否触发自动选择。例如:
使用 $article-preflight,检查指定的测试稿件。项目根目录是当前仓库,稿件路径是:.agents/skills/article-preflight/assets/before.mdx只输出报告,不修改文件。报告保留未完成项。这是供读者尝试的调用方式,本次没有操作客户端的 Skill 选择器。实际执行时,先要求 Codex 读取 .agents/skills/article-preflight/SKILL.md,按这份规则运行脚本并复核正文。这样可以验证规则文件和配套脚本能否配合,却不能证明自动发现已经生效。
如果新文件没有出现在客户端里,可以先核对目录和元数据,再重新启动 Codex。不要在规则尚未加载时,就用结果评价 Skill 的好坏。
先用故意留错的副本
测试副本保存在这个 Skill 的 assets/ 目录中,没有混进正式文章目录。副本里有三个固定项问题:发布日期写成“日期待定”,正文重复了文章标题,还链接到一个不存在的文章路径。另有一句“使用这个流程,检查时间减少了 40%”,但没有提供测试记录,需要另行核实。
before.mdx 的测试内容如下:
---title: 用 AI 检查一篇博客文章published: 日期待定description: 用于验证文章检查规则的错误样例,不属于正式文章。draft: true---
# 用 AI 检查一篇博客文章
下面是希望读者继续阅读的内容:[后续教程](/posts/article-that-does-not-exist/)。
建议上线前执行 pnpm check。目前还没有运行这项检查。
使用这个流程,检查时间减少了 40%。
## 命令示例
以下内容是代码示例,不是正文标题和实际链接:
```markdown# 示例标题[示例链接](/posts/only-in-code/)```把测试材料和正式内容分开,有两个好处。第一,错误是预先设计好的,报告出来后可以逐项对照;第二,不会为了验证规则而把故意错误留进正式文章。代码块里还放了一个一级标题和一个不存在的链接,用来确认检查器不会把示例代码当成正文。
第一次运行,报告比“通过”更有用
在项目根目录实际执行的命令是:
node .agents/skills/article-preflight/scripts/check.mjs .agents/skills/article-preflight/assets/before.mdx它输出 JSON 格式的检查结果,下面摘出其中三个问题:
published:未被 YAML 解析为有效日期正文一级标题:用 AI 检查一篇博客文章本地文章链接:/posts/article-that-does-not-exist/这三个结果都能回到输入中核对。日期字段不是有效日期;正文确实重复了文章标题;目标文件确实不在本地文章目录中。代码块里的示例没有被计入正文一级标题,说明当前解析范围和测试预期一致。
随后阅读正文,确认“减少了 40%”没有随附测试记录,语义复核将它列为待核实。要支持这个数字,需要补充同类任务、记录方式和比较范围。本次没有访问外部资料,因此没有确认它是真是假。
另一句“建议上线前执行 pnpm check。目前还没有运行这项检查。”没有状态冲突,复核时保留了它的未执行状态。检查器不能因为看见命令名,就把建议写成已通过。
修正后再跑一次
第二份副本把发布日期改成可解析的时间,删除重复的一级标题,把链接换成了项目中实际存在的文章,同时把没有依据的效果数字改成了“可以记录每次检查和人工复核所用的时间,再判断这个流程是否值得保留”。
node .agents/skills/article-preflight/scripts/check.mjs .agents/skills/article-preflight/assets/after.mdx复测结果是:日期被解析为 Date,正文一级标题为 0,本地文章链接为 1,问题列表为 []。这说明修改后的副本满足了当前三类规则。文章观点、事实依据、站内链接的线上状态和最终排版,仍然需要各自验收。
这次实操暴露出的限制
第一,本文验证的是当前会话显式加载规则并执行检查。CLI 只查询了版本,没有另外启动一次 CLI 检查,也没有比较不同客户端的行为。
第二,固定检查适合处理格式和明确约定,不适合单独判断复杂事实。日期能解析,不代表时间正确;文章链接有对应文件,不代表线上可用;出现一个百分比,也不代表这个百分比可信。
第三,本次没有做“不加载 Skill”的对照,没有重复采样,也没有计时。因此只能展示工作如何完成,不能据此说 Skill 提高了多少准确率或节省了多少时间。规则、客户端或输入格式变化后,可以先拿这些样例复测。
留下一份能接着用的报告
一个小型 Skill 是否值得保留,可以看它有没有减少重复解释,并且让下一步行动更明确。本次报告至少留下了检查对象、固定项结果、待核实内容和当前限制。作者可以据此修改文章,再决定是否进入完整的项目检查。
下次检查文章时,指定新稿件并读取同一份规则即可。若报告出现误报,先定位是哪条判断出了问题;若新增了必须检查的项目,再补规则和测试材料。只有遇到当前工具无法取得的资料,才需要考虑接入其他工具。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或打赏支持!




