# Agent Note: 仅使用 Oxlint 的修复工作流

Status: implemented

[English](2026-08-09-oxlint-only-fix-workflow.md) | 中文

## 问题

[仓库 linter 迁移](2026-07-29-oxlint-linter.md)保留了一次仅用于格式化的 ESLint 调用，因为当时认为 Oxlint 的 JavaScript 插件桥接层只能用于校验。固定版本的 Oxlint 工具链能够执行 `@stylistic/eslint-plugin` 提供的安全修复，因此单独的格式化器重复引入了配置边界、命令启动过程，以及对 `eslint` 和 `@typescript-eslint/parser` 的直接依赖。

单次调用 Oxlint 并不能实现等价替代。相互重叠的插件修复可能先应用一项变更，却留下由此新暴露的诊断；仓库中包含 `semi` 和 `object-curly-spacing` 违规的 fixture（测试前置数据）需要运行第二轮才能完全修复。工作流必须重试这种情况，同时不得打印第一轮中已经失效的诊断。

## 决策

仓库的所有 lint 与修复工作流都通过 [`scripts/run-oxlint.ts`](../../../../scripts/run-oxlint.ts) 调用 Oxlint。普通校验仍由单个进程执行，并直接继承输出。包含 `--fix`、`--fix-suggestions` 或 `--fix-dangerously` 的调用会捕获第一次 Oxlint 的运行结果；如果成功，就通过原有通道输出其 stdout 和 stderr；如果进程正常结束但状态非零，则丢弃其中可能已经失效的诊断，随后以继承输出的方式再运行一次相同命令。子进程被信号终止时，运行器会重新触发该信号，而不会重试或将其转换为退出码；第二个进程的结束结果作为最终结果。

`lint:fix` 包脚本和处理暂存文件的 lefthook 作业直接使用该运行器。类型感知的根配置仍会忽略 `oxlint-tsgolint` 无法分析、需要保留原始形态的 TypeGraph fixture；不加载项目的暂存配置会重新纳入该目录，保留其中有意设置的 `any` 与引号规则例外，并在完整的类型感知修复轮次之前应用其样式修复。仓库中不存在仅用于格式化的 ESLint 配置，也不直接依赖 `eslint` 和 `@typescript-eslint/parser` 这两个开发依赖。`@stylistic/eslint-plugin` 和 `eslint-plugin-sonarjs` 仍作为 Oxlint 的 JavaScript 插件保留，因为它们承载了已强制执行的规则；pnpm 仍会将 ESLint 作为这些插件声明的对等依赖（peer dependency）进行安装，但仓库中的配置和工作流均不会调用它。

## 验证

可执行 lint 约定将一项特意构造、会触发重叠修复的样式违规交给仓库运行器处理，并要求运行器成功退出且最终字节完全一致。同一约定还会固定完整的 Stylistic 规则集、不加载项目时对 TypeGraph fixture 的覆盖、包脚本、暂存文件钩子命令、已删除的格式化器配置，以及 ESLint 解析器和运行器不存在直接依赖这一事实。现有的可执行探针继续覆盖 Stylistic 和 SonarJS 兼容插件、不加载项目的暂存文件校验，以及类型感知的项目发现。

## 考虑过的替代方案

**保留仅用于格式化的 ESLint 轮次。** 这会保留 ESLint 内置的多轮修复行为，但在 Oxlint 已能执行相同插件修复的情况下，仍需维护第二个运行器、重复的格式化配置和直接依赖。

**仅运行一次带 `--fix` 的 Oxlint。** 这样更简单，但相互重叠的安全修复可能使命令只完成部分格式化并以非零状态退出，即使再次运行同一命令就能完成修复。

**采用 Oxfmt。** 迁移格式化器会改变仓库的输出约定，并产生无关的格式化 diff。它与移除冗余 ESLint 执行路径是两项独立决策。

**移除 JavaScript 兼容插件。** 这会消除这些插件引入的 ESLint 对等依赖图，但也会移除强制执行的 Stylistic 和 SonarJS 规则。追求依赖树纯净不能成为削弱质量约定的理由。

## 结果

贡献者、包脚本、钩子和 CI 统一使用一个 lint 运行器和一份规则配置。暂存配置会重复根配置的忽略清单，以便重新纳入仅用于格式化的 fixture，同时避免将 vendor 或生成文件纳入检查。修复进程正常结束但状态非零时，始终会额外执行一次重试；稳定存在且无法修复的错误也不例外。第一轮的每条输出流最多缓冲 64 MiB，因此重试成功时不会打印已经失效的诊断；进程创建或输出捕获失败（包括超出该上限）时会立即报告错误，不会重试。

依赖锁中仍可能因插件的对等依赖解析而包含 ESLint。要移除这个传递依赖，需要使用原生替代方案，或另行决定采用能够同时取代兼容插件的格式化器；这不属于本次工作流简化的范围。
