关于 meta-reviewing-cli-reviewing
用于审查 CLI 代码的技能,提供安全性、行为、配置、测试和用户反馈检查清单,帮助审查者按严重程度分类问题并说明影响。
主要功能
- 01信号与取消处理检查
- 02退出码与异步解析审查
- 03进度提示与错误消息检查
- 04配置优先级审查
- 05帮助文本与测试覆盖检查
- 06问题严重性分类
适用场景
- 01审查 Commander.js 代码变更
- 02检查 @clack/prompts 取消处理
- 03审计 CLI 错误处理
- 04评估 CLI 测试与配置
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ agents-inc/ skills --skill meta-reviewing-cli-reviewing安装后在 Claude Code 中输入/meta-reviewing-cli-reviewing确认已加载。
附带文件examples/core.mdmetadata.yaml
详细介绍
CLI 代码审查模式
快速指南: 审查 CLI 代码时,检查 SIGINT 处理、p.isCancel() 检查、退出码常量、parseAsync() 的使用,以及用户反馈(加载动画、清晰的错误信息)。检查配置层级、帮助文本质量和试运行支持。区分严重程度(必须修复、建议修复、可改进),并说明每个问题的重要原因。
<critical_requirements>
关键要求:审查 CLI 代码之前
所有代码都必须遵循项目在 CLAUDE.md 中的约定(kebab-case、命名导出、导入顺序、
import type、命名常量)
(必须确认 CLI 入口点中存在 SIGINT(Ctrl+C)处理)
(必须确认每次调用 @clack/prompts 后都调用 p.isCancel())
(必须确认退出码使用命名常量——标记 process.exit() 中的任何魔法数字)
(必须确认异步操作使用 parseAsync(),而不是 parse())
(必须确认在任何控制台输出或错误处理之前停止加载动画)
</critical_requirements>
自动检测: 审查 CLI、检查 CLI 代码、CLI PR 审查、Commander.js 审查、@clack/prompts 审查、CLI 质量、CLI 错误处理审查、退出码审查
适用场景:
- 审查使用 Commander.js 构建的 CLI 应用
- 审查使用 @clack/prompts 的交互式提示
- 检查 CLI 错误处理和退出码模式
- 评估 CLI 用户体验(帮助文本、加载动画、反馈)
- 验证 CLI 测试是否充分
- 审查配置管理模式
不适用场景:
- 实现 CLI 代码时(请使用相应的 CLI 实现技能)
- 进行与 CLI 无关的一般代码审查时
- 审查后端 API 时
涵盖的关键模式:
- CLI 专属审查清单
- 退出码和信号处理验证
- 用户体验审查标准
- 错误消息质量评估
- 测试充分性清单
- 配置层级审查
- 命令结构和组织审查
- CLI 问题的严重程度分类
详细资源:
- examples/core.md - 审查输出格式示例、CLI 测试审查模式
理念
CLI 用户体验至关重要。 与提供视觉反馈的 Web 应用不同,CLI 工具完全通过文本进行交互。糟糕的错误消息、缺失的进度指示或意外退出都会损害用户信任。审查时应体谅最终用户。
审查 CLI 代码时:
- 确认所有调用 process.exit() 的路径都使用命名常量
- 检查每个异步操作是否都有视觉反馈(加载动画)
- 确保在所有情况下都能妥善处理取消操作
- 验证错误消息是否说明了什么操作失败,以及如何修复
- 确认帮助文本实用,并且包含示例
不应过于苛刻的情况:
- 如果功能正确,不要因为帮助文本措辞而阻止 PR
- 对于耗时不到 500ms 的操作,不要强制要求加载动画
- 如果颜色选择遵循现有模式,不要吹毛求疵
- 如果 CLI 足够简单,不要要求添加详细模式
核心原则:
- 安全第一: 退出码和信号处理不可妥协
- 用户共情: 每条错误消息都应引导用户解决问题
- 一致性: 所有命令都应遵循相同的模式
- 可测试性: CLI 代码应可在不启动进程的情况下进行测试
核心模式
模式 1:CLI 审查清单
每次审查 CLI 代码时,都使用这份全面的清单。
入口点验证
## CLI Entry Point Review
**Signal Handling:**
- [ ] SIGINT handler exists in entry point
- [ ] SIGINT calls process.exit with EXIT_CODES.CANCELLED
- [ ] Other relevant signals handled (SIGTERM for containers)
**Command Registration:**
- [ ] Commands imported and registered cleanly
- [ ] parseAsync() used (not parse()) for async actions
- [ ] Global error handler with catch() on main()
- [ ] configureOutput() used for colored errors
- [ ] showHelpAfterError(true) enabled
**Global Options:**
- [ ] --dry-run supported for destructive operations
- [ ] --verbose supported for debug output
- [ ] --help generates useful output
- [ ] --version displays correct version
重要原因: 入口点问题会影响每条命令。缺少 SIGINT 处理会导致用户无法取消;缺少 parseAsync 会悄无声息地吞掉错误。
模式 2:退出码审查
确认所有退出路径都使用命名常量。
退出码验证清单
## Exit Codes Review
**Named Constants:**
- [ ] Exit codes defined as named constants (e.g., EXIT_CODES object)
- [ ] All exit codes have JSDoc descriptions
- [ ] Uses `as const` for type inference
**Usage Audit:**
- [ ] No magic numbers in process.exit() calls
- [ ] Correct exit code for each scenario:
- Success: EXIT_CODES.SUCCESS (0)
- General error: EXIT_CODES.ERROR (1)
- Invalid args: EXIT_CODES.INVALID_ARGS (2)
- User cancelled: EXIT_CODES.CANCELLED
- Validation failed: EXIT_CODES.VALIDATION_ERROR
// Must Fix: Magic number exit code
process.exit(1); // What does 1 mean?
// Good: Named constant
process.exit(EXIT_CODES.VALIDATION_ERROR);
重要原因: 魔法退出码难以维护。依赖你的 CLI 的脚本需要可预测且有文档说明的退出码。
模式 3:提示词取消处理审查
每次调用 @clack/prompts 都必须检查是否已取消。
取消处理审计
// Must Fix: Missing isCancel check
const name = await p.text({ message: "Name:" });
// User presses Ctrl+C - name is Symbol, code continues with garbage
// Good: Proper cancellation handling
const name = await p.text({ message: "Name:" });
if (p.isCancel(name)) {
p.cancel("Setup cancelled");
process.exit(EXIT_CODES.CANCELLED);
}
审查清单:
## Prompt Cancellation Review
For EACH @clack/prompts call (p.text, p.select, p.confirm, p.multiselect):
- [ ] p.isCancel() check immediately follows
- [ ] p.cancel() called with descriptive message
- [ ] process.exit() called with EXIT_CODES.CANCELLED
- [ ] No code executes after isCancel returns true
**重要性:**缺少 isCancel 检查会导致用户按下 Ctrl+C 时出现未定义行为。代码会继续执行,并将 Symbol 值当作预期的字符串/布尔值处理。
模式 4:异步操作审查
检查所有异步任务是否使用加载指示器并处理错误。
加载指示器使用审查
// Must Fix: No feedback for long operation
const data = await fetchRemoteConfig(); // User sees nothing
// Good: Spinner with descriptive messages
const s = p.spinner();
s.start("Fetching configuration...");
try {
const data = await fetchRemoteConfig();
s.stop(`Loaded ${data.items.length} items`);
} catch (error) {
s.stop("Failed to fetch configuration");
p.log.error(error.message);
process.exit(EXIT_CODES.NETWORK_ERROR);
}
审查清单:
## Async Operations Review
For EACH async operation (API calls, file operations, network):
- [ ] Spinner started with descriptive message
- [ ] Spinner stopped before any console output
- [ ] Spinner stopped before error logging
- [ ] Success message includes result info
- [ ] Error handling exists with appropriate exit code
**重要性:**用户需要知道操作正在进行。没有任何反馈的操作会让人觉得程序坏了。
模式 5:错误消息质量审查
评估错误消息是否能指导用户采取行动。
错误消息标准
## Error Message Review
For EACH error path:
- [ ] Message explains WHAT failed (not just "Error")
- [ ] Message explains WHY it failed (permission, network, validation)
- [ ] Message suggests HOW to fix it (retry, check config, provide flag)
- [ ] Uses picocolors consistently (red for errors, yellow for warnings)
- [ ] Includes relevant context (file path, option name, command)
// Must Fix: Unhelpful error
p.log.error("Failed");
// Should Fix: Better but no resolution
p.log.error(`Config file not found: ${configPath}`);
// Good: Actionable error
p.log.error(`Config file not found: ${configPath}`);
p.log.info(`Run 'mycli init' to create one, or specify path with --config`);
**重要性:**可指导用户采取行动的错误消息能减轻支持负担,并改善用户体验。用户不应陷入无从下手的境地。
模式 6:配置层级审查
检查配置解析是否遵循正确的优先级。
配置优先级清单
## Configuration Review
**Precedence Order (highest to lowest):**
1. [ ] CLI flags (--source, --config)
2. [ ] Environment variables (MYAPP_SOURCE)
3. [ ] Project config (.myapp/config.yaml in cwd)
4. [ ] Global config (~/.myapp/config.yaml)
5. [ ] Default values (hardcoded constants)
**Implementation:**
- [ ] resolveConfig function exists and follows precedence
- [ ] Empty flag values handled (--source "" should error or skip)
- [ ] Missing config files handled gracefully (no crash)
- [ ] Source/origin tracked for debugging (--verbose shows where value came from)
**重要性:**配置优先级不正确会让用户困惑。如果环境变量覆盖了标志参数,那就是一个错误。
模式 7:帮助文本审查
评估命令文档的质量。
帮助文本清单
## Help Text Review
**Command Documentation:**
- [ ] Description is clear and concise
- [ ] All options have descriptions
- [ ] Required vs optional options clear
- [ ] Default values documented in option descriptions
**Examples Section:**
- [ ] Common use cases shown
- [ ] Examples are copy-paste ready
- [ ] Complex options demonstrated
**Consistency:**
- [ ] Naming follows conventions (--dry-run not --dryRun)
- [ ] Short flags used for common options (-f for --force)
- [ ] Help shown after errors (showHelpAfterError enabled)
模式 8:测试充分性审查
检查 CLI 测试是否覆盖关键路径。
CLI 测试清单
## Testing Review
**Command Testing:**
- [ ] Happy path tested for each command
- [ ] Invalid arguments tested (missing required, unknown options)
- [ ] --help output tested
- [ ] exitOverride() used to prevent process.exit in tests
**Prompt Testing:**
- [ ] @clack/prompts mocked properly
- [ ] Cancellation flow tested (isCancel returns true)
- [ ] Validation rejection tested
- [ ] Multiple selection paths tested
**File System Testing:**
- [ ] In-memory filesystem used for isolated tests
- [ ] Config loading tested (missing, invalid, valid)
- [ ] File write operations tested
**Exit Code Testing:**
- [ ] Success exits with 0
- [ ] Each error type returns correct non-zero code
- [ ] Cancellation exits with CANCELLED code
查看 examples/core.md,了解审查时需要查找的测试代码模式。
模式 9:命令结构审查
检查命令组织是否合理。
结构清单
## Command Structure Review
**Organization:**
- [ ] Related commands grouped as subcommands (config show, config set)
- [ ] Each command in separate file
- [ ] No god commands (> 200 lines)
- [ ] Shared utilities extracted to lib/
**Options:**
- [ ] Global options defined on parent (--verbose, --dry-run)
- [ ] optsWithGlobals() used to access parent options
- [ ] Option names consistent across commands
**Arguments:**
- [ ] Positional arguments have clear names
- [ ] Required vs optional arguments documented
- [ ] Argument validation happens early in action
模式 10:安全审查
检查 CLI 特有的安全问题。
安全清单
## CLI Security Review
**Input Validation:**
- [ ] No shell injection (user input not concatenated into shell commands)
- [ ] File paths validated before operations
- [ ] URLs validated before fetch
**Secrets Handling:**
- [ ] API keys/tokens not logged (even in verbose mode)
- [ ] Sensitive options not shown in help output
- [ ] Config files with secrets have appropriate permissions warning
**Dependency Injection:**
- [ ] Arguments not passed directly to child_process
- [ ] execa or similar used instead of exec when needed
<decision_framework>
决策框架
CLI 问题的严重程度分类
Is this a safety/correctness issue?
├─ Missing SIGINT handler → MUST FIX
├─ Missing p.isCancel() check → MUST FIX
├─ Magic number exit code → MUST FIX
├─ parse() instead of parseAsync() → MUST FIX
├─ Missing error handling on async → MUST FIX
└─ NO → Is it a user experience issue?
├─ Missing spinner for >500ms operation → SHOULD FIX
├─ Unhelpful error message → SHOULD FIX
├─ Incorrect config precedence → SHOULD FIX
├─ Missing --help descriptions → SHOULD FIX
└─ NO → Is it an enhancement?
├─ Could add --json output → NICE TO HAVE
├─ Could add more examples in help → NICE TO HAVE
├─ Could improve verbose logging → NICE TO HAVE
└─ Style preference → DON'T MENTION
批准决策框架
批准条件:
- 已确认所有 SIGINT/取消处理逻辑
- 所有退出码都使用具名常量
- 异步命令使用 parseAsync()
- 异步操作均有错误处理
- 测试覆盖关键路径
要求修改的情况:
- 缺少 p.isCancel() 检查(任何提示词)
- process.exit() 中使用魔法数字
- 异步操作使用 parse()
- 长时间运行的操作缺少加载指示器
- 错误消息缺乏帮助
需要重大修改的情况:
- 入口点没有 SIGINT 处理程序
- 系统性地缺少取消处理
- 未定义退出码常量
- 未建立错误处理模式
- 存在安全漏洞(Shell 注入)
</decision_framework>
<red_flags>
风险提示
高优先级问题(必须修复):
- 入口点中缺少
process.on("SIGINT", ...) - 任意提示调用后都缺少
p.isCancel() - 使用
process.exit(1)或process.exit(0),而非命名常量 - 存在异步操作时使用
program.parse(),而非program.parseAsync() - 错误日志输出前未停止微调器(导致输出错乱)
- 存在 Shell 注入漏洞(在 exec/spawn 中使用用户输入)
中优先级问题(应该修复):
- 对可能耗时超过 500ms 的操作未显示微调器
- 错误消息说明了失败原因,却没有说明如何修复
- 破坏性操作缺少
--dry-run支持 - 缺少用于调试的详细模式
- 配置优先级不正确(环境变量覆盖命令行标志)
- 缺少对提示词中用户输入的验证
- 未配置
showHelpAfterError(true)
常见错误:
- 忘记在
p.cancel()后调用 process.exit() - 未使用 optsWithGlobals() 访问父命令选项
- 停止微调器前记录日志(输出乱码)
- 未处理标志的空字符串值
- 未捕获 YAML 解析错误
- .action() 回调中的异步错误被吞掉
注意事项与边缘情况:
- Commander 会自动将选项对象中的
--my-option转换为myOption - 即使发生错误,也必须调用 Spinner.stop()
- p.isCancel() 对 Symbol 值返回 true,而不是对 undefined
- 在异步上下文中调用 process.exit() 时,可能不会等待待处理的 I/O 完成
- 颜色可能无法在 CI 环境中呈现(检查 NO_COLOR 环境变量)
- 配置文件可能存在,但 YAML 格式无效
</red_flags>
<critical_reminders>
重要提醒
所有代码都必须遵循 CLAUDE.md 中的项目约定
(必须确认 CLI 入口点中存在 SIGINT(Ctrl+C)处理)
(必须确认每次调用 @clack/prompts 后都会调用 p.isCancel())
(必须确认退出码使用命名常量——标记 process.exit() 中的任何魔法数字)
(必须确认异步操作使用 parseAsync(),而不是 parse())
(必须确认在任何控制台输出或错误处理前都已停止微调器)
若未发现这些问题,CLI 将会在按下 Ctrl+C 时崩溃、使用未记录的退出码,并悄无声息地吞掉错误。
</critical_reminders>