
做 AI 辅助开发大半年我最大的感受不是模型不够聪明而是它经常被塞进上下文的无关代码带偏。明明只改一个支付模块的 bugAI 却把订单、库存、用户积分全翻了一遍最后给出一段逻辑错乱的代码。后来我做了个小工具context-mode用一套轻量的规则把该给 AI 看什么这件事管起来实测效果立竿见影上下文体积压到原来的十几分之一回答质量反而稳定了不少。这篇就把它的设计思路、核心实现和踩坑记录从头到尾摊开讲。context-mode不是一个复杂框架本质是一个命令行的上下文管理工具它扫描当前仓库按照项目级、文件级、任务级三种模式筛选出真正相关的文件打包成标准格式的上下文块供你直接粘贴给各种 AI 编程助手。适合受够了AI 回答跑偏的开发者也适合团队里想统一上下文格式、减少 token 浪费的人。1. 从上下文失控说起为什么需要 context-mode1.1 你可能会问直接把整个仓库丢给 AI 不行吗表面上确实能跑。现在不少助手支持把整个目录拖进去或者通过工具自动拉取全部文件。但问题也随之而来Token 成本暴涨。一个中型仓库几万到几十万行代码全量塞进去动辄几万甚至十几万 token一次对话就要烧掉大量额度团队多人用起来成本更明显。无关信息干扰判断。模型是概率生成上下文越长、干扰越多它越容易把某个不相关文件里的命名风格、历史接口误认为当前任务的约束。我见过最离谱的一次AI 因为扫描到了旧版接口定义在提交信息里把我改掉的函数名又写回去了。响应延迟明显变大。上下文越长首字延迟越高。在交互式编码场景里卡几秒再出来第一个字体验其实挺劝退。结果不可复现。如果每次给的上下文都不一样AI 给出的方案自然也不一样排查问题的时候很难说清上次它为什么能写对。那是不是该上 RAG检索增强生成我试过对代码仓库来说 RAG 有它的价值但有两个痛点一是搭建和调优成本不低二是它默认模糊匹配对精确引用比如某行的导入路径并不稳定。context-mode走的完全是另一条路线确定性的规则筛选每次打包的结果可预期、可复现不引入额外的大模型调用也没有索引和向量库要维护。1.2 context-mode 到底解决什么问题这个工具的核心目标有三个作用域控制。明确告诉 AI 这次只需要看这三类文件。Token 预算控制。设定一个上限超出就按优先级裁剪。上下文可复用。同一份上下文打包结果可以存成文件团队成员共用、CI 里生成避免每个人手动复制粘贴。用一句话概括它不是让 AI 更聪明而是让 AI 不犯看太多的错。它的设计原则也刻意保持简单不做语义检索只做路径规则和关键词打分不强制改变 AI 使用方式输出标准文本块粘贴就能用配置放在仓库里和代码一起走版本管理。2. context-mode 的三种作用域项目级、文件级、任务级2.1 项目级作用域全局视角下的重点文件这是默认模式适合需求评审、架构梳理、技术方案讨论。它的思路是不把所有文件都带上而是根据仓库特征挑出最能代表全局的文件。核心规则如下按优先级排序README、项目说明文档构建文件如pyproject.toml、go.mod、package.json入口文件如main.py、cmd/、src/index.ts目录结构树用tree命令生成的文件清单最近 7 天有提交记录的文件摘要通过git diff --stat拿。配置文件长这样# .contextmode.yaml mode: project token_budget: 6000 priority: - README.md - pyproject.toml - src/main.py - src/**/*.py ignore: - docs/** - **/*.lock - node_modules/** - **/*_test.gopriority里的 glob 规则决定文件优先级ignore自然不用多说。项目级模式我主要用于给 AI 讲背景我们是一个用 FastAPI 写的订单服务技术栈是什么样、入口在哪、最近改了什么让 AI 先从全局角度给方案。2.2 文件级作用域单点修改时的最小上下文文件级模式解决的是改一个函数但要带动一堆依赖的问题。它的逻辑是以指定文件为起点解析它的导入/引用关系把直接依赖的文件也一并带出来。实际命令cm mode file src/payment/service.py --depth 2--depth表示依赖层级默认是 1。层级 1 只包含该文件和它直接 import 的文件层级 2 还会包含这些文件各自 import 的文件。这个模式的底层实现依赖一个轻量 AST 解析函数比如对 Python 文件提取import和from ... import语句import ast from pathlib import Path def extract_imports(path: Path): tree ast.parse(path.read_text(encodingutf-8)) imports [] for node in ast.walk(tree): if isinstance(node, ast.Import): for alias in node.names: imports.append(alias.name) elif isinstance(node, ast.ImportFrom): if node.module: imports.append(node.module) return imports拿到导入列表后按照项目根目录映射成相对路径再递归解析。注意--depth不要超过 3超过三层后依赖爆炸token 预算很难控制住。文件级模式最适合的场景是AI 在哪里报错了你只想让它集中精力搞明白这个文件以及它直接牵连的代码。2.3 任务级作用域用自然语言定位相关文件任务级模式是我平时用最多的。它接受一句自然语言描述通过关键词匹配和路径打分从仓库里筛出最可能相关的文件。cm mode task 订单超时未支付状态一直停在 pending内部做的是这样几件事把任务拆成词项比如订单超时未支付状态pending对文件路径和文件名做关键词命中统计order、timeout、status这类词命中越多得分越高结合近期 git 提交记录里涉及的文件加权加分按得分排序取前 N 个文件再走 token 预算裁剪。这里没有用 AI而是简单的关键词倒排索引。因为对代码仓库来说文件名和路径本身就是最好的语义标签src/order/services.py这个词就把订单服务写在脸上了没必要动用向量模型。2.4 三种模式的选型建议模式典型上下文大小主要使用场景不适合场景项目级6k-10k token架构梳理、方案设计、新成员熟悉仓库精确到函数级的 bug 修改文件级2k-5k token修 bug、改单个模块、重命名重构需要全局视野的需求讨论任务级4k-8k token功能开发、跨模块排查、代码审查任务描述含糊且仓库过大时实际使用时还可以加--auto参数让它自动判断如果一条命令里既指定了文件又给了任务描述就按任务级处理并把指定文件设为必选。3. 核心实现拆解路径打分、token 预算与上下文打包3.1 路径打分机制是怎么运行的任务级模式是最依赖打分机制的部分我把打分公式简化成下面这个加权和score(file) priority_score match_score(file, keywords) * weight_match recent_change_score(file) * weight_recency - size_penalty(file)具体参数在配置里可以调match_weight: 1.0 recency_weight: 0.3 size_penalty_threshold_kb: 100 size_penalty_factor: 0.02match_score的计算方式是自己实现的简单计数器对关键词做词干化处理后统计命中次数。recent_change_score则取git log --name-only --since7 days ago文件出现次数越多得分越高。我举个例子一个电商仓库里搜索订单超时未支付路径 命中词 recency得分 得分 src/order/services.py ordertimeoutstatus 4 12.7 src/order/models.py orderstatus 1 9.2 src/payment/webhook.py 支付 2 8.1 src/user/serializers.py 无 0 1.5最后打包时只取前两个文件效果通常不错。打分的逻辑很朴素但胜在透明出了问题也容易调试。3.2 token 预算宁可少给不能乱给这是整个工具里最值得认真对待的部分。模型上下文窗口虽大但够用和塞满之间差距很大塞得越满越容易激活那些不相关的注意力。token_budget的默认值是 6000我根据实践总结出一个经验值修 bug1500-3000 token 足够一个小功能一个模块内4000-6000 token跨模块重构 / 方案设计8000-12000 token。实现上用的是tiktoken库统计每个文件的 token 数然后做贪心选择先把必选文件放进去再按得分从高到低依次添加直到预算耗尽。必选文件不会被裁剪这是硬约束。import tiktoken enc tiktoken.get_encoding(cl100k_base) def count_tokens(text: str) - int: return len(enc.encode(text)) def pack_files(file_scores, budget, must_include): chosen [] total 0 for f in must_include: total count_tokens(f.read_text()) chosen.append(f) for f, score in sorted(file_scores, keylambda x: -x[1]): if f in must_include or total budget: continue t count_tokens(f.read_text()) if total t budget: chosen.append(f) total t return chosen, total如果你希望超出预算时直接报错而不是静默裁剪可以加一个fail_loud: true防止关键时刻重要文件被悄悄丢掉。3.3 打包成标准上下文块选完文件之后context-mode会把它们拼装成一个标准格式的文本块开头是文件清单后面依次是文件内容用标记分隔cm-context modetask generated-at2025-01-03T14:22:00 cm-file-list - src/order/services.py - src/order/models.py /cm-file-list cm-file pathsrc/order/services.py # 文件内容... /cm-file cm-file pathsrc/order/models.py # 文件内容... /cm-file /cm-context这个标签故意做得机器可读后续接脚本、接插件都很方便。平时直接cm build -o context.md输出到文件然后整块内容粘到对话里。这个打包格式有一个容易被忽略的好处它让 AI 能区分当前对话的上下文和引用文件的内容减少混淆。实测发现加上cm-file这样的明确边界后AI 在回答中引用具体文件路径的次数明显变多。4. 实测效果一次 3 万行仓库的真实数据4.1 测试前提我拿一个真实的 Django PostgreSQL 项目做过一次对比仓库大概 3 万行 Python 代码、1200 多个文件平时给 AI 用的提示词是订单超时未支付问题排查。测试分两组A 组直接把整个src/目录拖给 AI大约 150k tokenB 组先用cm mode task 订单超时未支付状态一直停在 pending生成上下文大约 6.8k token。4.2 数据对比指标A 组全量目录B 组context-mode上下文大小约 150k token约 6.8k token首字响应时间约 18 秒约 3 秒单次对话成本估约 0.15 美元约 0.01 美元AI 代码中错误引用旧接口次数3 次0 次一轮对话内给出可用方案概率40%85%轮次本身也很有意思。A 组经常在第三轮之后就忘记最初的问题开始顺着某个不相关文件发挥B 组因为上下文里都是相关文件AI 每一轮的回答都围绕同一个目标展开很少跑偏。4.3 回答质量的变化不只体现在 token 数量上量化之外更明显的是行为层面的差异。A 组里 AI 会自己做文件联想比如看到订单模型就自动脑补了退款逻辑然后在代码里加了从没讨论过的字段。B 组由于上下文边界清晰AI 倾向于直接复用上下文里已有的模型定义很少自创接口。还有一个观察小上下文更容易触发 AI 主动提问XX 模块的兼容性需要确认吗而不是闷头写一大堆。这个差别对代码 review 非常友好因为问题被前置暴露了而不是等提交完才发现。5. 实际使用中的坑与对策5.1 配置项的优先级比想象中容易出问题priority和ignore同时命中时我最初的实现是ignore优先结果导致用户写了高优先级 README 也被忽略掉。后来改成显式优先规则priorityignore 默认。建议在文档里写清楚优先级否则团队里早晚有人踩这个坑。5.2 增量扫描的缓存失效第一次扫描后我加了文件缓存只检查mtime来决定是否重新解析。问题在于git pull或者git checkout会批量修改大量文件 mtime导致缓存大面积失效每次切换分支后第一次运行特别慢。解决方法是缓存时记录文件内容的哈希而不是只记 mtime。5.3 大文件和二进制文件是隐形杀手仓库里常有一些巨大的 JSON、pb.go或者图片占位文件它们既占 token 又没营养。我在默认ignore里加了规则ignore: - **/*.min.js - **/*.map - **/*.pb.go - **/data/*.json size_limit_kb: 200超过 200KB 的文件默认跳过如果要强制包含则必须显式写进priority。5.4 模型其实不需要太多相关文件这个问题是最难用代码解决的。打分机制很容易做到相关就给分但真正的艺术是刚好够用就停。我现在的策略是--strict模式下同一目录的文件最多取 3 个同一模块的兄弟文件优先选最近有 git 提交记录的。因为经验告诉我AI 看到一个模块里 5 个相似文件时很容易选错那个。这个限制在刚上线时被不少同事吐槽文件太少了但跑了两周之后大家反而接受了。因为 AI 写错的频率低了改的时间比翻上下文省得多。5.5 调试难上下文不完整时很难判断是没给对还是AI 笨这是所有上下文管理工具的通病。我的建议是给每次打包加一个--diff参数显示本次打包和上次打包的文件差异至少能快速定位是不是又少了某个关键文件。在输出上下文块时还会附带一行注释标注每个文件的得分和命中关键词方便人工检查cm-file pathsrc/order/services.py score12.7 matchedorder,timeout,status这个设计很大程度上减少了AI 答错了但不知道是不是我的锅的情况。6. 这个工具还能怎么扩展从个人脚本到团队基建6.1 与 CI/CD 集成自动生成上下文快照一个很自然的扩展是把context-mode加进pre-commit钩子每次提交自动生成一份context-snapshot.md随 PR 一起提交。这样 review 的人不用手动复制粘贴直接看快照就能知道提交者给 AI 提供了哪些上下文对审查代码合理性很有帮助。6.2 与编辑器插件联动目前我是把上下文块手动粘贴到对话窗口但接口留好了之后完全可以做编辑器插件选中几行代码按快捷键直接调用cm mode task xxx把生成的上下文插入到旁边的新对话面板。格式标准化的好处就在这里换模型、换工具都不用改内部逻辑。6.3 继续改进的方向评估集与回放如果想把这件事做得更严谨可以建一个小的评估集收集历史上有明确正确答案的任务自动运行context-mode生成上下文再让 AI 基于这些上下文输出代码和标准答案对比。这个思路能帮你比较不同token_budget、不同打分权重对最终效果的影响比拍脑袋调参靠谱得多。最后分享一个我自己的使用习惯context-mode生成完上下文后我不是立刻粘贴而是先扫一眼文件列表手动删掉一两个看起来相关但实际无关的文件再发给 AI。这个动作看着小但对回答质量的提升非常明显。工具能帮你把 100 个文件裁到 5 个剩下的那一步人工筛选才是决定 AI 输出质量上限的关键。