dsh-workspace-handoff 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,3 +1,232 @@
1
- # Temporary Holding Version
1
+ # dsh-workspace-handoff
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ 工作区会话迁移插件(零运行时依赖,仅 Node 内置模块)。
4
+
5
+ 把一个工作区里「干到哪儿了」打包成可迁移的 handoff 包,搬到另一个工作区根后自动重映射绝对路径、体检文件漂移,并落地成人类可读的 `HANDOFF.md` + 机器可读的 `handoff.json`。适用于换机器、换目录、换盘符,或把 A 仓库的进度接到 B 仓库继续干。
6
+
7
+ - 源码:https://github.com/geeklei/dsh-plugins/tree/main/dsh-workspace-handoff
8
+ - npm:https://www.npmjs.com/package/dsh-workspace-handoff
9
+ - 协议:MIT
10
+
11
+ ## 功能说明
12
+
13
+ 五个工具:
14
+
15
+ | 工具 | 作用 | 关键能力 |
16
+ |---|---|---|
17
+ | `pack_handoff` | 采集当前工作状态,打成 handoff 包(JSON 返回) | 目标 / 关键决策 / 未完成任务 / 备注;按 glob 展开上下文文件清单(相对路径 + 大小 + mtime + sha256);剪枝依赖与产物目录、符号链接防环、文件数预算;可选带 `.journal` 近期日志;包内含规范化 JSON 的 sha256 完整性摘要 |
18
+ | `migrate_handoff` | 把包迁移到另一个工作区根,只出报告不落盘 | 状态文本内的来源根前缀重映射(容忍斜杠反斜杠混写、Windows 盘符大小写);漂移检测五态(未变 / 已改 / 改名 / 缺失 / 新增);接续结论 clean / renamed / dirty / unchecked |
19
+ | `apply_handoff` | 在目标工作区落地接续产物 | 写 `.handoff/HANDOFF.md` 与 `.handoff/handoff.json`;同名冲突策略 overwrite / skip / versioned;支持 dry_run 只看计划;落地包重算完整性并记录迁移溯源 |
20
+ | `diff_handoff` | 对比两个 handoff 包,只读不落盘(v0.2) | 目标 / 决策 / 任务 / 备注逐项差异;清单文件五态对比(相同 / 内容已变 / 改名移动 / 仅 a 有 / 仅 b 有,缺哈希时保守归入 uncertain);结论 identical / state_diverged / files_diverged / diverged |
21
+ | `merge_handoff` | 合并两个 handoff 包为一个新包(v0.2) | 决策并集去重;任务按文本并集(任一侧完成即完成,可改策略);清单按路径并集、同路径默认较新侧为准;notes 默认拼接;产物可直接 `apply_handoff` 落地 |
22
+
23
+ handoff 包结构(`schema` 为 `handoff/v1`):
24
+
25
+ ```text
26
+ {
27
+ schema, kind, created_at, producer,
28
+ source: { root, platform, label },
29
+ state: { goal, decisions, open_tasks, notes, journal? },
30
+ context: { patterns, files[{path,size,mtime,sha256}], stats },
31
+ integrity: { algo, digest }
32
+ }
33
+ ```
34
+
35
+ 经 `merge_handoff` 产出的包会多一个 `merged` 字段(两侧来源、较新侧、合并策略),经 `apply_handoff` 落地的包带 `migrated` 字段——两者可并存,便于溯源。
36
+
37
+ 设计要点:
38
+
39
+ - **搬的是工作状态,不是消息流水**:`goal` / `decisions` / `open_tasks` 由模型自己声明,插件不读 dsh 会话历史,因此跨宿主、跨版本都能用,也不受会话导出格式变化影响。
40
+ - **清单只记相对路径**(统一 `/` 分隔):这是换机器、换盘符仍可解的前提;绝对路径只出现在 `source.root` 与文本描述里,迁移时按前缀重映射。
41
+ - **完整性可验**:对不含 `integrity` 的包做键升序规范化 JSON 后取 sha256;字段乱序不影响摘要,手工改内容会被检出。
42
+
43
+ ## 安装
44
+
45
+
46
+ ```bash
47
+ npm install dsh-workspace-handoff
48
+ ```
49
+
50
+ 本地开发调试(Debug patch):新建 debug.patch.yml
51
+
52
+ ```yaml
53
+ - insert:
54
+ - id: workspace-handoff
55
+ name: "E:/dsh-plugins/dsh-workspace-handoff/index.js"
56
+ ```
57
+
58
+ ## 使用说明
59
+
60
+
61
+ ### 1. 在来源工作区打包
62
+
63
+ ```javascript
64
+ const bundle = await tools.pack_handoff({
65
+ goal: "续写架构实战合集第 21 篇并设定时发表",
66
+ decisions: ["封面口径沿用浅底蓝字模板", "交付清单为唯一状态来源"],
67
+ open_tasks: ["写完 21 篇", { text: "已发 01 篇", done: true }],
68
+ notes: "源码目录 E:/work/arch-notes/src,配图脚本在 tools 下",
69
+ files: ["**/*.md", "src/**/*.ts"],
70
+ include_journal: true,
71
+ })
72
+ ```
73
+
74
+ 返回的 JSON 字符串原样保存(写入文件或直接带到另一台机器均可),也可只把 `files` 省掉——那时包内只有状态没有清单。
75
+
76
+ ### 2. 在目标工作区迁移
77
+
78
+ ```javascript
79
+ const report = await tools.migrate_handoff({
80
+ bundle, // JSON 字符串,或 handoff.json 的路径
81
+ target_root: "F:/new-machine/arch-notes",
82
+ })
83
+ ```
84
+
85
+ 关注 `verdict`:
86
+
87
+ - `clean` 清单文件全部一致,可直接接续
88
+ - `renamed` 内容都在、只是路径改了(改名或移动),按 `drift.moved` 里的新路径读
89
+ - `dirty` 有缺失或内容变更,接续前先确认原因
90
+
91
+ ### 3. 落地接续简报
92
+
93
+ ```javascript
94
+ await tools.apply_handoff({
95
+ bundle,
96
+ target_root: "F:/new-machine/arch-notes",
97
+ mode: "versioned",
98
+ dry_run: false,
99
+ })
100
+ ```
101
+
102
+ 生成 `目标工作区/.handoff/HANDOFF.md`(目标、决策、任务复选框、文件清单表、漂移表、近期日志、备注)与 `handoff.json`。在新工作区里让模型先读 `HANDOFF.md` 即可接续。
103
+
104
+ 主要参数:
105
+
106
+ | 参数 | 类型 | 说明 |
107
+ |---|---|---|
108
+ | `goal` | string | 必填,≤500 字符 |
109
+ | `decisions` / `files` / `prune` | array | 字符串数组;`files` 为 glob |
110
+ | `open_tasks` | array | 元素为字符串,或含 `text` 与 `done` 的对象 |
111
+ | `notes` | string | ≤4000 字符,其中绝对路径会被重映射 |
112
+ | `root` / `target_root` | string | 工作区根;`root` 默认当前目录 |
113
+ | `hash` / `check` / `strict` / `dry_run` / `force` / `include_journal` | boolean | 见上 |
114
+ | `a_bundle` / `b_bundle` | string | `diff_handoff` / `merge_handoff` 必填:两份包,JSON 字符串或文件路径 |
115
+ | `goal_from` / `notes_mode` / `file_policy` / `done_policy` | string | `merge_handoff` 合并策略,非法取值直接报错 |
116
+ | `max_files` / `max_hash_bytes` / `journal_limit` / `top` | number | 预算类参数,越界或传字符串会被拒 |
117
+
118
+ ### 4. 两份包对比与合并(v0.2)
119
+
120
+ 同一件事在两台机器各自推进、或一天内打了多个包时,用 `diff_handoff` 看清差异,用 `merge_handoff` 收拢成一份:
121
+
122
+ ```javascript
123
+ const d = JSON.parse(await tools.diff_handoff({
124
+ a_bundle: packOnLaptop, // 第一份包:JSON 字符串或文件路径
125
+ b_bundle: packOnDesktop, // 第二份包
126
+ }))
127
+ // d.verdict: identical / state_diverged / files_diverged / diverged
128
+ // d.state.open_tasks.status_changed: 两边都写过的任务谁改了完成状态
129
+ // d.files.moved: 一侧改名/移动的文件(按内容 sha256 互相认领,一份内容只认领一次)
130
+
131
+ const merged = await tools.merge_handoff({
132
+ a_bundle: packOnLaptop,
133
+ b_bundle: packOnDesktop,
134
+ goal_from: "newer", // newer(默认)/ a / b
135
+ file_policy: "newer", // 同路径冲突以哪侧为准
136
+ })
137
+ await tools.apply_handoff({ bundle: merged, target_root: "F:/new/arch-notes" })
138
+ ```
139
+
140
+ 合并策略默认「谁也不丢」:决策与任务取并集、同一条任务任一侧标记完成即视为完成、文件清单同路径以较新一侧条目为准。notes 默认两侧拼接,超过 4000 字会报错提示改用 `notes_mode=a/b` 取单侧。
141
+
142
+ glob 子集:`*`、`?`、`**`、`[...]`、`{a,b}`;只接受工作区内相对路径,`../` 形式直接报错。
143
+
144
+ ## 典型场景
145
+
146
+
147
+ - **换机器接续**:旧机 `pack_handoff` → 把 `handoff.json` 拷到新机 → 新机 `migrate_handoff` 看漂移 → `apply_handoff` 落地简报,模型读简报继续干。
148
+ - **目录改名或换盘符**:`E:/work/proj` 变成 `F:/new/proj`,简报里的路径自动改写,不必全文手找。
149
+ - **多工作区合并**:把散在两个目录的同一件事收拢时,先对旧目录打包,在新目录迁移时会把「内容相同但路径不同」的文件识别为改名,避免重复劳动。
150
+ - **交付前状态留档**:`apply_handoff` 产出的 `HANDOFF.md` 本身就是一份可交给同事或次日的自己的接续文档。
151
+ - **两台机器并行推进后收拢**:各自 `pack_handoff`,回来 `diff_handoff` 看清谁的哪份文件更新、任务状态差在哪,再 `merge_handoff` 合成一份落地,避免手工比对。
152
+ - **跨工作区风险预估**:`pack_handoff` 的文件清单 + 目标区 `migrate_handoff` 的漂移表,可以先看清「少了哪些、哪些被改过」再决定要不要接续。
153
+
154
+ ## 安全边界
155
+
156
+
157
+ - **只写两个自有文件**:`apply_handoff` 仅创建或覆盖 `out_dir` 下的 `HANDOFF.md` 与 `handoff.json`,不删除、不修改清单之外的任何既有文件;`migrate_handoff` 全程只读。
158
+ - **不越出工作区**:`out_dir` 必须是目标工作区内的相对路径,绝对路径与含 `..` 段一律拒绝;glob 模式同样禁止越界。
159
+ - **不改文件内容**:路径重映射只作用于 handoff 包内的状态文本,绝不写回源文件。
160
+ - **完整性优先拒绝**:包被手工编辑或传输损坏时,`migrate_handoff` 与 `apply_handoff` 默认报错;确认可信再显式传 `strict: false`(落地还需 `force: true`),且结果里会标注校验失败。
161
+ - **体积有预算**:`max_files` 上限 20000、`max_hash_bytes` 上限 64 MB、bundle 文件上限 8 MB;超限报错或计入 `skipped_large`,不会把大文件读进内存。
162
+ - **不含敏感兜底**:`notes` 与文件清单会如实记录文本与相对路径。若状态描述里含密钥,请自行脱敏后再打包;需要批量脱敏可先用 `dsh-dotenv-toolkit` 的 `mask_env` 或 `dsh-credential-scan`。
163
+ - **符号链接与特殊文件**:遍历跳过符号链接(防环)与非普通文件,不跟随也不报错。
164
+
165
+ ## 版本说明
166
+
167
+ v0.2.0:
168
+
169
+ - 新增 `diff_handoff`:两个 handoff 包的逐项对比(目标 / 决策 / 任务 / 备注 + 清单文件五态),一侧缺哈希时保守归入 `uncertain` 不误判;`top` 控制明细截断
170
+ - 新增 `merge_handoff`:决策并集去重、任务并集(`done_policy` 默认任一侧完成即完成)、清单按路径并集(`file_policy` 默认较新侧为准)、notes 拼接超限显式报错;产物重算完整性并带 `merged` 溯源字段,可直接 `apply_handoff` 落地
171
+ - `stateOf` 归一化加固:外部手工包的 `decisions` / `open_tasks` / `journal` 非数组或含脏项时安全降级,diff/merge 不再抛错
172
+ - 断言 160 → 280,`diff_handoff` / `merge_handoff` 同样经真实 `@deepseek-ai/dsh-tools` 包装层执行 happy path 与参数校验
173
+
174
+ v0.1.0(首个版本,四期插单):
175
+
176
+ - `pack_handoff` / `migrate_handoff` / `apply_handoff` 三个工具与 `handoff/v1` 包结构
177
+ - 零依赖 glob 子集编译与遍历、sha256 指纹与规范化 JSON 完整性摘要、绝对路径前缀重映射、五态漂移检测、overwrite / skip / versioned 落地策略与 dry_run
178
+ - 与 `dsh-workspace-journal` 的 `.journal` 约定兼容(可选带出近期条目)
179
+
180
+ 160 条断言,经真实 `@deepseek-ai/dsh-tools` 包装层执行每个工具的 happy path 与参数校验。
181
+
182
+ ## RoadMap
183
+
184
+
185
+ - ~~**v0.2**:`diff_handoff` 两包对比 + `merge_handoff` 合并~~ ✅ 已完成(0.2.0)
186
+ - **v0.3**:状态自动提炼——从 `.journal`、todo 与最近改动的文件推断 `goal` / `open_tasks`,把手填降为确认;支持 `include_context_preview` 带出关键文件前若干行,让简报脱离原目录也可读
187
+ - **v0.4**:真正的文件搬运——可选 `copy_files` 把清单内文件按重映射后的路径复制过去(默认仍只出报告,保持只读安全线)
188
+ - **v0.5**:与 `dsh-mindvault` 打通——`apply_handoff` 后可选把本次接续写成 raw 层溯源页,形成「一次迁移 = 一条可回溯记录」
189
+
190
+ 不做的事:不读 dsh 会话历史(交给 `dsh-session-exporter`)、不做网络同步(本地文件操作)、不删除任何既有文件。
191
+
192
+ ## 其他插件集成说明
193
+
194
+
195
+ - **`dsh-session-exporter`**:那边搬「说了什么」(完整消息流水,多种格式导出),这边搬「干到哪儿了」(结构化状态 + 文件清单)。接续时建议先读这边的 `HANDOFF.md`,需要回溯原话再查导出件。
196
+ - **`dsh-workspace-journal`**:`pack_handoff` 的 `include_journal` 直接读它的 `.journal/YYYY-MM.md`,把近期结论随包带走;包里的 journal 段落同样会被重映射。
197
+ - **`dsh-plugin-todo`**:任务清单可从 todo 取,写入 `open_tasks` 的 `{ text, done }` 形式,落地后变成 `HANDOFF.md` 里的复选框。
198
+ - **`dsh-mindvault`**:mindvault 管「知识与记忆页」的长期沉淀,本插件管「一次工作状态」的一次性搬运;`handoff.json` 可作为 raw 层溯源凭证用 `mem_save` 归档。
199
+ - **`dsh-hash-checksum`**:清单里的 `sha256` 与它的输出口径一致(十六进制小写),可互验。
200
+ - **`dsh-file-search` / `dsh-folder-tree`**:漂移表出现 `dirty` 时,用它们定位缺失文件的去向。
201
+
202
+ ## FAQ
203
+
204
+
205
+ **问:为什么不直接读会话历史,这样要手填 goal 太麻烦?**
206
+ 答:v0.1 刻意如此。不依赖宿主会话 API,插件在任何 harness 版本、任何工作区里行为一致,也不受会话结构变更影响;`goal` 一句话的成本远低于跨机器后发现导出格式对不上。自动从会话与 `.journal` 提炼状态已列入 RoadMap。
207
+
208
+ **问:notes 里的路径只替换了来源根,其他绝对路径没改?**
209
+ 答:是的,这是设计取舍。插件只替换 `source.root` 前缀,不猜测其他绝对路径——误替换比漏替换危险得多。命中要求前缀之后紧跟分隔符或结束,所以 `E:/work/proj-extra` 不会被 `E:/work/proj` 带走。
210
+
211
+ **问:`verdict` 是 renamed 是什么意思,能直接接续吗?**
212
+ 答:内容都在,只是路径变了(文件被改名或移动)。`drift.moved` 给了旧路径到新路径的对照表,按新路径读即可接续。
213
+
214
+ **问:校验失败说摘要不符,但我确实改过 notes。**
215
+ 答:改过内容摘要必然变。确认无误后传 `strict: false`(`apply_handoff` 还需 `force: true`)放行,结果里的 `integrity.ok` 会保留 false 作为标注。
216
+
217
+ **问:清单能包含二进制文件吗?**
218
+ 答:可以入清单(记大小与 sha256),但插件只搬清单不搬内容——它不会复制任何文件。需要把文件本体一起带走,请用 `files` 打清单后自行同步目录,或用 `dsh-xlsx-lite`、`dsh-jsonl-stream` 等针对具体类型处理。
219
+
220
+ **问:`diff_handoff` 会把内容相同但路径不同的文件判成「改名」,`merge_handoff` 会怎么处理?**
221
+ 答:diff 按 sha256 互相认领,判为 `moved` 给出 from→to 对照;merge 则按路径并集——两个路径都会保留在合并包清单里(同一份内容各一条记录),落地后的 `HANDOFF.md` 清单会同时列出,由接续方自行取舍。这比强行猜一个「正确路径」安全。
222
+
223
+ **问:为什么 `.git`、`node_modules`、`.handoff` 默认不进清单?**
224
+ 答:依赖可重装、产物可重建、`.handoff` 是本插件的输出,三者进清单只会造成噪声和自引用。需要改变范围时传 `prune`。
225
+
226
+ **问:`mode=versioned` 会一直堆积版本文件吗?**
227
+ 答:每次递增 `-v2`、`-v3`……不覆盖旧文件,便于对比与回溯。想收敛就删掉旧版本,插件自身不做清理。
228
+
229
+ ## 许可证
230
+
231
+
232
+ MIT
@@ -0,0 +1,3 @@
1
+ - insert:
2
+ - id: workspace-handoff
3
+ name: "dsh-workspace-handoff"