@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.
- package/README.md +123 -92
- package/docs/migrate.md +52 -41
- package/docs/releases/0.9.12.md +2 -3
- package/docs/releases/0.9.14.md +1 -5
- package/docs/releases/0.9.22.md +34 -0
- package/docs/releases/1.0.0.md +33 -0
- package/docs/troubleshoot.md +230 -46
- package/package.json +74 -71
- package/scripts/behavior.mjs +398 -0
- package/scripts/blank-dsh.mjs +41 -25
- package/scripts/blank-smoke.mjs +278 -109
- package/scripts/check-docs.mjs +7 -0
- package/scripts/e2e-migration.mjs +2 -2
- package/scripts/office.mjs +3 -3
- package/scripts/portability.mjs +71 -0
- package/scripts/style-gate.mjs +571 -0
- package/src/client.js +1028 -955
- package/src/index.mjs +313 -142
- package/src/lib/absrefs.mjs +1 -1
- package/src/lib/api.mjs +18 -10
- package/src/lib/archive.mjs +1 -1
- package/src/lib/brief.mjs +8 -6
- package/src/lib/config.mjs +23 -8
- package/src/lib/consent.mjs +54 -1
- package/src/lib/docmap.mjs +140 -0
- package/src/lib/drift.mjs +1 -1
- package/src/lib/events.mjs +1 -1
- package/src/lib/facts.mjs +19 -12
- package/src/lib/gates.mjs +30 -6
- package/src/lib/global-task.mjs +69 -0
- package/src/lib/guide.mjs +81 -85
- package/src/lib/handover-io.mjs +69 -6
- package/src/lib/identity.mjs +330 -0
- package/src/lib/legacy.mjs +89 -0
- package/src/lib/lint.mjs +17 -13
- package/src/lib/manual.mjs +135 -59
- package/src/lib/mover.mjs +23 -19
- package/src/lib/names.mjs +14 -19
- package/src/lib/office.mjs +63 -15
- package/src/lib/pack-ops.mjs +11 -11
- package/src/lib/pathkey.mjs +24 -0
- package/src/lib/peer.mjs +4 -4
- package/src/lib/phase.mjs +8 -13
- package/src/lib/profile.mjs +1 -1
- package/src/lib/reminder.mjs +110 -0
- package/src/lib/role.mjs +9 -6
- package/src/lib/scaffold.mjs +68 -26
- package/src/lib/schedule.mjs +5 -6
- package/src/lib/session-create.mjs +3 -3
- package/src/lib/session-ops.mjs +80 -51
- package/src/lib/sessions.mjs +41 -3
- package/src/lib/sevenzip.mjs +10 -1
- package/src/lib/status.mjs +337 -176
- package/src/lib/succession.mjs +16 -16
- package/src/lib/system.mjs +364 -0
- package/src/lib/zstd-log.mjs +1 -1
- package/src/selftest.mjs +6 -6
- package/src/templates/Changelog.skeleton.md +3 -3
- package/src/templates/Charter.skeleton.md +13 -9
- package/src/templates/Handover.global.skeleton.md +20 -13
- package/src/templates/Handover.template.md +89 -0
- package/src/templates/Plan.skeleton.md +34 -0
- package/src/templates/Prompt_Templates.md +101 -20
- package/src/templates/Runtime_Manual.md +263 -122
- package/src/templates/System.skeleton.md +31 -0
- package/src/templates/Systems.global.skeleton.md +13 -0
- package/src/templates/rules/Delegation_Rules.md +45 -0
- package/src/templates/rules/Handover_Rules.md +46 -23
- package/src/templates/rules/Migration_Rules.md +7 -7
- package/src/templates/rules/Misc_Task_Standard.md +17 -10
- package/src/templates/rules/Writing_Rules.md +33 -16
- package/src/templates/standards/README.md +37 -0
- package/src/templates/standards//345/206/231/344/275/234/350/247/204/350/214/203/README.md +120 -0
- 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
- package/src/tools/ho_backup.mjs +135 -0
- package/src/tools/ho_changelog.mjs +4 -4
- package/src/tools/ho_charter.mjs +7 -6
- package/src/tools/ho_checkpoint.mjs +36 -32
- package/src/tools/ho_docmap.mjs +239 -0
- package/src/tools/ho_gate.mjs +11 -5
- package/src/tools/ho_handover.mjs +61 -35
- package/src/tools/ho_office.mjs +46 -5
- package/src/tools/ho_profile.mjs +3 -3
- package/src/tools/ho_sessions.mjs +46 -26
- package/src/tools/ho_status.mjs +251 -0
- package/src/tools/ho_succeed.mjs +3 -3
- package/src/tools/ho_workspace.mjs +146 -42
- package/src/tools/peer.mjs +416 -0
- package/src/tools/ho_brief.mjs +0 -57
- package/src/tools/ho_diag.mjs +0 -87
- package/src/tools/ho_drift.mjs +0 -41
- package/src/tools/ho_init.mjs +0 -82
- package/src/tools/ho_pack.mjs +0 -38
- package/src/tools/ho_phase.mjs +0 -37
- package/src/tools/ho_snapshot.mjs +0 -104
- package/src/tools/ho_tokens.mjs +0 -57
- package/src/tools/peer_list.mjs +0 -37
- package/src/tools/peer_new.mjs +0 -75
- package/src/tools/peer_peek.mjs +0 -41
- package/src/tools/peer_rename.mjs +0 -31
- package/src/tools/peer_send.mjs +0 -52
- package/src/tools/peer_status.mjs +0 -46
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
/** ho_workspace —— 工作区生命周期:list / create / delete
|
|
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('
|
|
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('
|
|
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
|
-
'
|
|
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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
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('
|
|
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 + '
|
|
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 || '')
|
|
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")
|
|
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('
|
|
257
|
+
if (r.already) { out.push('已处于切换态(原路径即链接),无需再次切换。'); return { text: out.join(NL) }; }
|
|
154
258
|
out.push('已派出后台切换助手(pid ' + r.pid + ')。');
|
|
155
|
-
out.push('
|
|
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: '
|
|
164
|
-
out.push('
|
|
165
|
-
out.push(' 源 ' + st.src + (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
|
|
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('
|
|
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('
|
|
190
|
-
out.push('
|
|
191
|
-
out.push('
|
|
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('
|
|
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 + '
|
|
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('
|
|
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
|
}
|