您把一个陌生项目交给 Claude Code,得到这样的回答:"历史记录保存在浏览器中,保留最近 100 条,并且刷新页面后可以恢复"。这句话听起来足够具体,似乎可以直接写进 README。不过,先打开回答所依据的源文件更稳妥。

我们来拆解这样一条判断:把需求转成几个问题,阅读一小段代码,再确定需要在运行时核实哪些行为。最后,您会得到一份简短报告,说明哪些功能正常,哪些仍待核实。

聚焦一项需求,而不是浏览整个项目

Haiku 50 是一个小型应用,可根据关键词生成三行诗。用户选择语言、获取文本,并查看自己的生成记录。这里分析一项需求就够了:浏览器应保存最近 100 条生成记录,并且刷新页面后仍可访问。

Haiku 50 示例图。下文将结合代码和场景分析应用的行为。

这里的 Haiku 50 是教学示例。代码和智能体的回答都是为本文分析而编写的,预期行为也根据代码推导。我们不会检查项目的原始压缩包。文章已包含所需内容:无需下载应用、获取 API 密钥或运行生成操作。

我们不问"这里的一切是怎么工作的",而是提出三个问题:新生成的内容会写入存储吗?应用下次打开时会读取已保存的数据吗?记录超过 100 条后会怎样?

发给 Claude Code 的第一个请求可以这样写:

只调查生成记录。暂时不要修改代码。
找出从收到新诗到保存它的路径,
然后找出从打开页面到显示历史记录的路径。
检查保存记录的数量限制在哪里实现。
每项判断都注明文件、行号和简短代码片段。
另行列出仍需运行验证的内容。

这样的请求划定了清晰的搜索范围。如果智能体发现历史记录还涉及另一个文件,它可以说明为什么需要阅读该文件。您只需核对几个关键位置,比起通读对整个应用的复述,省事得多。

找到文件还不够,还得阅读

假设有一个教学文件 history.js。成功生成后,界面会调用 addToHistory(item),而 renderHistory 只负责显示传入的列表。这个示例中没有其他处理历史记录的函数。

const STORAGE_KEY = "haiku-history";
let history = [];

function addToHistory(item) {
  history.unshift(item);
  localStorage.setItem(STORAGE_KEY, JSON.stringify(history));
  renderHistory(history.slice(0, 100));
}

function startHistory() {
  renderHistory(history);
}
startHistory();

理解这段代码时,只需区分两个存储位置。history 数组存在于当前打开页面的内存中。localStorage 会在浏览器中保存网站数据,多次打开页面之间不会丢失;应用需要单独将数据写入其中,并在之后读出来。该存储与协议、主机和端口绑定,因此核实时要使用相同的网址和浏览器配置文件。localStorage 参考文档介绍了这一行为。

假设智能体这样回答:

历史记录会保存最近 100 条生成内容,并在刷新后恢复。
依据:history.js:4-13,
使用了 localStorage 和 slice(0, 100)。

回答给出了文件路径和行号。现在要核实这些依据是否能支持整句话。教学文件的第一行是 const STORAGE_KEY;空行也计入行号。

  • 第 5 行的 history.unshift(item) 会把新记录添加到数组开头;
  • 第 6 行调用 localStorage.setItem,将整个数组写入存储。转换为 JSON 是为了把它保存为字符串;
  • 第 7 行的 renderHistory(history.slice(0, 100)) 会把前 100 个元素传给界面。slice 会创建新数组,而不会缩短原数组,该方法的参考文档也说明了这一点;
  • 启动时,第 2 行的 let history = [] 会创建空数组,而 startHistory 函数及其在第 10-13 行的调用会将它显示出来。这里没有从存储中读取数据。

读过代码后,答案已经不同了:代码确实会写入历史记录,并将显示内容限制为 100 条。但这个实现没有在启动时恢复记录,也没有发现对已保存记录数量的限制。

在真实项目中,这段代码可能不够,因为数据加载有时会放在另一个模块里。因此可以追问智能体:"找出读取和写入这个键的所有位置,以及这些函数分别由谁调用。"我们的教学示例范围明确,可以继续了。

核实刷新后的行为

阅读代码后,我们知道了该从哪里寻找不一致之处。现在设定观察条件:页面在普通浏览器配置文件中通过同一个地址打开,存储功能可用,历史记录起初为空。教学场景中成功生成一条内容,并加上容易辨认的标记 A;各步骤之间代码不变。

下面是根据代码推演出的场景流程。再增加一步:刷新页面后生成一条新记录 B。

步骤界面预期haiku-history 键对应的存储预期
打开历史记录为空的页面空列表没有记录
添加生成记录 A一条记录 A包含记录 A 的数组
刷新同一页面空列表包含记录 A 的数组仍然存在
刷新后添加生成记录 B只有记录 B只包含 B 的数组;记录 A 被覆盖

要实际观察这个过程,可以在浏览器开发者工具的网站存储区域查看存储内容。打开指定键的值,再与刚刚添加的记录对照。只看界面上的空列表还不够。

说"刷新后所有内容都被删除了"并不准确。数据仍在存储中,只是应用没有读取它。在这个实现中,链条在已保存的字符串与传给界面的数组之间断开了。

接着添加记录,就能看到这个错误的后果。刷新后,内存中的数组为空,因此只有 B 会被加入;localStorage.setItem 随后会用只包含一条记录的数组覆盖已保存的历史记录。按需求,B 和 A 都应保留;按当前代码,最后只剩 B。显示和历史记录的保存都会受影响。

现在可以先写下代码层面的结论:"startHistory 没有读取已保存的历史记录;下一次添加会覆盖之前的记录。"要确认运行时行为,需要按顺序完成这四步,并在每一步之后对照界面列表和存储内容。

核实 100 这个数量

恢复记录的问题已经理清。需求还有另一部分:保存最近 100 条生成记录。只添加一条记录,无法核实这个上限。

我们用一段独立场景,先让历史记录保持为空。然后将 101 条预先准备好的记录依次传给同一个添加函数,标记从 1 到 101,中间不刷新页面。核实历史记录本身,不需要向生成器发送 101 次请求;这里关注的是拿到现成记录之后会发生什么。

新记录会插入数组开头。因此,最后一次添加后,界面会显示标记 101 到 2,共 100 条。但保存语句接收到的是完整数组:存储中会有标记 101 到 1,共 101 条。既要检查数量,也要核对具体记录。否则可能看不出应用保留的是最旧的记录,而不是最近的记录。

智能体的回答在这里说得太宽泛了。代码里确实有数字 100,但它限制的是显示内容。准确的代码结论是:"界面显示最近 100 条记录;在上述场景中,存储包含 101 条,因此保存历史记录数量的需求没有满足。"

这个场景核实了应用的哪一部分

预先准备好的记录可以让您单独核实历史记录。它们无法说明生成服务是否可用、诗歌质量如何,或选定的语言是否传递成功。这些需求需要各自的场景。

整理一份简短报告

把调查结果保存在项目旁边很方便,这样明天就不用重新读一遍对话。示例中,我们把文件命名为 HANDOFF_REVIEW.md。文件名只是示例;更重要的是,记录应能还原核实过程。

下面是阅读教学代码后整理的报告。其中的预期行为字段提示了运行时需要观察什么:

范围:生成记录,本文中的 history.js 教学版本。
调查方式:阅读教学代码片段。
未在浏览器中运行。

需求:在浏览器中保存最近 100 条生成记录,
并在刷新后显示这些记录。

根据代码:
- history.js:6,localStorage.setItem 会写入整个数组;
- history.js:7,renderHistory(history.slice(0, 100))
  只限制显示内容;
- history.js:10-13,startHistory 及其调用
  会启动显示,但不会读取已保存的数据。

根据代码推测的行为:
- 添加后,记录 A 会显示在界面上并写入存储;
- 刷新后,A 仍保存在存储中,但列表为空;
- 刷新后添加 B 会覆盖已保存的历史记录:
  最后只剩 B,而不是 B 和 A;
- 在另一场景中,从空记录开始添加 101 条后,
  界面会显示 100 条(101..2),存储中有 101 条(101..1)。

代码中发现的不一致:启动时不恢复记录,
因此下一次添加会覆盖原有历史记录;
保存的历史记录没有限制为最近 100 条。

尚未核实:生成器的运行情况、保存错误、
多个标签页同时操作的情况。

下一步:复现两个场景并记录观察结果。
然后修复启动时的读取逻辑和保存数组的数量限制,
再按相同场景重新检查。

在您自己的报告中,请用经过核实的项目版本替换"教学版本",例如注明 commit 和是否存在未提交的更改。运行后,另加一个"实际观察结果"字段,并附上结果或手动操作记录。将实际情况与预期行为对照;如果没有运行,就留空该字段,并注明尚未运行。本文包含了全部原始材料;在大型项目中,仅凭行号引用不足以复现核实过程。

这样的报告能帮助您为智能体安排下一项任务。智能体手里已经有两个具体的不一致之处,而不是一句"让历史记录更可靠"。同时,哪些结论还为时过早也一目了然。

亲自核实这条判断

我们在同一个教学实现中添加了一个清除按钮。它的处理函数如下:

function clearHistory() {
  history = [];
  renderHistory(history);
}

点击前,列表和存储中都有记录 A。点击后,列表变空。智能体写道:"按钮会彻底删除已保存的历史记录。"

哪些结论得到了证实:历史记录从界面消失了、记录从 localStorage 中删除了,还是两者都成立?接下来值得核实什么?先试着回答,再看解析。

解析

可以确认列表从界面消失了。代码也显示内存中的数组被清空了。不过,处理函数没有修改 localStorage,而 renderHistory 按照示例设定只负责绘制列表,所以已保存的 A 仍然存在。

下一步是在点击后立即读取 haiku-history 键的值。实现历史记录恢复后,也可以刷新页面,确认已删除的记录没有重新出现。在原始版本中,只刷新一次无法说明问题:即使没有执行清除操作,它启动时也会显示空列表。这个干扰不太容易察觉。

如何在 Claude Code 中布置这类任务

在实际项目中,先选一条会影响您下一步决策的判断。例如,历史记录是否可以直接使用,还是必须先修复恢复逻辑。给智能体指定一种便于打开源文件并核对结论的回答格式:

核实这项判断:[应用的具体行为]。
先找出相关文件并阅读必要的代码。
暂时不要修改代码。

请返回:
1. 代码能够证实的内容:文件、行号、简短片段。
2. 用于运行时核实行为的场景。
3. 实际执行了什么,以及观察到了什么。
4. 准确结论和仍然未知的部分。

如果无法运行应用,请明确说明。
不要把建议的场景写成已经完成的检查。

收到回答后,打开支撑关键结论的源文件,将观察结果与需求对照,然后再把结果记入笔记或文档。如果缺少依据,下一条请求也很明确:查找历史记录的读取位置、检查键值,再复现添加 101 条记录的场景。这样,调查就能以小而可核实的步骤推进。

如果只是询问函数名称,给出代码引用就够了。如果回答涉及数据保存、恢复或数量限制,就值得把相应行为完整追踪一遍。核实多少,取决于您准备作出什么判断。