跳到主要内容

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