关于 ship-a-feature
面向使用 `CLAUDE.md` 和 npm 脚本的项目的功能交付 Skill,检查功能是否可访问、已记录且易于查找,并验证稳定性标记及冻结代码树上的门禁。
主要功能
- 01检查导出入口是否可访问
- 02发现未记录的 API 变更
- 03验证文档与可发现性
- 04检查稳定性标记
- 05在冻结代码树上运行门禁
适用场景
- 01验证新导出的功能
- 02发现缺失的公开文档
- 03检查功能是否链接自相关文档
- 04合并前运行项目检查
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ zernie/ vigiles --skill ship-a-feature安装后在 Claude Code 中输入/ship-a-feature确认已加载。
附带文件scripts/ship-check.mjsship-a-feature.harness.mjs
详细介绍
发布一个功能(确保它真正发布,而不是只完成一半)
九行的完成定义已存在于 CLAUDE.md 的 cohesive-feature-delivery 中。2026-09-02,equivalentDisasters 经过测试、门禁检查并提交后发布,却在无人察觉的情况下未满足其中 四 行:所有公开入口都无法访问、邻近代码的 JSDoc 被悄悄窃取、没有文档、没有稳定性标记。写在一份 2000 行文件中的说明不会触发检查,下面这些会。以下每项检查都对应着它旨在防范的失败;绝非纸上谈兵。
1. 编写代码前——只有你能做出的三个决定
| 决定 | 标准 |
|---|---|
选哪个入口?(package.json exports 子路径) | 免费且确定性强 → .(src/test.ts)。会消耗模型费用 → ./eval(paid_ 前缀)。Hook 词汇 → ./hook。未从某个入口的 barrel 重新导出的符号,对于 require("vigiles") 来说就不存在(失败 1)。 |
稳定还是 experimental_? | STABILITY.md:已导出但不稳定 ⇒ 添加 @experimental 标签,并使用 experimental_ 名称。只有一天历史、只有一个使用方,并不意味着稳定。ESLint local/experimental-name 会检查标签与名称是否匹配;它无法写入标签(失败 4)。 |
公开的原因说明放在哪里?(document-the-why) | 在与相关声明相邻的 docs/*.md 小节中——对于 equivalentDisasters,应放在 docs/compiled-hooks.md 中“blocks 7/7”旁边。不放在 README(为保持简洁),也不放在本文(面向贡献者)。 |
请将答案写入 PR 正文。在你说明稳定性之前,下面的脚本不会通过稳定性检查。
2. 写完之后——一条命令,五项检查
npm run build
node .claude/skills/ship-a-feature/scripts/ship-check.mjs <symbol> [--stable "<why it is stable>"]
| 检查项 | 执行内容 | 针对的已验证故障 |
|---|---|---|
| REACHABLE | 对构建后的 dist/ 中每个 exports 子路径执行 import();该符号必须是至少 1 个对象的键 | src/guardrail-check.ts 中有 equivalentDisasters,src/test.ts 中却没有 → undefined(1) |
| SURFACE | 重新生成 api-surface/(api:report),然后读取 git diff:任何 +… (undocumented) 都会失败 | 插在 verifyGuardrail 的 JSDoc 和声明之间;diff 已经说明了这一点,但没人看(2) |
| DOCUMENTED | \b<symbol>\b 必须出现在 docs/**/*.md 或 README.md 中 | 直到有人提出要求,才有公开的说明页面(3) |
| FINDABLE | 必须有两个页面提及它——或者一个页面提及它,另一个页面链接到该章节的锚点 | 只记录在一处,而且是在介绍另一项功能的指南中,没有任何入站链接(5) |
| MARKED | 声明的 JSDoc 中有 @experimental,或名称带有前缀,或你传入了 --stable "<why>" | 导出仅一天,没有标签,也没有前缀;lint 规则无从判断(4) |
DOCUMENTED 和 FINDABLE 是两个不同的问题。 前者问的是某处是否记录了这项功能。后者问的是,不知道该打开哪个文件的读者能否找到它。experimental_equivalentDisasters(后来更名为 experimental_alternateSpellings)通过了第一项,却没通过第二项:它只在介绍另一项功能的指南中、以自己的标题解释了一次,而且没有任何链接指向那里——因此,读者查看测试指南、想知道“我的 guard 是否真的会拦截?”时,完全找不到它。添加指向它的链接即可满足 FINDABLE;不必重复解释。
✗ 表示需要修复并重新运行;脚本会在每项发现旁打印修复方法。✓ MARKED stable 行会附上你的理由——将其粘贴到 PR 正文中。
3. 门禁——在冻结的代码树上,于前台运行,并使用实际退出码
node .claude/skills/ship-a-feature/scripts/ship-check.mjs --gate # = npm run check, frozen
运行前后会对代码树进行哈希校验;如果门禁运行期间有任何更改,就会失败。以下是已验证的三种门禁误报情况,按这种方式运行即可杜绝:
- 运行期间编辑(5):
npm run check对只写了一半的文件执行 lint,并因某种中间状态下的错误而失败。退出前不要触碰代码树。 - 使用
until ! pgrep -f 'check.mjs'等待(6):等待进程的 shell 命令行本身就匹配该模式,因此永远不会结束。等了二十分钟,显示的仍是“仍在运行”。在前台运行门禁;如果必须在后台运行,就对自己启动的 PID 执行wait。绝不要对自己的命令中包含的字符串使用pgrep -f。 npx vitest run … | tail -8(7):管道的状态码是tail的状态码。三个测试失败,退出码却是 0。门禁之后不要接管道——重定向到文件,再查看文件。
已有机制覆盖——调用即可,不要重述
| 属性 | 负责人(不要重复) |
|---|---|
标签 @experimental ⇒ experimental_ 名称 | ESLint local/experimental-name(在 npm run lint 中) |
./eval 符号带有 paid_ | npm run exports:check(在 npm run check 中) |
| 与已提交快照相比的表面差异 | npm run api:check(在 npm run check 中) |
文档的 ts 代码块导入真实导出 | npm run docs:check — 仅在文档实际存在时(3) |
docs/ 中提及的每个 CLI 动词 | src/doc-command-coverage.ts — 仅检查动词,不检查 API 符号 |
| 完整命令列表 | npm run check(node scripts/check.mjs --list 会打印) |
4. 完成的定义
五行 ✓、一行 ✓ GATE,以及注明入口 · 稳定性决策 · 文档章节的 PR 正文。没人能 import 的功能就不算功能,没人能导航到的功能就不算有文档记录。