先记住这个答案
Agent 先解析 monorepo 的包依赖图,从任务入口出发做可达性分析,收集直接依赖的文件,再按包边界确定修改候选。改动只允许落在目标包及其受影响的直接消费者内,利用构建系统的依赖感知能力做范围校验,拒绝跨越无关包。通过对比修改前后依赖图差异,确认没有引入新的外部依赖或改动未声明文件。
- 用包边界隔离改动,禁止跨包乱改。
- 依赖图可达性分析确定受影响的消费者。
- 提交前对比依赖图变化,防新增隐式耦合。
依赖图驱动的范围圈定机制
Agent 启动时先加载 monorepo 的依赖图,如基于 package.json 的 workspace 依赖或 Bazel 的 BUILD 目标。每个节点代表一个包或模块,边表示直接依赖关系。对给定任务,Agent 提取入口文件或目标包,执行反向依赖分析,找出所有可能受影响的包,生成候选改动集。
再结合包边界过滤:若任务只需修改包 A 的内部实现,则候选集仅包含 A;若修改 A 的导出接口,则需将直接依赖 A 的包 B、C 纳入,但不应蔓延到 B 的依赖方,除非 A 的变更通过 B 传递。通过遍历依赖图,采用分层可达性,控制传播深度为 1(直接消费者)或按需扩展,并利用包管理器的 lockfile 交叉验证。
修复共享工具库 bug 的场景
假设 monorepo 有 packages/ui、packages/utils、apps/web 和 apps/api,其中 apps/web 依赖 utils 和 ui,apps/api 只依赖 utils。任务要求修复 utils 中日期格式化函数返回错误时区的问题。Agent 解析依赖图后,发现 utils 被 web 和 api 直接使用,因此先将候选集合限定为 utils 本身。
Agent 修改 utils/src/date.ts 并通过该包测试后,还需运行 web 和 api 中引用了此函数的测试模块,因为行为可能影响输出。但无需改动 web/api 的源文件。Agent 最终提交只含 utils 包内的单个文件变更,并在 PR 描述中列出受影响的消费者。通过对比依赖图前后差异,确认没有引入新导入或修改包导出清单。
失效条件与应对策略
依赖图可能不完整:monorepo 中存在动态导入或路径别名超出声明时,Agent 可能漏判。例如代码通过字符串拼接 require 模块,或者直接使用源码相对路径跨包引用,这绕过了包边界。此时基于 lockfile 的静态分析会失效,Agent 必须结合全仓库的 import 扫描补充边。
应对策略是维护一个“实际依赖”层,使用 AST 分析所有 import 语句生成更精确的边。当包边界被破坏时,Agent 应报告这类跨包直接引用为架构违规,而不是默默扩大改动范围。若不能全量扫描,可采用保守策略:将任何未解析的导入视为可能依赖所有包,但代价是范围变大,需人工确认。
容易答错的地方
- 认为改动越少越好
- 最小改动不等于只改一个文件。如果共享库的修改影响多个消费者,Agent 必须调整相关调用点以保持兼容,否则测试会失败。正确的范围是满足任务目标且通过所有相关测试的最小文件集合。
- 忽略传递依赖
- 只检查直接依赖方而忽略传递依赖可能导致构建破裂。例如修改包的默认导出类型,间接消费者可能因类型不匹配报错。Agent 应运行全仓库类型检查或受影响包的测试来捕获这类问题。
面试官还会怎么问?
当依赖图巨大时,如何高效计算可达集?
可预先构建依赖图的邻接表并缓存,使用 BFS 限制深度。对于常见任务,只计算直接消费者;需要更深时,用剪枝策略跳过测试无关的包。也可依赖 Bazel 的 query 命令快速获取 affected 集合。
如何处理 monorepo 中的循环依赖?
循环依赖会破坏层次化边界。Agent 应识别强连通分量,将整个分量视为单一改动单元。若必须打破循环,需应用依赖反转并经过架构评审,而不是让 Agent 擅自调整依赖方向。
如何验证改动没有超出边界?
在提交前运行仓库的 lint 规则,如依赖边界检查工具(Nx 的 enforce-module-boundaries 或自定义 ESLint 插件)。并计算 diff 文件列表,与预计算的允许集合比对,超出即中止并要求 Agent 重规划。
参考资料
示例用于理解所注明的运行环境与边界;延伸学习可结合原文中的更多案例。