# 解除对 rewind 的依赖 —— 设计记录（2026-08-17）

> **实施状态**：**Phase B（轨迹重放）已实现**（0.5.0，本会话完成）——`lib/trace/*`
> + GUI "Trace (session log)" 范围 + `/diff --trace` 命令 + 三个 trace HTTP 端点；
> 单测 33 项 + 集成 trace 流程全绿，真实会话日志端到端验证通过。**Phase A
> （自建快照捕获）未动**，按 §4 排期。README/ARCHITECTURE 已按实现更新。
>
> 用途：回答"能不能解除对 dsh-checkpoint-rewind 的依赖"。结论：**能**，分两个
> 阶段——**轨迹重放**（零写路径，立即可用）与**自建快照捕获**（完全自足，需扩展
> 写路径边界）。rewind 存在时继续消费（向后兼容、时间线合并），不存在时插件独立工作。
> 本文件所有"已核实"事实均于 2026-08-17 在本机（Node v24.9.0 / DSH 0.1.0-rc.5 部署）
> 与 harness 源码（`../deepseek-harness`，只读）验证。

## 0. 现状与目标

**现状**：本插件是 rewind 的纯消费者——读它的 `checkpoints` 存储域记录 + 快照目录
布局（git 未引用对象 / copy 目录）。没有 rewind 就没有检查点，时间线为空。
用户观察到其它插件各有方案（dsh-snapshot 自建快照、turn-rewind 变更台账、Claude
Code 回合级快照、Aider 自动 commit），问能否解除依赖。

**目标**：让本插件在 **rewind 缺席时也能回答问题**（"这次对话改了哪些文件、何时改的、
如何退回去"），同时保留现有能力；rewind 在场时行为不变（不重复捕获、时间线合并）。

## 1. 已核实事实（证据）

### 1.1 捕获层：事件对任何插件开放（rewind 不是唯一能捕获的）

- `fs/write-intent` / `fs/edit-intent` 是 Cordis **waterfall** 事件，由
  `packages/fs/tool-fs`（write.ts/edit.ts）与 `packages/fs/tool-str-replace-editor`
  在写盘前派发（`ctx.waterfall('fs/write-intent', target, actor, next)`）。
- `packages/fs/fs-observation-policy` 占用**决策槽**（监听且不调 `next()`），但
  观察型监听器（调 `next()` 放行）可共存——rewind 正是 prepend 直通监听
  （`lib/gate.mjs`/`index.mjs`，0.4.0 安装包源码已读）。
- `tools/pre-execute` 覆盖 bash/write/edit/str_replace_editor/pwsh/terminal_send
  等任意命令（rewind `mutationTools` 名单）——bash 类变更只有靠"执行前整工作区快照"。
- **结论**：自建捕获在技术上与 rewind 等价（同一组事件、同一"变更前"语义），
  不需要 rewind 的任何代码。

### 1.2 轨迹层：`session.jsonl.zstd` 可读，内容型工具参数携带完整内容

- 轨迹持久化在 `$DSH_HOME/sessions/<workspaceKey-->/<sessionId>/session.jsonl.zstd`
  （本机实测存在；harness 默认压缩 = zstd）。
- 格式为**多帧拼接**：header 帧 + 追加帧；`node:zlib.zstdDecompressSync` 对拼接流
  **只解首帧**（实测），须按帧扫描后逐帧解码——harness 的 `scanZstdFrames` 算法
  （`packages/session/session-persistence-jsonl/src/zstd.ts`）约 60 行、零依赖、可复用。
- **Node 版本**：`node:zlib` 的 zstd 需 **Node ≥ 23.5**（本机 v24.9.0 实测
  roundtrip OK；`^22.19` 无此 API——直接解析需运行时探测 `typeof zstdDecompressSync`，
  缺失则降级）。
- **事件形状（真实轨迹实测）**：`tool/call` = `{type, seq, time, data:{turn, step,
  callId, name, arguments}}`，`arguments` 为 JSON 字符串；`write` 参数
  `{file_path, content}`（实测含 8844 字符全量内容）、`edit` 参数
  `{file_path, old_string, new_string}`（实测）。`tool/result` 同 seq 对。
- **冷会话读取**：`sessionQuery.readSession(sessionId)` 返回完整事件日志
  （`{session, events}`，replay 校验后克隆，live/cold 皆可）——**首选路径，无需自解
  zstd**；本插件 labels 已依赖它。zstd 直接解析仅为 session-query 缺席时的兜底。
- **已知边界**：轨迹**不含** `fs/*-intent` 事件（rc.6 事件门关闭，已知事实）——
  因此 bash/pwsh/terminal_send 等任意命令对文件的修改**无法从参数推断**（除非有
  快照）。这是轨迹重放的固有盲区，不是实现问题。

## 2. 方案对比

| 方案 | 机制 | 写路径 | bash 盲区 | 历史会话（未装插件时） | 回滚质量 | 成本 |
|---|---|---|---|---|---|---|
| **B 轨迹重放** | 读 `session.jsonl.zstd`（或 `readSession`），重放 write/edit 重建任意时刻文件内容 | **零**（纯读） | 有（bash 修改不可见） | **可用**（轨迹天然持久，与是否装插件无关） | 中（只能恢复内容型工具碰过的文件；bash 改过的文件报"不可重建"跳过） | 低 |
| **A 自建捕获** | 监听同一组事件（fs/write-intent、fs/edit-intent、tools/pre-execute），变更前自存快照（copy 语义，自有目录 + 自有域） | 新写路径（自有存储，须扩展 AGENTS.md #1） | **无**（与 rewind 同质量） | 不可用（未装则无捕获） | 高（与 rewind copy provider 等价，复用现有回滚） | 中 |
| C git 自动 commit | 每次变更后向私有 ref 提交 | 写 git（违"绝不写 git"；未引用对象仍会被 gc） | 无 | 不可用 | 高（git 原生） | 低-中，但破坏现有安全边界、污染仓库、丢非 git 场景 |
| 现状（仅消费 rewind） | 读 rewind 域 + 快照目录 | 零 | 无 | 不可用 | 高 | 零，但依赖不解除 |

## 3. 推荐架构：B 先行，A 跟进，双源时间线

```
数据源（按优先级合并进同一时间线）：
1) rewind checkpoints 域（在场时）      ── 快照节点（现行为，不变）
2) 自有快照域 'cdp-snapshots'（A 落地后）── 快照节点（rewind 缺席时捕获）
3) 会话轨迹（B 落地后，兜底）            ── 轨迹节点（虚拟节点，重放内容）
   ├─ 首选 sessionQuery.readSession（live/cold，replay 校验）
   └─ 兜底 zstd 帧扫描 + node:zlib（session-query 缺席；node<23.5 降级）

节点模型：快照节点 = {id, time, trigger, turn, step, ref/provider}（现有）；
          轨迹节点 = {id: trace:<seq>, time, trigger: tool name, turn, step,
                     content: 重放到该点后的文件状态（write/edit 全量）}
diff：任意两节点（同源或跨源）→ 逐文件逐行（现有 LCS 引擎，复用）
回滚：快照节点 → 现有 rollback（copy 语义）；轨迹节点 → 只写"可重建"文件
      （内容型工具 track 的），其余文件报告"不可重建"跳过（绝不删除，同现有语义）
```

**关键决策**：
- **rewind 在场时不重复捕获**（检测 checkpoints 域有本会话记录即跳过自建捕获）——
  避免双份快照与事件竞争；rewind 缺席时自建捕获自动接管。
- **B 与 A 互补而非替代**：B 解决"历史与无生产者"（纯读、零风险）；A 解决
  "bash 盲区与回滚质量"（需写路径）。B 先落地即达成"可脱离 rewind 独立回答 diff"，
  A 再补"独立快照 + 独立回滚"。
- **轨迹节点的语义**：节点 = 工具调用边界；`/diff <trace节点A> <trace节点B>` 呈现
  重放内容差异；快照与轨迹节点可混排（按 time 排序），寻址（id 前缀/latest）沿用。
- **回滚安全契约不变**：预览先行、绝不删除（轨迹节点缺内容的文件 = 不可重建，跳过
  并报告）、路径校验、只写会话工作区、单次撤销（撤销记录复用现有 undoData 结构）。

## 4. 分阶段实施计划

### Phase B：轨迹重放（0.5.x，零写路径，先做）

1. `lib/trace/` 新模块：
   - `frames.mjs`：zstd 多帧扫描 + 逐帧解码（算法同构 harness `scanZstdFrames`，
     零依赖；`typeof zstdDecompressSync === 'function'` 门控，node 22 → 明确报错降级）；
   - `replay.mjs`：从事件流重放 write/edit/str_replace_editor → 每文件内容序列
     （`Map<relPath, Array<{seq, after}>>` 或按文件的内容版本表）；
   - `read.mjs`：数据源选择——`sessionQuery.readSession` 优先，zstd 兜底；
2. 服务层：`traceTimeline(sessionId)` / `traceDiff(from, to)` / `traceRollback(...)`
   （复用 `lib/diff/engine.mjs` LCS 与 `lib/rollback.mjs` 写路径）；
3. 时间线合并：现有 timeline 在"快照记录为空"时回落到轨迹节点；或始终追加
   "会话轨迹视图"（设计评审定：先做**回落**，合并混排后置）；
4. 端点/命令/GUI：`/diff --trace`、`GET /api/trace-timeline`、面板 scope 切换增加
   "Trace" 视图（复用现有面板组件，节点标 `⤷ trace`）；
5. 测试：真实轨迹 fixture（本机 3 会话 90 tool/call 已采样的形状）+ 帧扫描单测 +
   重放正确性（write/edit 序列 → 重建内容 == 实际文件）+ 冷会话 readSession 路径 +
   node 22 降级路径 + 集成（真 trace + 无 rewind 环境全流程）。

### Phase A：自建快照捕获（0.7.x，需边界扩展，走设计评审）

1. AGENTS.md 非协商条款 #1 扩展："自建快照"写路径——写**自有**存储域
   `cdp-snapshots`（version 1，schema 自声明）与自有快照目录
   `$DSH_HOME/dsh-checkpoint-diff/snapshots/<workspaceKeyHash16>/<uuid>/`
   （copy 语义：变更前拷贝 + manifest，与 workspace.mjs 同构算法；**不 fork rewind 代码**）；
2. 捕获器 `lib/capture.mjs`：prepend 直通监听 fs/write-intent（目标文件）、
   fs/edit-intent（目标文件）、tools/pre-execute（mutationTools 名单 → 整工作区）；
   rewind 在场（checkpoints 域有当前会话记录）→ 捕获器停用（避免双份）；
3. 配额/清理自管：独立 `maxSnapshots`/`maxBytes`（默认宽松，如 200/1 GiB）+
   手动清理命令，**不参与 rewind 逐出**、不写 git refs（绕开 gc 问题）；
4. 时间线合并：自有快照节点 + rewind 节点（若在）+ 轨迹节点（兜底）三源混排
   （按 time 稳定排序；provider 标注 cdp 自有/rewind/trace）；
5. 测试：捕获器事件顺序（intent 前捕获 = 变更前内容断言）、rewind 在场跳过、
   配额清理、三源合并、回滚全流程（复用现有 copy provider 语义 + 集成）。

### 收尾（两阶段共用）

- README/ARCHITECTURE/contract.md/competitive-analysis.md 更新："可脱离 rewind
  独立工作（轨迹重放 + 自建快照）"写进定位与对比表；
- 发布流程照旧（npm → profile → tag → dist-tag）；host 变更需重启 harness。

## 5. 边界与风险

- **Phase A 必须走 AGENTS.md #1 扩展评审**：新增写路径（自有快照存储）是
  "只读设计，唯一例外是回滚"的第二次扩展；约束保持可审计：只写自有域与自有目录、
  绝不碰 rewind 存储/git/会话、路径校验同 rollback、配额有上限。
- **B 的固有盲区**：bash/pwsh 修改不可见——文档必须写明"轨迹视图只能回答
  内容型工具引起的变更"；要完整答案请装 rewind 或等 Phase A。
- **Node 版本门控**：zstd 直接解析需 ≥23.5；readSession 路径无版本限制（优先）。
- **节点寻址歧义**：轨迹节点 id 用 `trace:<seq>` 前缀防与快照 id 冲突；
  跨源 diff（快照×轨迹）按时间对齐内容，无对齐点时拒绝并说明。
- **性能**：轨迹帧扫描 + 重放为全量读（万行级轨迹秒级），加缓存（按 sessionId
  + 文件大小写敏感键）；轨迹节点数远多于快照节点，GUI 默认折叠/限流。
- **不重复捕获**（A 与 rewind 共存）依赖"checkpoints 域有记录"检测——rewind 清了
  记录（`/rewind clear`）但快照目录还在时，自建捕获会接管，无冲突（语义 = 变更前）。
- **安全边界不变**：HTTP 面仍 GET-only（除 rollback/rollback-undo）；
  轨迹读取只读；回滚路径校验照旧。

## 6. 结论

解除依赖可行且分两步走：**Phase B（轨迹重放）零写路径、立即落地**，让插件在
rewind 缺席时也能回答"改了哪些文件、怎么改的"（内容型工具全覆盖，bash 盲区
文档化）；**Phase A（自建捕获）让插件成为独立的生产者+消费者**，与 rewind 同质量
的变更前快照与回滚，同时保留对 rewind 的向后兼容消费。竞品对照：dsh-snapshot 只有
A（自建捕获，无行级 diff、无轨迹兜底）；turn-rewind 只有"变更台账 + git 恢复"；
本插件两阶段后 = A + B + 行级 diff + 血缘 + 安全回滚，且**历史会话无需任何生产者
即可审计**（B 独有）。
