dsh-code-diff-stat
已验证dsh-code-diff-stat · v0.1.0 · MIT · Web 界面
DSH Web 插件:在聊天流中按轮次汇总代码改动量(+N −M),可展开查看每个文件明细。 · DSH plugin: per-turn code-change stats (+N −M) rendered directly in the chat flow, expandable to a per-file breakdown.
安装
dsh plugin add dsh-code-diff-stat 用 dsh --profile default --dump-config 确认 layer 已生效 —— 参见安装指南。
源码
发布到 npm 但没有公开仓库。安装前请检查包内容。
标签
作者
说明文档
dsh-code-diff-stat
English | 中文
在 DSH Web 聊天流里,按轮次汇总这次对话到底改了多少代码,并且点开就能看 diff、能撤销:
┌────────────────────────────────────────────────────────────┐
│ 本轮代码改动 +148 −9 15 个文件 [撤销本轮改动] ⌄ │
└────────────────────────────────────────────────────────────┘
展开 ⌄ 是每个文件的明细;点某个文件,就在右侧栏为它开一个 diff 面板:
┌ 对话流 ───────────────────────┐ ┌ 右侧栏 ──────────────────────┐
│ 本轮代码改动 +148 −9 15 个文件 │ │ [ definition.js ] [ view.js ]│
│ [撤销本轮改动] ⌃ │ │ ─────────────────────────── │
│ ≡ …/src/definition.js +140 −9│ │ …/src/definition.js +140 −9│
│ ≡ …/src/view.js +106 −0│ │ │ const type = event.type │
│ ≡ …/src/panel.js +134 −0│ │ │- if (type === 'turn/start'│
└───────────────────────────────┘ │ │+ if (type !== 'turn/start'│
│ │ 第 4 轮 │
└──────────────────────────────┘
面板是右侧栏里的一个标签页,标签名就是文件名。开面板读 diff 不会让对话流重排;同一个面板再点别的文件就换成那个文件。
它解决什么问题
让 agent 改完代码,你只能看到「改好了」,看不到改了多少、改了哪几行。这个插件把每一轮的改动量、逐文件明细和逐 hunk diff 直接放进对话流,并且给你一个按钮把这一轮改回去。
安装
dsh plugin --profile web add dsh-code-diff-stat
本地开发用链接安装:
dsh plugin --profile web add link:/绝对路径/dsh-code-diff-stat
装完重启 DSH。插件只挂在 Web profile 上,不替换任何产品自带的界面元素。
用法
| 操作 | 效果 |
|---|---|
| 看徽章 | 每轮结束后,那一轮改了多少行、几个文件 |
点 ⌄ |
展开每个文件各自的 +N −M |
| 点文件行 | 在右侧栏开一个 diff 面板查看该文件的改动 |
点 ↗ |
在右侧栏用编辑器打开该文件 |
| 点「撤销本轮改动」 | 二次确认后,把这一轮对文件的修改反向应用回去 |
撤销也可以直接打字:
/undo-diff 4 # 撤销第 4 轮(那个徽章上的轮次号)
命令会把每个文件的结果逐行列出来(✓ 已撤销 / – 跳过 / ✗ 失败)。
撤销的边界(重要)
撤销是反向应用日志里记录的 hunk,不是恢复快照。所以:
- 本轮新建的文件不会被删除。
fs服务不提供删除能力,而且一个你后来可能已经写过内容的文件,被悄悄删掉比「撤不干净」更糟。这类文件会标上「本轮新建」并在结果里列为跳过。 - 文件在本轮之后又被改过,反向 patch 可能找不到原文;
fs.editText的唯一性校验会让它拒绝而不是改错位置,该文件单独失败,其余文件照常撤销。 - 改动过大时插件只保留行数、不保留 diff 文本,这类文件无法撤销。
如果你要的是完整工作区还原(含删除新建文件),已装的 dsh-rewind-plugin 的 /rewind … both 用的是真正的 before 备份,语义更强。这个插件的撤销是「就地、按轮次、无额外存储」的轻量路线。
原理
数据全部来自已经落在会话日志里的持久事件,不读 git、不额外落盘、不发起任何请求:
| 来源 | 用途 |
|---|---|
tool/result 的 meta.diffs |
权威的「已应用改动」hunk,由 dsh-tool-fs 在结果期写入日志 |
tool/call 的 arguments |
当上一条不适用时的回退 |
turn/start / turn/end |
界定一轮的边界与行尾锚点 |
因此历史会话也能正确显示——重新打开很久以前的对话,徽章和 diff 照样在,因为它是从日志重放出来的。
撤销复用同一套折叠逻辑:宿主半自己从日志重新推导要反向应用的 hunk,而不是信任前端传来的数据。屏幕上显示的和按钮实际撤销的,是同一份计算,不可能对不上。
几个刻意的取舍
为什么对着 hunk 重新 diff,而不是直接数两侧行数。 meta.diffs 的每个 hunk 前后各带 3 行上下文,这些行在 oldText 和 newText 里同时出现。直接数会把「改 1 行」报成「+7 −7」。所以插件对每个 hunk 重新算一次最小行差异,上下文自行抵消。
为什么新建文件走参数回退。 dsh-tool-fs 对不存在的文件刻意写入 diffs: [](空数组),此时「已应用改动」就是整份文件,由调用参数描述。把空数组当成「无改动」会让模型新建的每个文件都消失。
覆盖已有文件走权威路径。 这种情况下日志里是真实的 before/after hunk,而参数只能看到写完的整份内容——用参数会把整个文件报成新增。
失败的调用不计入。 isError 的结果一律跳过:改动没落地,计进去就是虚报。
只认 append 来源的结果。 回退(rewind)产生的 replacement 副本是模型可见性的产物,不是对话记录,计入会重复。
没有改动的轮次不渲染任何行。 纯问答、纯读文件的轮次不会多出一个空框。
兼容性
- 宿主半只做一件事:注册
/undo-diff命令。没有常驻状态、没有额外存储。 - 客户端半注册的是新的 Chat 节点类型(
diff-stat)和自己的右侧栏标签页类型,都不是产品自带的任何一个;聊天节点槽是 keyed 且可合并扩展的,未被占用的 kind 直接不渲染,所以不存在替换产品行的问题。标签页的内容来自它自己的导航参数,而不是再读一遍日志。 - 撤销通过会话自己的命令通道派发(
SessionFace.command),不需要额外的 Remote API 或代码生成。 - 组合里没有 session 服务时,徽章照常显示,只是不出现撤销按钮。
- 跟随 DSH 主题 token,明暗主题自动适配。
- 中英双语。
开发
npm run build # src/ → lib/(无第三方构建依赖)
npm test # 38 个单元测试
npm run check # 构建 + 测试 + 打包/渲染/撤销契约校验
node scripts/inspect-session.mjs # 用真实会话日志对账
node scripts/verify-install.mjs # 校验安装态能否通过启动扫描
src/ 是可被 node --test 直接导入的标准 ESM;scripts/build.mjs 只做确定的几件事——把本地 import 按依赖顺序内联、把 react 换成加载器的 require、并在写出前先用 vm.Script 解析一遍产物——每一步都断言,源文件一旦偏离假设就构建失败,而不是打包出一个坏 bundle。
宿主半和客户端半共享同一个 src/pure.js,这是「显示的数字」和「撤销的动作」不会漂移的结构保证。
相关
做同一件事的还有 dsh-diff-stat 和 dsh-turn-diff,可以先看看哪个更合你的口味。
License
MIT