ygc-dsh-sync 1.3.3 → 1.3.5

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ygc-dsh-sync",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
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 MASTER = 'D:\\Ai工作空间\\技能库';
7
- const LOCAL = path.join(os.homedir(), '.agents', 'skills');
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
- console.log('母版路径存在:', fs.existsSync(MASTER));
10
- if (fs.existsSync(MASTER)) {
11
- const dirs = fs.readdirSync(MASTER, { withFileTypes: true }).filter((d) => d.isDirectory());
12
- console.log('母版子目录数:', dirs.length);
13
- const withSkill = dirs.filter((d) => fs.existsSync(path.join(MASTER, d.name, 'SKILL.md')));
14
- console.log('含 SKILL.md:', withSkill.length);
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
- const p = planSync({ masterRoot: MASTER, localRoot: LOCAL, modes: {} });
18
- console.log('\nplanSync 结果:');
19
- console.log(' 母版:', p.master.size);
20
- console.log(' 本地:', p.local.size);
21
- console.log(' 待拉取:', p.pull.length);
22
- console.log(' 待推送:', p.push.length, p.push.map((x) => x.name).join(', ') || '-');
23
- console.log(' 一致:', p.same.length);
24
- console.log(' 本机独有:', p.localOnly.length, p.localOnly.map((x) => x.name).join(', ') || '-');
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
+ }
@@ -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
- * 现在按**内容修改时间**判(不是文件 mtime——同步会刷它):
9
- * 谱系文件 `~/.dsh/storages/...` 旁的 `<母版>/.skill-lineage.json` 记着
10
- * 「两侧各自的内容何时变成现在这样」。谁晚谁是新版;只有一侧变过时,
11
- * 变的那侧就是最新的。
6
+ * 母版取消后,技能的「谁更新」不再是「母版 vs 本机」两方问题,而是
7
+ * 「我这格 vs 别人那格」多方问题。判据随之简化成一条:**内容指纹**。
8
+ * 时间戳只作展示(NAS 会刷 mtime),这一点在 1.2.x 已经踩过。
12
9
  *
13
- * 用法:
14
- * node tools/skill-lineage.mjs --status 只看判定(不写任何文件)
15
- * node tools/skill-lineage.mjs 比对 + 自动同步(同面板)
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
- * 默认母版路径读 ~/.dsh/storages/sync-backup.json 的 skills.masterRoot。
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 STATE_FILE = process.env.DSH_SYNC_STATE_FILE
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
- const args = process.argv.slice(2);
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 masterRoot() {
55
- const explicit = valOf('--master');
56
- if (explicit) return explicit;
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
- const s = JSON.parse(fs.readFileSync(STATE_FILE, 'utf8'));
59
- return (s.skills && s.skills.masterRoot) || '';
61
+ index = JSON.parse(fs.readFileSync(path.join(root, SKILLS_INDEX), 'utf8'));
60
62
  } catch {
61
- return '';
63
+ index = null;
62
64
  }
63
- }
64
65
 
65
- const master = masterRoot();
66
- if (!master) {
67
- console.error('没有母版路径:用 --master <路径> 指定,或先在插件里设置技能母版文件夹。');
68
- process.exit(2);
69
- }
70
- if (!fs.existsSync(master)) {
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
- const localRoot = valOf('--local') || LOCAL_SKILLS_ROOT;
76
- const lineagePath = path.join(master, LINEAGE_FILE);
77
- const VERDICT_LABEL = { master: '母版新', local: '本机新', unknown: '待你决定', same: '一致' };
78
-
79
- /* ------------------------------------------------------------ 只看状态 */
80
-
81
- if (has('--status')) {
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
- for (const r of rows) {
119
- if (has('--brief') && r.verdict === 'same') continue;
120
- console.log(`${(VERDICT_LABEL[r.verdict] || r.verdict).padEnd(5)} ${r.name.padEnd(34)} [${r.basis}] ${r.hint}`);
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
- process.exit(0);
94
+ console.log('');
95
+ console.log('下一步:面板「优化本机技能」出草稿;「立即共享」把本机备份出去并拉缺失。');
123
96
  }
124
97
 
125
- /* ------------------------------------------------------------ 建基线 */
98
+ /* ---------------------------------------------------------- --backfill */
126
99
 
127
- if (has('--reset')) {
128
- if (fs.existsSync(lineagePath)) {
129
- const bak = `${lineagePath}.bak-${Date.now()}`;
130
- fs.renameSync(lineagePath, bak);
131
- console.log(`已移除谱系文件,旧文件备份为 ${path.basename(bak)}`);
132
- } else {
133
- console.log('谱系文件本来就不存在。');
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
- process.exit(0);
136
- }
137
-
138
- if (has('--init')) {
139
- const only = valOf('--names')
140
- .split(',')
141
- .map((s) => s.trim())
142
- .filter(Boolean);
143
- const force = has('--force');
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
- if (mm.treeHash !== ll.treeHash && !force) {
161
- skipped.push({ name, why: '两侧内容不同,先比对/同步再说' });
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
- registered.push({ name, src: mm });
165
- }
166
-
167
- let map = lineage;
168
- for (const r of registered) {
169
- map = recordLineage(map, {
170
- name: r.name,
171
- baseline: r.src.treeHash,
172
- master: r.src,
173
- local: r.src,
174
- by: `baseline:${os.hostname()}`,
175
- });
176
- }
177
- const ok = saveLineage(lineagePath, map);
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
- process.exit(ok ? 0 : 1);
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
- const stamp = makeStamp();
202
- const pulled = dry || !plan.pull.length ? { pulled: [], errors: [] } : applyPull({ items: plan.pull, localRoot, stamp });
203
- const pushed =
204
- dry || !plan.autoPush.length
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.log(
236
- `已自动拉取 ${pulled.pulled.length}、已自动推送 ${pushed.pushed.length}、待你决定 ${plan.push.length}、一致 ${plan.same.length}`,
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);