ygc-dsh-sync 1.3.2 → 1.3.4
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/client.js +14 -0
- package/package.json +1 -1
- package/tools/_health.mjs +98 -17
- package/tools/skill-lineage.mjs +123 -205
package/client.js
CHANGED
|
@@ -151,6 +151,20 @@ function makePanel(React) {
|
|
|
151
151
|
}
|
|
152
152
|
}, []);
|
|
153
153
|
|
|
154
|
+
// `refresh` 是面板里多处 `await refresh()` 用到的名字。
|
|
155
|
+
// 1.2.x 一直没有定义过它(那些调用全落在 ReferenceError 上,被 catch 吞成
|
|
156
|
+
// 「操作失败」),本机在改这一栏时才发现。这里给它一个明确的实现:
|
|
157
|
+
// 重新拉一次 /state,顺带把待批草稿数与归属摘要刷一遍(都是轻量请求)。
|
|
158
|
+
const refresh = useCallback(async () => {
|
|
159
|
+
await load();
|
|
160
|
+
try {
|
|
161
|
+
const lr = await call('/skills/learning/list', {});
|
|
162
|
+
if (mounted.current) setLearningList(lr.items || []);
|
|
163
|
+
} catch {
|
|
164
|
+
/* 待批草稿读不到不影响主状态 */
|
|
165
|
+
}
|
|
166
|
+
}, [load]);
|
|
167
|
+
|
|
154
168
|
useEffect(() => {
|
|
155
169
|
mounted.current = true;
|
|
156
170
|
load();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ygc-dsh-sync",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "DSH 同步插件:会话按工作区落地、按机器隔离的跨机备份与拉取;技能进特征文件夹(整棵备份 + 说明 + 来源 + 更新时间),跨机只读拉取、同名不覆盖改走学习评估;本机技能优化(精炼 / 精简 / 合并重复 / 补工况 / 补踩坑 / 修死引用);技能市场装的第三方技能不管理、不同步、不优化。母版功能已取消。",
|
|
6
6
|
"icon": "icon.svg",
|
package/tools/_health.mjs
CHANGED
|
@@ -1,24 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 技能健康检查(1.3.x):母版取消后,这份脚本只回答「本机这一侧现在怎么样」。
|
|
3
|
+
*
|
|
4
|
+
* 原来它拿母版跟本机做 planSync 对比。母版功能已取消,那套对比没有意义了,
|
|
5
|
+
* 换成三件在 1.3.x 里真正会发生的事:
|
|
6
|
+
* 1. 归属:哪些技能归本插件管、哪些是市场第三方(不同步、不优化)
|
|
7
|
+
* 2. 体检:自研技能的机械问题分布
|
|
8
|
+
* 3. 特征本:自己的技能备份是否已经写出去、有多少、多大
|
|
9
|
+
*
|
|
10
|
+
* 用法:node tools/_health.mjs [--json]
|
|
11
|
+
*/
|
|
1
12
|
import fs from 'node:fs';
|
|
2
13
|
import path from 'node:path';
|
|
3
14
|
import os from 'node:os';
|
|
4
|
-
import { planSync } from '../lib/skills.js';
|
|
5
15
|
|
|
6
|
-
const
|
|
7
|
-
const
|
|
16
|
+
const { ownNames, scopeSnapshot } = await import('../lib/skill-scope.js');
|
|
17
|
+
const { inspectAll } = await import('../lib/skill-optimize.js');
|
|
18
|
+
const { mySkillsRoot, SKILLS_INDEX } = await import('../lib/skillshare.js');
|
|
8
19
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
const LOCAL = process.env.DSH_SYNC_SKILLS_ROOT
|
|
21
|
+
? path.resolve(process.env.DSH_SYNC_SKILLS_ROOT)
|
|
22
|
+
: path.join(os.homedir(), '.agents', 'skills');
|
|
23
|
+
|
|
24
|
+
const STATE = process.env.DSH_SYNC_STATE_FILE
|
|
25
|
+
? path.resolve(process.env.DSH_SYNC_STATE_FILE)
|
|
26
|
+
: path.join(os.homedir(), '.dsh', 'storages', 'sync-backup.json');
|
|
27
|
+
|
|
28
|
+
let state = {};
|
|
29
|
+
try {
|
|
30
|
+
state = JSON.parse(fs.readFileSync(STATE, 'utf8'));
|
|
31
|
+
} catch {
|
|
32
|
+
state = {};
|
|
33
|
+
}
|
|
34
|
+
const machineId = state.machineId || '';
|
|
35
|
+
const libraryRoot = (state.plugins && state.plugins.libraryRoot) || 'D:\\Ai工作空间\\插件库';
|
|
36
|
+
const config = { libraryRoot };
|
|
37
|
+
|
|
38
|
+
const wantJson = process.argv.includes('--json');
|
|
39
|
+
const out = {};
|
|
40
|
+
|
|
41
|
+
// ── 1. 归属
|
|
42
|
+
const scope = scopeSnapshot({ localRoot: LOCAL, overrides: (state.skills && state.skills.share && state.skills.share.scope) || {} });
|
|
43
|
+
out.scope = scope;
|
|
44
|
+
const own = ownNames({ localRoot: LOCAL, overrides: (state.skills && state.skills.share && state.skills.share.scope) || {} });
|
|
45
|
+
|
|
46
|
+
// ── 2. 体检
|
|
47
|
+
const insp = inspectAll({ localRoot: LOCAL, ownSet: own.names });
|
|
48
|
+
out.inspect = {
|
|
49
|
+
scanned: insp.scanned,
|
|
50
|
+
healthy: insp.healthy,
|
|
51
|
+
thirdPartySkipped: insp.thirdParty,
|
|
52
|
+
issues: insp.items.length,
|
|
53
|
+
top: insp.items.slice(0, 10).map((x) => ({ skill: x.name, score: x.score, action: x.action, codes: x.issues.map((i) => i.code).join(',') })),
|
|
54
|
+
codes: insp.codes,
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ── 3. 特征本
|
|
58
|
+
const root = machineId ? mySkillsRoot(config, machineId) : '';
|
|
59
|
+
const indexFile = root ? path.join(root, SKILLS_INDEX) : '';
|
|
60
|
+
let index = null;
|
|
61
|
+
try {
|
|
62
|
+
index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
|
|
63
|
+
} catch {
|
|
64
|
+
index = null;
|
|
15
65
|
}
|
|
66
|
+
out.feature = {
|
|
67
|
+
machineId,
|
|
68
|
+
root,
|
|
69
|
+
exists: Boolean(root && fs.existsSync(root)),
|
|
70
|
+
indexed: index ? index.counts : null,
|
|
71
|
+
generatedAt: index ? index.generatedAt : '',
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
if (wantJson) {
|
|
75
|
+
console.log(JSON.stringify(out, null, 2));
|
|
76
|
+
} else {
|
|
77
|
+
console.log('== 归属 ==');
|
|
78
|
+
console.log(` 本机技能根: ${LOCAL}`);
|
|
79
|
+
console.log(` 自研 ${scope.counts.own} · 第三方 ${scope.counts.thirdParty} · 待确认 ${scope.counts.weak}`);
|
|
80
|
+
console.log(` 证据: 市场台账 ${scope.evidence.ledger.count} 条(${scope.evidence.ledger.ok ? '读到' : '读不到'})`);
|
|
81
|
+
if (scope.thirdParty.length) {
|
|
82
|
+
console.log(` 前 8 个第三方: ${scope.thirdParty.slice(0, 8).map((x) => x.name).join(', ')}`);
|
|
83
|
+
}
|
|
16
84
|
|
|
17
|
-
|
|
18
|
-
console.log('
|
|
19
|
-
console.log(
|
|
20
|
-
|
|
21
|
-
console.log(
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
console.log('
|
|
85
|
+
console.log('');
|
|
86
|
+
console.log('== 体检 ==');
|
|
87
|
+
console.log(` 扫到 ${insp.scanned} 个(健康 ${insp.healthy},第三方跳过 ${insp.thirdParty}),有问题 ${insp.items.length} 个`);
|
|
88
|
+
const codeList = Object.entries(insp.codes).sort((a, b) => b[1] - a[1]);
|
|
89
|
+
if (codeList.length) console.log(` 问题分布: ${codeList.map(([c, n]) => `${c}×${n}`).join(' ')}`);
|
|
90
|
+
for (const x of out.inspect.top) console.log(` - ${x.skill}(分 ${x.score},建议 ${x.action})`);
|
|
91
|
+
|
|
92
|
+
console.log('');
|
|
93
|
+
console.log('== 特征本 ==');
|
|
94
|
+
if (!machineId) {
|
|
95
|
+
console.log(' 本机标识还没生成,跳过');
|
|
96
|
+
} else {
|
|
97
|
+
console.log(` 目录: ${root}`);
|
|
98
|
+
console.log(` 存在: ${out.feature.exists ? '是' : '否(下次同步会建)'}`);
|
|
99
|
+
if (index) {
|
|
100
|
+
console.log(` 索引: ${index.counts.total} 个技能 · ${(index.counts.bytes / 1048576).toFixed(1)} MB · ${index.generatedAt}`);
|
|
101
|
+
} else {
|
|
102
|
+
console.log(' 索引: 还没写(下次同步会写)');
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
package/tools/skill-lineage.mjs
CHANGED
|
@@ -1,243 +1,161 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* 技能谱系维护工具(1.3.x 重写)。
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* dsh-mnemon 采纳演化草稿时直接改 SKILL.md,**不动 version**。同步插件原来
|
|
6
|
-
* 只看 version,两边都写 1.0.0 但内容不同就一律卡在「待确认」。
|
|
4
|
+
* ## 它现在做什么
|
|
7
5
|
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* 变的那侧就是最新的。
|
|
6
|
+
* 母版取消后,技能的「谁更新」不再是「母版 vs 本机」两方问题,而是
|
|
7
|
+
* 「我这格 vs 别人那格」多方问题。判据随之简化成一条:**内容指纹**。
|
|
8
|
+
* 时间戳只作展示(NAS 会刷 mtime),这一点在 1.2.x 已经踩过。
|
|
12
9
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
* node tools/skill-lineage.mjs --dry 只比对不同步
|
|
17
|
-
* node tools/skill-lineage.mjs --init 把两侧一致的登记为基线
|
|
18
|
-
* node tools/skill-lineage.mjs --init --force 冲突项也按「母版为准」登记
|
|
19
|
-
* node tools/skill-lineage.mjs --reset 清空谱系(自动备份旧文件)
|
|
10
|
+
* 这个工具只保留两件在 1.3.x 里还有意义的事:
|
|
11
|
+
* 1. `--status` 打印本机 vs 各台机器的技能分布(只读)
|
|
12
|
+
* 2. `--backfill` 给缺 version 的自研技能补一个(原 add-version 的能力)
|
|
20
13
|
*
|
|
21
|
-
*
|
|
14
|
+
* 原来的 `--init/--force/--reset`(母版谱系基线)随母版一并取消:
|
|
15
|
+
* `.skill-lineage.json` 不再被任何判定读取,留着只会让人以为它还有用。
|
|
16
|
+
*
|
|
17
|
+
* ## 用法
|
|
18
|
+
*
|
|
19
|
+
* ```bash
|
|
20
|
+
* node tools/skill-lineage.mjs --status
|
|
21
|
+
* node tools/skill-lineage.mjs --backfill --dry
|
|
22
|
+
* node tools/skill-lineage.mjs --backfill
|
|
23
|
+
* ```
|
|
22
24
|
*/
|
|
23
25
|
import fs from 'node:fs';
|
|
24
26
|
import os from 'node:os';
|
|
25
27
|
import path from 'node:path';
|
|
26
|
-
import {
|
|
27
|
-
LINEAGE_FILE,
|
|
28
|
-
LOCAL_SKILLS_ROOT,
|
|
29
|
-
applyPull,
|
|
30
|
-
applyPush,
|
|
31
|
-
buildHistoryIndex,
|
|
32
|
-
loadLineage,
|
|
33
|
-
makeStamp,
|
|
34
|
-
planSync,
|
|
35
|
-
readMnemonLedger,
|
|
36
|
-
recordLineage,
|
|
37
|
-
saveLineage,
|
|
38
|
-
judgeUpdate,
|
|
39
|
-
scanSkills,
|
|
40
|
-
} from '../lib/skills.js';
|
|
41
|
-
import { readChangelogTimes } from '../lib/changelog.js';
|
|
42
28
|
|
|
43
|
-
const
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const has = (f) => args.includes(f);
|
|
31
|
+
|
|
32
|
+
const STATE = process.env.DSH_SYNC_STATE_FILE
|
|
44
33
|
? path.resolve(process.env.DSH_SYNC_STATE_FILE)
|
|
45
34
|
: path.join(os.homedir(), '.dsh', 'storages', 'sync-backup.json');
|
|
35
|
+
const LOCAL = process.env.DSH_SYNC_SKILLS_ROOT
|
|
36
|
+
? path.resolve(process.env.DSH_SYNC_SKILLS_ROOT)
|
|
37
|
+
: path.join(os.homedir(), '.agents', 'skills');
|
|
38
|
+
|
|
39
|
+
let state = {};
|
|
40
|
+
try {
|
|
41
|
+
state = JSON.parse(fs.readFileSync(STATE, 'utf8'));
|
|
42
|
+
} catch {
|
|
43
|
+
state = {};
|
|
44
|
+
}
|
|
45
|
+
const machineId = state.machineId || '';
|
|
46
|
+
const libraryRoot = (state.plugins && state.plugins.libraryRoot) || 'D:\\Ai工作空间\\插件库';
|
|
47
|
+
const overrides = (state.skills && state.skills.share && state.skills.share.scope) || {};
|
|
48
|
+
const config = { libraryRoot };
|
|
46
49
|
|
|
47
|
-
|
|
48
|
-
const has = (flag) => args.includes(flag);
|
|
49
|
-
const valOf = (flag) => {
|
|
50
|
-
const i = args.indexOf(flag);
|
|
51
|
-
return i >= 0 && args[i + 1] ? args[i + 1] : '';
|
|
52
|
-
};
|
|
50
|
+
/* ------------------------------------------------------------ --status */
|
|
53
51
|
|
|
54
|
-
function
|
|
55
|
-
const
|
|
56
|
-
|
|
52
|
+
async function status() {
|
|
53
|
+
const { ownNames } = await import('../lib/skill-scope.js');
|
|
54
|
+
const { collectSharedSkills, mySkillsRoot, SKILLS_INDEX } = await import('../lib/skillshare.js');
|
|
55
|
+
|
|
56
|
+
const own = ownNames({ localRoot: LOCAL, overrides });
|
|
57
|
+
const collected = collectSharedSkills({ config, machineId, localRoot: LOCAL, only: own.names });
|
|
58
|
+
const root = machineId ? mySkillsRoot(config, machineId) : '';
|
|
59
|
+
let index = null;
|
|
57
60
|
try {
|
|
58
|
-
|
|
59
|
-
return (s.skills && s.skills.masterRoot) || '';
|
|
61
|
+
index = JSON.parse(fs.readFileSync(path.join(root, SKILLS_INDEX), 'utf8'));
|
|
60
62
|
} catch {
|
|
61
|
-
|
|
63
|
+
index = null;
|
|
62
64
|
}
|
|
63
|
-
}
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
console.
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
console.error(`母版路径不存在:${master}`);
|
|
72
|
-
process.exit(2);
|
|
73
|
-
}
|
|
66
|
+
console.log('== 本机 ==');
|
|
67
|
+
console.log(` 技能根 ${LOCAL}`);
|
|
68
|
+
console.log(` 特征格 ${root || '(标识未生成)'}`);
|
|
69
|
+
console.log(` 自研 ${own.names.size} 个`);
|
|
70
|
+
console.log(` 第三方 ${own.thirdParty.length} 个(不同步、不优化)`);
|
|
71
|
+
console.log(` 索引 ${index ? `${index.counts.total} 个 · ${(index.counts.bytes / 1048576).toFixed(1)} MB` : '还没写'}`);
|
|
74
72
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const lineage = loadLineage(lineagePath);
|
|
83
|
-
const ledger = readMnemonLedger();
|
|
84
|
-
const history = buildHistoryIndex({ masterRoot: master, localRoot, changelogTimes: readChangelogTimes(master) });
|
|
85
|
-
const m = scanSkills(master);
|
|
86
|
-
const l = scanSkills(localRoot);
|
|
87
|
-
const names = [...new Set([...m.keys(), ...l.keys()])].sort();
|
|
88
|
-
|
|
89
|
-
const rows = [];
|
|
90
|
-
for (const name of names) {
|
|
91
|
-
const mm = m.get(name);
|
|
92
|
-
const ll = l.get(name);
|
|
93
|
-
if (!mm || !ll) continue;
|
|
94
|
-
if (mm.treeHash === ll.treeHash) {
|
|
95
|
-
rows.push({ name, verdict: 'same', basis: '-', hint: '两侧内容相同' });
|
|
96
|
-
continue;
|
|
73
|
+
console.log('');
|
|
74
|
+
console.log('== 各台机器 ==');
|
|
75
|
+
if (!collected.sources.length) {
|
|
76
|
+
console.log(' (插件库里还没有任何技能层)');
|
|
77
|
+
} else {
|
|
78
|
+
for (const s of collected.sources) {
|
|
79
|
+
console.log(` ${s.feature}${s.mine ? '(本机)' : ''} ${s.total} 个 · ${s.generatedAt || '-'}`);
|
|
97
80
|
}
|
|
98
|
-
const j = judgeUpdate({ name, master: mm, local: ll, record: lineage[name], ledger, history });
|
|
99
|
-
rows.push({ name, verdict: j.verdict, basis: j.basis || '-', hint: j.detail });
|
|
100
81
|
}
|
|
101
82
|
|
|
102
|
-
const byVerdict = rows.reduce((acc, r) => {
|
|
103
|
-
acc[r.verdict] = (acc[r.verdict] || 0) + 1;
|
|
104
|
-
return acc;
|
|
105
|
-
}, {});
|
|
106
|
-
|
|
107
|
-
console.log(`母版:${master}`);
|
|
108
|
-
console.log(`本机:${localRoot}`);
|
|
109
|
-
console.log(`谱系:${fs.existsSync(lineagePath) ? `${Object.keys(lineage).length} 条观测` : '还没建'}`);
|
|
110
|
-
console.log(`账本:${ledger.size} 个技能有记忆插件记录`);
|
|
111
|
-
console.log('');
|
|
112
|
-
console.log(
|
|
113
|
-
Object.entries(byVerdict)
|
|
114
|
-
.map(([k, v]) => `${VERDICT_LABEL[k] || k} ${v}`)
|
|
115
|
-
.join(' · ') || '没有可比技能',
|
|
116
|
-
);
|
|
117
83
|
console.log('');
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
84
|
+
console.log('== 与本机的关系 ==');
|
|
85
|
+
console.log(` 一致 ${collected.same.length}`);
|
|
86
|
+
console.log(` 本机缺 ${collected.missing.length}${collected.missing.length ? `:${collected.missing.slice(0, 8).map((x) => x.name).join(', ')}` : ''}`);
|
|
87
|
+
console.log(` 内容不同 ${collected.differ.length}${collected.differ.length ? `:${collected.differ.slice(0, 8).map((x) => x.name).join(', ')}` : ''}`);
|
|
88
|
+
console.log(` 他人第三方 ${collected.thirdParty.length}(本机不拉)`);
|
|
89
|
+
if (collected.warnings.length) {
|
|
90
|
+
console.log('');
|
|
91
|
+
console.log('提示:');
|
|
92
|
+
for (const w of collected.warnings.slice(0, 6)) console.log(` - ${w}`);
|
|
121
93
|
}
|
|
122
|
-
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log('下一步:面板「优化本机技能」出草稿;「立即共享」把本机备份出去并拉缺失。');
|
|
123
96
|
}
|
|
124
97
|
|
|
125
|
-
/*
|
|
98
|
+
/* ---------------------------------------------------------- --backfill */
|
|
126
99
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
100
|
+
/**
|
|
101
|
+
* 给缺 version 的技能补一个 `version: 1.0.0`。
|
|
102
|
+
*
|
|
103
|
+
* 只改 front matter,保留原编码(UTF-8 BOM 原样)与换行风格;
|
|
104
|
+
* 已有 version 的一律跳过,不覆盖别人的版本号。
|
|
105
|
+
*/
|
|
106
|
+
function backfill({ dryRun }) {
|
|
107
|
+
const IGNORED = /^(__pycache__|node_modules|\.git)$/;
|
|
108
|
+
let entries = [];
|
|
109
|
+
try {
|
|
110
|
+
entries = fs.readdirSync(LOCAL, { withFileTypes: true });
|
|
111
|
+
} catch {
|
|
112
|
+
console.error(`读不到技能根:${LOCAL}`);
|
|
113
|
+
process.exit(2);
|
|
134
114
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
const m = scanSkills(master);
|
|
146
|
-
const l = scanSkills(localRoot);
|
|
147
|
-
const lineage = loadLineage(lineagePath);
|
|
148
|
-
|
|
149
|
-
const registered = [];
|
|
150
|
-
const skipped = [];
|
|
151
|
-
|
|
152
|
-
for (const name of [...m.keys()].sort()) {
|
|
153
|
-
if (only.length && !only.includes(name)) continue;
|
|
154
|
-
const mm = m.get(name);
|
|
155
|
-
const ll = l.get(name);
|
|
156
|
-
if (!ll) {
|
|
157
|
-
skipped.push({ name, why: '本机没有' });
|
|
115
|
+
let touched = 0;
|
|
116
|
+
let skipped = 0;
|
|
117
|
+
for (const e of entries) {
|
|
118
|
+
if (!e.isDirectory() || e.name.startsWith('.') || e.name.startsWith('_') || IGNORED.test(e.name)) continue;
|
|
119
|
+
const file = path.join(LOCAL, e.name, 'SKILL.md');
|
|
120
|
+
let raw;
|
|
121
|
+
try {
|
|
122
|
+
raw = fs.readFileSync(file, 'utf8');
|
|
123
|
+
} catch {
|
|
158
124
|
continue;
|
|
159
125
|
}
|
|
160
|
-
|
|
161
|
-
|
|
126
|
+
const bom = raw.charCodeAt(0) === 0xfeff ? '\ufeff' : '';
|
|
127
|
+
const body = bom ? raw.slice(1) : raw;
|
|
128
|
+
const m = body.match(/^---\r?\n([\s\S]*?)\r?\n---/);
|
|
129
|
+
if (!m) {
|
|
130
|
+
skipped += 1;
|
|
162
131
|
continue;
|
|
163
132
|
}
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
console.log(`谱系文件:${lineagePath}`);
|
|
180
|
-
console.log(`${ok ? '已登记' : '写入失败'} ${registered.length} 个技能`);
|
|
181
|
-
if (registered.length) console.log(` ${registered.map((r) => r.name).join(', ')}`);
|
|
182
|
-
if (skipped.length) {
|
|
183
|
-
console.log(`跳过 ${skipped.length} 个:`);
|
|
184
|
-
for (const s of skipped) console.log(` ${s.name} — ${s.why}`);
|
|
133
|
+
if (/^version\s*:/m.test(m[1])) {
|
|
134
|
+
skipped += 1;
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
const eol = /\r\n/.test(body) ? '\r\n' : '\n';
|
|
138
|
+
const nextFm = `${m[1]}${eol}version: 1.0.0`;
|
|
139
|
+
const out = `${bom}${body.slice(0, m.index)}---${eol}${nextFm}${eol}---${body.slice(m.index + m[0].length)}`;
|
|
140
|
+
if (dryRun) {
|
|
141
|
+
console.log(` would add version: ${e.name}`);
|
|
142
|
+
} else {
|
|
143
|
+
fs.writeFileSync(file, out, 'utf8');
|
|
144
|
+
console.log(` added version: ${e.name}`);
|
|
145
|
+
}
|
|
146
|
+
touched += 1;
|
|
185
147
|
}
|
|
186
|
-
|
|
148
|
+
console.log('');
|
|
149
|
+
console.log(`${dryRun ? '预演' : '完成'}:处理 ${touched} 个,跳过 ${skipped} 个(已有 version 或无 frontmatter)`);
|
|
187
150
|
}
|
|
188
151
|
|
|
189
|
-
/*
|
|
190
|
-
|
|
191
|
-
const dry = has('--dry');
|
|
192
|
-
const plan = planSync({
|
|
193
|
-
masterRoot: master,
|
|
194
|
-
localRoot,
|
|
195
|
-
modes: {},
|
|
196
|
-
lineage: loadLineage(lineagePath),
|
|
197
|
-
mnemonLedger: readMnemonLedger(),
|
|
198
|
-
history: buildHistoryIndex({ masterRoot: master, localRoot, changelogTimes: readChangelogTimes(master) }),
|
|
199
|
-
});
|
|
152
|
+
/* -------------------------------------------------------------- 入口 */
|
|
200
153
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
? { pushed: [], errors: [] }
|
|
206
|
-
: applyPush({ items: plan.autoPush, masterRoot: master, backRoot: path.join(master, '_backup'), stamp });
|
|
207
|
-
|
|
208
|
-
// 记观测 + 基线
|
|
209
|
-
{
|
|
210
|
-
let map = loadLineage(lineagePath);
|
|
211
|
-
const now = Date.now();
|
|
212
|
-
for (const [name, m] of plan.master) {
|
|
213
|
-
const l = plan.local.get(name);
|
|
214
|
-
if (!l) continue;
|
|
215
|
-
map = recordLineage(map, {
|
|
216
|
-
name,
|
|
217
|
-
...(m.treeHash === l.treeHash ? { baseline: m.treeHash } : {}),
|
|
218
|
-
master: m,
|
|
219
|
-
local: l,
|
|
220
|
-
by: `cli:${os.hostname()}`,
|
|
221
|
-
at: now,
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
const aligned = [...plan.pull.map((x) => x.master), ...plan.autoPush.map((x) => x.local), ...plan.same.map((x) => x.master)].filter(Boolean);
|
|
225
|
-
for (const x of aligned) map = recordLineage(map, { name: x.name, baseline: x.treeHash, master: x, local: x, at: now });
|
|
226
|
-
if (!dry) saveLineage(lineagePath, map);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
console.log(`母版:${master}`);
|
|
230
|
-
console.log(`本机:${localRoot}`);
|
|
231
|
-
console.log('');
|
|
232
|
-
if (dry) {
|
|
233
|
-
console.log(`[只比对] 母版新 ${plan.pull.length}、本机新 ${plan.autoPush.length}、待你决定 ${plan.push.length}、一致 ${plan.same.length}`);
|
|
154
|
+
if (has('--backfill')) {
|
|
155
|
+
backfill({ dryRun: has('--dry') });
|
|
156
|
+
} else if (has('--status') || args.length === 0) {
|
|
157
|
+
await status();
|
|
234
158
|
} else {
|
|
235
|
-
console.
|
|
236
|
-
|
|
237
|
-
);
|
|
159
|
+
console.error('未知参数。可用:--status(默认)、--backfill [--dry]');
|
|
160
|
+
process.exit(2);
|
|
238
161
|
}
|
|
239
|
-
for (const x of plan.pull) console.log(` 拉 ${x.name} [${x.basis || 'new'}] ${x.reason || ''}`);
|
|
240
|
-
for (const x of plan.autoPush) console.log(` 推 ${x.name} [${x.basis || '-'}] ${x.reason}`);
|
|
241
|
-
for (const x of plan.push) console.log(` 判 ${x.name} [${x.basis || '-'}] ${x.reason}`);
|
|
242
|
-
for (const e of [...pulled.errors, ...pushed.errors]) console.log(` !! ${e.name}: ${e.message}`);
|
|
243
|
-
process.exit(0);
|