让 Claude Code 会话变成可检索的开发日志

2415 字
12 分钟
让 Claude Code 会话变成可检索的开发日志

用 Claude Code 写代码一段时间之后,我越来越清楚一件事:每次关掉窗口,我都在丢掉自己最详细的一份开发日志。

这篇文章讲的就是这件事。

你每天都在丢掉最有价值的开发日志#

想想你手里平时能留下来的”开发记录”都有些什么:

  • git log 只记录”最终做了什么”,被挤压成一两行 commit message
  • README / 注释 只记录结论,不记录你是怎么走到这个结论的
  • 自己的脑子 会忘,而且忘得比你想象的快——一周以前你为什么选这个方案,多数情况下想不起来

这三样东西加起来,都没有 Claude Code 一次会话留下来的东西多。

一次稍微复杂点的会话里同时保留了这些:你最初的问题、第一反应走的弯路、被排除的方案、转折点出现在哪一步、最后为什么选了现在这个做法、以及中间跑过的所有命令和看到的所有报错。这是你能拿到的最完整的一份”思考过程记录”,而且它连写都不用写——它在你用 Claude Code 的时候就已经自动生成了。

然后我们做了什么?关掉窗口。

为什么会话天然是资产#

一份东西要成为”资产”而不只是”数据”,我的判断标准是三条:

  1. 内容完整 —— 不只是结论,还有路径
  2. 能持久保存 —— 不依赖某个进程还活着
  3. 能被再次找到 —— 否则”有”等于”没有”

Claude Code 的会话在前两条上已经达标了:内容本来就完整,而且每条消息都落在 ~/.claude/projects/<project-slug>/*.jsonl 里——它一直都在你的磁盘上,不会因为关闭窗口而消失。

缺的只有第三条:一条能把它们再次找出来的检索线。

这就是我写 recall 这个小工具要解决的问题。与其说它是一个搜索工具,不如说它是把”会话”从数据变成资产的最后一公里。

但直接 grep jsonl 并不够用#

你可能会想:“那直接 rg 一下 jsonl 目录不就完了?”

我一开始也是这么做的。很快发现不够用,原因是原始 jsonl 有几个很具体的问题:

噪音太大。 一次 Read 一个几千行的源码文件,整个文件内容会原样嵌进 jsonl。一次 Bash 跑 pnpm build,几百行构建输出也都在里面。直接 grep 的结果里,一半以上的命中都是这种”工具输出碰巧包含了关键词”,跟你真正想找的对话内容无关。

结构嵌套,不是纯文本。 每条消息是 JSON,里面的 content 可能是字符串,也可能是一个由 text / tool_use / tool_result 等 block 组成的数组。直接用文本工具看,你会被转义字符和大段 JSON 淹没。

命中一行没有上下文等于没命中。 你想起”上次好像处理过某个报错”,grep 给你一条命中——然后呢?一条孤立的行告诉不了你前因后果,你只知道它出现过,不知道当时怎么处理的。

所以,想让会话真的可检索,不能只是能搜到,还得让搜到的东西可读。这是 recall 在设计上要解决的具体痛点,也是任何人想自己做类似工具时会碰到的同一堵墙。

recall 的三个关键决策#

这一节不讲命令用法——用法在 --help 里。我想讲的是几个取舍,它们决定了 recall 是能用的还是只能装样子的。

决策一:压缩工具调用的显示。
匹配的时候工具调用照样参与,但显示的时候,Read 只保留路径,Bash 只保留命令,Grep 只保留 pattern,工具的返回内容一律省略。这样你扫一眼就能看出”那段对话在干什么”,而不会被几千行的文件内容淹掉。代价是你看不到工具返回了什么——但要看的话可以直接打开原始 jsonl,工具是给你定位用的,不是给你全览用的。

决策二:返回命中行的 ±N 条上下文。
单条命中几乎没用,恢复上下文才有用。recall 默认把命中那条的前后各三条消息一起带出来,于是你看到的是”当时那段对话长什么样”,不是孤零零一行。这是从”能搜到”到”搜到的东西可读”的那一步。

决策三:故意不索引 tool_result。
这条要诚实讲:这是个取舍,不是优化。有些线索确实会藏在工具返回里——比如你让 Claude 读了某个文件,关键信息就在那个文件内容里。这种情况 recall 搜不到。

我接受这个漏,理由是:tool_result 体量太大,索引进去会让匹配充满噪音,而那些真正重要的决策、判断、对话上下文,几乎都发生在 assistant 和 user 的消息里,不在工具返回里。保留信噪比比做到”绝对不漏”更重要。

这三条合在一起,才让 jsonl 目录从”理论上能搜”变成”实际好用”。

memory + recall + 当前上下文:三层记忆#

recall 写完之后我才意识到,它不是孤立的。它和另外两件事放在一起,才组成一套可用的”跨会话记忆”:

是什么优点缺点
memory把过去会话里”值得记住的事实”浓缩写到若干 markdown 里,每次新会话自动加载命中快,不需要搜会漏细节,且一旦写错就一直错
recall原始会话的 jsonl + 一条检索线细节全,可回到”当时那段对话”本身得知道搜什么词
当前上下文正在进行的这次对话最新、最即时关闭即失,尤其容易被误以为”永远记得”

这三层不是替代关系,是递进关系。我自己用下来的顺序是:

  1. 先看 memory——如果上次的结论已经沉淀过,直接命中,成本最低
  2. memory 不够细再用 recall——回到原始会话,把当时真正发生的对话捞出来
  3. recall 也没搜到再现搜——可能是关键词想错了,也可能当时的讨论就没发生过

这个顺序不是规定,是自然结果:浓缩总比原始便宜,能用浓缩就不用原始

这里接上一篇 《AI 真正适合的,不是写博客,而是整理开发过程》。那篇讲 AI 最擅长的是把开发过程里的零碎素材整理成可读结构——但这件事有一个隐藏前提:碎片得先留得下来。关了窗口就丢掉的碎片,整理不了。

所以两件事合起来才完整:recall 负责让会话留得下来、找得到;AI 负责在需要的时候,把这些留下来的碎片再组织成你要的形态。一层管”留存”,一层管”重组”。

不必掩盖的局限#

写工具的人最容易犯的毛病是过度吹自己写的东西。recall 的局限很明显,写出来更坦诚:

  • 只是关键词匹配,不是语义搜索。 你想不起关键词就搜不到。“上次那个关于异步的 bug”这种模糊查询完全不行,你得能说出一个具体的词
  • tool_result 里的线索搜不到。 如前所述,这是主动放弃的范围
  • 进行中的会话不在索引内。 jsonl 要等消息落盘之后才能搜到,正在打字的那句话你是搜不到自己的

为什么我觉得仍然够用?因为真实场景里,“我上次好像处理过 X”这种问题,人脑几乎总能提供至少一个关键词——也许是报错里的某个函数名,也许是命令里的某个参数,也许是文件路径里的一段。只要能想出一个词,recall 就能把你送回那次对话的现场,剩下的事情就变成了读,而不是找。

能想起关键词 ≈ 能找到。这个近似等价,在我自己的使用里成立率很高。

关掉窗口不等于删除记忆#

写 recall 之前,我其实和大多数人一样,把 Claude Code 的会话当成临时对话——窗口关了,就当它没发生过。真正让我改观的不是写了这个工具,而是写完工具之后第一次真的找回了一次早就忘掉的调试过程。那一刻我意识到:原来那些我以为早就忘了的思考过程,其实一直都在磁盘上,只是没人去翻而已。

这件事改变的其实不是工具栈,是认知:

  • 会话不是对话,是日志
  • 关掉窗口不等于删除记忆
  • 走过的弯路值得留下来,哪怕只是因为下一次你会走同样的弯路

如果你也在用 Claude Code,哪怕不用 recall,我建议你至少做一件事:打开 ~/.claude/projects 看看。你会发现那里已经躺着几十 MB 甚至几百 MB 的开发日志,一行都没人读过。

先别急着清理它们。

Profile Image of the Author
xt53
Hi
分类
标签
站点统计
文章
10
分类
2
标签
19
总字数
22,614
运行时长
0
最后活动
0 天前

目录