Dependabot 修复 是做什么的?
系统性地修复一个失败的 Dependabot PR。用户会提供一个 PR 编号(例如 /dependabot-fix 42)。
此技能包含六个阶段(A 到 F)。在完成阶段 D 之前不要开始编辑代码。在完成阶段 B 之前不要深入查看日志。
开始时就设置好 scratch 空间:
mkdir -p ./tmp && touch ./tmp/null
PR_NUM=<来自用户提示>
REPO=EthicalML/ethical
阶段 A — 上下文
步骤 1 · PR 上下文
获取元数据,并生成一段 PR 的书面摘要(生态系统、分组、大小、是否为安全更新、涉及哪些文件)。不要打开源文件。
gh pr view $PR_NUM --repo $REPO --json title,body,headRefName,labels,files,mergeable,createdAt
gh pr diff $PR_NUM --repo $REPO | head -200
识别:
- 生态系统:
npm|github-actions——该仓库仅有这两种 - 分组:分组(
all,all-security)与单个 npm 主版本(主版本被有意排除在all组之外,因此每个主版本单独到达) - 大小:更改的文件数量和大约的 LOC 变化;对于 npm,检查
package.json是否移动,还是仅更改了package-lock.json - 任何更改的依赖是否可能影响渲染输出——这决定了阶段 E(参见步骤 8.5)
步骤 2 · 高层错误调查
列出失败的检查,并从每个失败作业的日志中捕获第一条和最后一条错误行。不要调查它们的含义——仅列举症状。
CI(.github/workflows/ci.yml)恰好有三个必需的作业:lint(ESLint + Prettier)、typecheck(npm run check:ratchet)和 build(演示模式中的生产 Astro 构建,无 FORM_ENDPOINT)。其他任何红色的都不是必需的检查。
gh pr checks $PR_NUM --repo $REPO
# 对于每个失败的检查,从 URL 获取作业 ID 并拉取日志
gh run view --job <JOB_ID> --repo $REPO --log 2>./tmp/null \
| grep -iE "error|exit code|##\[error\]|FAILED|assert|timed ?out" \
| head -20 > ./tmp/pr-${PR_NUM}-symptoms.txt
输出应为一个项目符号列表,例如:
lint:Prettier check failed: package.jsontypecheck:astro check reported 3 errors, baseline is 0build:[vite] Rollup failed to resolve import "..."
注意 CI 看不到的内容。 Lint、astro-check ratchet 和成功的构建对页面的外观没有任何说明。这个差距是阶段 E 的工作,并且是该仓库中此技能价值最高的部分。
阶段 B — 通过子代理引入上下文
生成两到三个并行的 explore 子代理,其范围限定为 PR 涉及的内容。事先不要自行阅读这些内容——委托给代理。
步骤 3 · 约定子代理
.github/copilot-instructions.md 是该仓库中规范的代理入口点(根目录的 AGENTS.md 和 CLAUDE.md 是指向它的符号链接)。让子代理读取它并返回:变更工作流、本地检查、提交/PR 约定,以及与涉及路径相关的任何注意事项。当 PR 可能涉及样式或 token ratchet 时,也指向 STYLES.md;当组件 API 可能被涉及,指向 REUSABLE.md。
步骤 4 · 验证工具子代理
让它读取 scripts/verify/README.md 并返回一个简报(约 40 行),内容是关于本地验证工具:verify:dom、verify:shots、verify:parity、verify:typewriter 和 check:ratchet 各自断言什么,主题标志和视口标志如何工作,输出位置,以及 parity 允许列表机制是什么以及不是什么。这个简报就是阶段 E 将执行的依据。
步骤 5 · 代码库子代理
让它为涉及的区域生成一个定向地图:
- diff 中的哪些包实际上被
src/**或scripts/**导入,以及在哪里被导入(某个未被任何内容导入的 bump 与astro的 bump 属于不同的风险类别) - 构建和检查命令(
npm run build,npm run lint,npm run format:check,npm run check:ratchet) - bump 是否涉及构建流水线(
astro,vite,@astrojs/*,pagefind,mdx/rehype/remark插件)还是仅涉及工具链(eslint,prettier,typescript) - 对于
github-actionsPR:哪些工作流发生变化,以及是否有任何更改影响部署路径(.github/workflows/deploy.yml)而不仅仅是 CI
子代理的简报共同构成阶段 C 的工作上下文。
阶段 C — 深度根因诊断
步骤 6 · 诊断
现在——并且仅现在——结合阶段 B 的全部上下文深入失败的作业日志。对于每个失败的检查,将第一条有意义的错误追溯至:
- 来自被 bump 依赖的直接回归(导出的删除、签名更改、更严格的验证、默认值更改)
- 传递性工具链问题(依赖需要比
.tool-versions中固定的版本更新的 Node;postinstall 需要更新的运行时;可选的原生绑定未解析) - 无害的 bump 暴露了预先存在的脆弱性(astro-check 基线、
-a###token ratchet 或STYLES.md哈希因格式化变动而错误) - 基础设施不稳定(注册表速率限制、取消的作业、
npm ci网络故障)
对于分组的 PR,分别诊断每个失败的检查——失败可能有独立的原因,批次中通常可追溯到不同的包。将发现记录在 ./tmp/pr-${PR_NUM}-diagnosis.md 中。
阶段 D — 修复设计
步骤 6.5 · 范围分类——这是修复,还是 Dependabot 配置问题?
在计划修复之前,检查 PR 是否在可修复的范围内。.github/dependabot.yml 将常规的 npm 次要/补丁更新分批到每个生态系统的单个 all 组中,加上一个 all-security 组,并有意将 npm 主版本从 all 组中排除,因此每个主版本单独到达。这种结构意味着两种不同的失败情况:
一个分组的 PR 在一个包上失败。 这是常见情况,并且在范围内。不要放弃整个批次。找到单个有问题的包,要么为其修复代码,要么固定该包(在 package.json 中缩小范围的条目,或忽略该包和版本的 Dependabot ignore 条目),以便批次的其余部分可以落地。放弃一个除一个错误外全部绿色的批次,会为了一个坏更新而抛弃十多个好更新。
范围拒绝触发器(任何一个就足够了——PR 是一个配置问题,而非修复):
- 单个分组 PR 包含 ≥ 2 个主版本 bump——在当前 npm 组配置下这应该是不可能的,所以如果发生,配置本身已漂移并且是真正的 bug
- 一个框架级包(
astro、vite、@astrojs/*、typescript、eslint、prettier、preact、pagefind)上的单个主版本,且这是一个真正的迁移——不是修复,而是一个项目 - 批次大部分是常规的,但失败需要对一个无法被绕过固定的包进行迁移
当触发器被触发时,不要尝试修复且不要自行关闭 PR——保持打开状态供仓库所有者处理。相反:
- 如果原因是配置漂移,准备
.github/dependabot.yml的修正作为单独的 PR(留待仓库所有者审查和合并)。否则,不需要更改配置,建议仅将迁移安排为单独的工作。 - 将范围拒绝决定以评论形式发布在 Dependabot PR 上:解释为什么无法一次性修复,链接任何配置 PR,并说明迁移将涉及什么。保持 PR 打开——不要暂停等待决定,不要关闭它。
- 跳过阶段 F 的提交流程——没有修复。报告内容会融入该评论中。
安全更新组(all-security)保持捆绑:安全 bump 是时间敏感的且足够罕见,拆分它们得不偿失。仅当有具体的阻塞因素时才拆分。
步骤 7 · 综合计划
编写一个涵盖以下内容的计划;按风险缩放深度:
| 部分 | 始终 | 如果风险 ≥ 中等 |
|---|---|---|
| 根因 | ✅ | ✅ |
| 预期更改的文件 | ✅ | ✅ |
| 修复方法(及考虑的替代方案) | ✅ | ✅ |
| 风险评级(低/中/高) | ✅ | ✅ |
| 复现步骤 | ✅ | ✅(必须可执行) |
| 手动测试策略 | ✅ | ✅ 扩展 |
| 回滚计划 | ✅ | |
| 影响范围(渲染输出/部署/URL) | ✅ |
风险 ≥ 中等,如果满足以下任何一项:
- bump 涉及构建流水线(
astro、vite、@astrojs/*、MDX/rehype/remark 插件、pagefind) - 修复编辑
src/下的任何内容而非仅仅是配置或锁定文件 - bump 可以更改生成的 HTML、CSS 排序、字体加载或水合行为
github-actions更改涉及deploy.yml(部署失败是实时站点中断,而非红色检查)
任何 npm 主版本默认至少为中等。主版本到达正好是因为它需要单独阅读和测试;以比同等大小的批次更严格的审查对待它。
步骤 8 · 手动测试策略(分层)
根据步骤 7 的风险评级对工作分层:
- 低(仅工具链)——应用修复,运行最狭窄的相关门控(
npm run lint、npm run format:check或npm run check:ratchet)。无需复现步骤。 - 中等(构建流水线或
src/编辑)——首先在 PR 分支本地复现失败,以证明回归是真实的(而不是 CI 制品)。然后应用修复,重新运行完整的本地检查:npm run lint && npm run format:check && npm run check:ratchet && npm run build。然后阶段 E。 - 高(主版本、任何可能更改渲染输出的内容、部署更改)——中等中的所有内容,加上阶段 E 的 parity 扫描是强制性的,加上针对构建的 PR head 运行
npm run verify:dom:all,如果涉及 hero 或 motion token,还要运行npm run verify:typewriter。
将所有 scratch 输出保留在 ./tmp/ 下(已添加到 gitignore)。在抑制输出时使用 ./tmp/null 作为接收器:
npm run build 2>./tmp/null
阶段 E — 针对合并基础的视觉对比
CI 证明站点仍然可以构建。它不能证明站点看起来仍然相同。依赖 bump 根据定义应该像素无关——如果不是,则某些内容渲染不同,而没有人会看到它。这个阶段是该技能在此仓库中存在的原因。
没有像素基线被提交(scripts/verify/out/ 和 tmp/ 已添加到 gitignore),因此比较必须在运行中生成:捕获合并基础,捕获 PR head,进行比较。
步骤 8.5 · 决定是否运行
每次 PR 扫描大约需要 4-5 分钟(两次构建加上对 37 条路由 × 2 个视口的截图扫描)。仅当更改不可能影响渲染时才跳过它。当满足所有以下条件时可跳过:
- PR 仅为
github-actions,或仅涉及.github/** src/、public/、astro.config.mjs或package.json的dependencies下的文件未更改- diff 仅限于 devDependencies,它们的输出不会到达
dist/(eslint*、prettier、@types/*、typescript,当 ratchet 未更改时)
任何其他情况——任何 astro、vite、@astrojs/*、preact、pagefind、MDX/rehype/remark、字体或 CSS 工具 bump,以及每个 npm 主版本——都必须运行扫描。若有疑问,运行它;五分钟比实时站点上的无声视觉回归更便宜。
在报告中明确说明采取了这个决策的哪个分支以及原因。
步骤 9 · 捕获和比较
Playwright 不是仓库依赖项;该工具从已集成到脚本中的 npx 缓存路径驱动它。使用纯静态服务器服务每个构建。
BASE_SHA=$(git merge-base origin/master HEAD)
mkdir -p ./tmp/parity
# --- 合并基础 ---
git checkout $BASE_SHA
npm ci
npm run build
cp dist/index.html ./tmp/parity/base-index.html # 我们正在服务的指纹
nohup npx --yes http-server dist -p 4126 --silent < ./tmp/null > ./tmp/parity/base-server.log 2>&1 &
BASE_SERVER=$!
npm run verify:shots:all
kill $BASE_SERVER
cp -R scripts/verify/out ./tmp/parity/base
# --- PR head ---
gh pr checkout $PR_NUM --repo $REPO
npm ci
npm run build
cp dist/index.html ./tmp/parity/head-index.html
nohup npx --yes http-server dist -p 4126 --silent < ./tmp/null > ./tmp/parity/head-server.log 2>&1 &
HEAD_SERVER=$!
npm run verify:shots:all
kill $HEAD_SERVER
cp -R scripts/verify/out ./tmp/parity/head
# --- 对比,按视口 ---
npm run verify:parity -- ./tmp/parity/base/1440 ./tmp/parity/head/1440 > ./tmp/parity/1440.json
npm run verify:parity -- ./tmp/parity/base/420 ./tmp/parity/head/420 > ./tmp/parity/420.json
过时的 dist 陷阱。 在两次构建中重用端口 4126 是产生自信而无意义的绿色的最可能方式:如果旧服务器实际上没有关闭,或者 dist 没有被重建,第二次扫描会拍摄第一次构建的照片,并且 parity 通过,因为你比较了两次相同的构建。每次运行都要防止:
- 在启动下一个服务器之前确认前一个服务器已关闭(
lsof -i :4126应返回为空)。 - 比较两个指纹——对于更改了
src/或构建流水线依赖的 PR,cmp ./tmp/parity/base-index.html ./tmp/parity/head-index.html返回 "identical" 是表示不信任该运行的信号,而不是结果。 - 如果扫描自身的 JSON 报告
compared: 0或每条路由缺失,则服务器未启动;该运行无效,而不是通过。
verify:shots 将暗色主题输出写入 out/<width>/。如果一个 bump 有可能对亮色主题产生不同影响(CSS 工具、颜色处理、token 发射),使用 --theme light 重复扫描,这将写入 out/light/<width>/,并且不会覆盖暗色树。
步骤 10 · 解释结果
verify-parity 每条路由报告两个数字,两者都很重要
…
[truncated for translation]