关于 project-audit
这是一个用于审计整个代码项目的 Agent 技能,从仓库冷启动检查项目现状,并生成供人阅读的报告和供后续审计读取的侧车文件。它按项目特征选择探测项,并区分发现、通过和因条件不足而无法判断的结果。
主要功能
- 01从已提交文件发现项目结构
- 02按项目特征选择检查项
- 03记录无法执行的探测及原因
- 04分别呈现机制与实际发生情况
- 05生成 HTML 报告和侧车文件
- 06以待办条目提出发现,不直接修改
适用场景
- 01接手久未维护的仓库时了解现状
- 02检查发布产物与源代码是否一致
- 03审查 CI 历史、遥测和采用情况
- 04将审计发现整理为项目待办
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ssheleg/ task-pipeline --skill project-audit安装后在 Claude Code 中输入/project-audit确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/portable-method.mdprobes.mdscripts/audit.py详细介绍
项目审计 — 当前项目的真实状况
关卡会询问某个产物是否合格。验收会询问清单上是否有任何内容被遗漏。两者都不会询问项目如今究竟是什么,而这正是操作人员打开一个自己已有一个月未接触过的代码仓库时唯一关心的问题。
这项技能从冷启动开始回答这个问题——无需简介、无需 REQ 表,也无需模块映射——并留下两个产物:一页供人阅读的报告,以及一份供下次审计读取的旁车文件。
目录
- 这不是什么
- 六个阶段
- 三种结论,以及第三种为何存在
- 版本检查无法发现的类别
- 两个产物
- 脚本做什么,以及你要做什么
- 退出标准
- 似是而非的理由
这不是什么
| 职责 | 适用场景 | |
|---|---|---|
references/portable-method.md | 方法 — L0→L7 阶梯、衔接点、轴向轮换 | 在一次运行中逐步检查单个交付物 |
| 此技能 | 流程 — 冷启动、探测、产出、报告 | 审计对象是整个项目 |
/skill-audit (make-skill) | 根据标准检查技能的构建情况 | 被审计对象是技能或插件 |
/ux-audit (super-ux) | 根据已记录的场景检查代码 | 问题关乎面向用户的行为 |
/seo-aeo-audit (seo-aeo-audit) | 检查公开页面在搜索引擎和答案引擎中的可见性 | 问题关乎机器能否找到它 |
第 4 阶段之前,请先阅读 references/portable-method.md。 其中包含独立审计的最低标准,无需同级技能,也无需网络访问。安装后可选阅读配套的任务流水线审计、待办事项和优先级排序参考资料,以深入了解;缺少这些资料绝不会阻碍此流程。
六个阶段
按顺序执行。每个阶段都会写入报告所依据的同一份数据。
1. 发现 — 在进行任何测量之前,了解这个项目是什么
scripts/audit.py 会以机械方式回答这些问题:语言、包管理器、单体仓库结构、子模块、CI、部署目标、已声明的遥测,以及项目自述的版本。它读取已提交的状态——git ls-files,而不是遍历目录——因为目录遍历会找到 node_modules 和构建残留,并报告一个克隆项目时根本不会出现的项目状态。
输出是一份概况,探测项由这份概况决定。跳过这一步,转而运行固定清单,就会让审计在每个代码仓库中都得出相同的九项发现。
2. 探测 — 根据概况选择探测项
每项探测都会声明所需条件(git、gh、npm、network、已连接的 MCP 服务)。如果不满足某项条件,探测结果会标记为盲区并说明原因——绝不会标记为缺失,也绝不会判定为正常。按技术栈分类的目录,以及如何为脚本尚未覆盖的项目结构添加探测项,详见
references/probes.md。
3. 生产环境——只有在代码库之外才能找到的证据
这是大多数审计会跳过的阶段,也是能发现代码库无法自证的问题的阶段:
- 已发布制品与源代码的对比——请参阅下一节;
- CI 历史记录:关注的不是“是否通过”,而是 发布 运行中失败的比例,以及失败是否被发现。
gh run list --limit 60 --json conclusion,name只需一条命令,也是最接近软件包生产日志的东西; - 遥测,无论有无。 只有部署后的项目缺少遥测才算一项发现;对库而言,这是一种设计选择。无论哪种情况,报告都会说明清楚,因为“未配置 Sentry”和“没有错误”不能被视为一回事;
- 采用情况,仅限有注册表提供数据的项目——下载量能将“是否用于生产环境”从主观判断变成可衡量的指标;
- 已连接的 MCP 服务——错误跟踪、分析、托管、数据库。询问项目使用什么,使用已连接的服务,并将其余部分记录为盲区。只读取聚合数据和指针,绝不读取原始内容(见 这两种制品)。
读取任何数据存储之前,必须按以下顺序操作——从错误的数据库得出的数据,比没有数据更糟,因为它会以测量结果的权威性被引用:
- 从平台自身的连接信息中确定数据存储,而不是从代码库中的脚本或文档中的名称判断;
- 通过针对产品持续写入的表执行最新写入聚合,证明数据存储处于活动状态,并将该时间戳与其支撑的每个数据一起写入报告;
- 如果连接了多个数据存储,则全部列出并注明各自的最新写入时间——审计只读取其中一个却不提及其他数据存储,读者就无法复现审计结果;
- 代码库的连接辅助程序是关于代码库的证据,而不是关于平台的证据。两者不一致时,本身就是一项发现;这条规则正是为此而制定的。
4. 接缝——交接给阶梯流程
现在,也只有现在,才针对发现的功能,按由下至上的顺序、依接缝逐一执行本地可移植方法的阶梯流程。在此发现的缺失,与其他缺失一样,都是发现。
5. 报告——两个文件,一条命令
python3 scripts/audit.py --root <path> 会写入旁置文件并打印摘要。
添加 --report 生成 HTML 页面,并在其旁添加 --no-open,即可写入但不打开。探测程序就在同一个脚本中,因此报告中的声明与实际运行的程序是同一个对象。
6. 提议——提交条目,不改代码
此技能不会提交任何内容。 发现的问题会以项目自身的术语转化为任务板条目,并依据任务板标题中声明的公式定价——随附的默认公式是 Sev × Blast + age_bonus(references/portable-method.md,本地优先级约定),由操作者决定是否接受。审计过程中绝不会按工作量排序:修复成本由修复者决定,而非发现者决定(references/portable-method.md)。边读取边修改的审计,无法重新运行以验证自身。在 task-pipeline 的 wave 模式中,这些条目和 JSON 旁置文件是 wave 0 的输入:它们会按 id 加入其清单。
发现必须将各个维度分开,否则就只是披着判决外衣的猜测
机制状态、利用/复现、暴露情况、观察到的发生率和影响不确定性,是五个维度,而不是一个。 如果一条记录只根据机制来写,在每个维度都说明自己掌握了什么之前,它就无法与真实发现区分开来——而一次审计中,连续 八条记录 都因测量结果而被改写,其中两条若按原文处理,就会毁掉库存,因为两条都把没有销售解读成没有需求。但纠正也有另一面:
- 已证实的缺陷,其发生率仍可能是未知的。 在本地复现的身份验证绕过或竞态,早在发生任何事故之前就是代码缺陷——复现本身就是证据,无需生产日志也足以将其列为发现。在没有生产日志的情况下,已确认的本地崩溃仍是代码缺陷,但外部发生率为未知。
- 未知 ≠ 0。 样本数为零不代表风险为零;缺少遥测数据会限制对暴露情况能做出的判断,却不会改变技术事实——记录中会明确说明是哪一种情况。
- 攻击者控制能力未知,会降低对可利用性的信心,但不会改变观察到的行为:实际看到的情况,就照实记录。
- 有记录在案的例外,不会让未通过的不变量变成通过——决策状态和技术有效性是下文所述的两个独立维度。
仍需考察发生率这一维度:这种机制多久触发一次? 可以查询、统计日志或读取遥测数据。如果答案是从不,就将记录按潜在风险估值(将补救措施与零发生率权衡);如果无法测量,则在发生率维度记录 blind——而发现仍由机制和复现这两个维度支撑。每条记录都应注明观察范围和时间,将代码机制、部署观察结果和假设分开——task-pipeline 中的 templates/finding-evidence.json 是最精简的架构,采集器会将 mechanism/incidence/observed_scope/observed_at 写入附属文件。只有当未知信息会改变行动方案时,才询问操作人员——访谈不是记录发现的前提。
由此可得两条规则:
- 提出补救措施前,先核查它会影响的对象范围。 两条记录都建议释放某类资产;测量结果显示,该资产类别正是产品自身有意保留的库存。
- 在核查会产生相应结果的路径之前,不要把“没有”当作证据。 零销售可能意味着没有需求,也可能意味着路径未正常运作,而这两种情况需要的补救措施截然相反。
已经决定的事情既不是发现,也不是什么都不是
只读模块、不读模块使用位置的审计,会把项目已经决定的事情报告为缺陷——用的还是项目自己的话,因为决定通常写在发现所引用的代码附近几行处。一次审计中,八条里有五条就是如此。
- 阅读调用处,而不只看定义。 在读过使用该模块的位置之前,不要写关于该模块的发现。
- 每条都要说明属于以下三种情况中的哪一种: (a) 尚未决定,(b) 已决定且在此处记录,(c) 已在别处决定但未传达。(a) 和 (c) 都需要处理——(b) 若仍然错误,也同样需要处理,因为决定状态和技术有效性是不同的维度。记录在案的决定也可能有误、过时,或违反外部契约;“这是设计如此”并不能证明它正确。因此,(b) 又分为:
- 已接受的权衡——项目有意选择承担真实且明确的代价的限制(在契约相符的前提下仅支持单一浏览器):标记为已接受的限制并注明代价,而不是缺陷。
- 有记录的违规——某项决定仍然违反安全不变量、外部契约或自身声明的目标(例如,ADR 允许记录刷新令牌):它仍然是一个
finding,并附上decision_id、重新审视的理由以及反面证据。记录审计误判的 (b)(误报),比删除该条更有价值;但将所有有记录的决定都排除在发现之外,会系统性地造成假阴性,而这是代价更高的漏报。
- 若判定为 (c),补救措施应是机械检查,而不是编辑。 写下却无人验证的规则,影响范围恰好止于写下它的地方;这五种情况下,可靠的修复都是添加一项检查,针对每个副本核对项目自身的指示,而不是修补恰好被发现的那个副本。
三种判定,以及第三种为何存在
clean · finding · blind。词汇表是封闭的;构造时会拒绝第四种值。
**blind 就是整个设计的核心。**没有它,无法检查的探测和什么也没发现的探测会产生相同的空白部分,而读者每次都会理解成后者。这是将本地方法中的沉默不等于读过从命令提升到探测:零退出码且没有输出,并不代表问题已有答案。
页面会将盲区列表单独呈现为一个章节,绝不会放在附录中。 审计的盲区是其结果的一部分。
版本检查看不到的类别
一个版本字符串,不止一棵代码树。 软件包的各个渠道提供的内容并不相同:注册表提供 标签,而插件市场和技能 CLI 提供 分支最新提交。如果分支在没有提升版本号的情况下超越了标签,每个渠道都会报告相同的版本号,却分发不同的代码。
截至 2026-08-22,在这个系列中,npm 提供的一个文件有 4344 行,而市场提供的同一文件有 4575 行,三个渠道报告的版本号都是 1.15.0。固定版本检查器始终显示通过——这是正确的,因为它比较的是那两个字符串。
因此,要比较代码树,而不是标签,并比较确实可能不一致的那一对。这个探针的初稿将注册表压缩包与标签进行比较,结果显示一致:它们按构建方式本就一致,因为注册表是从标签发布的。一个返回通过的同义反复,就是假通过的典型情形——某个机制只相信自己的回复。
另外还有两个陷阱,初稿都曾包含,现在已成为测试用例:
- 一个渠道有某个路径而另一个没有,这是打包差异,不是分歧。 压缩包只会打包其
files白名单允许的内容;把.github/算作不一致,导致在只有一个文件移了位置时报告了 22 个问题。 - 只有双方声称的版本相同时才进行比较。 分支已经提升到标签之后,就没有共同的版本声明,应标记为
blind,而不是clean。
产物——始终生成 sidecar,按需生成页面
每次运行都会写入 docs/audit/<date>-audit.json。只有使用 --report 时才会写入 HTML 页面。 三种输出模式,只有一种需要浏览器:stdout(--stdout——在终端显示摘要,除 sidecar 外不写入其他内容)、json(仅 sidecar,为默认模式——供机器读取,不生成页面,也不需要浏览器)、html(--report——同时生成页面)。仅输出 json 或 stdout 的运行绝不会打开浏览器,也不需要浏览器;浏览器只与 --report 有关。
这并非为了对称。sidecar 让它成为棘轮,而不是快照;下一次运行会读取它——跳过它就会悄无声息地让此后每次运行都变成首次运行。页面是报告,而报告是一种会在对话结束后继续留存的产物:没人要求的报告,就是没人委托、也没人维护的文档,放在 docs/ 下,只差一次 git add -A 就会进入产品的历史记录。在对话中作答;有人需要页面时再生成页面。
页面只包含汇总数据和指向其他内容的链接,绝不包含原始正文。 数量、主要类别、趋势,以及指向其所属系统中问题的链接——绝不包含堆栈跟踪、日志行或数据记录。报告是人们会转发的文件,而无法分享的报告没人会写第二次。
只报告秘密所在的位置和类别,绝不报告其值。 file:line、凭据类型和补救方法。值既不会出现在任何产物中,也不会出现在 stdout 中:审计不能成为凭据泄露的第二个地方。必须彻底脱敏,而不是只遮蔽前缀——半截凭据加上上下文,往往就足以还原完整内容。
“只读”针对的是目标,而不是磁盘。 审计会读取项目的源代码、数据和生产环境证据,不会修改其中任何内容,也不会提交任何东西。它确实会将自己的产物写入获准的输出目录(默认为 docs/audit/,可用 --out-dir 更改位置)——始终写入 sidecar,使用 --report 时写入页面。将 sidecar 写入该目录不违反只读原则;写入目标的源代码,或对任何内容执行 git add,才违反。
sidecar 让它成为棘轮,而不是快照。 每条发现都带有一个由探针及其位置生成的 ID,因此即使改写措辞也能保留;下一次运行会显示哪些问题已关闭、哪些是新增的,以及哪些问题已连续通过三次审计。最后这个数字本身也是一条发现:无人着手修复的缺陷,就是无人记录下来的决定。
脚本做什么,以及你做什么
| 脚本 | 你 |
|---|---|
发现、注册表、机械探测、sidecar、--report 页面、差异 | 接缝检查、MCP 证据、判断发现意味着什么 |
| 拒绝给出第四种判定、对机密信息脱敏、排除自身输出 | 决定严重程度和投入精力,编写适合本项目的修复方案 |
这种分工并非为了整洁。将判断编码进脚本,就会变成无人认同的门槛;将机械性步骤留在文字中,就会变成无人执行的步骤。这两种情况都曾出现在这一系列项目中,也都记录在其看板上。
先运行脚本,查看它的盲区清单,然后把时间花在它无法检查的地方。
完成标准
审计在以下条件满足时完成——标准取决于所要求的交付物,而非固定的页面和浏览器:
- 每项探测都有判定,每项
blind探测都说明原因; - sidecar 已写入(始终如此)。只有在使用
--report时,HTML 页面才会同时生成;其内部链接安全且有效,并记录了检查/渲染状态(已打开,或注明--no-open)——不使用--report时不会创建页面,运行仍算完成; - 报告中的每个数字都由本次运行执行的命令得出——重述计数属于断言(
evidence-docs); - 至少有一个数值通过形态不同的命令重新推导,且两者的输出都已打印。重复运行同一命令,只是在对第一次运行做拼写检查;
- 向操作人员打印拟议的条目,不向目标写入任何内容。
托词
| 借口 | 现实 |
|---|---|
| “测试通过了,所以项目很健康” | 测试比较的是有人认为应该写下的内容。之所以有阶段 3,是因为这一系列项目中最棘手的缺陷,在它影响的每个代码库里都被绿色测试套件掩盖了。 |
| “没有 Sentry,所以没有错误可报告” | 这是同一个空白部分,也是截然相反的两种事实。这正是 blind 的用途。 |
| “版本一致,所以各渠道也一致” | 实测结果:三个渠道、一个版本字符串、231 行差异。应比较代码树。 |
| “我会记下原始日志行,方便了解背景” | 那么报告就无法共享,也不会有人再次编写。汇总并标明出处。 |
| “我发现了凭据——我把它贴出来,这样我们就能确认是否有效” | 报告就会成为第二次泄露。注明位置和类别;解决办法是轮换凭据。 |
| “既然我在这里,发现的问题就应该直接修好” | 边读边改的审计无法重新运行来检查自身,下一次运行也无法区分修复和措辞改写。 |
| “每次都是同样的九项检查,所以我可以跳过发现阶段” | 那么这个工具无论输入什么都会返回相同答案,这说明的是工具本身,而不是项目。 |
| “没有发现问题——一切正常” | 这只适用于已运行的探测。大声说出这句话前,先看盲区清单。 |
触发条件
描述是简短版本。完整触发列表在 1.98.0 之前一直随附,现已原样移至此处,仍会触发此技能:
“当有人询问整个项目目前的真实情况时使用——哪些已完成、哪些只完成一半、哪些已损坏,以及哪些内容还无人查看。从冷启动开始检查:发现项目是什么,根据发现结果选择并运行一组探测,阅读生产证据(已发布产物与源代码对比、CI 历史记录、是否存在遥测),然后留下 JSON sidecar,以便下次审计能说明发生了什么变化;并在需要时生成独立完整的 HTML 报告。只读:提出看板条目,不提交任何内容。触发词——‘项目审计’、‘审计项目’、‘代码库审计’、‘项目现状’、‘有哪些未完成’、‘项目健康检查’、‘аудит проекта’、‘проаудируй проект’、‘состояние проекта’、‘что не доделано’、‘аудит кодовой базы’。不适用于:审计一次运行中的单项交付物(那是流水线自身的检查步骤)、审查差异,或检查某项技能的构建方式——说‘без диагностики’可选择退出。”