@mingmaple/dsh-handover 0.9.14 → 1.0.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.
Files changed (102) hide show
  1. package/README.md +123 -92
  2. package/docs/migrate.md +52 -41
  3. package/docs/releases/0.9.12.md +2 -3
  4. package/docs/releases/0.9.14.md +1 -5
  5. package/docs/releases/0.9.22.md +34 -0
  6. package/docs/releases/1.0.0.md +33 -0
  7. package/docs/troubleshoot.md +230 -46
  8. package/package.json +74 -71
  9. package/scripts/behavior.mjs +398 -0
  10. package/scripts/blank-dsh.mjs +41 -25
  11. package/scripts/blank-smoke.mjs +278 -109
  12. package/scripts/check-docs.mjs +7 -0
  13. package/scripts/e2e-migration.mjs +2 -2
  14. package/scripts/office.mjs +3 -3
  15. package/scripts/portability.mjs +71 -0
  16. package/scripts/style-gate.mjs +571 -0
  17. package/src/client.js +1028 -955
  18. package/src/index.mjs +313 -142
  19. package/src/lib/absrefs.mjs +1 -1
  20. package/src/lib/api.mjs +18 -10
  21. package/src/lib/archive.mjs +1 -1
  22. package/src/lib/brief.mjs +8 -6
  23. package/src/lib/config.mjs +23 -8
  24. package/src/lib/consent.mjs +54 -1
  25. package/src/lib/docmap.mjs +140 -0
  26. package/src/lib/drift.mjs +1 -1
  27. package/src/lib/events.mjs +1 -1
  28. package/src/lib/facts.mjs +19 -12
  29. package/src/lib/gates.mjs +30 -6
  30. package/src/lib/global-task.mjs +69 -0
  31. package/src/lib/guide.mjs +81 -85
  32. package/src/lib/handover-io.mjs +69 -6
  33. package/src/lib/identity.mjs +330 -0
  34. package/src/lib/legacy.mjs +89 -0
  35. package/src/lib/lint.mjs +17 -13
  36. package/src/lib/manual.mjs +135 -59
  37. package/src/lib/mover.mjs +23 -19
  38. package/src/lib/names.mjs +14 -19
  39. package/src/lib/office.mjs +63 -15
  40. package/src/lib/pack-ops.mjs +11 -11
  41. package/src/lib/pathkey.mjs +24 -0
  42. package/src/lib/peer.mjs +4 -4
  43. package/src/lib/phase.mjs +8 -13
  44. package/src/lib/profile.mjs +1 -1
  45. package/src/lib/reminder.mjs +110 -0
  46. package/src/lib/role.mjs +9 -6
  47. package/src/lib/scaffold.mjs +68 -26
  48. package/src/lib/schedule.mjs +5 -6
  49. package/src/lib/session-create.mjs +3 -3
  50. package/src/lib/session-ops.mjs +80 -51
  51. package/src/lib/sessions.mjs +41 -3
  52. package/src/lib/sevenzip.mjs +10 -1
  53. package/src/lib/status.mjs +337 -176
  54. package/src/lib/succession.mjs +16 -16
  55. package/src/lib/system.mjs +364 -0
  56. package/src/lib/zstd-log.mjs +1 -1
  57. package/src/selftest.mjs +6 -6
  58. package/src/templates/Changelog.skeleton.md +3 -3
  59. package/src/templates/Charter.skeleton.md +13 -9
  60. package/src/templates/Handover.global.skeleton.md +20 -13
  61. package/src/templates/Handover.template.md +89 -0
  62. package/src/templates/Plan.skeleton.md +34 -0
  63. package/src/templates/Prompt_Templates.md +101 -20
  64. package/src/templates/Runtime_Manual.md +263 -122
  65. package/src/templates/System.skeleton.md +31 -0
  66. package/src/templates/Systems.global.skeleton.md +13 -0
  67. package/src/templates/rules/Delegation_Rules.md +45 -0
  68. package/src/templates/rules/Handover_Rules.md +46 -23
  69. package/src/templates/rules/Migration_Rules.md +7 -7
  70. package/src/templates/rules/Misc_Task_Standard.md +17 -10
  71. package/src/templates/rules/Writing_Rules.md +33 -16
  72. package/src/templates/standards/README.md +37 -0
  73. package/src/templates/standards//345/206/231/344/275/234/350/247/204/350/214/203/README.md +120 -0
  74. package/src/templates/standards//345/206/231/344/275/234/350/247/204/350/214/203//344/276/235/346/215/256_/350/256/272/346/226/207.md +389 -0
  75. package/src/tools/ho_backup.mjs +135 -0
  76. package/src/tools/ho_changelog.mjs +4 -4
  77. package/src/tools/ho_charter.mjs +7 -6
  78. package/src/tools/ho_checkpoint.mjs +36 -32
  79. package/src/tools/ho_docmap.mjs +239 -0
  80. package/src/tools/ho_gate.mjs +11 -5
  81. package/src/tools/ho_handover.mjs +61 -35
  82. package/src/tools/ho_office.mjs +46 -5
  83. package/src/tools/ho_profile.mjs +3 -3
  84. package/src/tools/ho_sessions.mjs +46 -26
  85. package/src/tools/ho_status.mjs +251 -0
  86. package/src/tools/ho_succeed.mjs +3 -3
  87. package/src/tools/ho_workspace.mjs +146 -42
  88. package/src/tools/peer.mjs +416 -0
  89. package/src/tools/ho_brief.mjs +0 -57
  90. package/src/tools/ho_diag.mjs +0 -87
  91. package/src/tools/ho_drift.mjs +0 -41
  92. package/src/tools/ho_init.mjs +0 -82
  93. package/src/tools/ho_pack.mjs +0 -38
  94. package/src/tools/ho_phase.mjs +0 -37
  95. package/src/tools/ho_snapshot.mjs +0 -104
  96. package/src/tools/ho_tokens.mjs +0 -57
  97. package/src/tools/peer_list.mjs +0 -37
  98. package/src/tools/peer_new.mjs +0 -75
  99. package/src/tools/peer_peek.mjs +0 -41
  100. package/src/tools/peer_rename.mjs +0 -31
  101. package/src/tools/peer_send.mjs +0 -52
  102. package/src/tools/peer_status.mjs +0 -46
@@ -1,14 +1,18 @@
1
- /** ho_workspace —— 工作区生命周期:list / create / delete(官方 workspaceRegistry)+ 全局根目录搬迁。 */
1
+ /** ho_workspace —— 工作区生命周期:list / create / delete / init / scaffold / migrate(旧体系迁移)+ 全局根目录迁移。 */
2
2
  import fs from 'node:fs';
3
3
  import { nameOf } from '../lib/names.mjs';
4
4
  import path from 'node:path';
5
+ import { pathKey } from '../lib/pathkey.mjs';
5
6
  import { textOutput } from '../lib/output.mjs';
6
7
  import { NL } from '../lib/text.mjs';
7
8
  import { workspaceRows } from '../lib/registry.mjs';
9
+ import { createGlobalWorkspace, ensureScaffold } from '../lib/scaffold.mjs';
10
+ import { legacyInfo, migrateLegacyDocmaps } from '../lib/legacy.mjs';
11
+ import { syncGlobalRefs } from '../lib/system.mjs';
8
12
  import { probeMove, startMove, moveStatus, cleanupOld, stageSwitch, repairLink, stageRetarget } from '../lib/mover.mjs';
9
13
  import { currentRoot } from '../lib/api.mjs';
10
14
 
11
- const BAD = '不能删除「全局」工作区(那是管理会话的家)。';
15
+ const BAD = '不能删除「全局」工作区:该工作区为管理会话所在工作区。';
12
16
 
13
17
  function gb(n) {
14
18
  const v = Number(n) || 0;
@@ -20,7 +24,7 @@ function gb(n) {
20
24
 
21
25
  function renderProbe(p) {
22
26
  const out = [];
23
- out.push('搬迁预检:' + (p.ok ? '通过' : '不通过'));
27
+ out.push('迁移预检:' + (p.ok ? '通过' : '不通过'));
24
28
  out.push(' 源 ' + p.src + (p.srcIsLink ? '(已是链接,指向 ' + p.linkTarget + ')' : ''));
25
29
  out.push(' 复制源 ' + p.copyFrom);
26
30
  out.push(' 目标 ' + p.dst);
@@ -28,7 +32,7 @@ function renderProbe(p) {
28
32
  if (p.freeBytes !== null && p.freeBytes !== undefined) out.push(' 目标盘可用 ' + gb(p.freeBytes));
29
33
  for (const w of p.warnings || []) out.push(' 注意:' + w);
30
34
  for (const x of p.problems || []) out.push(' 未通过:' + x);
31
- if (p.ok) out.push('提示:复制完会生成切换脚本;切换那一步需要**完全退出 DSH** 再跑(源目录被占用)。');
35
+ if (p.ok) out.push('提示:复制完成后生成切换脚本;切换步骤须完全退出 DSH 后再执行(源目录被占用)。');
32
36
  return out.join(NL);
33
37
  }
34
38
 
@@ -36,7 +40,7 @@ export function pmWorkspaceTool(env) {
36
40
  return {
37
41
  name: nameOf('workspace'),
38
42
  description:
39
- '工作区 list、create、delete;move-* 搬整个全局根目录(探针、执行、状态、清旧副本)。删除只删注册。',
43
+ '工作区:init 首次建立全局体系、scaffold 补齐缺失目录与说明、migrate 旧体系迁移、list、create、delete、move-* 迁移整个全局根目录。删除只删注册。',
40
44
  parameters: {
41
45
  type: 'object',
42
46
  additionalProperties: false,
@@ -44,15 +48,18 @@ export function pmWorkspaceTool(env) {
44
48
  properties: {
45
49
  action: {
46
50
  type: 'string',
47
- enum: ['list', 'create', 'delete', 'move-probe', 'move', 'move-switch', 'move-status', 'move-retarget', 'move-repair', 'move-cleanup'],
48
- description: 'list / create / delete / move-probe / move / move-switch / move-status / move-retarget / move-repair / move-cleanup',
51
+ enum: ['init', 'scaffold', 'migrate', 'list', 'create', 'delete', 'move-probe', 'move', 'move-switch', 'move-status', 'move-retarget', 'move-repair', 'move-cleanup'],
52
+ description: 'init / scaffold / migrate / list / create / delete / move-probe / move / move-switch / move-status / move-retarget / move-repair / move-cleanup',
49
53
  },
50
- path: { type: 'string', description: 'create 用:目录绝对路径;默认要求已存在,ensure=true 时自动创建' },
51
- title: { type: 'string', description: 'create 用:显示标题' },
52
- ensure: { type: 'boolean', description: 'create 用:目录不存在时自动创建(默认 false,不替你建目录)' },
53
- workspace: { type: 'string', description: 'delete 用:工作区 id、标题、路径' },
54
- to: { type: 'string', description: 'move 用:新根绝对路径(父目录可自动建)' },
55
- confirm: { type: 'boolean', description: 'move-cleanup 用:true 才真的删旧副本' },
54
+ parent: { type: 'string', description: 'init 用:目标父目录的绝对路径(不存在时创建);全局根建于其中' },
55
+ name: { type: 'string', description: 'init 用:全局根目录名,缺省 _Workspace' },
56
+ register: { type: 'boolean', description: 'init 用:建立后一并注册为工作区(缺省 false)' },
57
+ path: { type: 'string', description: 'create 用:目录绝对路径;缺省要求已存在,ensure=true 时自动创建' },
58
+ title: { type: 'string', description: 'create、init 用:工作区显示标题(init 缺省「全局」)' },
59
+ ensure: { type: 'boolean', description: 'create 用:目录不存在时自动创建(缺省 false,不代建目录)' },
60
+ workspace: { type: 'string', description: 'delete 用:工作区 id、标题或路径' },
61
+ to: { type: 'string', description: 'move 用:新根绝对路径(父目录可自动创建)' },
62
+ confirm: { type: 'boolean', description: 'move-cleanup 用:为 true 才实际删除旧备份' },
56
63
  },
57
64
  },
58
65
  output: textOutput(),
@@ -61,6 +68,103 @@ export function pmWorkspaceTool(env) {
61
68
  const action = String(args.action || '');
62
69
  const out = [];
63
70
 
71
+ if (action === 'init') {
72
+ const parent = String(args.parent || '').trim();
73
+ if (!parent) throw new Error('action="init" 需要 parent(目标父目录绝对路径)');
74
+ const r = createGlobalWorkspace(parent, { name: args.name });
75
+ if (!r.ok) {
76
+ out.push('未建立:' + r.error);
77
+ if (r.root) out.push('目标:' + r.root);
78
+ out.push('');
79
+ out.push('用法:ho_workspace(action="init", parent="<已存在的目录绝对路径>")。插件仅新建,不覆盖。');
80
+ return { text: out.join(NL) };
81
+ }
82
+ out.push('已建立全局文件体系:' + r.root);
83
+ out.push(' 目录 ' + r.created.length + ' 个:' + r.created.slice(0, 5).join('、') + (r.created.length > 5 ? ' 等' : ''));
84
+ out.push(' 文件 ' + r.written.length + ' 个:' + (r.written.length ? r.written.join('、') : '(均已存在,未覆盖)'));
85
+ if (r.existed) out.push(' 目标原为空目录,直接使用。');
86
+ if (r.createdParent) out.push(' 父目录原不存在,已创建。');
87
+ if (r.skipped.length) out.push(' 跳过(已存在、未覆盖):' + r.skipped.join('、'));
88
+ let registered = null;
89
+ if (args.register === true) {
90
+ if (!reg || typeof reg.create !== 'function') {
91
+ out.push('');
92
+ out.push('注册未完成:workspaceRegistry 不可用。请将下列路径粘贴到 DSH 的新建工作区窗口:');
93
+ out.push(' ' + r.root);
94
+ return { text: out.join(NL) };
95
+ }
96
+ const title = String(args.title || '').trim() || '全局';
97
+ try {
98
+ const w = await reg.create(r.root, title);
99
+ registered = w;
100
+ out.push('');
101
+ out.push('已注册工作区:' + title + '(' + String(w && w.id ? w.id : '?') + ')');
102
+ } catch (e) {
103
+ out.push('');
104
+ out.push('注册失败:' + String(e && e.message ? e.message : e));
105
+ out.push('可将下列路径粘贴到 DSH 的新建工作区窗口:');
106
+ out.push(' ' + r.root);
107
+ return { text: out.join(NL) };
108
+ }
109
+ }
110
+ out.push('');
111
+ out.push('下一步:');
112
+ if (!registered) {
113
+ out.push(' 1. 注册为工作区:ho_workspace(action="create", path="' + r.root + '", title="全局"),或将该路径粘贴到 DSH 的新建工作区窗口;');
114
+ out.push(' 2. 新建一个标题含「全局」的会话,闸门、交接体检与会话水位即接入。');
115
+ } else {
116
+ out.push(' 新建一个标题含「全局」的会话,闸门、交接体检与会话水位即接入。');
117
+ }
118
+ out.push('(也可在设置页的 dsh-handover 中完成「首次安装」,效果相同。)');
119
+ return { text: out.join(NL) };
120
+ }
121
+
122
+ if (action === 'scaffold') {
123
+ const root = currentRoot(env);
124
+ if (!root) throw new Error('未找到全局根(工作区未接入)');
125
+ const r = ensureScaffold(root);
126
+ if (!r.ok) {
127
+ out.push('未补齐:' + r.error);
128
+ out.push('根目录:' + root);
129
+ return { text: out.join(NL) };
130
+ }
131
+ out.push('已核对文件体系:' + root);
132
+ out.push(' 新建目录 ' + r.created.length + ' 个:' + (r.created.length ? r.created.join('、') : '(无缺失)'));
133
+ out.push(' 新建文件 ' + r.written.length + ' 个:' + (r.written.length ? r.written.join('、') : '(无缺失)'));
134
+ out.push(' 已存在未覆盖 ' + r.skipped.length + ' 个' + (r.skipped.length ? ':' + r.skipped.join('、') : ''));
135
+ if (r.missingTemplates && r.missingTemplates.length) {
136
+ out.push(' 内置模板缺失 ' + r.missingTemplates.length + ' 个(未写空文件):' + r.missingTemplates.join('、'));
137
+ }
138
+ out.push('只新建,不覆盖、不删除:已有文件的内容未作任何改动。');
139
+ const legacy = legacyInfo(root);
140
+ if (legacy.exists) out.push('提示:检测到旧文档定位(' + legacy.dir + '),执行 ho_workspace(action="migrate") 并入体系注册。');
141
+ return { text: out.join(NL) };
142
+ }
143
+
144
+ if (action === 'migrate') {
145
+ const root = currentRoot(env);
146
+ if (!root) throw new Error('未找到全局根(工作区未接入)');
147
+ const pre = legacyInfo(root);
148
+ if (!pre.exists) {
149
+ out.push('没有旧文档定位:' + pre.dir);
150
+ out.push('判定:无需迁移。');
151
+ return { text: out.join(NL) };
152
+ }
153
+ const r = migrateLegacyDocmaps(root);
154
+ out.push('旧文档定位迁移:' + (r.ok ? '完成' : '部分完成'));
155
+ out.push(' 已并入体系注册 ' + r.converted.length + ' 份' + (r.converted.length ? ':' + r.converted.join('、') : ''));
156
+ out.push(' 全局旧内容保留为工作区文档清单 ' + r.kept.length + ' 份' + (r.kept.length ? ':' + r.kept.join('、') : ''));
157
+ if (r.archived) out.push(' 旧目录已归档(未删除):' + r.archived);
158
+ for (const p of r.problems || []) out.push(' 未迁移:' + p);
159
+ try {
160
+ const g = syncGlobalRefs(root);
161
+ out.push(' 全局总注册已重建:' + g.rows.length + ' 个任务');
162
+ } catch (e) {
163
+ out.push(' 全局总注册重建失败:' + String(e && e.message ? e.message : e));
164
+ }
165
+ return { text: out.join(NL) };
166
+ }
167
+
64
168
  if (action === 'list') {
65
169
  if (reg && typeof reg.list === 'function') {
66
170
  let list = [];
@@ -75,7 +179,7 @@ export function pmWorkspaceTool(env) {
75
179
  }
76
180
  }
77
181
  const rows = workspaceRows();
78
- out.push('工作区(退化:读 workspace.json,' + rows.length + ' 个)');
182
+ out.push('工作区(退化读取 workspace.json,' + rows.length + ' 个)');
79
183
  for (const r of rows) out.push(' ' + r.id + ' ' + r.title + ' ' + r.path + ' 会话 ' + r.sessions);
80
184
  return { text: out.join(NL) };
81
185
  }
@@ -89,13 +193,13 @@ export function pmWorkspaceTool(env) {
89
193
  if ((!st || !st.isDirectory()) && args.ensure === true) {
90
194
  try { fs.mkdirSync(p, { recursive: true }); st = fs.statSync(p); } catch (e) { throw new Error('建目录失败:' + p + '(' + String(e && e.message ? e.message : e) + ')'); }
91
195
  }
92
- if (!st || !st.isDirectory()) throw new Error('目录不存在或不是目录:' + p + '(默认不替你建目录;要建请传 ensure=true,或先手工建好)');
196
+ if (!st || !st.isDirectory()) throw new Error('目录不存在或不是目录:' + p + '(缺省不代建目录;需要创建时传 ensure=true,或先行手工建好)');
93
197
  const title = String(args.title || '').trim() || p.split(/[\\/]/).filter(Boolean).pop() || p;
94
198
  let w = null;
95
199
  try { w = await reg.create(p, title); } catch (e) { throw new Error('建工作区失败:' + String(e && e.message ? e.message : e)); }
96
200
  out.push('已注册工作区 ' + String(w && w.id ? w.id : '?') + ' ' + String(w && w.title ? w.title : title));
97
201
  out.push('路径 ' + String(w && w.path ? w.path : p));
98
- out.push('同一路径重复调用返回原记录、不改标题;新登记的工作区排在列表最前。');
202
+ out.push('同一路径重复调用返回原记录,不改标题;新登记的工作区排在列表最前。');
99
203
  return { text: out.join(NL) };
100
204
  }
101
205
 
@@ -105,13 +209,13 @@ export function pmWorkspaceTool(env) {
105
209
  if (!key) throw new Error('delete 需要 workspace(id、标题、路径)');
106
210
  let list = [];
107
211
  try { list = reg.list() || []; } catch { list = []; }
108
- const norm = (s) => String(s || '').replace(/\\/g, '/').replace(/\/+$/, '').toLowerCase();
212
+ const norm = (s) => pathKey(String(s || ''));
109
213
  const hit = list.find((w) => String(w.id) === key || w.title === key || norm(w.path) === norm(key));
110
214
  if (!hit) throw new Error('找不到工作区:' + key);
111
215
  if (String(hit.title || '').indexOf('全局') !== -1) throw new Error(BAD);
112
216
  const ok = await reg.delete(hit.id);
113
217
  out.push((ok ? '已删除工作区注册 ' : '该工作区不存在(无操作)') + String(hit.id) + ' ' + String(hit.title || '-'));
114
- out.push('注意:只删注册;目录与全部会话日志都保留在原处。');
218
+ out.push('注意:只删注册;目录与全部会话日志均保留在原处。');
115
219
  return { text: out.join(NL) };
116
220
  }
117
221
 
@@ -128,11 +232,11 @@ export function pmWorkspaceTool(env) {
128
232
  if (!p.ok) return { text: renderProbe(p) + NL + '未开始复制。' };
129
233
  const r = await startMove({ src, to });
130
234
  if (!r.ok) {
131
- out.push('复制完成但**校验有差异**,未生成切换脚本。');
235
+ out.push('复制完成,但校验存在差异,未生成切换脚本。');
132
236
  out.push(' 文件 源 ' + r.report.files + ' / 目标 ' + r.report.copies);
133
237
  out.push(' 缺失 ' + r.report.missingCount + ' 条' + (r.report.missing.length ? ':' + r.report.missing.join('、') : ''));
134
238
  out.push(' 多出 ' + r.report.extraCount + ' 条' + (r.report.extra.length ? ':' + r.report.extra.join('、') : ''));
135
- out.push('处置:清空目标目录后重跑,或先停掉在写文件的会话。');
239
+ out.push('处置:清空目标目录后重跑,或先停止正在写入文件的会话。');
136
240
  return { text: out.join(NL) };
137
241
  }
138
242
  out.push('复制并校验通过:' + r.report.files + ' 文件 · ' + gb(r.report.bytes));
@@ -141,44 +245,44 @@ export function pmWorkspaceTool(env) {
141
245
  out.push(' 切换助手 ' + r.marker.script.helper);
142
246
  out.push(' 手工启动器(Windows、POSIX) ' + r.marker.script.cmd + ' 、 ' + r.marker.script.sh);
143
247
  out.push('');
144
- out.push('下一步(三条,顺序不能换):');
248
+ out.push('下一步(3 步,顺序不可调换):');
145
249
  out.push(' ① 完全退出 DSH(源目录被占用,改名会失败)');
146
- out.push(' ② 执行 ho_workspace(action="move-switch") 派后台助手(推荐);助手会等待 DSH 退出后自动完成');
147
- out.push(' ③ 重新启动 DSH;确认无误后在设置页点「删除旧副本」');
250
+ out.push(' ② 执行 ho_workspace(action="move-switch") 派出后台助手(推荐用法);该助手会在 DSH 退出后自动完成');
251
+ out.push(' ③ 重新启动 DSH;确认无误后可在设置页删除旧备份');
148
252
  return { text: out.join(NL) };
149
253
  }
150
254
 
151
255
  if (action === 'move-switch') {
152
256
  const r = stageSwitch({ relaunch: args.relaunch === true });
153
- if (r.already) { out.push('已经是切换态(旧路径就是 junction),无需再切。'); return { text: out.join(NL) }; }
257
+ if (r.already) { out.push('已处于切换态(原路径即链接),无需再次切换。'); return { text: out.join(NL) }; }
154
258
  out.push('已派出后台切换助手(pid ' + r.pid + ')。');
155
- out.push(' 它会在你**完全退出 DSH** 后自动完成:旧目录改名、原位建 junction、校验指向' + (r.relaunch ? ',然后重新启动 DSH' : '') + '。');
259
+ out.push(' 该助手会在完全退出 DSH 后自动完成:旧目录改名、原位建立链接、校验指向' + (r.relaunch ? ',然后重新启动 DSH' : '') + '。');
156
260
  out.push(' 日志:' + r.log);
157
- out.push(' 提醒:从现在起不要改这个工作区里的文件(改动只会落在还活着的旧目录上)。');
261
+ out.push(' 提醒:此后不得修改该工作区内的文件(改动只会落在仍在使用的旧目录上)。');
158
262
  return { text: out.join(NL) };
159
263
  }
160
264
 
161
265
  if (action === 'move-status') {
162
266
  const st = moveStatus(); // 以标记里的源路径为准:修改登记后当前根已是新位置,拿它比对会误报
163
- const label = { none: '没有搬迁记录', verified: '已复制校验通过,等待切换', switched: '已切换(旧路径是 junction)', partial: '复制有差异,未生成脚本' };
164
- out.push('搬迁状态:' + (label[st.phase] || st.phase));
165
- out.push(' 源 ' + st.src + (st.linkTarget ? '(→ ' + st.linkTarget + ')' : ''));
267
+ const label = { none: '没有迁移记录', verified: '已复制并校验通过,待切换', switched: '已切换(原路径为链接)', partial: '复制有差异,未生成脚本' };
268
+ out.push('迁移状态:' + (label[st.phase] || st.phase));
269
+ out.push(' 源 ' + st.src + (st.linkTarget ? '(当前为链接,指向 ' + st.linkTarget + ')' : ''));
166
270
  if (st.dst) out.push(' 目标 ' + st.dst);
167
271
  if (st.marker && st.marker.files) out.push(' 清单 ' + st.marker.files + ' 文件 · ' + (st.marker.digest || '').slice(0, 16) + '…');
168
272
  if (st.retarget) {
169
273
  if (st.retarget.needed) {
170
- out.push(' DSH 登记仍指着旧路径:' + st.retarget.total + ' 处');
274
+ out.push(' DSH 登记仍指向旧路径:' + st.retarget.total + ' 处');
171
275
  for (const f of st.retarget.files) out.push(' ' + f.path + ' ×' + f.count);
172
- out.push(' 修改登记:ho_workspace(action="move-retarget")(退出 DSH 后由助手改写,先备份)');
276
+ out.push(' 修改登记:ho_workspace(action="move-retarget")(退出 DSH 后由助手改写,改写前先备份)');
173
277
  } else {
174
278
  out.push(' DSH 登记已在新位置(无需修改登记)');
175
279
  }
176
280
  }
177
281
  if (st.olds && st.olds.length) {
178
- for (const o of st.olds) out.push(' 旧副本 ' + o.path + ' ' + gb(o.bytes));
282
+ for (const o of st.olds) out.push(' 旧备份 ' + o.path + ' ' + gb(o.bytes));
179
283
  out.push(' 删除:ho_workspace(action="move-cleanup", confirm=true)');
180
284
  } else if (st.phase === 'switched') {
181
- out.push(' 没有旧副本残留');
285
+ out.push(' 无旧备份');
182
286
  }
183
287
  return { text: out.join(NL) };
184
288
  }
@@ -186,32 +290,32 @@ export function pmWorkspaceTool(env) {
186
290
  if (action === 'move-retarget') {
187
291
  const r = stageRetarget();
188
292
  if (r.already) { out.push('DSH 登记已在新位置,无需修改登记。'); return { text: out.join(NL) }; }
189
- if (!r.ok) { out.push('派助手失败:' + r.error); out.push(' 手工执行:' + (r.script && r.script.helper)); return { text: out.join(NL) }; }
190
- out.push('已派出登记修改登记助手(pid ' + r.pid + ')。');
191
- out.push(' 它会在你完全退出 DSH 后改写这些登记(每份先备份):');
293
+ if (!r.ok) { out.push('派出助手失败:' + r.error); out.push(' 手工执行:' + (r.script && r.script.helper)); return { text: out.join(NL) }; }
294
+ out.push('已派出修改登记的助手(pid ' + r.pid + ')。');
295
+ out.push(' 该助手会在完全退出 DSH 后改写下列登记(每份先备份):');
192
296
  for (const f of (r.plan.files || [])) if (f.count) out.push(' ' + f.path + ' ×' + f.count);
193
- out.push(' 只改登记文本,不动任何数据文件;日志:' + (r.script && r.script.log));
297
+ out.push(' 只改登记文本,不改动任何数据文件;日志:' + (r.script && r.script.log));
194
298
  return { text: out.join(NL) };
195
299
  }
196
300
 
197
301
  if (action === 'move-repair') {
198
302
  const r = repairLink();
199
303
  if (r.already) { out.push('原路径已是指向新根的链接,无需重建。'); return { text: out.join(NL) }; }
200
- out.push('已重建链接:' + r.src + ' → ' + r.dst + '(' + r.kind + ')');
201
- out.push(' 说明:只建链接,未改动任何文件。');
304
+ out.push('已重建链接:' + r.src + ' 指向 ' + r.dst + '(' + r.kind + ')');
305
+ out.push(' 说明:仅建立链接,未改动任何文件。');
202
306
  return { text: out.join(NL) };
203
307
  }
204
308
 
205
309
  if (action === 'move-cleanup') {
206
- if (args.confirm !== true) throw new Error('move-cleanup 需要 confirm=true(会真删旧副本目录)');
310
+ if (args.confirm !== true) throw new Error('move-cleanup 需要 confirm=true(将实际删除旧备份目录)');
207
311
  const r = await cleanupOld({ confirm: true });
208
312
  out.push(r.note);
209
- for (const p of r.removed) out.push(' 已删 ' + p);
313
+ for (const p of r.removed) out.push(' 已删除 ' + p);
210
314
  if (r.bytes) out.push(' 释放 ' + gb(r.bytes));
211
315
  return { text: out.join(NL) };
212
316
  }
213
317
 
214
- throw new Error('action 只能是 list / create / delete / move-probe / move / move-switch / move-status / move-retarget / move-repair / move-cleanup');
318
+ throw new Error('action 只能是 init / scaffold / migrate / list / create / delete / move-probe / move / move-switch / move-status / move-retarget / move-repair / move-cleanup');
215
319
  },
216
320
  };
217
321
  }