Storybook PR 维护实战:修复 Lint 与 TypeScript 错误的完整工作流(fix-linting-types-on-pr 技能解析)
本文以 Storybook 仓库中的 Agent 技能文档 SKILL.md 为主体,完整讲解“在 Pull Request 上检出代码、修复全部 linting 与 TypeScript 错误、再推回远端”的八步标准流程。结合仓库中真实的 npm scripts、Nx 任务定义与类型检查脚本源码,你可以掌握该流程每一步背后的底层机制:为什么必须先编译再检查、yarn lint 实际执行的是什么、yarn nx run-many -t check 如何按包过滤类型诊断。
1. 技能定位:这个 SKILL 解决什么问题
.agents/skills/fix-linting-types-on-pr/SKILL.md 是 Storybook 仓库为 AI Agent 定义的一组可复用工作流之一(同级目录还有 canary、docs-review、github-qa-labels、handle-pr-comments、open-pr 等技能)。该技能的 frontmatter 明确界定了触发场景:
---
name: fix-linting-types-on-pr
description: Checks out a PR (including fork PRs), fixes all linting and
TypeScript errors, then pushes the changes back. Use when asked to fix
lint, types, or TS errors on a PR.
---
一句话概括:检出 PR(包括来自 fork 的 PR)→ 自动修复 linting 和类型问题 → 将修复推回。文档将整个过程拆成 8 个步骤加一组边界性 Notes,本文按原步骤顺序展开,并用仓库源码印证每一步的必要性。
2. Step 1–2:获取 PR 号并检出 PR
- Step 1:如果用户已经给出 PR 号则直接使用,否则先询问。
- Step 2:使用
gh pr checkout检出 PR:
gh pr checkout <PR_NUMBER>
选择 gh pr checkout 而非手动 git fetch + 分支切换的关键原因,是它对 fork 来源的 PR 同样有效:文档明确指出,该命令会自动建立正确的远端跟踪关系并切换到 PR 分支——即使 PR 来自外部贡献者的 fork,后续步骤(尤其是最后一步 git push)才能正确推送回贡献者的源分支,而不是误推到本仓库。
这一点在 Step 8 会得到呼应:正因为 gh pr checkout 设置了正确的 upstream 跟踪,最后才能“直接 git push”。
3. Step 3:安装依赖
yarn
Storybook 是一个大型 Yarn Workspaces 单仓。从根目录 package.json 可以看到:
packageManager固定为yarn@4.18.0,即必须使用 Yarn 4(Berry);workspaces.packages覆盖了code/addons/*、code/builders/*、code/core、code/frameworks/*、code/lib/*、code/presets/*、code/renderers/*、agent-eval、scripts等几十个内部包;postinstall脚本会执行husky,在检出分支后重新挂载 Git hooks。
在一个几十个 workspace 包、且 resolutions 中锁定大量版本(如 typescript、react、playwright)的仓库里,跳过安装直接跑 lint/check 会因缺少内部包产物与类型声明而产生大量假性错误——这正是文档把“安装依赖”放在“修复错误”之前单独一步的原因。
4. Step 4:先编译仓库,再谈检查
yarn nx run-many -t compile
文档给出了一行解释:先编译可以确保 linter 所引用的 TS 声明文件已经存在。这一行在本仓库里有非常具体的对应实现,定义在 nx.json 的 targetDefaults.compile 中:
"compile": {
"dependsOn": ["^compile"],
"command": "node --disable-warning=MODULE_TYPELESS_PACKAGE_JSON ./scripts/build/build-package.ts --cwd {projectRoot}",
"cache": true,
"inputs": ["production", "^production"],
"outputs": [
"{projectRoot}/dist",
"{workspaceRoot}/code/bench/esbuild-metafiles/{projectName}"
]
}
从源码结构看有三个要点:
- 依赖拓扑先行:
dependsOn: ["^compile"]表示每个包会先编译它依赖的其他 workspace 包(^表示“被依赖项目”),从而保证跨包引用链上的dist产物完整; - 产物落在
{projectRoot}/dist:Storybook 的包之间通过 workspace 互相引用,被引用包的类型入口指向dist下编译出的声明文件。若未编译,下游包的类型检查会因找不到声明而报“模块不存在/隐式 any”等假错; cache: true:compile 结果可被 Nx 缓存,在修复循环中重跑时命中缓存、显著提速。
5. Step 5:修复 linting 错误
yarn lint
这一步对应根目录 package.json 中的 "lint": "cd code; yarn lint",即进入 code/ 目录执行 code/package.json 的 lint 脚本链:
"lint": "yarn lint:js",
"lint:js": "yarn lint:js:cmd . --quiet",
"lint:js:cmd": "oxlint --report-unused-disable-directives-severity=error"
也就是说,Storybook 的 JS/TS linting 由 oxlint(Rust 实现的高速 linter,仓库锁定版本 1.72.0)驱动。其中 --report-unused-disable-directives-severity=error 值得注意:代码中残留的、已经不起作用的 eslint-disable / oxlint-disable 注释本身会被当作错误报出——修复 PR 时如果顺手删掉了触发规则的代码,必须同时清掉失效的 disable 注释,否则 yarn lint 依然不过。
lint 行为的具体规则定义在 code/.oxlintrc.json,它 extends 根目录 .oxlintrc.json,并加载了 local-rules、eslint-plugin-storybook、eslint-plugin-playwright、eslint-plugin-compat 等插件。其中的规则对“修 lint 不修逻辑”原则有直接约束,例如:
no-restricted-imports禁止从react-aria、react-stately、es-toolkit等包的根入口导入,要求使用具名子模块入口;compat/compat按 browserslist 目标(chrome ≥ 131、safari ≥ 18.3、node ≥ 20 等,见 code/package.json)检查浏览器 API 兼容性;ignorePatterns排除了**/*.vue、**/*.svelte、storybook-static、sandbox等目录。
修复时只处理这些由 linter 报出的问题(错误级别必须清零,warn 级规则如 import/no-named-as-default 可作为参考),不要做超出 lint 范围的重构。
6. Step 6:手动修复 TypeScript 错误并循环验证
yarn nx run-many -t check
check 目标同样定义在 nx.json:
"check": {
"dependsOn": [{ "projects": ["*"], "target": "compile" }],
"command": "yarn exec jiti ./scripts/check/check-package.ts --cwd {projectRoot}",
"cache": true,
"inputs": ["default", "^production"],
"configurations": { "production": {} }
}
两个细节印证了“先 compile 再 check”的必然性:dependsOn 声明了所有项目的 compile 都先于 check 执行;cache: true 使类型检查结果可缓存。
真正执行检查的脚本是 scripts/check/check-package.ts,阅读源码可以看到它的完整行为契约:
- 对每个含
tsconfig.json的包,以 原生 tsc(typescript-native)执行tsc --project tsconfig.json --noEmit --pretty false(check-package.ts#L34-L44); - 设置了 10 分钟超时与 64MB 输出缓冲,超时/被信号终止时明确区分“编译器挂死或崩溃”与“类型错误”,避免把环境问题误当类型问题去修(check-package.ts#L46-L63);
- 最关键的是按包过滤诊断:通过 scripts/check/utils/typescript.ts 中的
filterToPackageDiagnostics,只有落在被检查包自身目录内的诊断才会使其 check 失败,其他 workspace 包引入的诊断被忽略——因此run-many并发跑全部包时,每个包的报错都可以精确归因到具体目录,修复时可以直接定位文件。
文档列出的常见类型错误修复手法(逐条人工编辑、而非盲改):
- 添加或修正类型标注(type annotations);
- 修正错误的泛型(generics)用法;
- 解决违反 strict mode 的
any赋值; - 补齐缺失的 import 或 re-export。
每修完一批编辑就重跑一次 yarn nx run-many -t check,确认错误确实消除再继续。由于 check 有缓存且按包并发,这个“小批量—重跑”的循环在几十个包的仓库里是控制错误面、避免引入新类型错误的实用策略。
7. Step 7–8:只提交改动的文件并推回
git add <files-you-modified>
git commit -m "Maintenance: Fix linting and TypeScript errors"
- 严禁
git add -A:PR 分支上可能残留无关的未跟踪文件,全量暂存会把它们误提交进贡献者的 PR; - 提交信息统一使用
Maintenance: Fix linting and TypeScript errors这类维护型文案,与 PR 的功能变更在 changelog 语义上区分开(仓库的 pr-log 配置将maintenance标签映射为 “Maintenance” 分区,见 code/package.json 的pr-log.validLabels)。
git push
对 fork PR,gh pr checkout 已设置好指向贡献者远端分支的 upstream,直接 git push 即可把修复叠加到 PR 分支上,维护者无需在本地重新发起 PR。
8. Notes:边界条件与升级人工确认的时机
文档末尾 Notes 定义了这套流程的安全边界,全部是“什么情况下不该继续自动修”的判断准则:
- 只修明确的 linting / TypeScript 问题,不重构逻辑——保持 diff 最小、可审查;
- 若某个类型错误需要非平凡的代码改动(例如要改公共 API 签名),必须先向用户(维护者)说明并请求确认,而不是擅自改;
- 若
gh pr checkout因 fork 权限失败,应告知用户:可能需要贡献者授予 fork 写权限,或由维护者直接向其分支推送; - 推送后需与维护者确认 CI 已变绿,流程才算闭环。
这条 CI 闭环与仓库的 CI 检查链是一一对应的:code/package.json 的 ci-tests 脚本为
yarn task --task check --no-link --start-from=install && yarn lint && cd .. && yarn test
即“check(含全量 compile 依赖)→ lint → 单测”三段式。本技能在 PR 分支上把前两段(check 依赖的 compile、lint)完整重放了一遍,再推回触发 CI 验证,因此推送后 CI 的主要风险只剩下第三方 flaky 用例——这也是第 4 条 Notes 要求人工确认的原因。
9. 流程小结:可复制的八步检查单
| 步骤 | 命令 / 动作 | 仓库内对应实现 |
|---|---|---|
| 1 | 确认 PR 号(用户给出或询问) | — |
| 2 | gh pr checkout <PR_NUMBER> |
兼容 fork PR,建立正确 upstream |
| 3 | yarn |
Yarn 4.18.0 workspaces,postinstall: husky |
| 4 | yarn nx run-many -t compile |
nx.json compile 目标:dependsOn: ["^compile"],产物 {projectRoot}/dist |
| 5 | yarn lint |
code/ 下 oxlint 1.72.0,规则见 code/.oxlintrc.json |
| 6 | yarn nx run-many -t check + 人工修类型 + 循环重跑 |
scripts/check/check-package.ts:原生 tsc --noEmit,按包过滤诊断 |
| 7 | git add <files-you-modified> + git commit |
禁用 git add -A |
| 8 | git push,并确认 CI 变绿 |
对应 ci-tests:check → lint → test |
这套流程的价值在于:它把“修 PR 上的 lint/类型问题”这件高频维护工作压缩成了可预测、可缓存、可归因的机械步骤——编译保证声明产物存在,oxlint 与按包过滤的 tsc 保证错误精确定位,最小化暂存与 fork 权限处理保证了操作不会伤及 PR 分支上的他人改动。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00