跳到主要内容

差异沙箱与 CLI 工作流(人审边界)

30 秒导读: Plandex 里 AI 写的代码不会直接落进你的项目。它先被攒在计划自己的 结果里(一个"累积 diff 沙箱"),你在客户端 diff 里审、reject 里挑掉不要的、apply 时才真正写进磁盘;apply 还能顺手跑命令、失败自动 debug;不满意再 rewind 时间旅行退回去。 这一章讲的就是这条 plan → 沙箱 → 人 review → apply → rewind 的安全闭环。

本章属于 Plandex 讲解的一部分。diff 是怎么被服务端算出来的02-build-apply服务端怎么规划01-tell-loop;这里只讲改动进了沙箱之后,客户端如何审阅、 应用与回滚。全景与阅读顺序见 index


1. 这是什么(零基础也能懂)

1.1 一句话定义

Plandex 的"差异沙箱"是指:AI 生成的所有文件改动,先被保存在这个计划(plan)自己的状态里, 和你真实的工作区文件完全隔离。只有你显式执行 plandex apply,这些改动才会被写进项目。

官方 README 把它叫做 "cumulative diff review sandbox"(累积式 diff 审阅沙箱),原话是:

"A cumulative diff review sandbox keeps AI-generated changes separate from your project files until they are ready to go."(README.md:83)

1.2 解决什么问题 / 给谁用

设想你在终端里让 AI 帮你改一个大项目,横跨十几个文件。你最怕两件事:

  1. AI 改错却已经污染了你的工作区 —— 想退回去,却分不清哪些是它改的、哪些是你自己改的。
  2. 改动不可控地边生成边落盘 —— 一半对一半错,现场一片狼藉。

沙箱就是为了消掉这两个恐惧:AI 边聊边改,改动只在计划里累积;你的 git 工作区在你点头之前 一个字节都不动。给谁用?任何想让 AI 大规模改代码、又想保留"人在最后一道闸"的工程师。

1.3 用起来什么样

一次典型的人审闭环,命令行长这样:

pdx tell "把用户表加一个 email 字段并更新相关代码" # AI 规划+生成改动 → 进沙箱
pdx diff # 审:看沙箱里累积的 diff
pdx reject src/experimental.go # 挑掉不想要的那个文件的改动
pdx apply # 拍板:把剩下的改动写进项目(可跑命令、可自动提交)
pdx rewind 1 # 后悔了:时间旅行退回上一步

1.4 一句话直觉/类比

把它想成 Git 的暂存区(staging area),但更强。 普通暂存区只是"待提交";Plandex 沙箱是 "待落盘 + 可累积 + 可整段时间旅行"的暂存区——AI 往里堆改动,你在闸门口逐个放行或退回。

本节到此不碰代码。记住一句话:沙箱 = 改动的隔离缓冲区,apply 是唯一的落盘闸门。


2. 顶层全景(它大概怎么转)

2.1 一张图:从沙箱到磁盘的闸门

先说怎么读这张图:左边是服务端持有的沙箱状态,右边是你的真实磁盘;中间的四个客户端命令 就是你操作这道边界的四个动作。改动只能经 apply 从左流到右。

服务端(计划状态) 客户端命令 真实磁盘 / git
┌───────────────────────────┐
│ 累积 diff 沙箱 │ ── diff ──► 只读审阅(终端/浏览器 UI)
│ CurrentPlanFiles.Files │ ── reject ─► 从沙箱剔除某些文件的改动
│ (AI 攒的所有文件改动) │
│ │ ── apply ──► ┌─────────────────────────┐
│ │ │ ① 写文件到工作区 │
│ │ │ ② (可选)跑 _apply.sh │──► 你的项目文件
│ │ │ ③ (可选)git 自动提交 │──► git 历史
│ 计划历史(git-based) │ ── rewind ─► 退回历史某一步,可选反向改磁盘
└───────────────────────────┘

2.2 部件一句话职责

部件干什么在哪个文件(相对克隆根)
沙箱状态 CurrentPlanFiles存 AI 攒的待落盘文件改动app/shared/data_models.go:271
diff 命令只读审阅沙箱里的累积 diffapp/cli/cmd/diffs.go:49
reject 命令从沙箱剔除指定文件的改动app/cli/cmd/reject.go:30
apply 命令把沙箱改动落盘、跑命令、提交app/cli/cmd/apply.go:30
应用核心写盘 + 回滚计划 + 提交编排app/cli/lib/apply.go:37
命令执行/失败重试_apply.sh、失败自动 debugapp/cli/plan_exec/apply_exec.go:26
rewind 命令时间旅行退回计划历史某步app/cli/cmd/rewind.go:53
REPL 外壳交互式 shell,派发上述命令app/cli/cmd/repl.go:68
流式 TUI接收服务端流、实时渲染app/cli/stream/stream.go:18

2.3 主线走一遍(高层)

  1. tell 一个任务 → 服务端规划、生成改动 → 改动攒进沙箱(不落盘)。见 01
  2. diff 沙箱里累积的 diff;reject 剔除你不要的部分。
  3. apply 落盘:先写文件,若沙箱里有 _apply.sh 就跑命令,再(可选)自动 git 提交。
  4. 命令失败?进入自动 debug:回滚本次改动 → 把报错喂回给模型 → 重新规划 → 重新 apply。
  5. 结果不满意?rewind 时间旅行退回计划历史某一步,并可选把磁盘反向改回去。

下面逐个机制拆开讲。


3. 核心原理

3.1 累积 diff 沙箱:改动为什么不直接落盘

它要解决的小问题: AI 边生成边写盘,会让你无法区分"AI 的改动"和"你自己的改动",一旦 出错难以干净回退。

思路: 让服务端把 AI 的每次文件改动攒在计划状态里,客户端拿到的是一个 map[路径]→新内容 的集合(CurrentPlanFiles.Files),磁盘在 apply 之前完全不动。这就是"沙箱"——它是一份 与工作区平行的、可累积的期望态

它长什么样: apply 一开始就从服务端拉这份沙箱态,再据此决定要动哪些文件:

currentPlanFiles := currentPlanState.CurrentPlanFiles
toApply := currentPlanFiles.Files // 待写入的文件:路径 → 新内容
toRemove := currentPlanFiles.Removed // 待删除的文件
hasExec := currentPlanFiles.Files["_apply.sh"] != "" // 沙箱里是否带了要执行的命令

真实实现见 MustApplyPlanAttempt(app/cli/lib/apply.go:135-140)。注意 _apply.sh 是一个 约定的特殊路径:它不是普通源文件,而是"apply 时要执行的命令"载体(§3.4)。

关键细节: 沙箱里也可能有"待构建(pending build)"的改动——即模型的回复还没被转成具体文件 diff。apply 会先检查 HasPendingBuilds(),必要时先把它们 build 出来再继续 (app/cli/lib/apply.go:75-113,构建流水线属于 02 的范畴)。

3.2 审阅与剔除:diff 和 reject

diff —— 只读地审。 plandex diff 向服务端要已算好的 diff 文本,然后要么在终端分页显示, 要么起一个临时本地 web 服务用 diff2html 在浏览器里做并排/逐行高亮视图:

  • 拉 diff:api.Client.GetPlanDiffs(...)(app/cli/cmd/diffs.go:67)。
  • 浏览器 UI::0 让 OS 随机选端口起 http.Serve,把 diff JSON 注入模板 (app/cli/cmd/diffs.go:79-138)。这是纯审阅,不改任何东西。

reject —— 从沙箱剔除。 审的时候发现某个文件的改动不想要,plandex reject <文件...>--all 把它从沙箱拿掉。三种入口:

用法行为服务端调用
reject --all剔除沙箱里全部改动RejectAllChanges(app/cli/cmd/reject.go:61)
reject a.go b.go剔除指定文件的改动RejectFiles(app/cli/cmd/reject.go:98)
reject(无参)弹多选框让你勾RejectFiles(app/cli/cmd/reject.go:143)

注意 reject 只动沙箱、不动磁盘——因为改动本来就还没落盘。它调完服务端 API 就结束了 (reject 函数,app/cli/cmd/reject.go:30)。真正落盘是 apply 的事。

补充:app/cli/lib/context_display.go 提供的是上下文条目的图标/标签映射 (GetContextLabelAndIcon,context_display.go:5)以及按序号/名字查上下文的辅助函数, 服务于这些 review 界面把文件、URL、笔记等条目显示得清楚,本身不参与 apply/reject 逻辑。

3.3 apply:唯一的落盘闸门

它要解决的小问题: 把沙箱的期望态安全地写进磁盘——写一半失败不能留下烂摊子。

思路:先记好"怎么退回去",再动手写。 ApplyFiles 在写每个文件前,先把该文件的旧内容 存进一个回滚计划(reversion),新建的文件则记下"回滚时要删掉"。这样任何一步失败都能原样退回。

// 示意,非源码:apply 写文件时同步攒回滚信息
if 文件已存在 {
updatedFiles = append(updatedFiles, path)
toRevert[dstPath] = ApplyReversion{Content: 旧内容, Mode: 旧权限} // 存旧内容以便回滚
} else {
updatedFiles = append(updatedFiles, path)
toRemoveOnRollback = append(toRemoveOnRollback, dstPath) // 新建的文件,回滚时删掉
}
os.WriteFile(dstPath, []byte(新内容), 0644) // 才真正落盘

真实实现是并发的:每个文件一个 goroutine,用 errCh 汇总错误 (ApplyFiles,app/cli/lib/apply.go:622-750)。返回的 *ApplyRollbackPlan 就是"后悔药" (结构见 app/cli/types/apply.go:30,HasChanges() 判断有没有可回滚的东西,types/apply.go:36)。

回滚长这样: Rollback 把每个 ToRevert 的旧内容写回、把 ToRemove 的新建文件删掉, 并额外扫一遍"apply 之后新出现、但原本不该有的路径"一并清掉 (Rollback,app/cli/lib/apply.go:752-820)。

顶层编排MustApplyPlan / MustApplyPlanAttempt(app/cli/lib/apply.go:37:43):

MustApplyPlanAttempt:
拉沙箱态 → (需要则先 build) → (非 auto 则)问"apply 到 N 个文件?"
→ ApplyFiles 写盘(同时攒 toRollback)
→ 若有 _apply.sh 且未 --no-exec → handleApplyScript(跑命令)
→ onExecSuccess: 通知服务端标记已应用 → (可选)git 自动提交

MustApplyPlan 只是 MustApplyPlanAttempt(params, 0) 的入口(apply.go:37-41);带 attempt 计数是为了支撑失败重试(§3.5)。命令行入口 apply(app/cli/cmd/apply.go:30)负责把 CLI flag 翻成 ApplyFlags(AutoConfirm/AutoCommit/NoCommit/AutoExec/NoExec/AutoDebug),并挂上失败回调 OnExecFail

3.4 apply 时执行命令:_apply.sh 与"先试探再确认"

它要解决的小问题: 很多改动光写文件不够,还要跑一下(装依赖、迁移、build)。但跑命令有副作用, 不能不问自取。

思路:把命令也放进沙箱(_apply.sh),落盘后先给你看、确认后再跑;跑之前先"试探性应用"文件。

流程(handleApplyScriptexecApplyScript,app/cli/lib/apply.go:254:332):

  1. _apply.sh 内容用 markdown 高亮打印给你看,问 Execute now?(除非 --auto-exec)。
  2. 你若拒绝,且文件已试探性写盘,会让你选:保留文件改动 还是 回滚 (app/cli/lib/apply.go:300-317)。
  3. 你若同意,拼出带 set -euo pipefail 的脚本、实时逐行打印输出、等待退出码 (execApplyScript,app/cli/lib/apply.go:391-521)。

成功才算数、失败要能收拾。 成功走 onExecSuccess(通知服务端 + 可选提交);失败调 onExecFail(§3.5)。中途 Ctrl+C 会问你"命令到底成没成功?",答"取消"就整段回滚 (app/cli/lib/apply.go:531-549)。

3.5 命令执行的失败回滚与自动 debug

它要解决的小问题: apply 跑的命令挂了(比如测试没过),AI 应该能自己看报错、自己修

思路:失败 → 回滚本次改动 → 把报错当 prompt 喂回模型 → 重新 tell+apply,循环直到成功或用尽次数。

这条循环在 getOnApplyExecFail(app/cli/plan_exec/apply_exec.go:26)里,它是个自引用闭包:

命令失败 (status, output)

├─ AutoDebug 还有次数? ── 是 ─► proceed=true(自动重试)
│ └ 否 ─► 让你选:Debug and retry / 全自动 / 回滚退出 / 直接应用退出

└─ proceed: 回滚本次改动(Rollback)
→ 把 "Execution failed with exit status N. Output:..." 当 prompt
→ TellPlan(重新规划修复) [见 01-tell-loop]
→ MustApplyPlanAttempt(..., attempt+1) ← 递归回到 apply,attempt+1

关键细节:

  • 回滚在重试前发生:重试要基于干净的状态,所以先 lib.Rollback(toRollback, true) (app/cli/plan_exec/apply_exec.go:118-121)。
  • attempt 就是"已 debug 几次",和 AutoDebug 阈值比对决定还自不自动 (apply_exec.go:32-42);默认次数 AutoDebugTries=5(app/shared/plan_config.go:10)。
  • "Debug in full auto mode" 会顺手把计划切成全自动模式(AutoModeFull)再继续 (apply_exec.go:66-105)。

为什么要进程组隔离? 跑用户命令可能 fork 出一堆子进程,Ctrl+C 或超时要连锅端,否则会有 孤儿进程。所以启动命令时设 Setpgid 让它们自成一个进程组,杀的时候杀整组(负 PID):

// app/cli/lib/apply_proc.go
func SetPlatformSpecificAttrs(cmd *exec.Cmd) {
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} // 命令自成进程组
}
func KillProcessGroup(cmd *exec.Cmd, signal syscall.Signal) error {
return syscall.Kill(-cmd.Process.Pid, signal) // 负 PID = 杀整组
}

Linux 上再加一层:MaybeIsolateCgroup 通过 systemd 起一个 transient scope (plandex-<uuid>.scope,KillMode=control-group),把命令关进 cgroup,停 scope 就能可靠地 干掉 cgroup 里所有残留进程(app/cli/lib/apply_cgroup_linux.go:20)。非 Linux 平台是 no-op (app/cli/lib/apply_cgroup_other.go:8)。中断时的 2 秒软杀→SIGKILL 升级逻辑见 app/cli/lib/apply.go:479-495

3.6 git 集成与自动提交

它要解决的小问题: apply 之后最好把改动落成一个 git 提交,且提交信息要贴切。

思路:客户端只提交"Plandex 这次动过的那些文件",提交信息由服务端用模型生成。

  • 只提交动过的文件: commitApplied 拿到更新文件列表,调 GitAddAndCommitPaths——它逐个 git add <path>、再 git commit 时带上这些 paths,不会把你其它未暂存的改动裹进去 (app/cli/lib/git.go:34-57)。所有 git 命令共享一把 gitMutex 串行化(git.go:13)。
  • 提交信息哪来: apply 调服务端 ApplyPlan,服务端返回一个 commitSummary (apiApplyPlan,app/cli/lib/apply.go:576);再由 PendingChangesSummaryForApply 组装成最终 提交信息(app/shared/plan_result_pending_summary.go:14)。
  • 摘要本身也是模型生成的: 服务端 genPlanDescription 让 commitMsg 角色的模型为每条回复生成 一句提交信息(app/server/model/plan/commit_msg.go:21);多条待应用改动时, GenCommitMsgForPendingResults 再把它们合并成一条总的提交信息 (app/server/model/plan/commit_msg.go:187)。commitMsg 用的是哪个模型属于 04-models-roles 讲的角色/模型包。
  • 提交是可选的: --skip-commit(NoCommit)或非 git 仓库就跳过;非 --auto-commit 时会先问 你 Commit Plandex updates now?(commitApplied,app/cli/lib/apply.go:594-620)。

git.go 里还有一组给别处用的 git 辅助:GitStashCreate/GitStashPop(应用前后暂存你的工作区)、 GitClearUncommittedChanges、以及从计划日志解析时间戳的 GetGitLogTimestamp(git.go:221,rewind 用它)。

3.7 rewind:时间旅行退回计划历史

它要解决的小问题: 试了几步发现方向错了,想整段退回到之前某个状态——不只是撤销文件, 连计划的对话/规划历史也要一起退。

思路:计划历史是 git-based 的一串提交;rewind 到某个 sha,就是把计划状态退回那一步,再 (可选)把磁盘文件反向改回去以匹配。

rewind 命令(app/cli/cmd/rewind.go:53)接受步数或 sha,无参则列出历史让你选:

pdx rewind # 弹历史列表,选一个步骤退回去
pdx rewind 2 # 往回退 2 步
pdx rewind <sha> # 退回指定提交

难点在"要不要动磁盘",以及冲突处理。 退回计划状态很干净,但你磁盘上的文件可能: (a) 需要跟着改回去;(b) 你在 apply 之后又手动改过——这就是冲突。rewind 先做一次分析:

AnalyzeRewind(app/cli/lib/rewind.go:107)对比三份状态——目标计划态、当前计划态、磁盘态:

产出含义
RequiredChanges从当前态到目标态,磁盘上需要改哪些文件(空串=删)
Conflicts其中哪些文件你在磁盘上手动改过(revert 会覆盖你的改动)

有了分析,命令按 flag/配置决定要不要 revert:--revert/--skip-revert 显式指定,否则读计划配置 AutoRevertOnRewind(默认 true,app/shared/plan_config.go:86)。有冲突时一定会停下来问你, 选项是"覆盖 / 只退计划不动文件 / 取消"(app/cli/cmd/rewind.go:347-388)。

确认后 ApplyRewindChanges 才真正改磁盘:写文件、删文件、清理空目录 (app/cli/lib/rewind.go:258)。若配置允许,revert 完还能自动 git 提交,提交信息里列出被撤销的 那些 apply(app/cli/cmd/rewind.go:471-486)。哪些 apply 会被"撤销"由 GetUndonePlanApplies 按目标 sha 的时间戳筛出(app/cli/lib/rewind.go:17)。

一句话记住:apply 是"沙箱→磁盘"的正向闸门,rewind 是"退回历史 + 反向改磁盘"的时光机, 两者都以你的显式确认 + 冲突保护为边界。

3.8 交互外壳 REPL 与流式 TUI

前面这些命令你可以一条条敲,也可以进 plandex repl 里像聊天一样连续用。REPL 是这条闭环的外壳

REPL 的本质:一个补全型 prompt,把你的输入派发成子命令。 runRepl(app/cli/cmd/repl.go:68) 起一个 go-prompt,支持两种模式:

模式图标干什么
chat💬只对话/提问,不写代码
tell⚡️真正实现、生成改动进沙箱
  • 你输入普通文本 → 按模式转成 tell/chat 子命令执行(executor,app/cli/cmd/repl.go:465-483)。
  • \ 开头是命令(\diff\apply\quit…),@ 引用文件进上下文;补全和模糊纠错在 completer(repl.go:526)与 findSimilarCommands(repl.go:1367)。
  • 关键点:REPL 不在自己进程里跑子命令,而是 fork 同一个二进制——ExecPlandexCommandWithParams (app/cli/lib/repl.go:152)用 exec.Command(os.Args[0], args...) 起子进程,直连 std 流,并设 PLANDEX_REPL=1 等环境变量。好处:子命令里的 os.Exit 不会杀掉你的 REPL (repl.go 顶部注释,app/cli/lib/repl.go:141-143)。它同样用负 PID 把中断信号转发给子进程组 (repl.go:214-217)。

流式 TUI:实时看 AI 在干什么。 当计划在生成时,客户端连服务端的,把消息喂给一个 bubbletea TUI 实时渲染(回复文本、每个文件的构建进度)。接线在 OnStreamPlan (app/cli/stream/stream.go:18):

// 示意,非源码:客户端流回调
OnStreamPlan = func(params) {
if params.Err != nil { // 掉线(心跳丢失/EOF)→ 自动重连
streamtui.Send(错误消息)
api.Client.ConnectPlan(planId, branch, OnStreamPlan) // 重连
return
}
streamtui.Send(*params.Msg) // 正常消息 → 送进 TUI 渲染
}

真实实现里遇到 "missing heartbeats"/EOF 会自动 ConnectPlan 重连 (app/cli/stream/stream.go:20-37);TUI 的模型状态(reply、逐文件 token 数、缺文件提示等) 定义在 app/cli/stream_tui/model.go:38

顺带区分:app/cli/lib/active_stream.go 里的 SelectActiveStream(active_stream.go:11)是 选一个正在运行的计划流去连接/接管(比如后台跑的计划),它负责"连哪个流",而 stream.goOnStreamPlan 负责"流来了怎么渲染"。


4. 巧妙之处(可借鉴的技术)

  • 写盘即攒回滚信息,让"试探性应用"成为可能。 apply 在写每个文件前存旧内容,于是可以先落盘 再跑命令,失败/拒绝就精确回退——而不是跑完命令才敢动文件。ApplyFiles (app/cli/lib/apply.go:622)+ Rollback(apply.go:752)。

  • 自动 debug 是一个自引用闭包 + attempt 计数的递归。 失败回调把自己传给下一次 apply, 天然形成"修→跑→再修"的有界循环,边界就是 AutoDebug 次数。 getOnApplyExecFail(app/cli/plan_exec/apply_exec.go:26)。

  • 进程组 + cgroup 双层收尾。 负 PID 杀整组保证跨平台的基本清理,Linux 再用 systemd scope 兜底 残留进程——跑不可信命令时的可靠回收。apply_proc.go:14 + apply_cgroup_linux.go:20

  • REPL 用 fork 子进程做隔离。 子命令的 os.Exit、崩溃都困在子进程里,REPL 主循环稳如泰山; 代价是每条命令一次 fork。ExecPlandexCommandWithParams(app/cli/lib/repl.go:152)。

  • 提交只包动过的文件。 GitAddAndCommitPaths 逐路径 add、commit 带 paths,不裹挟你其它改动—— 沙箱"隔离"承诺一直贯彻到 git 层。app/cli/lib/git.go:34

  • rewind 三态对比 + 冲突必停。 目标态/当前态/磁盘态三方比对,只有你手改过的文件才算冲突并强制 询问,避免静默覆盖人的劳动。AnalyzeRewind(app/cli/lib/rewind.go:107)。


5. 边界与局限(诚实)

  • 沙箱隔离的是"计划改动 vs 工作区",不是安全沙箱。 apply 跑的 _apply.sh 是在你的真实 shell、真实项目目录里执行的(execApplyScript,app/cli/lib/apply.go:399-401)。进程组/cgroup 只解决"进程清理",不限制命令能碰什么。命令内容执行前会展示给你确认(除非 --auto-exec)。

  • cgroup 隔离只在 Linux + systemd 用户管理器可用时生效。 连不上就退化为 no-op、只靠进程组 (apply_cgroup_linux.go:28-31)。

  • rewind 的磁盘冲突判断依赖能读到磁盘内容。 冲突检测比较磁盘与计划态 (AnalyzeRewind,app/cli/lib/rewind.go:156-200);它保护的是"你手改过的文件",无法感知 git 层面更复杂的历史。

  • git 提交信息生成依赖模型调用。 服务端模型调用失败会报错 (genPlanDescription,app/server/model/plan/commit_msg.go:113-121);这属于 04 的模型编排范畴。

  • 本章不覆盖 diff 是怎么算出来的。 客户端 diff 只是取用服务端已算好的结果 (GetPlanDiffs);diff/构建的生成逻辑见 02-build-apply


6. 横向对比

同 shelf 里其它编码 agent 也各有"人审边界"的取舍,可对照阅读:

  • 与"直接改工作区 + 靠 git 兜底"的 agent 相比,Plandex 把隔离前移到服务端沙箱,apply 之前工作区零改动,回退粒度更细(逐文件 reject + 整段 rewind)。
  • 与"每步都要人点确认"的严格模式相比,Plandex 用 ApplyFlags 的一组 auto 开关 (app/cli/types/apply.go:7)把"人审"做成可调档:从全手动到全自动 debug 连续可选。

跨库原理(沙箱/人审边界、命令执行控制)的统一对比见总库 doc;本章聚焦 Plandex 的实现。


7. 代码地图(导航索引)

主题文件(相对克隆根)关键符号
应用编排入口app/cli/lib/apply.goMustApplyPlan / MustApplyPlanAttempt
写盘 + 回滚计划app/cli/lib/apply.goApplyFiles / Rollback
执行 _apply.shapp/cli/lib/apply.gohandleApplyScript / execApplyScript
应用后 git 提交app/cli/lib/apply.gocommitApplied / apiApplyPlan
CLI apply 入口app/cli/cmd/apply.goapply / applyCmd
回滚计划类型app/cli/types/apply.goApplyRollbackPlan / ApplyReversion / HasChanges
失败自动 debugapp/cli/plan_exec/apply_exec.goGetOnApplyExecFail / getOnApplyExecFail
进程组杀灭app/cli/lib/apply_proc.goSetPlatformSpecificAttrs / KillProcessGroup
cgroup 隔离(Linux)app/cli/lib/apply_cgroup_linux.goMaybeIsolateCgroup
cgroup 隔离(其它)app/cli/lib/apply_cgroup_other.goMaybeIsolateCgroup
审阅 diffapp/cli/cmd/diffs.godiffs / showGitDiff
剔除改动app/cli/cmd/reject.goreject
上下文条目展示app/cli/lib/context_display.goGetContextLabelAndIcon
rewind 命令app/cli/cmd/rewind.gorewind / formatLogMessage
rewind 分析/应用app/cli/lib/rewind.goAnalyzeRewind / ApplyRewindChanges / GetUndonePlanApplies
git 集成app/cli/lib/git.goGitAddAndCommitPaths / GitStashPop / GetGitLogTimestamp
提交信息生成(服务端)app/server/model/plan/commit_msg.gogenPlanDescription / GenCommitMsgForPendingResults
REPL 外壳app/cli/cmd/repl.gorunRepl / executor / completer
REPL 子进程派发app/cli/lib/repl.goExecPlandexCommandWithParams
流回调 → TUIapp/cli/stream/stream.goOnStreamPlan
选择活跃流app/cli/lib/active_stream.goSelectActiveStream
流式 TUI 模型app/cli/stream_tui/model.gostreamUIModel