首页
/ 深入解读 Cline 仓库的 AI 编程助手规则文件:general.md 如何沉淀工程"部落知识"

深入解读 Cline 仓库的 AI 编程助手规则文件:general.md 如何沉淀工程"部落知识"

2026-09-06 19:15:59作者:申梦珏Efrain

Cline 仓库在 .clinerules/general.md 中维护了一份面向 AI 编程助手(也完全适用于人类开发者)的"部落知识"手册。它记录的不是架构文档式的常规信息,而是那些"读几个文件就能猜到"之外的反直觉经验:Bun 工具链与 Node 运行时的边界、如何避开构建产物做代码搜索、gRPC/Protobuf 通信的改动清单、全局状态键的多点接线陷阱、ChatRow 取消态的推断模式,以及调试 harness 的 env 继承问题。读完本文,你可以在 Cline 这个 VS Code 扩展 + CLI + SDK 仓库中避开作者曾经踩过、并用文件固化下来的典型坑。

一、这份规则文件的定位:高信噪比的"纠错记录"

.clinerules/general.md 开篇即自我定义:它是"在这个代码库中高效工作的秘密 sauce",收集的是微妙、非显而易见的模式——决定一次修改是"快速搞定"还是"来回折腾数小时"的那些细节。

文件明确给出了何时应该往里面添加条目的触发条件:

  • 用户不得不介入、纠正或手把手指导;
  • 某个东西经过多次来回尝试才跑通;
  • 你为了理解某个东西读了很多文件才发现真相;
  • 一次改动触及了你原本完全猜不到的文件;
  • 某个行为与你的预期不同;
  • 用户显式要求"把这个加到 CLAUDE.md"。

并且要求主动建议添加——出现上述情况时不要等别人开口。同时文件给出了反向边界:不该添加那些"读几个文件就能明白的东西、显而易见的模式或标准实践"。这份文件追求的是高信噪比,而不是面面俱到。

这种"由纠错事件驱动的增量沉淀"是一个值得借鉴的团队知识管理范式:条目不是事先设计的目录,而是事故与摩擦的化石记录。同目录下还有一组按主题拆分的规则文件(如 bun-and-node.mdnetwork.mdprotobuf-development.md),general.md 中通过 @.clinerules/xxx.md 形式的引用把它们串联起来。

二、Bun 管工具链,Node 管运行时:不可混淆的边界

general.md 的 Misc 部分第一条就划定了全仓库(含 apps/vscode)的工具链规则:

  • 包管理与任务运行一律用 bunbun run Xbun installbunx <bin>bun file.ts永远不要用 npm/npx;
  • Node 仍是运行时——VS Code 扩展宿主和独立的 cline-core 都跑在 Node 上,因此源码中 Node 运行时的令牌(node: 导入、process.versions.nodeengines.node 等)是合法的,不能被"顺手修"成 bun

这一点在同目录的 bun-and-node.md 中展开为一张"保留清单 vs 改写清单":esbuild 的 platform: "node"TARGET_NODE_VERSIONprebuild-install --target=<node version>NODE_PATH=... node cline-core.jsELECTRON_RUN_AS_NODE 等都属于 Node 运行时/ABI 引用,原样保留。该文档还给出测试运行器的判定规则:测试文件 import ... from "bun:test"import ... from "mocha" 二选一——前者由 bun test 执行,后者需要在真实 VS Code 扩展宿主(Node)下由 @vscode/test-cli 执行。

另一个容易踩的坑写在 Misc 第 3 条:这是一个 VS Code 扩展,验证构建前先查 package.json 里有哪些脚本。例如编译命令是 bun run compile 而不是 bun run build。在 apps/vscode/package.json 中可以印证:compile 脚本是 bun run check-types && bun run lint && bun esbuild.mjs,而 packageprotosnode scripts/build-proto.mjs)等各有分工,仓库里并不存在名为 build 的顶层脚本。

此外还有两条杂项规则值得保留在团队规则里:

  • 读取用户可编辑的配置文件时,使用 @cline/shared/node 提供的 readFileStrippingUtf8Bom / readFileSyncStrippingUtf8Bom / stripUtf8Bom 去除 UTF-8 BOM,但不要剥掉工具处理或传给模型的、属于用户的文件中的 BOM(对应实现可在 sdk/packages/shared/src/parse/string.ts 一带查证);
  • 修复 provider/配置管线时,避免按 provider 字符串做硬编码分支,应优先使用 provider 元数据、共享 catalog/默认值、显式的协议/客户端能力声明,或按数据形状生效的集中式归一化工具;如果某个 provider 例外似乎不可避免,停下来解释原因,而不是添加临时性的字符串匹配。

三、代码搜索:绕开构建产物与生成代码

general.md 用专门一节警告:多个目录里装着构建产物或生成代码,直接对它们做 grep/search_files 会得到嘈杂或不可用的结果。原文的表格(目录均为 apps/vscode 下的相对位置)如下:

目录 是什么 为什么是问题
out/ esbuild 打包输出 以压缩 JS 形式镜像 src/ 结构——每次搜索都在单行文件上得到重复命中
dist/ 打包后的扩展 整个扩展被 bundle 成一个压缩的 extension.js(约 1 行)
dist-standalone/ 独立构建输出 同样的压缩问题
src/generated/ 生成的 protobuf 代码 proto/ 自动生成,不是事实来源
src/shared/proto/ 生成的 proto 类型定义 proto/ 自动生成,不是事实来源
node_modules/ 依赖 巨大,且不是项目源码

文档给出两条标准操作:

用文件工具搜索时,把路径指向 src/ 而不是项目根,并用 file_pattern 过滤(file_pattern 是最有效的过滤器,如 "*.ts""*.tsx""*.proto"):

search_files(path="src/core", regex="myFunction", file_pattern="*.ts")

用 grep 直接搜时,排除构建目录并限定源码扩展名:

grep -rn "myFunction" src/ --include="*.ts" --exclude-dir={out,dist,node_modules,generated}

必须搜索压缩文件(例如验证某个改动是否进了构建产物)时,由于压缩文件通常是一行超长代码,普通 grep 会把整个文件当上下文打印,文档给出三个替代方案:

  • grep -oP 只抽取匹配点及有限上下文:
    grep -oP '.{0,40}myFunction.{0,40}' dist/extension.js
    
  • 直接读 out/src/ 下的文件——它们带 source map,比完全 bundle 的 dist/extension.js 可读得多;
  • 用 source map(out/src/*.js.mapdist/extension.js.map)把压缩输出回溯到原始源码位置。

四、gRPC/Protobuf 通信:一次功能改动要触及的完整清单

Cline 的扩展后端与 webview 之间通过"基于 VS Code 消息传递的类 gRPC 协议"通信,proto 文件是协议的事实来源。注意:general.md 中的 proto/cline/... 等路径都是相对 apps/vscode 子项目的,换算到仓库根即 apps/vscode/proto/cline/

Proto 文件组织规则proto/cline/ 下每个功能域一个 .proto 文件,仓库中实际可见 task.protoui.protoaccount.protostate.proto 等 18 个文件):

  • 简单数据用 proto/cline/common.proto 里的共享类型(StringRequestEmptyInt64Request);
  • 复杂数据在功能自己的 .proto 里定义自定义 message;
  • 命名约定:Service 用 PascalCaseService,RPC 用 camelCase,Message 用 PascalCase
  • 流式响应使用 stream 关键字(如 account.proto 中的 subscribeToAuthCallback)。

任何 proto 改动之后必须运行 bun run protos,生成的代码落在四处:

  • src/shared/proto/ — 共享类型定义;
  • src/generated/grpc-js/ — 服务实现;
  • src/generated/nice-grpc/ — Promise 风格的客户端;
  • src/generated/hosts/ — 生成的 handlers。

新增枚举值(例如新的 ClineSay 类型)时,除了 proto 本身,还必须更新 src/shared/proto-conversions/cline-message.ts 中的转换映射。

新增 RPC 方法需要:在 src/core/controller/<domain>/ 下实现 handler;webview 侧通过生成客户端调用,例如 UiServiceClient.scrollToSettings(StringRequest.create({ value: "browser" }))

文档用一个真实功能 explain-changes 演示了"一个功能到底要碰哪些文件":

  • proto/cline/task.proto — 新增 ExplainChangesRequest message 与 explainChanges RPC;
  • proto/cline/ui.proto — 在 ClineSay 枚举中新增 GENERATE_EXPLANATION = 29
  • src/shared/ExtensionMessage.ts — 新增 ClineSayGenerateExplanation 类型;
  • src/shared/proto-conversions/cline-message.ts — 新增对应 say 类型的映射;
  • src/core/controller/task/explainChanges.ts — handler 实现;
  • webview-ui/src/components/chat/ChatRow.tsx — UI 渲染。

这个清单的价值在于:它把"看起来只加个按钮"的功能,还原成了横跨 proto、共享类型、转换层、controller、webview 六层的真实工作量。

五、新增全局状态键:漏掉任何一步都是静默失败

general.md 把"添加全局状态键"列为典型的静默失败陷阱——必须同时完成三步(路径同样相对 apps/vscode):

  1. 类型定义:在 src/shared/storage/state-keys.tsGlobalStateSettings 接口中加入该键;
  2. 若需要默认值或转换,也在 state-keys.ts 中一并处理;
  3. 初始化之后通过 StateManager 读写(setGlobalState() / getGlobalStateKey())。

文档进一步强调了一条存储架构约束:持久状态是文件后端的,经由 StateManager 管理;不要对 VS Code ExtensionContext 存储新增运行时读写——那个存储只是遗留迁移的来源。

还有两个"接线遗漏"陷阱:

设置管线双路径陷阱:如果一个键可以在设置界面切换,必须同时接两条 controller 更新路径:

漏掉其中一条,现象就是"开关在一个界面看起来变了,但后端状态没变"。

Webview 回环陷阱:设置变更必须在状态载荷中回环,需要:

  • 把字段加入 proto/cline/state.protoUpdateSettingsRequest(webview 更新请求用),然后运行 bun run protos
  • 把键加入 Controller.getStateToPostToWebview()(位于 src/core/controller/index.ts);
  • 确保 ExtensionState 与 webview 默认值都包含该键(src/shared/ExtensionMessage.tswebview-ui/src/context/ExtensionStateContext.tsx)。

缺了这条回环,后端值更新了,但 webview 里的开关"卡住"或自动弹回去。

六、StateManager 缓存 vs 直接访问 globalState

文档明确了状态访问的默认姿势:StateManagerinitialize() 期间从文件后端存储填充一个内存缓存,绝大多数场景都应使用 controller.stateManager.setGlobalState() / getGlobalStateKey()

// 写入(常规模式)
controller.stateManager.setGlobalState("myKey", value)

// 初始化后读取
const value = controller.stateManager.getGlobalStateKey("myKey")

唯一例外是宿主迁移代码:它可能在文件后端存储初始化之前就读取遗留的 VS Code 存储。此时才允许直接触碰 context.globalState,且仅用于把遗留 ExtensionContext 值拷贝进共享的文件后端存储。

七、ChatRow 的取消/中断态:从上下文推断,而非读取消息内容

这是全文最"反直觉"的一段。问题背景:ChatRow 展示加载/进行中状态(spinner)时,任务取消不会更新消息内容——取消发生时,消息里的 status 字段(以 JSON 形式存在 message.text 里,如 "generating""complete""error")会永远停留在 "generating",没有任何代码去更新它。因此取消状态必须推断

推断模式是两个条件的组合:

  1. !isLast —— 这条消息已不是最后一条,说明它之后发生过别的事(被中断);
  2. lastModifiedMessage?.ask === "resume_task" || "resume_completed_task" —— 任务刚被取消、正等待恢复。

文档用 generate_explanation 的真实代码说明:

const wasCancelled =
    explanationInfo.status === "generating" &&
    (!isLast ||
        lastModifiedMessage?.ask === "resume_task" ||
        lastModifiedMessage?.ask === "resume_completed_task")
const isGenerating = explanationInfo.status === "generating" && !wasCancelled

两个条件为什么缺一不可:!isLast 捕获"取消 → 恢复 → 又干了别的 → 这条旧消息已过期"的场景;ask === "resume_task" 捕获"刚取消、还没恢复、这条消息在技术上仍是最后一条"的场景。webview-ui/src/components/chat/BrowserSessionRow.tsx 使用类似的 isLastApiReqInterruptedisLastMessageResume 模式,可作为第二处参照。

后端侧同样有配套约定:流式处理被取消时,在流式函数返回后检查 taskState.abort,做妥善清理(关标签页、清注释等)。

八、调试 harness:启动前先清掉继承来的 VS Code/Electron 环境变量

Cline 的调试 harness(apps/vscode/src/dev/debug-harness/server.ts)用 Playwright 的 _electron.launch({ env: { ...process.env, ... } }) 启动一个子 VSCode。文档指出一个隐蔽的坑:如果 harness 本身是从一个由 VS Code 派生的进程里运行的(Cline 扩展宿主、集成终端、或 VS Code 内的 agent),父进程的 VS Code/Electron 环境变量会泄漏进子进程并弄坏启动。

最致命的是 ELECTRON_RUN_AS_NODE=1:它让子 VSCode 二进制以纯 Node 身份运行,从而拒绝所有 VS Code CLI 参数。症状是:

.../Visual Studio Code.app/Contents/MacOS/Code: bad option: --extensionDevelopmentPath=...
Error: Process failed to launch!   (Playwright _electron.launch)

文档特别强调这不是 harness README 里提到的 macOS Playwright 偶发问题,而是 env 继承问题。修复方式是在启动前剥掉继承变量:

env -u ELECTRON_RUN_AS_NODE -u ELECTRON_NO_ATTACH_CONSOLE \
    -u VSCODE_CLI -u VSCODE_CODE_CACHE_PATH -u VSCODE_CRASH_REPORTER_PROCESS_TYPE \
    -u VSCODE_CWD -u VSCODE_ESM_ENTRYPOINT -u VSCODE_HANDLES_UNCAUGHT_ERRORS \
    -u VSCODE_IPC_HOOK -u VSCODE_NLS_CONFIG -u VSCODE_PID -u VSCODE_L10N_BUNDLE_LOCATION \
    bun src/dev/debug-harness/server.ts --auto-launch --skip-build

先自检环境:env | grep -iE 'electron|vscode_';只要存在 ELECTRON_RUN_AS_NODE=1,就必须先清洗再启动。

文档还收录了三条在实操中确认的 harness 使用细节,都是"试错多次才换来"的经验:

  • 扩展宿主是 ESMVSCODE_ESM_ENTRYPOINT),所以 ext.evaluate 里没有 require,模块内部函数也拿不到全局。要检查内部构造器(如 buildBedrockProviderConfig)时,用 ext.set_breakpoint 打断点,再用暂停时的 callFrameId 通过 ext.evaluate 读局部变量——不要试图 require() 整个 bundle;
  • web.evaluate 把表达式包成单个返回表达式,多语句片段必须写成 IIFE (() => { ...; return x; })(),否则报 SyntaxError: Unexpected token ';'
  • webview 设置输入是 vscode-text-field Web Component,内部是带防抖的 React onChange。对某些字段,web.evaluate 里直接 .value + 派发事件并不可靠;应聚焦其 shadow DOM 里的内层 input,再用真实按键输入(ui.type + ui.press Tab,或点击下拉项)让值真正持久化。

九、对团队工程实践的三点启示

回顾 general.md 的条目,可以看到一份高质量 AI 编程助手规则文件的共同特征:

  1. 条目源于事故,而非设计:Bun/Node 边界、env 继承、双路径设置接线,全都对应一次"用户不得不介入"的真实故障,因此每条都自带症状描述与可复现的修复命令;
  2. 给出"漏掉会怎样"的后果:如"漏掉一条更新路径 → 开关看起来变了但后端没变",这让规则具备了可自检性;
  3. 用真实改动做样例explain-changes 六文件清单、generate_explanationwasCancelled 代码,都是从仓库真实代码中摘出的活例子,而非假想 API。

配合 AGENTS.md 与同目录的主题化规则文件(storage.mddebug-harness.mdsdk-migration.md),Cline 展示了如何把一个大型多产品仓库(VS Code 扩展、CLI、SDK)的隐性经验,变成 AI 与新人共享的、可检索的工程知识层。

登录后查看全文
热门项目推荐
相关项目推荐