Skip to content

pstack 拆解 13 · 理解(三):讲清楚、找回来、看波及 ​

AI coding 之后,代码是模型写的,理解却必须发生在你这边。看懂它怎么运作只是第一步:你还得能把它讲清楚、能在离开后把上下文捡回来、能在动手前判断一个改动会波及哪里。

这一篇讲这三件事。

teach ​

teach 背后的想法是:人是通过把系统重新组装一遍来理解的,不是通过收到一份概括。 一份摘要给的是结论,而理解来自看见结论是怎么长出来的。所以它层叠地建,一次只加一个部件,让你看着系统自己组装起来。

它站在 how 和 why 之上,把两者织成一个朴素的解释。它的目标只有一个:让人真的懂,而不是让人收到一份摘要。

它先决定你应该带着哪几件事离开。这几件事从"你为什么问"里选:你是要改它、审它、调它,还是刚接手。你显然已经会的,它跳过;它把深度放在你问题所在的地方。

然后它让 how 和 why 去干活,自己不重做。子系统跑两个,小改动可能只跑一个。它默认把 why 收窄,因为完整的一轮很慢,收窄的方式是把问题问得具体。

它有几条硬规则,值得单独记住。

先给朴素定义,用资深工程师口头会说的方式把这个东西是什么讲清楚,再接到眼前的例子上,然后往上盖。最小的完整答案先行,一两句,不要一上来就是密不透风的一段。

东西有三个以上活动部件时,不要画一张全图,画一串逐步叠加的图。每张在上一张上只加一个部件,让人看着系统自己组装起来。一张留到最后的全图是参考资料,不是教学。

不要打印框架标签。"关键洞察""核心要点""拿走的这一句",这类话一个都不要出现。直接说。

它还要求这是一场对话,不是一场表演。不搞小测验,不做节奏表演,不宣布"这句要记牢"。想停下来的时候,就停下来让你接话。

有一个和 why 有关的细节:teach 要原样保留 why 的置信语言。那些 hedge 是调查结论,不是文风,不能当成风格改掉。

它有个用法值得偷:"convince me"。让 agent 说服你某个改动修的是根因不是症状,解释就从一次参观变成一场你可以戳的论证。

recall ​

recall 背后的想法是:你的上下文不只在你自己的聊天里。 你自己的历史记录了你做过什么、决定了什么;而同一段代码在别人名下的经历(用户反复上报的症状、上线又被回滚的修复、还在报的错误)住在共享记录里。一个 bug 尾巴很长的功能,大部分故事都在那儿。所以记忆是可以重建的,只要你知道去两份记录里找。

它把你自己的近期上下文重建出来,走到共享记录那一步时,复用 why 的每源手册。它的流程是六步。

分类,再路由。 一个具体的、要恢复的旧会话,是 session-pickup,不是它。把习惯变成持久 skill,是 automate-me。它管的是跨多个近期聊天的工作上下文。如果你已经给了一份完整的状态胶囊(路径、分支、改动),它直接用,跳过挖掘。

先锁范围,再搜索。 把窗口定下来("最近"是一个真实的区间,默认最近 7 天),把话题和 workspace 定下来(默认当前工作区,绝不未经要求去读另一个项目的 transcript)。把范围复述给你听。绝不悄悄把"全部"变成"最近 N 个"。

在聊天历史里并行 fan out。 派并行 subagent,每个拿语料的一段切片。给每个 subagent 的指令很具体:按真实修改时间排候选(ls -t),绝不按 UUID 名字排;先 grep 话题,只读命中的聊天,而且只读其中相关的区域;跳过当前这次聊天和明显的噪声(subagent 聊天、eval 聊天、测试聊天)。每个返回同一个 schema,一个聊天一块:话题、用户目标、决定、未结的线、卡点和纠正、以及产物(PR、ticket、分支),每条都引用聊天 UUID。

扫共享记录。 只要话题点到了一个功能、文件、子系统、区域或 bug,这一步就是默认动作,不是判断题,而且"我在 X 上的工作"也不能豁免它。它把这件事交给 why 的源调查员,只把问题从"为什么这样建"改成"现在是什么状态、试过什么没成立、用户还在报什么"。它复用 why 的每源手册,和聊天挖掘并行跑,并且继承它的姿态:一个源一个调查员,空结果也是发现,某个 MCP 不可用就跳过并说明。

对着实时状态验证。 把挖掘和扫描翻出来的 PR、分支、ticket,用 git 和 gh 核实。当答案取决于一个 agent 实际做了什么(它跑了什么工具、读了哪些文件、撞了什么错误),去读完整 transcript,不读裁剪过的本地副本。

按契约写简报。 输出分四块:胶囊(最多 5 条,说明这是什么、整体到哪了);线程(一行一条,前缀一个状态标签,[merged #N]、[open PR #N]、[in flight <branch>]、[verified, uncommitted]、[reverted #N]、[planned, not started],没标签的线程就是还没做完);问题(最多 5 条反复出现的,包含用户还在报的症状、以及上线又被回滚的修复,好让下一次从上次失败的地方开始);下一步(一个最有用的具体动作)。

它还有一条克制的规矩:当胶囊和线程行超出一屏,先删细节,不要删线程。

blast-radius ​

blast-radius 背后的想法是:一个改动的影响面不由 diff 决定。 真正的破坏往往在 grep 看不到的地方:你调用的库的内部、一个 API 返回的 JSON、另一个语言读的同一段字节、下游三跳之后的代码。而且还有一层更根本的:一份"听起来对"的分析,无论真假都同样有说服力,所以它不交论文,它找出安全所依赖的那一两个事实,然后跑代码证明掉。

它找的是一个改动可能在别处弄坏什么。列出调用方不是活儿,grep 十秒就有了。真正的活儿是那份具体清单:你调用的库的源码,它固定的版本和本地补丁,一个 API 返回的 JSON,一个数据库列,一个 wire format,另一个语言读的同一段字节,一个 feature flag,下游三跳之后的代码;还有"什么时候跑"这件事,微任务、卸载和清理、不同框架的生命周期。

信心分五级,尽量往下走,走到哪一级要说明。

  1. 你这么说。单凭这个没有价值。
  2. 你指出了那一行,给出真实的 file:line。
  3. 你证明了坏情况到不了,一步步走过失败路径,它够不着。
  4. 你跑了它。一个脚本或测试,调用真实的代码,如果你错了它会大声失败。
  5. 你在运行的应用里复现了它。

通常一个很小的脚本就够了,它 import 应用发布时用的同一个库,调用你担心的那个函数。

它交回来的东西也有固定形状:它做什么(包括不明显的那部分)、它安全所依赖的那一个事实(证明到第几级,没证明就写"未证明")、风险(每条写明怎么坏、file:line、概率和代价、怎么查)、已排除项、以及合并前最便宜的那个能抓住真 bug 的测试。

它也是横向组合的例子:它会去调用 how、why,大改动时还会调用 arena,让多个模型回答同一个问题再合并。

参考 ​

原文都在 pstack 上游仓库(cursor/plugins):