乐于分享
好东西不私藏

当 AI 写代码遇到工程问题:一个开源插件如何系统性地解决它们

当 AI 写代码遇到工程问题:一个开源插件如何系统性地解决它们

本文基于对 everything-claude-code(简称 ECC)的深度研究。ECC 是一个为 Claude Code 构建的生产级插件集合,包含 28 个专用代理、119 个技能、60 个命令和 99+ 个自动化钩子。它的价值不在于功能列表,而在于它对”AI 辅助编程”中若干核心工程问题给出了系统性答案。


unsetunset一、背景:AI 编程助手面临的真实问题unsetunset

用过 Claude Code、Cursor 或 Copilot 的开发者大概都有过类似体验:

  • 对话进行到后半段,模型开始”忘事”,重复犯错;
  • 换个会话,之前建立的项目背景全部消失,需要重新解释;
  • 让模型自主完成一个大任务,跑着跑着就偏了,或者陷入死循环;
  • 一个简单的问题,模型却调用了十几个工具,花了大量时间读文件。

这些不是偶发的 bug,而是 AI 辅助编程的结构性问题。ECC 的有趣之处在于:它把这些问题逐一拆解,并用工程手段给出了可落地的解法。

本文从六个维度梳理这些解法背后的原理。


unsetunset二、Token 优化:让每一分钱花在刀刃上unsetunset

AI 模型按 token 计费。对于长时间、高频率的编程会话,token 消耗是真实的成本压力。ECC 从三个维度应对:

2.1 按任务复杂度分配模型

不是所有任务都需要最强的模型。ECC 把 28 个专用代理按能力分三档:

  • Opus:用于需要深度推理的任务——系统架构设计、复杂功能规划。这类任务占比低,但复杂度高,值得投入最强算力。
  • Sonnet:用于日常编码工作——代码审查、测试编写、文档更新。覆盖绝大多数场景。
  • Haiku:用于探索性工作——读文件、搜索代码、运行测试、收集信息。这类工作量大但含金量低。

关键一步:通过环境变量 CLAUDE_CODE_SUBAGENT_MODEL=haiku,所有子代理(Task 工具启动的后台任务)默认跑在 Haiku 上。子代理的典型任务是”读 20 个文件,返回一个摘要”——Haiku 完全够用,成本是 Sonnet 的约三分之一。

这个设计的本质是职责分离:贵的模型只做推理,便宜的模型做信息收集。

2.2 战略性释放上下文

AI 模型有上下文窗口限制。随着对话推进,窗口逐渐填满,模型质量开始下降。常见做法是等到快满了再压缩(ECC 观察到默认触发阈值是 95%),但此时质量已经明显劣化。

ECC 的策略是在逻辑边界主动压缩,而非等到被迫。具体原则:

时机
是否压缩
理由
探索阶段结束,进入实现阶段
探索内容量大,提炼后的计划才是真正有用的
完成一个功能里程碑
已完成的代码通过 git 固化,不需要留在 context
调试结束,转向新功能
调试痕迹会污染后续工作
正在实现相关改动中途
变量名、中间状态是活跃依赖

还有一个被忽视的成本来源:MCP 服务器。每个 MCP 工具的 schema 约 500 个 token,而这些 schema 在每次工具调用时都被传给子代理。一个装了 30 个工具的 MCP 服务器,消耗的 token 比所有技能文档加起来还多。

2.3 把低价值工作放到后台

ECC 的钩子系统(hooks)大量使用异步执行:代码格式化、lint 检查、构建分析、会话保存——这些工作在后台完成,主模型无需等待。

更重要的是,连续学习系统(后文详述)也在后台跑:一个 Haiku 子代理悄悄分析你的操作模式,主会话完全感知不到它的存在。


unsetunset三、记忆持久化:跨会话不失忆unsetunset

Claude Code 的每次会话都是独立的——上次讨论过的上下文,下次启动后全部消失。这对于持续开发一个项目来说非常麻烦。

ECC 用一套文件系统机制解决这个问题。

3.1 每次响应后自动保存

每当 Claude 完成一次响应(Stop 事件),一个后台脚本会悄悄运行:

  1. 读取本次会话的完整对话记录(JSONL 格式);
  2. 从中提取:用户说了什么(最后 10 条,每条截取前 200 字)、用了哪些工具、修改了哪些文件;
  3. 把这些信息写入 ~/.claude/sessions/ 下一个以日期命名的 Markdown 文件。

这个文件不是原始对话记录,而是结构化摘要

## Session Summary
### Tasks(用户做了什么)
- 分析 token 优化的三个维度
- 研究跨会话上下文保存机制

### Files Modified(修改了哪些文件)
- scripts/hooks/session-end.js

### Tools Used(用了什么工具)
Read, Grep, Glob, Agent

关键设计:脚本每次响应都运行,但通过 HTML 注释标记(<!-- ECC:SUMMARY:START/END -->)实现幂等更新——每次只替换摘要块,用户手写的备注不会被覆盖。

3.2 下次启动时自动注入

新会话开始时(SessionStart 钩子),另一个脚本:

  1. 查找最近 7 天内的 session 文件;
  2. 读取最新一个,通过 stdout 直接输出给 Claude;
  3. Claude Code 把 hook 的 stdout 自动注入到 context——不需要用户做任何操作。

效果:打开新会话,Claude 就像刚从昨天的工作状态醒来,知道你在做什么、改了哪里、下一步是什么。

3.3 Compact 前的标记

当 Claude 执行上下文压缩(compact)时,ECC 在 session 文件中追加一条时间戳记录:[Compaction occurred at 15:30:22] - Context was summarized

这让下次会话能推断出:上次有过压缩,某些细节可能已丢失——从而主动补充而非假设模型什么都记得。


unsetunset四、持续学习:从操作日志到可重用知识unsetunset

记忆持久化解决了”不忘事”的问题。持续学习解决更进一步的问题:如何从重复模式中提炼规律,让模型在这个项目上越用越顺手?

ECC 的持续学习系统(v2.1)引入了”本能(instinct)”这个概念。

4.1 观察层:100% 可靠的数据采集

系统在每次工具调用前后(PreToolUse/PostToolUse 钩子)捕获事件:

{
"timestamp""2026-03-23T10:30:00Z",
"event""tool_complete",
"tool""Edit",
"input""src/hooks/useAuth.ts ...",
"project_id""a1b2c3d4e5f6",
"project_name""my-react-app"
}

为什么用钩子而不是让 Claude 自己总结?因为依赖 Claude 自我报告的可靠率只有 50-80%,而钩子是确定性触发的,**可靠率 100%**。

数据写入前经过密钥清洗(替换 api_key、token 等敏感字段),按项目隔离存储(用 git remote URL 的哈希值作为项目 ID,同一仓库在不同机器上得到相同 ID)。

4.2 分析层:后台 Haiku 挖掘模式

积累了足够的观察数据后(默认 20 条),一个后台 Bash 守护进程启动一个 Haiku 子代理来分析。它寻找四类模式:

  • 用户纠正:用户说”不对,用 X 而不是 Y”——提炼成”做 X 时,应该用 Y”
  • 错误解决:工具输出含错误,然后被修复,同类错误多次出现——提炼成”遇到这类错误,试试这个方法”
  • 重复工作流:相同的工具调用序列出现 3 次以上——提炼成”做某事时,按这个步骤来”
  • 工具偏好:总是先 Grep 再 Edit,而不是直接 Edit——提炼成”修改代码前先搜索确认”

只有 3 次以上观察才会产生本能,置信度从 0.3(试探性)到 0.9(接近确定),并随时间动态调整。

4.3 知识分层:项目级与全局级

一个关键设计:本能有作用域之分。

  • 项目级:React 函数组件约定、Django REST 模式、特定文件结构——只适用于当前项目
  • 全局级:安全规范、通用最佳实践、工具工作流偏好——在所有项目中通用

当同一个本能在 2 个以上项目中出现、且置信度均 ≥ 0.8 时,自动升级为全局级。

这防止了一个常见问题:某个项目的特有约定”污染”了其他项目的建议。


unsetunset五、验证循环:用评测驱动开发unsetunset

ECC 引入了一套完整的评测(eval)框架,理念来自”AI 开发的单元测试”:先定义成功标准,再写实现代码

5.1 两类评测,两种标准

能力评测(Capability Eval):验证新功能是否实现了预期能力。

回归评测(Regression Eval):验证旧功能没有被破坏。

两类评测采用不同的通过标准:

  • 能力评测:pass@3 ≥ 90%——3 次尝试中至少一次成功,且成功率不低于 90%
  • 回归评测:pass^3 = 100%——3 次连续尝试全部通过

为什么有这个区别?能力评测针对新功能,允许模型需要几次尝试才做对;回归评测针对已有功能,一次失败都是问题。

5.2 三种评分器

评测系统支持三类评分器,按可靠性递减排序:

代码评分器(确定性):跑测试、检查构建、用正则匹配文件内容。结果只有 PASS/FAIL,没有歧义。优先使用。

规则评分器(模式匹配):检查代码中是否出现了特定模式(比如”是否有重试逻辑”)。确定性略低于代码评分器,但适用于测试覆盖不到的场景。

模型评分器(LLM 打分):让另一个模型评价输出质量。适用于”代码是否结构清晰”这类主观问题。概率性的,不能用于关键路径的验收。

文档明确警告了评测反模式:用已知的 eval 案例过拟合 prompt、只测正常路径不测边界、在追求通过率时忽视成本和延迟。

5.3 技能自我改进循环

更底层的地方,ECC 还有一套技能健康度系统:

每次技能被使用后,记录一条观察(成功/失败、错误信息、用户反馈)。积累足够数据后,系统可以做两件事:

  1. A/B 对比:同一技能的原始版本 vs 修改版本,哪个成功率更高?自动推广更好的版本。
  2. 失败驱动修改:某个技能反复在特定任务上失败,自动生成修改提案——”在这个场景下加一个验证步骤”。

unsetunset六、并行化:让多个 AI 同时工作unsetunset

单个 AI 的速度有限。ECC 提供了完整的并行化工具链,核心是 Git Worktree 隔离

6.1 每个工作进程独立隔离

并行执行的核心挑战是:多个 AI 同时修改代码,如何避免互相干扰?

ECC 的答案:每个工作进程(worker)获得一个独立的 git worktree——本质上是同一个仓库在不同目录下的独立副本,有自己的分支、自己的工作目录。

主仓库(main 分支)
├── worker-1 → /tmp/repo-session-auth-worker/(auth 分支)
├── worker-2 → /tmp/repo-session-test-worker/(test 分支)
└── worker-3 → /tmp/repo-session-docs-worker/(docs 分支)

每个 worker 的文件系统完全独立,互相看不见对方的改动。完成后通过 git merge 汇合,冲突在合并时而不是编写时处理。

通信通过三个文件完成:task.md(要做什么)、handoff.md(做完了什么)、status.md(当前状态)。没有共享内存,没有网络调用,极其简单可靠。

6.2 任务依赖图(DAG)驱动级联

多个任务之间往往有依赖关系。ECC 的 DevFleet 系统把任务组织成有向无环图(DAG):

M1: 搭建项目结构(无依赖)
M2: 实现 user model(依赖 M1)
M3: 实现 auth middleware(依赖 M1)   ← M2 和 M3 可并行
M4: 集成测试(依赖 M2 和 M3)

只需手动触发 M1,后续任务在依赖满足时自动分发(auto_dispatch: true)。默认最多 3 个任务同时运行,超出的排队等待。

更复杂的场景(如大型 RFC 分解)还支持按任务复杂度分配不同深度的质量流水线:简单改动只需实现+测试,复杂改动要经过研究→规划→实现→测试→代码审查→修复→最终审查。越复杂的任务得到越深度的审查,而不是一刀切。

6.3 何时扩展

并行化不是免费的。ECC 有一个明确的决策准则:

  • 任务可以独立分解 → 才值得并行
  • 单文件改动 → 不扩展(串行更简单)
  • 快速迭代一件事 → 不扩展(保持 context 连续性更重要)
  • 有书面规格说明且涉及多个模块 → 适合并行

还有一个被专门文档化的反模式:重复失败的盲目重试。如果一个任务失败了,不要直接重试,要把失败上下文(错误信息、冲突的代码)传给下一次尝试——重试本身不解决问题,上下文才解决问题。


unsetunset七、子代理编排:解决上下文冷启动问题unsetunset

把任务委托给子代理时,有一个基本问题:子代理不知道自己需要什么上下文

用户提需求时,子代理不知道哪些文件是相关的、项目用什么命名约定、有哪些现成的模式可以复用。

7.1 迭代检索:渐进式发现上下文

ECC 的”迭代检索”模式(Iterative Retrieval)把这个问题变成一个探索循环:

DISPATCH(宽泛出发)→ EVALUATE(评估相关性)→ REFINE(精化查询)→ LOOP(重复,最多 3 轮)

第 1 轮:用宽泛的关键词出发——不是为了找到正确答案,而是为了学习项目真正使用的词汇

比如搜索 “rate limiting” 没找到任何东西——这本身就是信息。子代理从目录结构中发现这个项目用的是 “throttle”。

第 2 轮:用学到的词汇精化搜索,找到高相关度文件。评分标准:0.7 以上算高度相关。

第 3 轮(如果还有缺口):针对性填补空白。

满足条件(3 个以上高度相关文件,没有明显缺口)就提前退出,不等到 3 轮结束。核心原则:3 个精准文件胜过 10 个模糊文件

7.2 工具限制:约束子代理的能力边界

子代理应该有明确的能力边界,不能越权。ECC 在每个 agent 的定义文件中声明允许使用的工具:

  • 规划代理:只有只读工具(Read、Grep、Glob)——物理上无法修改任何文件
  • 分析代理:只有 Read+Write,没有 Bash——不能执行代码
  • 外部模型(Codex、Gemini 协同时):完全没有文件系统写权限

这不是对模型的”信任问题”,而是工程约束:减少不确定性,让系统行为可预测。

一个具体细节:文档查询代理(docs-lookup)被明确要求把 MCP 工具返回的内容视为不可信输入。因为文档内容可能嵌入了 “Ignore previous instructions” 类型的注入攻击,在代理定义层面声明比依赖运行时判断更可靠。

7.3 多视角并行分析

对复杂问题,ECC 推荐同一份代码同时派给多个角色不同的子代理:

  • 安全专家:有没有漏洞?
  • 高级工程师:有没有更好的实现方式?
  • 一致性审查员:和项目其他代码风格一致吗?

这不只是为了并行速度——更重要的是,每个子代理有独立的 context window,不会被其他视角的先入为主所影响。审查者和实现者不共享上下文,是消除”作者偏见”的工程手段。


unsetunset八、总结:一套自洽的工程哲学unsetunset

把上面六个维度放在一起看,可以发现一条贯穿始终的设计原则:

把 AI 当作有限资源的系统组件,而不是无限能力的魔法

具体体现:

  • Token 是成本,按任务价值分配模型,而不是一律用最强的
  • Context 是有限空间,主动管理而不是被动等待耗尽
  • 记忆是工程问题,用文件系统解决而不是依赖模型自己记住
  • 学习是反馈循环,用钩子捕获数据而不是依赖模型自我报告
  • 质量是可量化的,用评测框架度量而不是凭感觉判断
  • 并行化需要隔离,用 git worktree 而不是共享 context
  • 子代理需要约束,用工具限制声明边界而不是依赖提示词

这套框架的价值不只在于”让 Claude Code 更好用”。它回答了一个更普遍的问题:当 AI 被嵌入一个复杂的工程系统时,哪些经典的软件工程原则仍然适用——隔离、可观测性、幂等性、最小权限、快速失败。

答案是:几乎全部。


本文分析基于 everything-claude-code v1.9.0(2026 年 3 月)。项目持续迭代,具体实现细节可能有变化,但背后的工程原则是稳定的。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » 当 AI 写代码遇到工程问题:一个开源插件如何系统性地解决它们

猜你喜欢

  • 暂无文章