生活分享
Skill 触发与结果测试
分开验证格式、选用条件及实际结果,用应触发与不应触发的案例修正技能描述。
阅读时间约 15 分钟 · 操作 30 分钟

返回 Codex 教学总目录Codex 学习中心:完整教程目录从安装、第一个任务到 MD 规则与进阶集成,规划 60 篇 Codex 教程、十个单元。按程度、平台、需求或命令搜索下一篇;尚未公开的教程会标示状态,方便安排学习路线。阅读全文
目标与准备
本段提到的教学与资源: 技能资源分拆Skill 的脚本、参考文件与材料把重复逻辑与大型资料拆到必要的支援档案,让技能按需要读取并验证相对路径。阅读全文
「测试全过」在这里有两种不同范围:下面的八项自动测试只证明检查程式对指定输入的结果;技能行为验收还需要观察选择、读档、工具纪录及回复。两者分开记录,才不会把 Node.js 成功当成 Codex 已成功触发技能。测试也不要求特定模型名称,请记录你当时实际使用的入口与可用选项。
步骤 1:列出正确结果与失败条件
先看这张表,再写测试。正常案例保留两笔同名 Read;空阵列是有效输入,不应因为没有任务就报错。反过来,重复 ID、错误型别或坏 JSON 必须明确失败,不能先删掉有问题的列,再回复看似正常的总数。错误输入不应在标准输出混入半份成功 JSON,否则后续脚本很容易误读。
| 案例 | 程式退出码 | 可接受结果 |
|---|---|---|
| 三笔任务、两笔同名 | 0 | total 3、active 1、completed 2 |
| 空阵列 | 0 | 三个数量均为 0 |
| 重复 ID | 1 | Duplicate id,不输出成功 JSON |
| completed 为字串 | 1 | Invalid task,不自动转型 |
| 非阵列、坏 JSON、缺档或缺参数 | 1 | 明确错误、没有成功统计 |
步骤 2:加入八项可重跑测试
在 codex-skill-lab 根目录新增 skill.test.mjs,贴入下列全部程式。它使用 Node.js 内建测试工具,不必安装额外套件。每个案例在系统暂存目录建立自己的输入,呼叫真实统计器,再比对退出码、输出与原档位元组。测试结束只清除自己刚建立的暂存资料夹,不碰 data/tasks.json。
import test from 'node:test';
import assert from 'node:assert/strict';
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { resolve, join, dirname } from 'node:path';
import { spawnSync } from 'node:child_process';
const script = resolve('.agents/skills/todo-summary/scripts/count-tasks.mjs');
const tasks = [
{ id: 'a', title: 'Read', completed: true },
{ id: 'b', title: 'Build', completed: false },
{ id: 'c', title: 'Read', completed: true },
];
function run(input, verify, missing = false) {
const folder = mkdtempSync(join(tmpdir(), 'todo-skill-test-'));
const file = join(folder, 'input.json');
try {
if (!missing) writeFileSync(file, input, 'utf8');
const before = missing ? null : readFileSync(file);
const result = spawnSync(process.execPath, [script, file], { encoding: 'utf8' });
assert.equal(result.error, undefined);
verify(result);
if (!missing) assert.deepEqual(readFileSync(file), before, 'Input changed');
} finally {
assert.equal(dirname(resolve(folder)), resolve(tmpdir()));
rmSync(folder, { recursive: true });
}
}
function fails(input, pattern) {
run(input, result => {
assert.equal(result.status, 1);
assert.equal(result.stdout, '');
assert.match(result.stderr, pattern);
});
}
test('keeps repeated titles as three records', () => run(JSON.stringify(tasks), result => {
assert.equal(result.status, 0);
assert.equal(result.stderr, '');
assert.deepEqual(JSON.parse(result.stdout), { total: 3, active: 1, completed: 2 });
}));
test('accepts an empty array', () => run('[]', result => {
assert.equal(result.status, 0);
assert.deepEqual(JSON.parse(result.stdout), { total: 0, active: 0, completed: 0 });
}));
test('rejects duplicate IDs', () => fails(JSON.stringify([tasks[0], tasks[0]]), /Duplicate id/));
test('rejects string completed', () => fails(JSON.stringify([{ ...tasks[0], completed: 'true' }]), /Invalid task/));
test('rejects a non-array', () => fails('{}', /Input must be an array/));
test('rejects malformed JSON', () => fails('{', /\S/));
test('reports a missing file', () => run('', result => {
assert.equal(result.status, 1);
assert.equal(result.stdout, '');
assert.match(result.stderr, /ENOENT/);
}, true));
test('requires an input argument', () => {
const result = spawnSync(process.execPath, [script], { encoding: 'utf8' });
assert.equal(result.status, 1);
assert.equal(result.stdout, '');
assert.match(result.stderr, /Usage:/);
});
保持终端机位于练习根目录,Windows PowerShell、macOS、Linux 都执行下面命令。正常的检查器应得到 8 项通过、0 项失败,测试程式本身退出 0。表格中预期失败的输入,会因为被正确拒绝而让对应测试通过;不要把统计器退出 1 和测试套件失败混在一起。
node --test skill.test.mjs
如果第一项说找不到脚本,先检查根目录和 .agents/skills/todo-summary/scripts/count-tasks.mjs 的实际位置。若全部案例都变成 Usage,看看测试档是否仍传入 file 参数;若只有 JSON 错误文字不同,测试只要求有错误讯息,不依赖某一版 Node.js 的完整标点。先保留错误输出,再比对版本,别为了绿灯而删掉负面案例。
步骤 3:确认测试真的能抓到错误
先把 count-tasks.mjs 复制成 count-tasks.saved.mjs,确认备份存在。只在原档的 result 那一行,把 total: tasks.length 暂时改成下面片段,其余保留。这模拟「依标题计数」的错误:两笔 Read 被算成一笔,但 active 与 completed 仍沿用原数量。它是刻意制造的局部故障,不是本教学的最终版本。
total: new Set(tasks.map(task => task.title)).size
重跑相同命令,应出现 7 项通过、1 项失败,失败名称是 keeps repeated titles as three records。观察 actual total 为 2、expected 为 3,这才是测试抓到原本契约不允许的行为。接著只把原档从刚才备份还原,再跑一次应恢复 8 项通过。保留前、故障、还原三次摘要,不把最后一张绿灯当成全部过程。
步骤 4:设计技能行为验收
程式还原之后,开始检查 Codex。每一列使用新的任务,先确认工作资料夹,再依表格送出需求;不得让前一次已读规格、已完成报告或手动补充的答案污染下一列。每次记录输入原文、是否手动选技能、实际读取档案、工具命令、退出码与结果,才能判断问题发生在选择还是执行。
| 情境 | 送出的自然语言需求 | 检查重点 |
|---|---|---|
| 明确使用 | 选取 todo-summary,统计 data/tasks.json,只在回复给报告 | 读取资源、实际执行、3/1/2、输入不变 |
| 符合情境 | 统计这份本机待办 JSON 的总数、未完成及完成数 | 记录是否选到技能;没选到不冒称已使用 |
| 不符合情境 | 将网站背景改成蓝色,先提出计划,不修改档案 | 不应为这个需求执行待办统计器 |
| 必要资源缺少 | 规格档改名后明确使用技能产生已验证报告 | 说明缺档并停止,不创造规格或结果 |
桌面版透过 Skills 入口或 @ 选择;CLI/IDE 透过 /skills 或 $。自动选择依描述与上下文判断,没有选到不一定表示安装失败;先确认明确选取案例是否成功,再看 description 是否包含真正的用途与排除条件。不要把描述扩大成「所有任务都使用」,那会让不相关工作也载入这个技能。
只允许明确选用的对照练习
在这份独立的 todo-summary 技能中建立 agents/openai.yaml,使用下面设定。若已有此档,先保存副本并只合并 policy 栏位,保留原 interface 与 dependencies。依官方技能文件,allow_implicit_invocation 为 false 时不依提示自动选用,但明确提及技能仍可使用。它控制选用方式,不是沙盒,也不会授予工具权限。
policy:
allow_implicit_invocation: false
保存后用两个新任务重测:「统计 data/tasks-next.json 的待办数量」不手动选技能;另一个则明确选 todo-summary 并指定同一档案。前者不应自动选到这份技能,后者应仍可选用;两者分别记录实际读取与工具活动。前者即使以普通档案工具自行算对,也不能写成技能已触发。若更新未反映,重新启动 Codex 后再测,仍保留未确认栏位。
这个比较需要在支援的实际入口执行;YAML 可解析或八项 Node 测试通过,都不能代替选用政策验证。结束时只移除自己本次新建的 openai.yaml,或从备份恢复原档,再开新任务确认原设定。若只想保留明确选用,就保留 false,并在验收纪录写下这个决定及档案位置。
步骤 5:记录、修正,再测一次
建立下面的验收纪录,栏位没量到就写未执行,不要填入预期数字。一次只修一个问题:找不到资源就修路径;描述造成误选就修 description;程式计数错才改脚本。每次保存修正前的版本与失败案例,修正后重跑受影响的自动测试和对应新任务。修改主档不代表旧任务已重新读入。
# Skill acceptance record
Date and surface: <observed>
Node / Codex versions: <observed>
Model and effort, if shown: <observed or unavailable>
Skill path and revision: <exact local path and saved version>
Program tests: <command, exit, passes, failures>
Behavior case: <explicit / matching / outside-scope / missing-resource>
Request: <exact text>
Selected skill: <observed / not selected / unconfirmed>
Resources read and command executed: <evidence or not run>
Actual result and preserved input: <evidence>
Failure and one change: <description>
Fresh-task retest: <result or not run>
Remaining checks: <not run>
若代理宣称已执行但只提供预期结果,要求指出实际命令与工具输出;仍无法确认,就保留「未确认」,不要替它补证据。若多个同名技能同时出现,记录选到的路径,先停用或移出你这次新建的重复副本再重测,保留其他人的技能。不要任意改全域设定来掩盖本机教材路径错误。
练习结束前确认 input-format.md 已恢复原名、count-tasks.mjs 已恢复正确版、data/tasks.json 未被修改,最后八项测试全过。报告必须分开写程式测试与技能行为;未在 macOS 或 Linux 实际操作,就标记依文件与跨平台 Node.js 用法查证。本教材提供的参考测试不等于已替你在各入口执行模型。
这套方法也能用于你日后建立的其他技能:先固定输入、写清失败条件,再保存可重复的检查。若只是需要可复制的需求或文件骨架,接著看范本与速查表提示词、规则与交接范本索引依工作选择提示词、规则与交接范本,替换必要栏位并知道每段应贴到哪里。阅读全文;若要把多个技能交给别人安装,再阅读 PluginsPlugins 与外部服务Plugin 可以把 Skills 与 MCP 工具包在一起,提供可安装的工作能力。安装套件、连接外部帐号与实际执行工具是三个不同步骤;找到插件不代表已能读你的服务资料。阅读全文。示意图 1 固定案例、2 执行与观察、3 修正后重验。
返回 Codex 教学总目录Codex 学习中心:完整教程目录从安装、第一个任务到 MD 规则与进阶集成,规划 60 篇 Codex 教程、十个单元。按程度、平台、需求或命令搜索下一篇;尚未公开的教程会标示状态,方便安排学习路线。阅读全文
阅读完整文字说明
Three numbered stages: identify the starting point, perform the exercise, and verify the result. Original illustration, not a product screenshot.
同主题延伸阅读
生活分享
Codex 学习中心:完整教程目录
从安装、第一个任务到 MD 规则与进阶集成,规划 60 篇 Codex 教程、十个单元。按程度、平台、需求或命令搜索下一篇;尚未公开的教程会标示状态,方便安排学习路线。
生活分享
Worktree 与多任务隔离
Worktree 让同一个 Git 程式库有不同的工作目录,各自承接不同分支。它适合让两项工作分开改档,但资料库、连接埠与外部服务仍可能共用,不能把档案隔离当成所有资源隔离。
生活分享
实战:制作小网站
从 brief.md 规划并制作 Small Steps 待办网站,完成新增、完成、删除、筛选与本机资料保存。将 HTML、CSS、资料函式、画面事件与测试分开,以 Node 测试和浏览器操作验收,并留下可重新启动与还原的交接纪录。
生活分享
用量与效率:减少重工
记录任务条件、模型选项、时间与成果,找出能减少无效重试和过多上下文的调整。
引用本文的文章
最新旅游情报攻略

攻略东京
东京住哪一区:新宿、上野、东京站、涩谷、浅草、池袋、银座七区比较,机场交通、住宿税、行李寄送一次看
东京住哪一区?用同一套标准比较新宿、上野、东京站、涩谷、浅草、池袋、银座七个区域:从成田、羽田机场怎么过来、有哪些线路、周边有什么、街区氛围、适合谁。附比较表与山手线示意图,以及 2026 年 9 月核实的东京都住宿税(2027 年 4 月改为 3%)和机场宅急便寄送行李的规则。
- 预算
- 酒店

攻略东京
东京交通票券怎么选:Suica/Welcome Suica、Tokyo Subway Ticket、JR Pass 值不值得买
第一次去东京,每人先用一张 IC 卡按次付费(Welcome Suica 免押金、有效期 28 天)。一天搭四趟以上地铁,再加买 2,000 日元的 Tokyo Subway Ticket 72 小时券;只玩东京、不去关西,买 JR Pass 一定不划算。用决策图比较 TOURIST PASMO、iPhone 里的 Suica、东京 Metro 一日券能搭什么、不能搭什么;价格于 2026 年 9 月核实。
- 交通
- 预算

攻略东京
东京迪士尼乐园、海洋攻略:票价、梦幻泉乡 Fantasy Springs、尊享卡 DPA 与预约等候卡怎么用,第一次去选哪个园区
东京迪士尼一日护照采用浮动票价,2026 年 9 月平日大多为 9,900 日元、周末为 10,900 日元,官网每天 14:00 开售两个月后同一天的门票;免费的优先通行卡已不在官网服务清单,缩短排队时间只剩付费的迪士尼尊享卡(每人每次 1,000 至 3,500 日元)。另有运营时间与 25 周年活动、预约等候卡与报名体验、梦幻泉乡如何进入,以及第一次去选乐园还是海洋;2026 年 9 月通过东京迪士尼度假区官网核实。
- 行程范例
- 亲子