☰
context-mode:把终端工作上下文变成可切换的模式,告别环境错乱
2026/10/8 7:48:54 网站建设 项目流程

前两周有个瞬间让我特别想动手做点工具:我坐在终端前面,准备切换到当前项目的一个分支,结果发现环境变量还是上一个项目的,连提示符都写着别人的仓库名。这已经不是第一次了——上次是写代码写到一半发现编译报错,最后定位到根因是当前目录还停在另一个服务里。单独看都是几秒钟的小事,但一天里反复发生十几次,累积起来非常可怕。我就是在这种状态下开始琢磨"context-mode"这件事的:能不能用一种明确、可复用的方式,把每一个工作任务的上下文冻结下来,切换时一键生效,而不是靠脑子记。

context-mode这个想法说白了就是:把"我现在正在干什么"变成一种可切换、可保存、可分享的显式状态。它适合那些每天要在多种任务之间来回横跳的人——写业务代码、查线上日志、做代码审查、给团队写文档、临时改个脚本,每种任务需要不同的环境变量、不同的命令习惯、甚至不同的AI提示词视角。文章不会涉及任何复杂框架,核心是我自己落地的一套配置和组织方法,你能直接抄走用。

1. 为什么我会自己做一套context-mode

1.1 那些被"上下文"吃掉的时间

我先算一笔很保守的账。假设你一天要切换六次工作任务,每次切换产生的"重新进入状态"的成本大约是五分钟——这五分钟里,你在找上次看到哪一行、回忆这个项目的环境变量是什么、翻历史命令找上一次用到的启动参数、还要重新给AI解释一遍背景。一天就是三十分钟,一个月就是十一个小时。

问题在于,这种成本在感官上是"不存在"的,因为它被分散在无数个细小的操作里。你不会觉得哪一步特别慢,但整个人的工作节奏就是拖沓的。我试过用待办清单来缓解,发现清单只能解决"记得要做什么",解决不了"做的时候环境对不对"。环境不对的时候,你人已经进入状态了,结果是命令报错、AI答非所问、上下文彻底断裂,然后又要从头开始解释。

context-mode的想法很简单:把一份任务对应的所有隐性前提——目录、环境变量、别名、AI提示词、终端布局——全部显式写到一个模式文件里。切换任务不再靠脑子重新加载,而是执行一条命令,让终端带着全套环境一次性跳转。

1.2 现成工具无法覆盖的三个盲区

我不是没试过现成的方案,但它们的覆盖面都有缺口。

第一类是IDE自带的workspace,比如VS Code的工作区、JetBrains的项目级配置。它能保存打开的文件夹、运行任务、调试配置,但限制很明显:它管不到终端里那些shell层的状态,更管不到AI工具的system prompt。我日常一半的操作在IDE之外,光靠它不够。

第二类是tmuxinator、tmux-resurrect这类终端布局工具。它们能把窗口、面板、启动目录编排得很好,但本质上只管"窗口长什么样",不管"环境变量是什么"和"AI看到什么背景"。布局是外壳,上下文是内核,两者经常是分离的。

第三类是dotfiles里的环境变量和别名,比如在.zshrc里写一堆export和alias。这种方式能全局生效,可是它把所有项目的变量揉在一起,项目切换时变量不会跟着变,反而容易串味。

所以我的结论是,需要的是一个处在"环境管理"和"任务编排"之间的东西:它既不是包管理器,也不是窗口管理器,而是专门管"当前工作语境"的薄层。

1.3 我给context-mode定的五条设计原则

一开始我很容易把方案做复杂,后来收敛到五条原则,每条都是从使用体验倒推出来的。

第一,切换要快。不管有多少环境变量要改、多少个hook要跑,整个切换过程必须控制在几百毫秒内,绝不能有那种"等它加载"的卡顿感。

第二,状态要透明。任何时候我都应该能问一句"我现在在哪个context",并且立即看到这个context里定义了哪些变量、哪些别名、哪些路径。不能出现"系统悄悄改了我的环境"这种失控感。

第三,模式要可版本化。所有模式定义都是纯文本文件,放在Git仓库里,改过什么、为什么改,都能追溯。这样即使三个月后我忘了当初为什么这么配,也能从提交记录里看出来。

第四,不覆盖用户手动意图。context-mode只管它自己定义过的变量。如果我在当前会话里手动export过一个变量,切换模式时绝对不能把它清掉,除非我在配置里显式声明要管理它。

第五,团队可复用。模式文件应该是可以分享的,新人拿到一份仓库配置,不需要我再口述就能拥有和我完全一致的工作语境。这点到后面尤其重要。

2. context-mode 核心设计:一个模式定义全部状态

2.1 一个context到底代表什么

我最初把context简单理解成"一组环境变量",后来发现这个定义太窄了。实际工作中,一个任务状态至少包含四样东西。

第一是目录锚点。每个模式都应该有一个默认的工作目录,切换时自动cd过去,省去在多个项目间跳来跳去的"我到底该在哪个目录敲命令"的犹豫。

第二是环境变量。不同的任务需要不同的运行时变量,比如调试模式可能需要DEBUG=1、日志模式可能需要LOG_LEVEL=debug、发布时可能需要NODE_ENV=production。这些变量如果不跟着模式走,很容易在错误的环境里执行了正确的命令。

第三是命令别名和函数。进入代码审查模式后,我需要一组专门看diff、看提交历史的快捷命令;进入写作模式后,我需要一组检查语法的命令。它们不属于某个特定项目,而是属于"我当前在做的事"。

第四是AI的提示词视角。这个是我后来才加的,也是我觉得价值最大的部分。同一个AI工具,写代码时它应该站在开发者视角帮你补逻辑,做审查时它应该站在挑剔的审查者视角挑毛病,写文档时它应该站在读者视角看表达。上下文决定了AI应该以什么身份回答问题,而不是每次都在聊天框里重新描述背景。

2.2 模式文件长什么样

我最终选择的存储格式是YAML,原因很简单:它是可读性最好的配置格式,适合给人和程序共同维护。每个模式对应一个文件,命名就是模式名,放在~/.context-mode/modes/下面。

一个典型的review模式文件看起来是这样的:

name: review description: 代码评审模式,专注于diff阅读和风险发现 workdir: "{{PROJECT_ROOT}}" # 将被自动替换 env: LOG_LEVEL: warn GIT_PAGER: cat REVIEW_STRICT: "1" aliases: diff: "git diff --stat && git diff" log: "git log --oneline -20" conflict: "git diff --name-only --diff-filter=U" hooks: on_enter: - echo "已进入review模式,严格检查为主" - git status --short | head -20 on_exit: - echo "退出review模式" ai_context: | 你现在是一名代码审查者。请以挑剔的态度审查代码变动, 重点关注:安全隐患、边界条件缺失、与既有架构不一致、 可读性问题。先给结论,再给具体行级建议,不要客套。

这个文件里每一段都对应我前面说的四样东西。env是环境变量,aliases是命令别名,hooks是切换时自动执行的命令序列,ai_context会被喂给AI工具作为它处理当前会话时的背景设定。workdir用了模板变量是因为团队不同成员的项目路径可能不同,具体值统一写在全局配置文件里。

2.3 加载顺序与变量优先级

设计context-mode时最头大的是变量优先级问题。我查阅Linux环境加载顺序的经验,结合shell初始化的常规实践,整理出了一套和自己习惯匹配的规则,这里说明一下。

加载顺序从低到高是:系统级环境变量(比如/etc/profile)→ 用户级dotfiles(比如.zshrc)→ context-mode的全局配置文件 → 当前激活的模式文件 → 用户当前shell里的手动export。

最大的原则是"用户手动export永远最大"。比如我在debug模式下手动把LOG_LEVEL改成了verbose,那就是真实的调试需求,context-mode绝不会在下一次切换时悄悄把它改回去。要实现这一点,不是靠写逻辑去判断哪个变量是用户手动设的,而是采取了一个很笨但有效的方法:切换模式时,不直接修改当前shell的变量,而是通过重新加载一段"上下文脚本"来构建环境。

这个"上下文脚本"每次切换时都会完全重新生成,里面包含模式文件定义的全部状态。你之前在这个session里export过的变量,如果模式和它不冲突,则原样保留;如果冲突,则以后者为准。这样既避免了残留,又不会把手动操作覆盖掉。

3. 从零搭一个可用的context-mode脚手架

3.1 目录结构与初始化

项目的主目录结构很简单,我用的是最朴素的方案:

~/.context-mode/ ├── init.sh # 全局初始化脚本,放在 .zshrc 里 source ├── config.yaml # 全局配置,模式间共享的信息 ├── modes/ # 每个任务模式一个 YAML 文件 │ ├── feature.yaml │ ├── review.yaml │ ├── debug.yaml │ ├── gitops.yaml │ └── writing.yaml └── commands/ └── cm.zsh # 核心切换命令实现

init.sh负责在shell启动时加载基础别名、定义cm函数,然后把commands目录里的实现source进来。我选择用zsh函数而不是独立的Python脚本来做核心切换,是因为zsh函数运行在当前shell进程里,可以直接修改环境变量、别名、当前目录,这些操作是外部脚本做不到的。

3.2 核心切换逻辑实现

核心切换逻辑是一个叫cm的zsh函数,它负责解析参数、加载配置、执行hook。这里分享一下最关键的切换动作,我简化了错误处理,保留了主干。

# ~/.context-mode/commands/cm.zsh _cm_modes_dir="$HOME/.context-mode/modes" _cm_active_mode="" function cm() { local mode_name="$1" local mode_file="$_cm_modes_dir/${mode_name}.yaml" if [[ -z "$mode_name" ]]; then echo "当前context-mode: ${_cm_active_mode:-none}" return 0 fi if [[ ! -f "$mode_file" ]]; then echo "未找到模式: $mode_name" >&2 ls "$_cm_modes_dir" | sed 's/\.yaml$//' return 1 fi # 1. 触发旧模式的 on_exit hook if [[ -n "$_cm_active_mode" ]]; then _cm_run_hook "$_cm_active_mode" "on_exit" fi # 2. 解析 YAML 中的 env / aliases / workdir / hooks _cm_parse_mode "$mode_name" _cm_apply_env "${_cm_parsed_env[@]}" _cm_apply_aliases "${_cm_parsed_aliases[@]}" # 3. 切换目录 if [[ -n "${_cm_parsed_workdir:-}" ]]; then cd "${_cm_parsed_workdir}" fi # 4. 写入 AI 上下文元信息 _cm_write_ai_context "$mode_name" # 5. 触发新模式的 on_enter hook _cm_run_hook "$mode_name" "on_enter" # 6. 记录当前模式 _cm_active_mode="$mode_name" export CM_MODE="$mode_name" echo "context: ${mode_name}" }

这段代码里有几个细节值得说。

第一步先跑旧模式的on_exit,是为了清理现场。比如debug模式退出时,一般会执行一条unset DEBUG的hook,防止变量带到下一个模式。

第二步解析YAML和第五步执行hook,解析用了一个小的命令行工具来读取YAML字段,然后存到全局的_cm_parsed_*变量里。这部分的实现很长,但思路很简单,就是把YAML中env下的键值对逐个变成export语句。

第三步切换目录放在设置环境变量之后,因为有些环境变量的值里会引用workdir。比如PROJECT_ROOT就是一个全局模板变量,在apply阶段会被替换成当前激活的项目路径。

第四步写AI上下文,我会在下面单独展开。

3.3 把上下文喂给AI助手

上下文要能直接传达给AI,才能解决"AI每次都要重新解释背景"这个痛点。我的方案是:在进入每个模式时,生成一个标记文件,指向当前模式及其描述。

文件路径是~/.context-mode/current_context.txt。内容很简单:

mode: review description: 代码评审模式,专注于diff阅读和风险发现 project: my-server timestamp: 2025-01-15 14:22:03

然后我在常用的AI命令行工具或IDE插件的配置里,让它自动读取这个文件并作为system prompt的前置内容。这里要注意的是提示词内容的写法:越具体、越带约束的提示词,越容易把AI限制住,尤其是代码生成场景。我给ai_context字段定了一个尺度——只告诉AI"你现在是谁、关注什么、输出风格是什么",不告诉它"具体怎么做、每一步干什么"。

比如feature模式的AI上下文是:

你现在是一名前端业务开发工程师,负责在既有代码库中实现新功能。 请优先保持已有代码风格,考虑边界状态和错误处理。 回答时先说明思路,再给代码片段。

review模式的AI上下文就是"严格审查者,关注安全和边界,先给结论再给证据"。同一个AI,因为上下文文件被切换而自动改变视角,这就是context-mode最直接的价值。

3.4 五组开箱即用的模式配置

我把自己日常最常用的五种模式整理成了标准配置,放进仓库里。这里给一份简表:

模式名适合场景关键环境变量核心别名AI视角
feature开发新功能NODE_ENV=developmentdev、test:watch业务开发工程师,保持风格
review代码审查REVIEW_STRICT=1diff、conflict挑剔审查者,先结论后建议
debug排查问题LOG_LEVEL=debugtrace、gc调试助手,引导定位证据链
gitops版本库维护GIT_PAGER=catclean、sync、amend版本管理助手,强调可回滚
writing写文档/方案FORMAT_MODE=markdownfix-typo、lint-doc文档编辑,关注表达清晰度

每种模式的文件结构完全一致,团队里其他人想新增模式,只需要照着已有格式写一个YAML文件,不需要改核心逻辑。

4. 三场真实场景演练

4.1 场景一:从功能开发切到线上日志排查

我以一次真实的线上事故为例来演示。上午我在feature模式下写新功能,环境变量是标准的开发配置,别名也都是跟测试相关的快捷命令。突然群里报接口超时,我得立刻切到debug模式去查日志。

操作只需要一条命令:cm debug。执行之后发生了什么?首先它运行了feature模式的on_exit,把开发模式下设置的那些测试专用别名清掉。然后读取debug.yaml,把LOG_LEVEL从error改成debug,把NODE_ENV切回production-like,cd到日志项目目录,顺手把滚动日志的tail命令作为别名加好。最后on_enter hook自动跑了一条echo "当前日志级别: debug",让我清楚地知道环境已经变了。

这个过程中我没有手动做过任何一件"回忆项目路径、翻历史命令"的事。从发现问题到输出第一条日志筛选命令,时间缩短了一半以上。

4.2 场景二:把AI从"生成者"切成"审查者"

代码在review模式下的表现,和平时写代码时完全不一样,因为AI看到的上下文变了。在feature模式里让AI解释一段算法,它会顺着你的思路补充实现细节;切换到review模式后,同一个AI会更倾向于挑毛病,比如指出"这里的并发更新是不安全的""这个边界值在金额为负时没有处理"。

执行cm review,AI工具的插件检测到current_context.txt里的mode字段变了,自动切换system prompt。它不会再以为自己是来帮忙写功能的,而是以审查视角工作。我在自己的实际使用中,这个模式对减少低级遗漏非常有帮助,相当于每一版代码都自动过了一道用挑剔视角读代码的关卡。

4.3 场景三:团队共享一套context-mode配置

把模式文件放到Git仓库后,团队共享就顺理成章了。新人接入时只需要做两件事:把context-mode配置仓库clone下来,然后修改全局config.yaml里的PROJECT_ROOT指向自己的项目路径。因为所有模式定义都在版本控制里,即使配置有过变更,也能通过提交记录追溯到"为什么这个模式里的变量被改了"。

这种共享还有个隐形收益:团队对"代码审查应该关注什么""线上排查时应该看哪些日志级别"这类问题的理解被统一了。context文件本身变成了一份活的团队协作规范。它没有强制任何人,但所有人都默认用了同一套工作语境。

5. 常见问题与排查套路

5.1 切换后环境变量不生效怎么办

最常见的问题是切换模式后,当前终端里新开的子进程没有拿到新环境变量。原因通常是环境变量只在当前shell进程里export了,但子进程(比如tmux里创建的新面板)不会继承当前shell的环境变量。

解决办法是在init.sh里加一个update命令,它会把当前模式文件里的环境变量重新export到所有已注册的tmux面板。这里有一个关键操作:需要主动告知tmux"有几个面板需要同步",否则同步机制不会覆盖全部面板。

5.2 alias和函数被旧模式锁住

切换模式后,旧的别名不一定立即失效,因为zsh的alias一旦定义,在会话生命周期内是固定的,除非显式unalias。我在debug模式里踩过这个坑:从debug切到writing后,写文档时敲了一个本来应该在debug模式里的命令,结果还是执行的是旧逻辑。

解决方案很简单:在on_exit hook里统一跑unalias -m '*debug_*'这类批量清理命令,或者在切换时先禁用当前模式定义过的所有别名,再应用新模式。这样能避免出现"旧模式的幽灵别名"。

5.3 context的嵌套与堆栈管理

实际操作中不可能永远单层模式。我经常在review模式下临时要查一个debug日志,如果直接从review切到debug,之前建立的审查状态就丢了。我为此给cm加了一个栈机制:cm push debug会暂时进入debug模式,cm pop会切回之前的review模式。

栈管理用起来特别顺手,它允许你在一个核心任务中临时"借道"另一个上下文,办完事再回来。需要注意的点是栈的容量要有限制,我限制为4层,防止自己一路push下去忘记自己最初在干什么。

5.4 与shell框架的加载顺序冲突

如果你用了oh-my-zsh或者zinit,它们的别名和补全系统加载顺序可能和context-mode冲突。我自己遇到的情况是:.zshrc里先source了oh-my-zsh,再source init.sh,导致部分别名被覆盖,因为oh-my-zsh的一些插件会在它之后执行。

解决的常规做法是让init.sh最后加载,并在cm命令里执行一次rehash和compinit,确保补全信息是最新的。同时我在init.sh开头做了一个防御,检测是否已经加载过框架,避免重复source导致变量被重置。

5.5 常见问题速查表

现象原因处理办法
切换后env未更新子进程继承的是旧环境使用同步命令更新所有tmux面板
旧alias仍然生效zsh alias不会自动清理在on_exit统一批量unalias
嵌套模式下栈溢出忘记pop设置最大栈深度,超限自动报警
与oh-my-zsh别名冲突加载顺序不对init.sh放在最后,切换时rehash
AI没有切换视角current_context.txt没被读取检查AI插件的配置路径是否指向该文件
团队内变量路径不同workdir硬编码全部改为引用全局PROJECT_ROOT

6. 复盘:三个坑和两条心得

6.1 几乎让我放弃的三个设计

第一个坑是过度自动化。我最初想做一个"智能检测",根据当前目录和近期命令自动判断上下文并切换,结果误判率高得吓人,经常在关键时刻跳到错误模式,比手动切更浪费时间。后来果断去掉自动切换,改回手动触发加"推荐提示",只在终端右侧显示一条建议,不自动执行。

第二个坑是AI提示词写太满。早期版本的ai_context字段我写了一大段详细规则,结果AI变得极度死板,连基本的灵活性都没了。后来我把提示词压缩成"身份+关注点+输出风格"三要素,效果反而好了很多。

第三个坑是试图把context-mode变成一个通用的、可配置的框架,加入了插件系统、扩展点、事件总线。做了一半发现,抽象层越多,配置负担越重,最终变成一个只有我自己能维护的玩具。后来我刻意砍掉所有可扩展设计,回归到"纯粹的状态文件+几个shell函数"。

6.2 后续我打算怎么扩展

目前context-mode已经稳定运行了一段时间,我考虑的两个扩展方向都不涉及大改动。

一个是把模式定义从YAML迁移到JSON Schema,这样可以用工具自动校验字段,团队共享时能更早发现配置错误。另一个是让context的状态可以跟着Git分支走:每个分支维护自己的CM_MODE值,checkout时如果不匹配就提示,这样分支切换和工作上下文切换能协同起来。

我个人在实际操作中最强烈的感受是:能感觉到"工作状态"正在取代"记忆文件"成为我日常开发的真正入口。过去切换任务靠的是在脑子里翻找"上次做到哪"的碎片记忆,现在只需要问自己一句"接下来是什么模式",然后按一下切换。这个转变不大,但实实在在影响了我每一天的工作效率,也是我推荐你也试一把的原因。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询