
aider 实战复盘让/drop不再误清启动时通过--read指定的只读参考文件【免费下载链接】aideraider is AI pair programming in your terminal项目地址: https://gitcode.com/GitHub_Trending/ai/aider这是一篇基于 aider 官方录音会话文档 dont-drop-original-read-files 的技术复盘文章。该会话记录了 aider 开发者使用 aider 自身去修改 aider 的一个真实迭代过程让无参执行/drop以及/reset时保留启动时通过--read传入的只读文件只清理会话中的可编辑文件与其他临时添加的文件。读完本文你将理解 aider 中只读文件 / 可编辑文件的上下文模型、/drop命令的内部实现与测试保障并掌握一种用 AI 结对编程改造 AI 工具的可复用工作流。一、背景这个录音会话在解决什么问题aider 允许你把文件分成两类带进聊天上下文可编辑文件启动时通过位置参数或--file传入会话中可用/add添加模型可以修改它们只读文件启动时通过--read传入会话中可用/read-only添加模型只能参考、禁止编辑。问题出在/drop命令上。它用于从会话中移除文件以释放上下文空间。改进之前直接输入无参的/drop裸/drop会把可编辑文件、会话中途添加的只读文件以及启动时用--read特意指定的只读文件一并清空。而--read文件通常是每次启动时都要加载的固定参考资料规范、接口定义、依赖清单等一执行/drop就被清掉用户需要重启会话才能恢复非常打断工作流。因此本次需求非常明确对应录音 0:01 的旁白Were going to update the /drop command to keep any read only files that were originally specified at launch.即/drop与/reset应保留启动时指定的只读文件只清理可编辑文件与之后新增的文件。与此同时用户显式执行/drop 某个文件时仍然应当可以把它移除——保留并不等于锁定。这段会话对应的页面属于 Screen recordings 系列全文以 asciinema 终端录像 分段旁白 音频片段的形态存在音频元数据见 assets/audio/dont-drop-original-read-files/metadata.json主题是 aider 开发者用 aider 增强 aider。这类页面最有价值的并不是操作演示本身而是它浓缩出的完整开发流程本文会逐段还原并结合当前仓库源码做深度讲解。二、先建立概念aider 的只读文件机制在进入/drop的改动前先看只读文件在当前仓库中是如何落地的这直接决定了改动的形态。1. 启动入口--read只读文件的主要入口是命令行参数--read定义在 aider/args.pygroup.add_argument( --read, actionappend, metavarFILE, helpspecify a read-only file (can be used multiple times), ).complete shtab.FILE它采用actionappend意味着可以多次传参也可以一次传多个文件。2. 主流程解析与传递在 aider/main.py 中--read的值会被解析成绝对路径并且支持传目录目录会被递归展开为其中所有文件read_only_fnames [] for fn in args.read or []: path Path(fn).expanduser().resolve() if path.is_dir(): read_only_fnames.extend(str(f) for f in path.rglob(*) if f.is_file()) else: read_only_fnames.append(str(path))随后这个列表被分两路传递作为original_read_only_fnames传入Commandsaider/main.pyCommands是承载/drop、/reset、/add、/read-only等会话内斜杠命令的对象作为read_only_fnames传入Coder.create(...)aider/main.py进入 AI 编码核心。3. Coder 侧只读文件进上下文的方式Coder在初始化时把启动传入的只读文件解析为绝对路径集合abs_read_only_fnames并在文件不存在时给出告警而非静默失败aider/coders/base_coder.pyif read_only_fnames: self.abs_read_only_fnames set() for fname in read_only_fnames: abs_fname self.abs_root_path(fname) if os.path.exists(abs_fname): self.abs_read_only_fnames.add(abs_fname) else: self.io.tool_warning(fError: Read-only file {fname} does not exist. Skipping.)当这些文件内容被注入 prompt 时会带上明确不可编辑的系统提示前缀aider/coders/base_prompts.pyHere are some READ ONLY files, provided for your reference. Do not edit these files!与此同时终端 UI 也会用(read only)标注它们见 aider/io.py 中format_files_for_input的相关实现/tokens命令统计上下文占用时同样会把read-only文件单列。由此得到一个重要事实在 Coder 内部可编辑文件abs_fnames与只读文件abs_read_only_fnames是两套独立的集合。所谓保留启动时的只读文件在实现上就是对后一个集合做一次过滤——这正是下面核心改动的着眼点。三、/drop与/reset的核心改动逻辑1. 状态记录original_read_only_fnamesCommands对象的构造函数接收original_read_only_fnames并保存为集合aider/commands.py# Store the original read-only filenames provided via args.read self.original_read_only_fnames set(original_read_only_fnames or [])这是判断某只读文件是否来自启动时--read的唯一依据。为了让会话中切换模型等操作重建的Commands不丢失这份记忆clone()也会把它带过去aider/commands.pydef clone(self): return Commands( self.io, None, voice_languageself.voice_language, ... original_read_only_fnamesself.original_read_only_fnames, )2. 核心_drop_all_files无参/drop与/reset最终都汇聚到_drop_all_filesaider/commands.pydef _drop_all_files(self): self.coder.abs_fnames set() # When dropping all files, keep those that were originally provided via args.read if self.original_read_only_fnames: # Keep only the original read-only files to_keep set() for abs_fname in self.coder.abs_read_only_fnames: rel_fname self.coder.get_rel_fname(abs_fname) if ( abs_fname in self.original_read_only_fnames or rel_fname in self.original_read_only_fnames ): to_keep.add(abs_fname) self.coder.abs_read_only_fnames to_keep else: self.coder.abs_read_only_fnames set()理解这段逻辑要抓住三点可编辑文件无条件全部清空第一行直接把abs_fnames置空只读文件做白名单过滤仅当存在original_read_only_fnames时遍历当前所有只读文件同时用绝对路径和相对路径两种形式与启动名单比对命中的保留其余丢弃无启动只读文件时行为不变else分支清空全部只读文件兼容旧版行为。同时支持绝对路径与相对路径双比对是因为--read传入的相对路径在解析后可能以多种形式存在用get_rel_fname归一化后比对更稳妥——这也解释了为什么源码要同时检查abs_fname in ... or rel_fname in ...。3.cmd_reset复用同一清理逻辑/resetDrop all files and clear the chat history内部同时调用_drop_all_files()与_clear_chat_history()aider/commands.py因此启动时传入的只读文件在/reset后同样会被保留。配套代码注释也明确说明清理的是历史消息与新增的文件。4. 显式/drop 文件依然可以删掉只读文件保留策略只作用于全量清理不阻止用户显式点名移除。cmd_drop在带参数时会先对只读文件做匹配aider/commands.py# Handle read-only files with substring matching and samefile check read_only_matched [] for f in self.coder.abs_read_only_fnames: if expanded_word in f: read_only_matched.append(f) continue # Try samefile comparison for relative paths try: abs_word os.path.abspath(expanded_word) if os.path.samefile(abs_word, f): read_only_matched.append(f) except (FileNotFoundError, OSError): continue for matched_file in read_only_matched: self.coder.abs_read_only_fnames.remove(matched_file) self.io.tool_output(fRemoved read-only file {matched_file} from the chat)也就是说对只读文件的删除走的是子串匹配 os.path.samefile兜底对可编辑文件的删除则额外支持 glob 通配符。这样既实现了启动只读文件默认受保护又保留了用户手动清除它们的逃生通道。5. 用户可见的行为差异无参/drop时的提示信息会根据是否有启动只读文件而变化aider/commands.pyDropping all files from the chat session except originally read-only files. # 有 --read 传入时 Dropping all files from the chat session. # 无 --read 传入时四、测试保障行为被完整固化在测试套件中本仓库的测试直接印证了上述所有行为边界全部集中在 tests/basic/test_commands.py值得逐条对照测试方法行号验证的行为test_drop_with_original_read_only_filestests/basic/test_commands.py构造 1 个启动只读文件 1 个可编辑文件 1 个后加只读文件执行裸/drop后可编辑文件与后加只读文件被清空启动只读文件被保留test_drop_specific_original_read_only_filetests/basic/test_commands.py对启动只读文件显式执行/drop orig_read_only.txt可以被正常移除保护不是锁定test_drop_with_no_original_read_only_filestests/basic/test_commands.py没有启动只读文件时裸/drop行为不变提示 Dropping all files from the chat session.test_reset_with_original_read_only_filestests/basic/test_commands.py/reset同样保留启动只读文件同时清空聊天历史test_reset_with_no_original_read_only_filestests/basic/test_commands.py无启动只读文件时/reset全清test_reset_after_coder_clone_preserves_original_read_only_filestests/basic/test_commands.py经历Coder.clone如模型切换场景后original_read_only_fnames状态不丢失test_drop_bare_after_coder_clone_preserves_original_read_only_filestests/basic/test_commands.pyclone 之后裸/drop仍能正确保留启动只读文件这几条测试恰好覆盖了保留路径、显式删除路径、无名单路径、reset 路径、clone 状态传播五类关键场景是本次改动在回归层面的完整契约。除此之外test_cmd_drop_*系列tests/basic/test_commands.py还覆盖了目录 drop、glob 通配符 drop、按 git 根路径 drop 等既有行为保证了新逻辑不破坏旧功能。五、从录音会话提炼的可复用自举开发工作流该录音最独特的价值在于它完整记录了开发者如何指挥 aider 去修改 aider 自身。把旁白时间线dont-drop-original-read-files 的 Commentary 部分映射到最终仓库代码上可以还原出一条极具参考价值的协作流程0:01 精确描述需求开场就给定验收标准——/drop保留启动时通过--read指定的只读文件。不模糊、可测试是整段 AI 协作的起点。0:10 定位代码范围明确改动涉及的模块——主 CLI 参数解析与斜杠命令处理即main.py、args.py、commands.py这几个文件避免 AI 漫无目的地发散。0:20–1:20 先讲清变更语义再动手在让 AI 写代码前先解释清楚行为变化保留什么、清理什么对应最终实现的_drop_all_files白名单过滤思路。1:30 代码评审并拒绝不优雅的实现旁白明确说 Id prefer not to usehasattr(), lets ask for improvements——开发者对 AI 的初版方案提出代码风格要求并让它自行改进。这类要求重构的指令正是把 AI 输出从能跑推向可维护的关键一步。1:45 手动测试在实际环境中验证行为是否符合预期即上文第三节展示的提示文案与文件保留效果。2:10 跑既有测试套件做回归确保改动没有破坏原有的/drop、/reset、/add、/read-only相关行为。2:19 让 AI 补测试直接要求 aider 为本次改动增加测试覆盖对应第四节中那一批test_drop_with_original_read_only_files等用例。2:50 审查测试并收尾确认测试断言合理后结束迭代。这套流程本质上是一种人机循环的最小闭环精确需求 → 缩小代码搜索面 → 语义澄清 → 编码 → 风格评审与重构 → 手工冒烟 → 回归测试 → 补测试 → 复审。它不依赖任何特殊工具任何开发者都能在自己项目里复现。你也可以直接参考 Screen recordings 系列的其他会话观察同一套工作流在不同功能/architect自动接受、--read文件被丢弃等问题上的变体运用。六、实用总结在自己的会话中用好这套语义基于以上源码与测试分析你可以在日常使用中这样利用该行为启动时把始终需要的参考资料放进--read例如架构文档、数据库 schema、编码规范使用aider --read docs/spec.md --read config/schema.json它们会被标记为 read-only 并注入系统提示上下文。放心使用裸/drop释放上下文可编辑文件与会话中途新增的文件会被清空而--read的参考文件会原样保留提示会明确告诉你 except originally read-only files。想要换一套参考文件时显式点名删除/drop docs/spec.md仍然有效支持相对路径、绝对路径子串匹配可编辑文件还可使用 glob如/drop *.log。/reset做干净重启时同理它会保留--read文件但清空历史消息如果连参考文件也想全部重置直接重启 aider 进程即可。注意适用前提上述保留只针对启动时通过--read或--read目录递归展开传入的文件会话中用/read-only新增的文件不属于受保护名单会被裸/drop清掉。需要留意的是录音页面里展示的是该功能落地的开发过程快照当前仓库中的实现commands.py、main.py、base_coder.py及相关测试是经过后续演化的最新形态但保留启动只读文件这一核心语义始终如一你可以用文中所给的文件路径直接查阅验证。【免费下载链接】aideraider is AI pair programming in your terminal项目地址: https://gitcode.com/GitHub_Trending/ai/aider创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考