mcpField
A
audit-program

audit-program

github.com/foldkit测试与质量1.1k52 次安装1 周前更新
代码仓库

关于 audit-program

audit-program 是用于审查现有 Foldkit 程序的只读技能,供希望检查代码是否符合 Foldkit 指南和示例的团队使用。它按范围审查、机械检查和报告组织流程,并要求先获批准再修复。

主要功能

  1. 01按路径或关注领域限定审查
  2. 02对照 Foldkit 指南与示例
  3. 03检查 lint 配置和诊断
  4. 04执行机械扫描与结构审查
  5. 05修复前必须获得同意

适用场景

  1. 01将 Foldkit 页面与对应层级示例对照
  2. 02在批准改动前发现代码质量问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/foldkit/foldkit --skill audit-program

安装后在 Claude Code 中输入/audit-program确认已加载。

附带文件agents/openai.yaml

详细介绍

根据与 generate-program 相同的标准审查现有 Foldkit 程序:这份代码的质量应当与 packages/typing-game/client/src/ 或 packages/website/src/ 中的手写代码不相上下。不是“能运行”,也不是“结构有效”,而是达到打字游戏的质量。

推荐的配置: 如果在被审查的项目中将 repos/foldkit/ 作为 git subtree 引入,审查的保真度会大幅提高。否则,你只能依据 generate-program/architecture.md 和 conventions.md 中的快照进行评估。引入 subtree 后,你可以依据实时范例(examples/、packages/typing-game/、packages/website/)进行评估,而这些范例正是上述快照所概括的内容。如果缺少 subtree,建议在开始审查前添加,并将其固定到与已安装的 foldkit 包匹配的发布 git 标签,以确保范例与项目编译所用的 API 一致:git subtree add --prefix=repos/foldkit https://github.com/foldkit/foldkit.git "foldkit@$(node -p "require('./node_modules/foldkit/package.json').version")" --squash。对于版本号(x.y.z-canary.<commit>)没有对应标签的 canary 安装,则固定到其所指提交的完整哈希;GitHub 会在 https://github.com/foldkit/foldkit/commit/<commit> 展示该提交。

操作原则:先报告,未经同意不做任何更改

第 6 阶段结束前,审查均为只读。输出是一份结构化报告。在用户看过报告并明确批准修复之前,绝不编辑任何文件,无论问题看起来多么明显、改动多么微小,也无论你多么有把握。第 7 阶段(提供修复)为可选阶段,须经用户同意后才能按条目或批次进行。

具体而言:

  • 第 1 至第 6 阶段仅进行调查:阅读代码、运行 grep、启动审查子 Agent、综合整理发现。不得使用 Edit、Write,也不得运行会修改文件的 Bash 命令。
  • 在第 6 阶段结束时提交报告后,停止并等待。询问是否应用修复。不要在提交报告的同一条回复中附带修复。
  • 即使用户同意修复,结构性和语义性更改仍需逐项确认。只有用户明确接受批量处理时,才可批量进行机械性修复。
  • 如果用户在看到发现之前就说“修复它”或“进行更改”,仍须先提交报告。报告才是审查的价值所在;直接着手修改会跳过咨询环节,用户也就无法对各项内容进行排序或拒绝。

审查的目标是找出用户可以采取行动的问题,而不是暗中替用户处理。

解析 $ARGUMENTS:

  • 为空 → 端到端审查 src/,覆盖所有维度
  • 路径(文件或目录)→ 仅审查该路径,覆盖所有维度
  • 关注领域(a11y、effects、naming、decomposition、forms、routing、subscriptions、submodels、types、testing)→ 仅沿该维度审查 src/
  • 同时提供路径和关注领域 → 同时缩小路径和维度范围

如果参数有歧义(既可能是路径,也可能是关注领域),请询问用户要选哪一个。不要猜测。

第 1 阶段:确定审查范围

用一句话概述审查计划,并在阅读代码前征求确认:

“我会审查 src/page/room/ 的命名、分解方式和 Effect-TS 惯用法一致性,并与 typing-game 的房间页面进行对比。这样可以吗?”

仅当范围明确无误时才跳过确认(例如单个文件或单个关注领域)。其他情况下,先用一句话概述计划成本很低,还能让用户在你把精力花在错误目标上之前调整方向。

阶段 2:阅读权威参考资料

按顺序阅读。每次审计都必须阅读,不可走捷径。这些资料是审计评判所依据的规范。

  1. 架构指南:TEA 不变量、Submodel 和 OutMessage 模式、Flags、Subscriptions、Mount / Command / ManagedResource / CustomElement 的选择
  2. 约定指南:命名、Effect-TS 惯用写法、Schema 模式、视图约定
  3. 验证清单:权威的机械检查 + 质量标准参考
  4. 盲点:那些能通过类型检查、lint 和测试的故障模式

这四个文件只是一个快照。如果被审计项目包含 repos/foldkit/,应以实时源代码为准,而不是这里的内容:阅读 .d.ts 或示例,不要依据记忆中的签名进行评判。

不要略读。审计的信噪比取决于审计人员在阅读被审计代码前是否已充分掌握标准。

阶段 3:梳理项目

深入审查前,先为被审计代码建立一个模型:

  1. 文件树。列出范围内的每个文件,并用一句话描述(update.ts: 处理 ClickedSubmit、UpdatedEmail 等事件)。
  2. 层级。与 generate-program 层级阶梯进行匹配:
    • Tier 1:单页、无异步、单文件
    • Tier 2:计时器、订阅、简单状态管理
    • Tier 3:异步、加载/错误、表单
    • Tier 4:路由、多页面
    • Tier 5:嵌套领域、CRUD
    • Tier 6:Submodel、OutMessage、多步骤流程
    • Tier 7:实时、WebSocket、ManagedResources
  3. 使用的 Foldkit 模块。搜索从 foldkit 导入的 AsyncData、Calendar、File、Http、Update、Port、Subscription、Command、Mount、ManagedResource、CustomElement、Dom,以及 foldkit/fieldValidation 和从 @foldkit/ui 导入的任何组件。HTTP 用法表现为从 effect/http 导入的 HttpClient / HttpClientRequest,而不是 foldkit 导入,因此要单独搜索这些内容;从 foldkit 导入的 Http 只是用于提供客户端的 layer。这会告诉你哪些清单章节适用。
  4. 匹配层级的范例。至少选择一个范例,与被审计代码一同阅读:
    • Tier 1-2 → ${CLAUDE_SKILL_DIR}/../../examples/counter/src/main.ts、${CLAUDE_SKILL_DIR}/../../examples/stopwatch/src/main.ts
    • Tier 3 → ${CLAUDE_SKILL_DIR}/../../examples/weather/src/main.ts、${CLAUDE_SKILL_DIR}/../../examples/form/src/main.ts
    • Tier 4 → ${CLAUDE_SKILL_DIR}/../../examples/routing/src/main.ts
    • Tier 5 → ${CLAUDE_SKILL_DIR}/../../examples/kanban/src/
    • Tier 6 → ${CLAUDE_SKILL_DIR}/../../examples/auth/src/、${CLAUDE_SKILL_DIR}/../../examples/job-application/src/
    • Tier 7 → ${CLAUDE_SKILL_DIR}/../../packages/typing-game/client/src/page/room/
  5. Foldkit UI 集成。如果有从 @foldkit/ui 导入的内容,也要阅读 ${CLAUDE_SKILL_DIR}/../../examples/ui-showcase/src/main.ts 和 ${CLAUDE_SKILL_DIR}/../../examples/ui-showcase/src/ui/,了解接线模式。

范例是对比目标。发现可疑模式时,请思考:范例是否采用了不同做法? 如果是,就标记出来。

阶段 4:运行 linter,然后执行机械扫描

先检查 linter

@foldkit/oxlint-plugin 提供了 24 条 AST 规则,涵盖本审计评估的许多方面:带键的映射行、不使用数组索引作为键、硬编码的路由字符串、带标签调用的空对象、外部链接上的 Rel、modifyFields 内的展开语法、对子级输出使用 Got* 包装、PascalCase 的 Command.define 绑定、execute 忽略其元素的 Mount、模块级可变状态,以及 NoOp Messages。它们会解析代码,因此能捕获 grep 遗漏的问题。

先确认这些规则是否真的处于启用状态;这和是否找到软件包名称不是一回事。配置才是权威依据,而不是 lint 输出。 先定位配置,再读取其中的内容。lint 脚本可能会通过 --config 指定配置;在工作区中,配置通常位于应用目录的上层,而非与应用并列:

grep -n '"lint"' package.json
find . .. ../.. -maxdepth 1 -name '.oxlintrc.json' 2>/dev/null

找不到配置与找到一个不包含 Foldkit 的配置,是两种不同的结果;如果只看 stdout,它们看起来完全相同。 对不存在的文件执行 grep,与对未提及 Foldkit 的配置执行 grep,都会不打印任何内容。应根据 find 是否找到文件来判断:

  • 未找到配置:结果为未确定,绝不是未启用。项目很可能由本次审计未覆盖到的父级工作区执行 lint。
  • 找到配置:用 grep 搜索其中的 foldkit。如果在 extends 中找到指向插件 recommended.json 的条目(脚手架会写入该条目),或者找到通过路径说明符指定插件名称的 jsPlugins 条目(monorepo 或 vendored 配置会这样接入插件,Foldkit 仓库本身也是如此),则规则处于启用状态。如果没有匹配项,但配置中有指向其他位置的 extends 条目,就继续追踪该文件并重复检查:共享的内部配置可能会在上一级接入插件。如果没有匹配项,也没有可继续追踪的内容,则规则未启用。

lint 输出可以确认规则已启用,但绝不能据此排除规则已启用。诊断项标记为 foldkit(rule-name),所以看到 foldkit( 就能证明规则已运行;即使所有规则都在运行,搜索 foldkit/ 也不会找到任何内容。没有任何 foldkit( 匹配项根本说明不了什么:通过检查的项目就是这种情况。绝不要根据安静的 lint 运行推断“未启用”。

  • 已启用:运行项目的 lint 脚本。每条 foldkit(...) 诊断都是一个发现,应像其他发现一样报告;对于同一问题,它的优先级高于 grep 匹配项,因为该规则实际解析了代码。不要手动重新推导这些发现。
  • 未启用:将其作为首条 QUALITY 项报告,并以工具配置问题的方式措辞。不要将其列为 BLOCKER:该类别用于结构错误的代码,且其条目格式要求包含 file:line,而缺少 devDependency 并没有这样的信息。将其列在那里还会导致所有手写应用仅因工具配置问题就不合格。尽管如此,这通常仍是审计中最具杠杆效应的修复,因此应将其列为 QUALITY 列表的首项:只需一个 devDependency 和一行 extends,就能持续检查二十多项不变量,而本审计只是一次性快照。然后再使用 grep,并在报告中说明这只是较弱的替代方案,因为它会漏掉经过包装的调用和重命名的辅助函数。未运行 lint 的项目即使 grep 结果干净,也只是较弱的证据,不能算通过。
  • 未确定:仍然运行 lint 脚本,并将任何 foldkit(...) 诊断按“已启用”处理,因为一条诊断就足以得出结论。如果没有诊断,则按上述方式使用 grep,但要说明配置接入情况尚未确定,而非缺失,并列出你搜索过的路径。不要建议安装一个可能已经在执行这些规则的插件。

通过 create-foldkit-app 创建的项目默认已安装并接入该插件,因此缺少该插件的受审计项目通常早于该脚手架,或是移除了配置。

然后执行扫描

运行 ../generate-program/checklist.md 中 机械扫描 部分的规范 grep。该清单是唯一依据。它涵盖了没有任何规则涉及的内容(API 漂移、采用 @foldkit/ui、Effect 惯用写法、a11y 细节、测试结构),以及有意保持狭窄设计的规则所留出的边界;每条此类 grep 都会注明它补充的规则,以及该规则的适用范围到哪里为止。不要在这里重复这些命令,也不要仅仅因为某条规则涉及相同主题就删除扫描:应检查该规则是否确实覆盖了此情况。另请注意,recommended.json 会对 **/*.test.ts 和 **/*.test.tsx 禁用所有规则,因此对于 Subagent E 的测试结构检查,linter 毫无帮助。每个命中项要么是问题,要么有 // NOTE: 说明。不得有未说明的命中项。

对于每个命中项,请判断:

  • 上方是否有 // NOTE: 说明偏离的理由?
  • 如果有,请评估该理由。它是否成立,还是只是在为行为找借口?常见的错误理由:“会需要重复状态”(大多数 @foldkit/ui 组件都有可在行内构造的小型模型,而受控渲染辅助函数完全不需要状态),“交互逻辑太特殊”(toView 回调可以处理任意 HTML),“我们不想连接 toParentMessage”(这是确保交互式小组件符合 a11y 要求不可避免的成本,每次使用都只需付出一次)。
  • 如果没有 NOTE,则属于问题。

机械扫描能捕获容易发现的问题。它们是审查的底线,而非上限。

第 5 阶段:结构审查

对照代码,逐一检查 ../generate-program/checklist.md 中的每个类别。每个类别、每个条目,都要检查实际文件。

通过子 Agent 并行处理(Tier 4+)

对于重要的审计任务,应并行处理。在一条消息中启动多个子 Agent,让它们并发运行。每个子 Agent 负责一个维度,并行读取规范参考和相关的待审计文件。

使用 Agent,并设置 subagent_type: general-purpose。建议的分工如下:

  • Subagent A(结构正确性)。检查模型 schema 是否完整、Message 联合类型是否覆盖全面、Message.match 是否穷尽、每个 Succeeded* 是否都有对应的 Failed*、每个 Command 标识是否都通过 Command.define 定义为 PascalCase 常量、每个路由变体是否都已渲染,以及是否存在无用的状态变体。原生 Web 组件应通过 CustomElement.define 绑定,而非通过 OnMount + Subscription + 标签名称注册表绑定。凡是自定义元素的表面仅包含类型化属性、观察属性和分发的 CustomEvent,都应标记通过 OnMount 接入的情况。

  • Subagent B(Effect-TS 惯用写法)。检查 pipe 是否将转换后的值作为主体,形成清晰的从左到右数据流,而非仅仅重排普通调用。若顺序承载了含义,单次转换也有效,包括将 OutMessage 附加到已有的更新返回值。优先使用 Option.match,而不是 Option.map(...).pipe(Option.getOrElse(...));优先使用 Array.match 处理空/非空分支;对 Model 数组进行分支判断时,优先使用 Array.match,而不是 .length === 0 / .length > 0(isArrayEmpty / isArrayNonEmpty 谓词接受可变的 Array<A>,不接受 ReadonlyArray);优先使用 modifyFields,而不是展开语法;如果 setter 只转换当前字段,则使用无点式 modifyFields;优先使用可调用构造函数,而不是 as Type;对于“零个或一个 Command”,使用 Array.fromOption;在谓词中使用 Equal.equals;对不会失败的 Effect 不要使用 Effect.ignore。检查是否使用 AsyncData,而不是手写远程数据联合类型。如果更新返回类型只有一处 Message.match 使用,则将其内联。如果其他 matcher、辅助函数或导出签名会复用该类型,则创建 UpdateReturn 别名。对于已由 Message.match<UpdateReturn> 约束的更新,不要重复标注返回类型。 当更新无法发出 OutMessage 时,使用 Update.Return<Model, Message>。缺少 outMessage 字段表示该更新没有发出任何内容。outMessage?: never 守卫可防止包含 OutMessage 的结果进入只保留其 Model 和 Commands 的代码。手写的类型别名必须保留该守卫。生产者在静态创建的 Commands 为空时省略 commands。返回计算得出的 Commands 集合时,无需检查它们是否为空,并且绝不写字面量 commands: []。

    将每个结果绑定到一个以操作命名的值,并通过点访问来使用。不要解构或重命名 model、commands 或 outMessage。可选的 Commands 直接传给 Command.mapMessages;需要具体数组的操作才使用 result.commands ?? []。点访问可让操作及其返回的所有字段一同清晰可见,但无法阻止有人忽略 outMessage。参见 blindSpots.md 中的 repeated-scaffolding 和 manual-update-return-unpacking。

    标记这样的同一 Model 操作:手动传递 result.model 和 result.commands,而后续操作又会使用前一操作生成的 Model;这类操作应使用 Update.combine。Update.combine 需要两个或更多 Steps。不要将单个 Step 包装在 Update.combine 中;直接调用该操作。组合多个 Step 时,将内联 Step 参数命名为 stepModel。构造新结果时,如果 OutMessage 已确定,就直接将其包含在结果中。对于现有的普通返回值或类型为 OutMessage | undefined 的值,使用 Update.withOutMessage:将现有返回值传入该辅助函数;新结果字面量则作为第一个参数传入。标记重复实现 Update.withOutMessage 的局部辅助函数或条件展开。

    仅当至少有一个子项 OutMessage 应继续传递给当前 Submodel 的父级时,子项折叠才包含 toParentOutMessage。对于部分转发,匹配每个子项变体,并对在此处停止传递的变体返回 undefined。如果所有变体都在此处停止传递,则省略该属性。foldOutMessage 仍会在本地处理每个变体,包括继续向上传递的变体。标记 toParentOutMessage: () => undefined。

  • 子 Agent C(命名与拆分)。maybe* 仅用于 Option<T>,is* 仅用于布尔值;不使用含义不明确的缩写或未解释的单字母名称;允许使用领域内的常见简称;使用 Updated*,而非 Changed*;Completed* 遵循 Command 名称的动词优先形式;命名的辅助函数使用具体动词,而非泛化动词;超过约 15 行的处理程序应提取出来;超过约 30 行的视图分支应提取出来;函数不得超过约 40 行。

  • 子 Agent D(Foldkit UI 与无障碍性)。手写的 input / textarea / button / dialog 会被标记,除非有 NOTE 说明理由;标签与输入框通过 For(id) + Id(id) 配对;动态错误通过 Role('alert') 或 AriaLive('polite') 播报;仅含图标的按钮必须有 AriaLabel;外部链接必须带有 Rel('noopener noreferrer');每个路由恰好有一个 h1;优先使用语义化地标(main、nav、header、footer),而非堆叠 div;保留焦点可见性(没有 outline-none,除非用 focus-visible: 替代)。

  • 子 Agent E(测试,Tier 3+)。必须存在包含 story 管线的 story.test.ts;每个可能失败的 Command 都要测试 Succeeded* 和 Failed* 路径;至少有一个多步骤交互测试;Submodel 测试断言 outMessage;通过 Command.resolve(Definition, resultMessage) 解析 Commands,而非直接运行 Effects。Tier 3+ 必须有 scene.test.ts,缺少该文件会阻塞审核。每个 scene 块都必须至少包含一个 expect(...) 或交互式解析。定位器必须具备无障碍性(使用 role、label、text),而非 placeholder 或 CSS 选择器。

每个子 Agent 的提示词均为独立完整内容:其中包含要阅读的权威参考资料、要审核的文件列表、评分维度,以及 Phase 6 的报告格式。每个子 Agent 都会以最终报告中的一个部分形式返回其发现。 对于 Tier 1-2 或聚焦审查,请直接进行审查。范围足够小,拆分给子 Agent 带来的额外开销大于节省的成本。

逐项检查这些盲点

逐项检查 ../generate-program/blindSpots.md 中的每个条目。该文件是规范列表,与 generate-program 的审查循环共享,因此两项技能依据同一标准进行评估。对每个条目,在笔记中输出一行:<slug>: clean | flagged at <file:line>: <issue>。没有记录不代表通过。

最终范例对比

随机挑选一个已审查的文件,并将它与层级匹配范例中的对应文件并排阅读。自问:

  • 已审查的文件看起来像是出自同一人之手吗?
  • 拆分方式显得必然,还是随意?
  • 如果删掉任何一行,审查者会注意到吗?

如果任一问题的答案是「不会」或「我会注意到」,就在报告中标记该文件。

Phase 6:综合发现

严格按以下结构输出报告。不要添加评论,不要填充内容。每一行都必须是一项用户可以采取行动的发现。

## BLOCKERS
Items that are structurally wrong, logically buggy, or violate
Foldkit invariants. Must fix.
Each item: `path/to/file.ts:line: <what's wrong>. Fix: <action>`.
If none: write `None.`

## QUALITY
Items that work but fall short of the bar: generic naming, inline
handlers that should be extracted, native methods instead of Effect
modules in pipes, views that should be decomposed, branch keys or
key-only wrapper elements that view identity makes redundant, etc. Should fix.
Each item: `path/to/file.ts:line: <the gap>. Idiomatic version: <what to write>`.
Cite the exemplar when relevant: "typing-game does this as X at
page/home/update/handleKeyPressed.ts:33-40".
If none: write `None.`

## NICE-TO-HAVE
Polish items that would push quality further but aren't required:
additional tests, slightly better names, minor refactors.
If none: write `None.`

## VERDICT
One of:
- `PASS`: the code is at the bar.
- `NEEDS-WORK`: there are BLOCKERS or QUALITY items to address.

具体说明。毫不留情。不要降低标准。如果不确定是否达标,就与范例对比。如果范例不会那样写,就标记出来。

对于每项发现,提供足够的上下文,让读者无需重新阅读文件也能采取行动:

  • 差:update.ts:47: 命名问题
  • 好:update.ts:47: 名为 'ChangeEmail' 的消息应改为 'UpdatedEmail',以符合此文件中其他地方使用的动词优先、过去时约定(UpdatedPassword、UpdatedUsername)

当多项发现相互重叠时(例如某个辅助函数既有命名问题又有长度问题),请分别报告。不要合并:用户需要分别处理每一项。

Phase 7:提供修复方案(可选)

交付报告后立即停止。 将报告作为单独一条消息发送。不要暂存编辑,不要预先编写打包多项更改的修复计划,也不要在报告的同一条回复中写「我会开始应用这些修复」。

然后,在单独一轮中询问是否应用修复。按可逆性和影响范围分组:

  • 机械式修复(单行编辑、名称更改、导入顺序调整、({}) → ()):可提议一次性批量处理。即使是批量处理,也需要明确同意。「应用全部 12 项机械式修复?」是一个需要明确回答的真实问题;不要假定用户同意后就继续。
  • 结构性修复(提取处理函数、引入 Submodel、拆分视图、用 @foldkit/ui 组件替换手写组件):一次提议一项,逐项确认,并在应用前展示差异。
  • 语义性修复(更改状态结构、用判别联合类型替换布尔值、重构更新逻辑):用文字描述更改,获得明确批准后再应用。

不要悄悄应用修复。不要应用用户未批准的修复。如果用户说「审查我的应用」,但没有说「并修复发现的问题」,就只提供报告,并在进行其他操作前征求同意。

如果用户拒绝修复,审查到此结束,并提供报告。

如果用户接受部分修复,应用这些修复,然后运行四条门禁命令并报告是否通过:

npm run format      # FIRST: rewrites files
npm run lint
npm run typecheck
npm run test

先运行格式化,因为它会重写文件。然后运行 lint、类型检查和测试,以验证将要提交的确切代码。如果应用修复后任何门禁失败,原样展示失败信息。不要自动回滚。不要抑制错误。

最后附上差异摘要:哪些发现已解决,哪些被拒绝,哪些仍未处理。

重点关注事项说明

当 $ARGUMENTS 将范围缩小到某个重点领域时,Phase 5 会缩减为相关的子 Agent 和盲点。报告仍使用 BLOCKERS / QUALITY / NICE-TO-HAVE / VERDICT 结构,但只涵盖该范围。

盲点以其 slug 命名,见 ../generate-program/blindSpots.md。请按 slug 交叉引用,切勿按位置引用。

关注点子 Agent盲区
a11yDhand-rolled-widgets、a11y-gaps、aria-role-confusion
effectsBrepeated-scaffolding、manual-update-return-unpacking、functions-doing-two-things、effect-module-inconsistency、stuttery-modifyFields-setters、hand-rolled-async-state、manual-cache-orchestration
namingCnaming-drift、messages-naming-the-effect、view-named-after-namespace
decompositionCrepeated-scaffolding、functions-doing-two-things
formsD + 表单专属(采用 Input / Textarea、使用 fieldValidation、标签/输入框配对)hand-rolled-widgets、a11y-gaps
routingA + 路由专属(双向解析器用法、将路由视图作为无键路由分支的身份边界、Internal 情况下的 urlToString)hard-coded-route-paths、data-derived-keys
subscriptionsA + 订阅专属(Subscription.make 结构、始终处于活动状态时的 {} 字段、在 Stream.map 内进行消息映射)state-machine-edges
testingEmissing-scene-test
submodelsA + 子模型专属(Got* 封装、可选的 outMessage 返回值、父级 ↔ 子级 Message 隔离)manual-update-return-unpacking、flat-parent-message-union、view-named-after-namespace
types(内联)类型形状和别名manual-update-return-unpacking、array-type-syntax、unearned-type-aliases、hand-rolled-async-state
对于有针对性的审计,请跳过 Phase 4 的完整 grep 区块,只运行与审计重点相关的 grep(例如,对于 a11y,运行 label without For、outline-none without focus-visible 和 _blank 扫描,并检查每个命中项的属性块,查看是否包含 Rel)。

评论

支持 Markdown