ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

context-mode:管理AI编程助手的上下文,让代码生成更精准

context-mode:管理AI编程助手的上下文,让代码生成更精准 去年年底我接手了一个六万多行的老服务类型是 TypeScript 写的订单中台。代码量不算夸张但文件数量接近七百个接口调用链很长很多模块还有历史包袱。当时我习惯性地打开 AI 编程助手把相关文件批量塞进上下文让它帮忙改一个补偿逻辑。结果它给我返回了一个方案开头就是根据 deprecated 的 legacyClient 接口……。我愣了一下这个接口三个月前就废弃了凡是读过项目 README 的人都不会用。可我确实没把 README 喂给它。这次翻车让我开始琢磨一个问题AI 助手的能力边界其实不是模型决定的而是上下文决定的。你把什么代码、什么约定、什么边界信息放进它的视野它就只能在那里面找答案。后来我把这套管理AI 视野的方法固化成了一个项目工作流起名叫 context-mode。它不是某个大厂的官方功能而是一套基于范围Scope、焦点Focus、记忆Memory三个核心部件的上下文管理模式配合一个轻量 CLI 脚本来控制 AI 助手每次会话能看到什么、以什么视角工作、还记得哪些关键决策。这篇内容非常适合正在重度使用 AI 辅助编程、但又觉得AI 越改越乱的开发者。尤其是维护中大型仓库、经常在多任务间切换、或者经历过AI 给出的代码和项目里已有的约定完全对不上这类问题的人。我会把 context-mode 的原理、落地步骤、实测数据、踩坑过程全部拆开讲代码和配置可以直接抄。1. 为什么我会专门折腾一套 context-modeAI 的很蠢就藏在上文里1.1 上下文窗口不是越大越好很多人有个直觉上下文窗口越大AI 越聪明。这个直觉在短文本上成立但在真实的大型代码仓库里基本是反的。业界有个很经典的现象叫大海捞针——你往上下文里塞一万行无关代码再问一个只和其中三行相关的问题模型的表现会明显下降。不是它变笨了而是注意力被稀释了。可以把它理解成一个嘈杂的会议室。你叫来一个水平很高的顾问但会议室里同时有四十个人在翻材料、窃窃私语其中三十个人的材料和本次议题毫无关系。顾问确实很聪明但他也在不断判断哪些声音值得听而这个判断过程本身就会出错尤其是在信息量大到接近窗口上限的时候。长上下文的退化不是线性的接近边界时错误率会加速上升。所以 context-mode 解决的第一件事是砍掉噪音。它不是靠提示词说请忽略无关文件而是在物理层面控制 AI 根本看不到哪些文件。与其让模型在七万行代码里做检索不如我从源头把范围压到三千行相关代码。这个思路听起来像作弊但实测效率提升非常明显。1.2 上下文漂移与AI 精神分裂我用 AI 辅助写代码遇到的最让人头疼的问题不是它写不出来而是它精神分裂。上午它刚按新约定写了一个模块下午在另一个会话里它又开始按旧约定写理由仅仅是这次你没把新约定的文件放进去。这就是上下文漂移。上下文漂移的本质是模型每个会话都是失忆的。它只认识你当前喂给它的东西。你这次喂了接口 B它就认为世界是围绕接口 B 运转的下次你忘了喂它就会想当然地编一个接口 C。在大型项目里这种漂移造成的返工是巨大的。我有一次让 AI 重构一个分页查询它连续两次把排序字段写成了 price而项目里所有其它模块统一用的是 sort_score。原因很简单——我这次会话没有把那个定义排序字段的公共工具文件加进来。这种情况靠记得每次都把公共文件加上根本不现实人总会漏。context-mode 的思路是把这些每次都必须出现的公共约定做成固定组件由工具自动注入而不是依赖你手动拖拽。这就像给 AI 配了一个项目入场的 checklist而不是指望它每次随机应变。1.3 context-mode 在解决哪一层问题必须说清楚context-mode 不是提示词工程的替代品它和写一个更好的 prompt解决的是不同层面的问题。提示词工程解决的是AI 怎么理解任务context-mode 解决的是AI 的决策依据是否完整。你给一个没看过错误堆栈的 AI 写再好的提示词它也只能瞎猜。我更愿意把 context-mode 定位成一个信息供给控制系统。它做的事情非常朴素决定 AI 看到什么、按什么顺序看、记住什么。这三个决定比怎么说重要得多。因为模型输出质量的上限就是你喂给它的信息质量的上限。好比你让一个米其林大厨做菜食材清单是他自己列还是你帮他筛好做出来的东西天差地别。AI 不是不会做菜而是经常被塞进一堆过期罐头和冷冻肉。理解了这一层再看接下来的 Scope、Focus、Memory 三个部件就会很清楚每个部件在堵哪个漏洞。2. context-mode 的三个核心部件范围、焦点、记忆2.1 范围Scope把仓库变成一张可折叠地图Scope 解决的是AI 能看到哪些文件的问题。它本质上是一套 glob 规则外加一个文件数量上限。我的实现里用了一个配置文件.context-mode/config.yml长下面这样project: order-service scope: include: - src/** - tests/** - docs/adr/** exclude: - src/legacy/** - **/*.generated.* - **/*.spec.ts max_files: 40 max_tokens_estimate: 30000include 定义 AI 默认能够看到的范围exclude 定义即使符合 include 也要排除的范围max_files 是单次会话的文件数量上限。为什么要有 max_files因为即使所有文件都符合 include一次性塞几百个文件仍然会稀释注意力。我通常建议上限在 3050 个文件之间具体要看仓库复杂度。exclude 里那些规则都是踩过坑之后加进去的。.generated.*文件是自动生成的AI 看到它反而会被带偏以为那是手写代码的风格.spec.ts测试文件在写业务逻辑时通常不需要看但做重构时又很重要。所以 exclude 也不是死的后面会讲怎么配合 Focus 做动态调整。做 Scope 时心里要有一张可折叠地图的意识。地图不能把所有街道都画出来但也不能漏掉主干道。include 里的src/**是主干道docs/adr/**是重要的决策记录tests 是辅助验证信息。地图画到这个粒度就够用了。2.2 焦点Focus同一份代码不同任务不同读法Scope 控制看到什么Focus 控制怎么看。同一个文件做 bugfix 和做 code review 时AI 应该关注的东西完全不同。改 bug 时你希望它先复现问题链路review 时你希望它直接输出问题清单和优先级而不是顺便帮你改代码。context-mode 的 focus 机制就是一组预设的视角模板focus: default: feature modes: feature: prompt_prefix: 本次目标是新增功能。严格遵守现存代码风格不要修改未列入范围的模块。 file_priority: - docs/adr/** - src/domain/** - src/app/** bugfix: prompt_prefix: 本次目标是定位并修复缺陷。先输出复现链路和根因再给修复方案。不要顺手重构。 file_priority: - tests/** - src/infra/** - src/domain/** review: prompt_prefix: 本次只做代码评审。输出问题清单、严重级别和修改建议不要直接改代码。 file_priority: []file_priority 是个细节。它控制 AI 在读取文件时的排序优先级高的文件会出现在上下文更靠前的位置。大语言模型对上下文前部和尾部的内容记忆更牢把 ADR 决策记录放在前面等于先告诉它这个项目的铁律是什么再让它看具体代码。这比把所有文件按字母序丢进去要有效得多。Focus 还有一个价值它让 AI 在不同的任务里表现得像不同的人。feature 模式下它是谨小慎微的新功能开发bugfix 模式下它是保守的排障医生review 模式下它是挑剔的代码审查者。你不需要在每次对话里长篇大论解释你的期望切换一个模式就够了。2.3 记忆Memory让跨会话的关键信息可回放前两个部件解决的是当前会话的问题Memory 解决的是下次会话怎么办。模型不会记住上次你让它改了哪些约定但你可以替它记。context-mode 维护一个记忆目录.context-mode/memory/里面是一条条短小的 Markdown 卡片每条记录包含时间、决策/约定、原因、过期时间。CLI 在每次会话启动时把最近 N 条卡片的摘要注入到上下文中。这样即使你换了一个全新的会话AI 也能知道项目里禁止使用 legacyClient 接口这条约定。我的记忆卡长这样--- date: 2025-06-18 status: active expires: 2025-09-18 --- 主题分页查询统一使用 sort_score 字段 项目所有分页接口禁止使用 price 字段排序。历史原因已记录在 ADR-004。 这是全局约定所有新增和重构代码都必须遵循。记忆卡最怕的是又臭又长。我一开始写了很多长篇大论但 AI 在上下文里对长文本的利用率并不高。后来定下规矩一张卡不超过六行只写三条信息——主题、结论、一句话理由。其余细节丢到 docs 里让 AI 需要时再去查。记忆卡是索引不是档案馆。3. 落地实操把一个六万行的老仓库接入 context-mode3.1 第一步盘点仓库划定三层目录接入 context-mode 的第一个动作不是写配置而是盘仓库。我花了一个下午把整个 src 目录过了一遍把所有目录分成三层核心域业务主链路、支撑域工具、基础设施、依赖域生成的、历史遗留的、几乎不变的。拿我那个订单服务举例核心域是src/domain、src/app支撑域是src/infra、src/lib、src/utils依赖域则是src/legacy、src/client/openapi自动生成的 SDK。分层之后Scope 的规则就很好写了默认只对核心域和支撑域开放依赖域在多数任务里直接排除。这一步看起来简单但非常关键。因为很多项目的问题不是没有目录层级而是目录名没有体现这是什么性质。如果你的仓库全是src/a、src/b这种无意义命名建议先花时间重命名或补充 README否则 Scope 规则只能靠 exclude 一个个点名维护成本很高。3.2 第二步编写初始配置并跑通最小闭环盘完目录就写一份最简配置。不要一上来就追求完美先跑通一个最小闭环。我的建议是最简配置只需要 include 两个目录加上 exclude 三个目录然后运行 CLI 的 status 命令看看实际生效的文件清单# 初始化 context-mode cm init # 查看当前模式和建议注入上下文中的文件 cm status # 切换焦点模式 cm switch bugfix # 查看最近记忆 cm logcm status 的输出会直接列出当前 focus 模式、将被注入上下文文件数量、预计 token 大小、最近记忆条目数。第一版配置不用纠结数量先看到这个文件列表确实覆盖了我经常改的那几个目录就算跑通了。我当时第一版 scope 配置的 include 是src/domain/**和tests/**结果 cm status 列出的文件清单里没有src/lib/payment.ts而那次会话恰好要改支付回调。这事提醒我scope 配置必须和实际任务对齐写完配置要观察几天把每次AI 说看不到某文件的情况都记下来然后把这些文件补进 include。这本质上是拿历史任务反推上下文的通勤路线。3.3 第三步用 Git 钩子把切换动作自动化focus 最理想的状态是自动切换而不是每次都打开终端敲命令。我实现的方案是挂在 Git 钩子上检测当前分支名按前缀自动切 focus。分支名feature/xxx就切到 feature 模式fix/xxx或bugfix/xxx切到 bugfix 模式release/*切到 review 模式。这个规则简单直接团队里大家基本都遵守分支命名规范所以对开发体验几乎零侵入。在.git/hooks/pre-commit里我加了一段逻辑#!/usr/bin/env bash BRANCH_NAME$(git branch --show-current) case $BRANCH_NAME in feature/*) cm switch feature /dev/null ;; fix/*|bugfix/*) cm switch bugfix /dev/null ;; release/*) cm switch review /dev/null ;; esac除此之外我更推荐在 AI 会话工具的自定义指令里加一个动态加载步骤每次新会话开始先执行cm context --branch $(git branch --show-current)把对应的上下文描述注入系统提示。这样即使你忘了手动切换AI 打开的那一刻看到的也是正确的范围、焦点和记忆摘要。3.4 第四步把记忆卡接入月度评审记忆卡的维护是个长期活。我的做法是每月底花半小时做一次记忆卡评审把过期的删掉把仍在生效但描述不准确的更新把最近踩的坑写成新卡片。这个动作看似和写代码无关但它决定了下一个月的 AI 会话质量。记忆卡相当于 AI 的团队新人手册手册过时了新人再聪明也会按错误的方式做事。举个例子我们团队有一次统一了错误码规范旧规范是返回字符串ORDER_NOT_FOUND新规范是返回数字异常码2404。如果记忆卡里没有及时更新AI 大概率还会写旧规范。所以记忆卡的增删改和代码变更一样要有节奏不能只写不看。4. 实测接入 context-mode 后到底发生了什么4.1 数据对比token、轮次、返工率我在订单服务上跑了六周的对照观察。前面三周用传统方式——手动拖文件进上下文后面三周全面切换到 context-mode。我记录了三个指标单次任务平均消耗 token、单次任务平均对话轮次、返工率定义为AI 给出的方案被人工打回重新修改的次数占比。指标接入前接入后变化平均单次任务 token 消耗约 128k约 45k下降约 65%平均单次任务对话轮次11 轮6 轮减少约 45%返工率48%17%下降约 64%token 下降其实是最不重要的收获毕竟 token 便宜。真正让我惊喜的是返工率。接入前几乎每两次任务就有一次要打回重来而且重来的时候还得手动告诉 AI你漏看了 xxx 文件。接入之后AI 第一次给出可用方案的概率大大提升了。这不是模型变强了而是它看到的东西刚好够用不多也不少。4.2 一个真实的 bugfix 会话复盘举一个具体的例子。我们的订单超时补偿任务表现是一笔订单支付成功后如果回调没有及时返回系统会自动发起逆向退款。某天线上出现了一批支付成功但被错误退款的订单。接入 context-mode 之前我如果手动喂文件通常会把交易流水服务、支付回调服务、订单状态机、退款模块、数据库实体全部拖进去大约 15 个文件。AI 在这些文件里会迷失方向它可能花好几轮问退款状态和支付状态之间的关联逻辑在哪甚至会把退款失败重试当成根因。接入之后我切换 bugfix 模式context-mode 自动注入了 payment-callback handler、订单状态机、最近一周的状态流转日志这三类文件。AI 第一轮就给出了正确的排查路径回调成功更新状态的动作和超时补偿扫描任务之间存在竞态条件扫描任务读到了过期快照于是把已支付订单判定为超时。它给出的修复方案是在状态机里增加一个支付确认中的中间态避免补偿任务误判。这个方案和团队里资深工程师的结论基本一致。它之所以能做到核心原因是上下文里没有塞退款模块的无关文件AI 不会被退款为什么失败这个问题带偏。聚焦是它这次表现好的唯一秘诀。4.3 参数调优经验max_files 和记忆数量怎么定max_files 的参数我建议跟着项目的核心链路大小走。如果项目核心链路有 300 个文件你非要把 max_files 设成 20AI 永远看不全链路。我的经验是先跑一周默认配置观察 AI 频繁说看不到文件 X 的内容把这些需求列出来然后取核心链路文件数的 1/3 到 1/2 作为 max_files。记忆卡数量方面recent_entries 我设置在 2030 之间。太多会让 AI 分不清优先级太少则覆盖不了项目里的核心约定。记忆卡本身要有 freshness 机制——超过三个月的约定如果没有确认仍然有效就自动降权。我实现里给每条卡片加了 expires 字段CLI 在注入时过滤掉已过期的条目。这就避免了记忆卡里存着五年前的过时规范AI 还拿它当圣旨的尴尬局面。5. 踏过的坑context-mode 失败的三次现场5.1 坑一scope 收得太狠AI 看不到根因context-mode 接入后第一次严重的翻车是我把src/legacy目录整个排除掉了。理由很正当这是一批历史代码规范差、依赖重新功能不应该碰它。但现实打脸很快。有一次 AI 排查一个数据不一致问题它反复在src/domain里绕圈子始终找不到根因。我后来手动看了一遍 legacy 代码才发现问题就出在一个老工具函数上——那个函数在新代码里仍在被调用只是调用点被包装了一层。修复这个问题的关键不是把 legacy 加回来而是建立一条依赖可达性原则排除文件之前先确认它不会被任何 include 范围内的文件直接或间接引用。如果存在引用关系就不能简单排除而是要把这个模块是旧的、不要改它但可以读它作为一条规则写进配置。此后我加了一个exclude_with_read的字段允许某些目录只读不写、参与推理但不参与修改。这个字段后来成了我所有项目的标配。5.2 坑二记忆卡开始说谎记忆卡的问题不是过期而是看似没过期其实已经不符合现状。有一次我们调整了接口鉴权方案从内部 token 换成了网关签名。我在记忆卡里写了一条新约定所有内部接口使用网关签名鉴权。但我没有删旧的卡片内部接口使用 service-token 鉴权。两条卡片同时存在于记忆库里AI 在 feature 模式读取时根据 file_priority 排序旧卡片排在前面导致它新写的接口全都是 service-token 鉴权。从那之后我规定记忆卡评审时不允许只新增不删除而且每条卡片里要写替代了哪条旧约定。一张卡的完整生命周期必须能追溯到上一张卡。同时我增加了一条 CLI 命令cm conflicts它会扫描记忆库里主题相似但结论冲突的卡片提醒你处理。记忆卡本身是会被模型当真的所以它比代码注释更需要严谨。5.3 坑三模式切换导致连续两次会话口径不一致还有一个坑特别隐蔽上午我用 feature 模式让 AI 设计了一个模块拆分方案方案里说复用旧模块 core 里的工具函数。下午我切到 bugfix 模式修另一个问题时AI 基于相同文件却提出建议重写 core 模块。两次会话给出的方向完全相反原因是 focus 切换时记忆库里没有记录feature 模式刚做过的决策AI 并不知道上午已经定了复用的方向。我的解决方案是给 focus 增加一个会话上下文记录功能。每次cm switch时自动写入一条临时记忆当前分支 feature/xxx 正在执行模块拆分核心决策是复用 core 模块若有冲突请参考记录。这样下午的 bugfix 会话至少能看到这个方向的存在不会完全无视项目当前进行中的决策。上下文的主导权从模型自由发挥变成了项目实际状态引导这个坑就基本堵住了。6. 哪些场景真的适合 context-mode哪些是画蛇添足6.1 适合中大型老仓库、多任务并行、评审重构我用下来最受益的场景有三个。第一就是中大型老仓库文件多、历史包袱重、上下文噪声大context-mode 的裁剪能力能把信噪比拉高一大截。第二是多任务并行这边在加功能、那边在改 bug、明天还要做评审没有 focus 模式的话AI 很容易把不同任务的上下文混在一起给出张冠李戴的方案。第三是代码评审和重构review 模式强制 AI只看不改这比自己反复强调不要改代码有效得多。它把 AI 从抢着干活的实习生变成了旁观者清的前辈。判断你的项目是否适合可以看三个信号你是不是经常发现 AI 引用了废弃的接口你是不是经常得在对话里补一句请先看下 xxx 文件你是不是觉得 AI 给的方案经常风格不一致只要中了两条就值得试一下。6.2 不适合实验性原型、单文件脚本、刚起步的空仓库但也得泼点冷水context-mode 不是万能的。如果你在做实验性原型代码每天都在推翻重来Scope 配置今天写了明天就失效几乎是负资产。单文件脚本场景更不需要你一个文件全喂进去也就几千 token搞一套 Scope/Focus 纯属浪费精力。刚起步的空仓库也不用配项目还没形成约定配出来的规则大概率是错的。这类小场景最朴素的做法就是全喂。既然上下文塞得下就没必要做裁剪。context-mode 解决的是塞不下或者塞进去有害的问题它不该成为所有项目的仪式感。工具用在哪里、用多重要看项目阶段而不是为配置而配置。6.3 建议的落地顺序先 Scope再 Focus最后 Memory最后给一个落地的顺序参考。我的建议是分三周推进不要一天全上。第一周只做 Scope把所有AI 说看不到文件的情况记录下来把 include/exclude 磨到一个相对干净的状态。第二周加 Focus先接 feature 和 bugfix 两个模式跑熟之后再补 review。第三周再接入 Memory而且是每天只加两三条真正重要的约定不要一次性倒垃圾式地写五十张卡。这个顺序的核心逻辑是先确保 AI 看得见再控制它怎么看最后让它记得住。顺序反了比如先上 Memory 后做 Scope记忆卡里全是你以为很权威但文件都看不见的内容那 AI 输出的东西会更离谱。工具链的搭建和代码架构一样依赖关系必须先理清。我个人在完整跑了两轮项目之后最大的体会是context-mode 教给我的不是怎么配置一个工具而是我对AI 到底为什么犯错的理解变深了。大多数时候模型并没有不够聪明它只是少了某个关键信息。你能做的不是祈祷模型变强而是保证它每次开工前站在一个信息完备但不冗余的位置上。如果你也被 AI 上下文的混乱折磨过建议先从 Scope 开始试一周后再回来看我写的 Focus 和 Memory你会体感完全不一样。
返回列表