首页
/ axios 仓库贡献规范深读:AGENTS.md 如何统一定义人与 AI Agent 的工程实践

axios 仓库贡献规范深读:AGENTS.md 如何统一定义人与 AI Agent 的工程实践

2026-09-06 18:39:58作者:袁立春Spencer

axios 是一个面向浏览器与 Node.js 的 Promise 风格 HTTP 客户端。它的根目录维护了一份名为 AGENTS.md 的"规范级贡献者指南",同时约束人类维护者与 AI 编码代理:从安装安全、构建命令、包结构、架构边界,到错误码约定、拦截器执行顺序、请求生命周期和安全敏感代码的回归红线,全部以可直接执行的规则形式固化在仓库中。读完本篇,你将掌握在 axios 仓库中安全安装依赖、正确运行各类测试套件、按架构边界修改代码,以及理解其原型污染防护等安全机制的完整方法。

AGENTS.md 的定位:唯一权威贡献者指南

axios 自我定位是一个 Promise based HTTP client,用于浏览器和 Node.js。默认实例由 index.jslib/axios.js 导出;浏览器构建使用 XHR 或 Fetch 适配器,Node 端使用 HTTP/HTTPS 适配器,而平台选择逻辑集中在 lib/platform/ 下。

AGENTS.md 明确声明自己是"该仓库中人类与 AI 代理共同的唯一权威(canonical)贡献者指南"。配套地,.github/copilot-instructions.md 只是一个"薄垫片(thin stub)",其内容指向 AGENTS.md,并同步了其中"承重(load-bearing)"的安全规则子集;一旦两者漂移,以 AGENTS.md 为准。这种"单一权威 + 工具入口垫片"的组织方式,使得 Copilot、Claude Code 等 AI 工具与人类维护者遵循同一套规则。此外,CLAUDE.md 仅包含一行 @AGENTS.md 引用,说明该规范已事实上成为整个仓库工程约定的单一事实来源。

安装与供应链安全:npm ci 与 ignore-scripts

AGENTS.md 的"Setup And Safety"一节把安装流程当作安全边界来对待,核心规则如下:

  • 统一使用 npm ci 安装;仓库 .npmrc 中写死了 ignore-scripts=true,CI 同样使用 npm ci --ignore-scripts。已在仓库中确认 .npmrc 内容即为单行 ignore-scripts=true
  • 禁止删除 ignore-scripts=true。如果新装后确实需要 git hooks,只需一次性执行 npm rebuild husky && npx husky 恢复 husky 钩子,而不是放开安装脚本执行。
  • 依赖的增改属于安全敏感操作:package-lock.json 会经过 lockfile-lint 校验 npm HTTPS 主机来源与 integrity 哈希。
  • 涉及 package、lockfile、GitHub Actions 的更新 PR 仅限维护者/机器人;外部协作者应直接关闭这类 PR。Dependabot 保留 7 天延迟,除非出现严重漏洞需要维护者主导的手动更新。
  • 即使设置了 ignore-scripts,build/test/lint 工具在运行阶段仍会执行依赖代码;因此能用"聚焦检查"证明改动时,就不要跑完整构建。
  • 未经讨论不得新增运行时依赖——axios 的依赖面被刻意维持得极小。

package.json 可以印证这一"极小依赖面"策略:运行时依赖仅有 4 个(follow-redirectsform-datahttps-proxy-agentproxy-from-env),其余全部是 devDependencies 中的构建与测试工具链。

命令体系:构建、Lint 与分层测试

AGENTS.md 的"Commands"一节定义了仓库内的标准命令,均可在 package.jsonscripts 字段中得到逐条对应:

用途 命令 说明
构建发布产物 npm run build 对应 gulp clear && cross-env NODE_ENV=production rollup -c:先 gulp clear 删除 dist/,再由 Rollup 产出浏览器 ESM/UMD/CJS 与 Node CJS 各 bundle
仅 Lint 源码 npm run lint 对应 eslint lib/**/*.js;聚焦检查可写 npx eslint lib/path/to/file.js
单元测试 npm run test:vitest:unit 对应 vitest run --project unit;聚焦单文件:npm run test:vitest:unit -- tests/unit/path.test.js
浏览器测试 npx playwright install(CI 用 npx playwright install --with-deps),再 npm run test:vitest:browser:headless 与 CI 行为对齐
Smoke/模块兼容套件 npm run buildnpm pack,把 tarball 安装进对应的 tests/smoke/*tests/module/* 包,再运行该套件自己的 npm 脚本 关键区别:这些套件测的是打包后的产物,不是源码树

vitest.config.js 进一步揭示了测试项目的划分:unit(Node 环境,匹配 tests/unit/**/*.test.js)、browser(Playwright 驱动 Chromium)与 browser-headless(Chromium/Firefox/WebKit 三浏览器 headless,并加载 tests/setup/browser.setup.js)。

文档同时固化了 CI 的执行顺序:install → build → Playwright 安装 → unit → browser headless → pack → CJS/ESM 模块与 smoke 测试 → Bun/Deno smoke 测试。这一顺序解释了为什么 smoke 套件必须先 build 再 pack——它们消费的是与用户实际安装一致的 tarball。

包形态(Package Shape):ESM 源码、按环境切分的导出

"Package Shape"一节规定了 axios 的包结构与发布契约:

  • 源码是 ESM("type": "module");公开 ESM 入口为 index.js,它把 lib/axios.js 的默认实例解包成命名导出——实际可以看到 index.jscreateAxiosAxiosErrorCanceledErrorCancelTokenAxiosHeadersHttpStatusCodegetAdaptermergeConfigtoFormData 等 17 个成员具名导出,同时保留 default,从而在 ESM 与 CJS 消费侧保持静态属性一致。
  • 禁止手工编辑 dist/:它是被忽略的、由 Rollup 从 lib/ 生成的产物。
  • 运行时导出按环境切分。package.jsonexports 字段展示了这一设计:bundefault 环境分别映射 ./dist/node/axios.cjs(require)与 ./index.js(import);react-nativebrowser 环境则映射浏览器产物。同时 browserreact-native 字段把 ./lib/adapters/http.js./lib/platform/node/index.js./lib/platform/node/classes/Buffer.js./lib/platform/node/classes/FormData.js 重定向到 lib/helpers/null.js 或浏览器平台实现,完成"Node 文件 → 浏览器/null 替换"。
  • 公共运行时导出、index.d.ts(ESM 类型)与 index.d.cts(CJS 的 export = axios 类型)必须在 API 变更时保持同步。
  • lib/env/data.jsgulp version 在版本号变更时生成(对应 preversion: gulp version 脚本),日常功能开发不应直接编辑它。

预发布记录规范:CHANGELOG 与 PRE_RELEASE 双轨制

axios 把"未发布变更"与"已发布记录"严格分轨,这在很多项目里是容易混乱的角落,AGENTS.md 给出了明确规则:

  • 用户可见的未发布变更写入 PRE_RELEASE_CHANGELOG.md,而不是 CHANGELOG.md;后者是"发布所有"的文件,只在准备真正发布时更新。
  • 推迟的 README、文档站、examples、迁移指南(MIGRATION_GUIDE.md)以及多语言文档更新,统一记录在 PRE_RELEASE_DOCS.md 中;要求提供足够的上下文以便发布时套用,禁止存"脆弱的 diff 或只有行号的笔记"。
  • 在功能/修复工作中,除非任务明确是发布准备,否则不要更新 README 或文档站;正确做法是把"文档将来应该写什么"记入 PRE_RELEASE_DOCS.md,留待发布阶段统一落地。

架构边界:core / adapters / platform / helpers 的分工

AGENTS.md 的"Architecture Boundaries"一节是理解 axios 源码结构的核心地图,每条边界都可在源码中得到验证:

lib/core/——领域逻辑层。 负责请求分发、配置合并、拦截器、请求头与错误。关键类包括:

lib/adapters/——I/O 层。 执行真正的网络请求。默认适配器偏好顺序为 ['xhr', 'http', 'fetch'],能力选择发生在 lib/adapters/adapters.js。打开该文件可以看到 getAdapter(adapters, config) 的完整算法:把输入归一化为数组后逐项检查——若项是已解析句柄(函数、nullfalse)则直接使用;否则按名称查 knownAdapters 表,未知名称直接抛 AxiosError('Unknown adapter ...');对 fetch 这类惰性适配器还会调用其 get(config) 做能力探测。全部落选时,函数汇总每个适配器的拒绝原因(区分"is not supported by the environment"与"is not available in the build"),抛出带 ERR_NOT_SUPPORT 码的 AxiosError。文档特别强调:按能力选择适配器,而不是按环境名称判断

lib/platform/——平台选择层。 lib/platform/index.js 默认聚合 Node 平台实现(./node/index.js./common/utils.js);浏览器构建则依靠打包期的 browser 字段与 Rollup alias 替换到 lib/platform/browser

lib/helpers/——通用工具层。 约束是"应保持通用、可脱离 axios 复用",不得把 axios 特有的请求生命周期逻辑放进这里。

新文件风格要求:lib/**/*.js 必须使用显式 .js 扩展名的 ESM 导入、在既有文件使用 'use strict'; 的位置保持一致、axios 源自的失败一律抛 AxiosError

命名约定与错误处理规范

命名约定(可直接作为代码评审检查单):

  • 类用 PascalCase:AxiosAxiosErrorInterceptorManager
  • 函数用 camelCase:buildURLmergeConfigdispatchRequest
  • 错误码用 UPPER_SNAKE_CASE 常量挂到 AxiosError 上:ERR_NETWORKETIMEDOUT 等;
  • 内部类槽位使用 Symbol 键(例如 lib/core/AxiosHeaders.js 中的 const $internals = Symbol('internals')),而非下划线前缀属性。

错误处理规则与 lib/core/AxiosError.js 的实现一一对应:

  • axios 源自的失败必须抛 AxiosError 而不是裸 Error,构造参数为 (message, code, config, request, response) 五元组,让消费方可以做结构化内省。源码中构造函数会把 configrequestresponse 挂到错误实例上,并从 response.status 派生 status 字段。
  • 第三方错误用静态方法 AxiosError.from(error, code, config, request, response) 包装。从源码看,from 还处理了 Node 双栈连接失败产生的 AggregateError(message 为空时聚合 errors[]),并把原错误挂到不可枚举cause 上——注释明确说明这是为了避免包装的 socket/request 循环引用破坏 pino/winston 等结构化日志。
  • 权威错误码清单定义在 lib/core/AxiosError.js 文件末尾,共 14 个:ERR_BAD_OPTION_VALUEERR_BAD_OPTIONECONNABORTEDETIMEDOUTECONNREFUSEDERR_NETWORKERR_FR_TOO_MANY_REDIRECTSERR_DEPRECATEDERR_BAD_RESPONSEERR_BAD_REQUESTERR_CANCELEDERR_NOT_SUPPORTERR_INVALID_URLERR_FORM_DATA_DEPTH_EXCEEDED
  • 配置选项校验统一走 validator helper(lib/helpers/validator.js),不得自造临时校验路径。

另一个值得注意的实现细节:AxiosError.toJSON() 支持通过请求配置中的 redact 数组合做敏感键脱敏——匹配键(不区分大小写、任意深度)在序列化快照中被替换为 [REDACTED ****] 常量。这意味着把 AxiosError 直接 JSON.stringify 进日志时,可以防止 auth 等字段意外落盘,这是错误处理规范之外的又一安全设计。

拦截器执行顺序与请求生命周期

拦截器顺序在 axios 中既影响行为也影响测试,AGENTS.md 把它写成硬性规则:

  • 请求拦截器:后注册先执行(LIFO)
  • 响应拦截器:先注册先执行(FIFO)
  • 两者都支持 synchronous: true(当链中无异步 handler 时避免 Promise 包装开销)与 runWhen: (config) => boolean 条件执行;
  • 新增内建拦截器时必须把顺序写入文档。

由此形成的完整请求生命周期共 9 步:

  1. 用户调用 axios() 或方法别名(get/post 等);
  2. 通过 mergeConfiglib/core/mergeConfig.js)合并实例默认值与请求配置;
  3. 执行请求拦截器(LIFO);
  4. lib/adapters/adapters.js 的能力检查选定适配器;
  5. 依次应用 transformRequest 函数;
  6. 适配器执行 HTTP 请求;
  7. 依次应用 transformResponse 函数;
  8. 执行响应拦截器(FIFO);
  9. AxiosResponse 兑现 Promise,或以 AxiosError 拒绝。

取消机制的不变量

取消(Cancellation)一节给出三条不变量,适用于任何生命周期阶段(包括响应体读取中途):

  • CancelToken(旧式,见 lib/cancel/CancelToken.js)与 AbortSignal(现代 API)双通道并存,不得破坏任何一条路径;
  • 取消必须在请求的任意阶段生效,含 in-flight 的 body 读取;
  • 结算或取消时必须移除 signal 监听器,防止内存泄漏。

常见陷阱清单:四条可执行的"禁止事项"

AGENTS.md 的"Common Pitfalls"是把历次修复经验固化的负空间清单:

  1. 禁止原地变更 config 对象——merge/transform 一律返回新对象;
  2. 禁止假设浏览器或 Node 特有全局变量存在——先做能力检查;
  3. 禁止直接使用 Function.prototype.bind——必须用 lib/helpers/bind.js。查看该文件可见它只有几行:fn.apply(thisArg, arguments) 包装,通过 apply 转发原始 arguments,这是库内其他代码依赖的行为;
  4. 库代码中禁止抛裸 Error——一律 AxiosError 并附合适错误码。

测试体系:运行时优先的分层布局

测试规则("Tests"一节)与 tests/ 目录结构完全对应:

  • 布局按运行时优先组织:单元测试 tests/unit/**/*.test.js、浏览器测试 tests/browser/**/*.browser.test.js(对应 vitest 的 browser/browser-headless 项目)、smoke 套件 tests/smoke/esm/**/*.smoke.test.jstests/smoke/cjs/**/*.smoke.test.cjs
  • 本地 HTTP 服务统一用 tests/setup/server.js 创建,并在 try/finally 中清理——文档特别警告"泄漏的服务会导致 Vitest 挂死";
  • 行为涉及打包/导入时,CJS 与 ESM 的 smoke 覆盖必须保持对齐;
  • 类型兼容由两个模块套件分别守护:tests/module/cjsTypeScript 4.9tests/module/esmTypeScript 5.x;改动声明文件(index.d.ts/index.d.cts)时须运行对应套件;
  • 浏览器测试会替换 XHR 等全局对象,因此必须在清理钩子中恢复全局、重置 spy。

安全敏感代码:原型污染防护与威胁模型联动

AGENTS.md 的最后一节定义了"不可回归"的安全红线,并且每一条都能在源码中找到落点:

  • 配置读取禁止原型遍历。对于影响行为的配置读取,不得使用 in、解构或直接 config.foo 访问不可信配置,必须用自有属性检查。这正对应 utils.hasOwnProp 与本地 own() helper 的使用模式——在 lib/core/mergeConfig.js 中可以看到大量 utils.hasOwnProp(config2, prop) 形式的读取,合并入口甚至为 hasOwnProperty 自身恢复了不可枚举的自有槽位。
  • 合并与对象物化必须持续过滤 __proto__constructorprototype,这里的回归就是安全 bug。源码印证:mergeConfig.js 的深合并循环开头即有 if (prop === '__proto__' || prop === 'constructor' || prop === 'prototype') return; 的守卫。
  • 触及 URL 构建、重定向、代理/环境变量处理、XSRF、socket 路径、解压限制或适配器的改动,应先查阅 THREATMODEL.md 并补充聚焦的回归测试。
  • withXSRFToken 的跨域行为保持显式:只有 true 才强制跨域附加 XSRF 头,不得扩大该触发条件。
  • 不得弱化 beforeRedirect、代理、socketPath 的防护,除非有覆盖凭据泄漏与 SSRF 类场景的测试兜底。

小结:一份可被机器执行的工程契约

AGENTS.md 的价值在于它把"隐性工程共识"翻译成了对人和 AI 同样生效的显式契约:安装安全(npm ci + ignore-scripts)、命令语义(build/lint/unit/browser/smoke 各自的适用对象与前置条件)、包形态(ESM 源码 + 按环境切分的 exports + 双类型声明文件同步)、架构边界(core/adapters/platform/helpers 四分层与"按能力选适配器")、命名与错误码约定、拦截器 LIFO/FIFO 顺序、取消双通道不变量、分层测试布局,以及原型污染过滤与安全敏感改动的强制威胁模型评审。对照 lib/adapters/adapters.jslib/core/AxiosError.jslib/core/mergeConfig.js 等源码即可逐条验证这些规则不是空泛口号,而是与实现深度咬合的工程红线。对于希望深入 axios 内部机制的开发者,这份文档是最快的"仓库地图"入口。

登录后查看全文
热门项目推荐
相关项目推荐