sumulige-claude 1.3.2 → 1.4.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/.claude/.sumulige-claude-version +1 -0
- package/.claude/AGENTS.md +6 -6
- package/.claude/commands/workflow.md +81 -0
- package/.claude/hooks/auto-handoff.cjs +0 -0
- package/.claude/hooks/hook-dispatcher.cjs +304 -0
- package/.claude/hooks/hook-registry.json +73 -0
- package/.claude/hooks/lib/cache.cjs +161 -0
- package/.claude/hooks/lib/fs-utils.cjs +133 -0
- package/.claude/hooks/memory-loader.cjs +0 -0
- package/.claude/hooks/memory-saver.cjs +0 -0
- package/.claude/hooks/rag-skill-loader.cjs +84 -4
- package/.claude/settings.json +8 -82
- package/.claude/settings.local.json +4 -1
- package/CHANGELOG.md +70 -0
- package/README.md +158 -1
- package/cli.js +1 -1
- package/config/version-manifest.json +85 -0
- package/lib/commands.js +139 -0
- package/lib/incremental-sync.js +274 -0
- package/lib/version-manifest.js +171 -0
- package/package.json +1 -1
- package/template/.claude/commands/workflow.md +81 -0
- package/template/.claude/hooks/auto-handoff.cjs +353 -0
- package/template/.claude/hooks/hook-dispatcher.cjs +304 -0
- package/template/.claude/hooks/hook-registry.json +73 -0
- package/template/.claude/hooks/lib/cache.cjs +161 -0
- package/template/.claude/hooks/lib/fs-utils.cjs +133 -0
- package/template/.claude/hooks/memory-loader.cjs +208 -0
- package/template/.claude/hooks/memory-saver.cjs +268 -0
- package/template/.claude/hooks/rag-skill-loader.cjs +84 -4
- package/template/.claude/settings.json +36 -70
- package/template/CHANGELOG.md +297 -0
- package/template/README.md +558 -88
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "version-manifest-schema.json",
|
|
3
|
+
"$comment": "Version compatibility matrix for incremental sync",
|
|
4
|
+
|
|
5
|
+
"current": "1.3.3",
|
|
6
|
+
|
|
7
|
+
"history": [
|
|
8
|
+
{
|
|
9
|
+
"version": "1.3.0",
|
|
10
|
+
"date": "2025-01-20",
|
|
11
|
+
"breaking": false,
|
|
12
|
+
"changes": [
|
|
13
|
+
{ "type": "skill", "action": "merge", "from": ["librarian", "architect", "builder", "reviewer", "conductor"] }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
"version": "1.3.1",
|
|
18
|
+
"date": "2025-01-21",
|
|
19
|
+
"breaking": false,
|
|
20
|
+
"changes": [
|
|
21
|
+
{ "type": "command", "action": "add", "name": "audit" },
|
|
22
|
+
{ "type": "command", "action": "add", "name": "handoff" },
|
|
23
|
+
{ "type": "command", "action": "add", "name": "gha" }
|
|
24
|
+
]
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"version": "1.3.2",
|
|
28
|
+
"date": "2025-01-21",
|
|
29
|
+
"breaking": false,
|
|
30
|
+
"changes": [
|
|
31
|
+
{ "type": "hook", "action": "add", "name": "memory-loader" },
|
|
32
|
+
{ "type": "hook", "action": "add", "name": "memory-saver" },
|
|
33
|
+
{ "type": "hook", "action": "add", "name": "auto-handoff" }
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
"version": "1.3.3",
|
|
38
|
+
"date": "2025-01-22",
|
|
39
|
+
"breaking": false,
|
|
40
|
+
"changes": [
|
|
41
|
+
{ "type": "sync", "action": "add", "feature": "--hooks option" }
|
|
42
|
+
]
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
"version": "1.4.0",
|
|
46
|
+
"date": "2025-01-22",
|
|
47
|
+
"breaking": false,
|
|
48
|
+
"changes": [
|
|
49
|
+
{ "type": "hook", "action": "add", "name": "hook-dispatcher" },
|
|
50
|
+
{ "type": "config", "action": "add", "name": "hook-registry.json" },
|
|
51
|
+
{ "type": "hook", "action": "update", "name": "rag-skill-loader", "feature": "cache" },
|
|
52
|
+
{ "type": "lib", "action": "add", "name": "hooks/lib/fs-utils.cjs" },
|
|
53
|
+
{ "type": "lib", "action": "add", "name": "hooks/lib/cache.cjs" },
|
|
54
|
+
{ "type": "config", "action": "update", "name": "settings.json", "feature": "dispatcher pattern" },
|
|
55
|
+
{ "type": "sync", "action": "add", "feature": "--incremental option" },
|
|
56
|
+
{ "type": "command", "action": "add", "name": "workflow" }
|
|
57
|
+
]
|
|
58
|
+
}
|
|
59
|
+
],
|
|
60
|
+
|
|
61
|
+
"syncRules": {
|
|
62
|
+
"hook": {
|
|
63
|
+
"add": "copy",
|
|
64
|
+
"update": "copy",
|
|
65
|
+
"remove": "delete"
|
|
66
|
+
},
|
|
67
|
+
"config": {
|
|
68
|
+
"add": "copy",
|
|
69
|
+
"update": "merge"
|
|
70
|
+
},
|
|
71
|
+
"command": {
|
|
72
|
+
"add": "copy",
|
|
73
|
+
"update": "copy"
|
|
74
|
+
},
|
|
75
|
+
"skill": {
|
|
76
|
+
"add": "copy",
|
|
77
|
+
"update": "copy",
|
|
78
|
+
"merge": "reorganize"
|
|
79
|
+
},
|
|
80
|
+
"lib": {
|
|
81
|
+
"add": "copy",
|
|
82
|
+
"update": "copy"
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
package/lib/commands.js
CHANGED
|
@@ -335,6 +335,52 @@ const commands = {
|
|
|
335
335
|
// -------------------------------------------------------------------------
|
|
336
336
|
sync: async (...args) => {
|
|
337
337
|
const forceCheckUpdate = args.includes("--check-update");
|
|
338
|
+
const syncHooks = args.includes("--hooks");
|
|
339
|
+
const incrementalSync = args.includes("--incremental");
|
|
340
|
+
const forceSync = args.includes("--force");
|
|
341
|
+
|
|
342
|
+
// 增量同步模式
|
|
343
|
+
if (incrementalSync) {
|
|
344
|
+
console.log("🔄 Running incremental sync...");
|
|
345
|
+
console.log("");
|
|
346
|
+
|
|
347
|
+
const projectDir = process.cwd();
|
|
348
|
+
const incrementalLib = require("./incremental-sync");
|
|
349
|
+
const result = incrementalLib.incrementalSync(projectDir, { force: forceSync });
|
|
350
|
+
|
|
351
|
+
if (result.needsFullSync) {
|
|
352
|
+
console.log("ℹ️ " + result.message);
|
|
353
|
+
console.log(" Run: smc sync (without --incremental)");
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
if (result.hasBreakingChanges) {
|
|
358
|
+
console.log("⚠️ " + result.message);
|
|
359
|
+
console.log("");
|
|
360
|
+
result.changes.forEach((c) => {
|
|
361
|
+
if (c.breaking) {
|
|
362
|
+
console.log(` 🔴 v${c.version}: Breaking changes`);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (!result.success) {
|
|
369
|
+
console.log("❌ " + result.message);
|
|
370
|
+
return;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
console.log(`✅ ${result.message}`);
|
|
374
|
+
if (result.results) {
|
|
375
|
+
const applied = result.results.filter((r) => r.result.success && !r.result.skipped);
|
|
376
|
+
applied.forEach((r) => {
|
|
377
|
+
console.log(` ✅ [${r.version}] ${r.change.type}: ${r.change.name || r.change.feature}`);
|
|
378
|
+
});
|
|
379
|
+
}
|
|
380
|
+
console.log("");
|
|
381
|
+
console.log("✅ Incremental sync complete!");
|
|
382
|
+
return;
|
|
383
|
+
}
|
|
338
384
|
|
|
339
385
|
console.log("🔄 Syncing Sumulige Claude to current project...");
|
|
340
386
|
console.log("");
|
|
@@ -387,6 +433,88 @@ const commands = {
|
|
|
387
433
|
}
|
|
388
434
|
}
|
|
389
435
|
|
|
436
|
+
// Sync hooks if --hooks flag is provided
|
|
437
|
+
if (syncHooks) {
|
|
438
|
+
const templateHooksDir = path.join(TEMPLATE_DIR, ".claude", "hooks");
|
|
439
|
+
const projectHooksDir = path.join(projectConfigDir, "hooks");
|
|
440
|
+
const templateSettingsFile = path.join(
|
|
441
|
+
TEMPLATE_DIR,
|
|
442
|
+
".claude",
|
|
443
|
+
"settings.json",
|
|
444
|
+
);
|
|
445
|
+
const projectSettingsFile = path.join(projectConfigDir, "settings.json");
|
|
446
|
+
|
|
447
|
+
if (fs.existsSync(templateHooksDir)) {
|
|
448
|
+
ensureDir(projectHooksDir);
|
|
449
|
+
|
|
450
|
+
// Sync hook files (only add new ones, don't overwrite)
|
|
451
|
+
const hookFiles = fs
|
|
452
|
+
.readdirSync(templateHooksDir)
|
|
453
|
+
.filter((f) => f.endsWith(".cjs"));
|
|
454
|
+
let syncedCount = 0;
|
|
455
|
+
|
|
456
|
+
hookFiles.forEach((hookFile) => {
|
|
457
|
+
const src = path.join(templateHooksDir, hookFile);
|
|
458
|
+
const dest = path.join(projectHooksDir, hookFile);
|
|
459
|
+
|
|
460
|
+
if (!fs.existsSync(dest)) {
|
|
461
|
+
fs.copyFileSync(src, dest);
|
|
462
|
+
setExecutablePermission(dest);
|
|
463
|
+
syncedCount++;
|
|
464
|
+
console.log(` ✅ Added ${hookFile}`);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
if (syncedCount > 0) {
|
|
469
|
+
console.log(`✅ Synced ${syncedCount} new hook(s)`);
|
|
470
|
+
} else {
|
|
471
|
+
console.log("✅ Hooks up to date");
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Sync settings.json (merge new lifecycle hooks)
|
|
476
|
+
if (
|
|
477
|
+
fs.existsSync(templateSettingsFile) &&
|
|
478
|
+
fs.existsSync(projectSettingsFile)
|
|
479
|
+
) {
|
|
480
|
+
try {
|
|
481
|
+
const templateSettings = JSON.parse(
|
|
482
|
+
fs.readFileSync(templateSettingsFile, "utf-8"),
|
|
483
|
+
);
|
|
484
|
+
const projectSettings = JSON.parse(
|
|
485
|
+
fs.readFileSync(projectSettingsFile, "utf-8"),
|
|
486
|
+
);
|
|
487
|
+
|
|
488
|
+
// Merge new lifecycle hooks
|
|
489
|
+
const lifecycleHooks = [
|
|
490
|
+
"SessionStart",
|
|
491
|
+
"SessionEnd",
|
|
492
|
+
"PreCompact",
|
|
493
|
+
"env",
|
|
494
|
+
];
|
|
495
|
+
let updated = false;
|
|
496
|
+
|
|
497
|
+
lifecycleHooks.forEach((hook) => {
|
|
498
|
+
if (templateSettings[hook] && !projectSettings[hook]) {
|
|
499
|
+
projectSettings[hook] = templateSettings[hook];
|
|
500
|
+
updated = true;
|
|
501
|
+
console.log(` ✅ Added ${hook} hook`);
|
|
502
|
+
}
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
if (updated) {
|
|
506
|
+
fs.writeFileSync(
|
|
507
|
+
projectSettingsFile,
|
|
508
|
+
JSON.stringify(projectSettings, null, 2),
|
|
509
|
+
);
|
|
510
|
+
console.log("✅ Updated settings.json");
|
|
511
|
+
}
|
|
512
|
+
} catch (e) {
|
|
513
|
+
console.log("⚠️ Failed to merge settings.json");
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
390
518
|
// Sync todos directory structure
|
|
391
519
|
const todosTemplateDir = path.join(TEMPLATE_DIR, "development", "todos");
|
|
392
520
|
const todosProjectDir = path.join(projectDir, "development", "todos");
|
|
@@ -403,6 +531,17 @@ const commands = {
|
|
|
403
531
|
console.log("⚠️ Failed to sync skills");
|
|
404
532
|
}
|
|
405
533
|
|
|
534
|
+
// Update project version after full sync
|
|
535
|
+
try {
|
|
536
|
+
const versionManifest = require("./version-manifest");
|
|
537
|
+
const currentVersion = versionManifest.getCurrentVersion();
|
|
538
|
+
if (currentVersion) {
|
|
539
|
+
versionManifest.setProjectVersion(projectDir, currentVersion);
|
|
540
|
+
}
|
|
541
|
+
} catch (e) {
|
|
542
|
+
// Version manifest not available, ignore
|
|
543
|
+
}
|
|
544
|
+
|
|
406
545
|
console.log("");
|
|
407
546
|
console.log("✅ Sync complete!");
|
|
408
547
|
|
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Incremental Sync Manager
|
|
3
|
+
*
|
|
4
|
+
* 增量同步功能,只同步变更的文件
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const versionManifest = require('./version-manifest');
|
|
10
|
+
|
|
11
|
+
const TEMPLATE_DIR = path.join(__dirname, '../template');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 复制文件
|
|
15
|
+
*/
|
|
16
|
+
function copyFile(src, dest) {
|
|
17
|
+
const dir = path.dirname(dest);
|
|
18
|
+
if (!fs.existsSync(dir)) {
|
|
19
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
20
|
+
}
|
|
21
|
+
fs.copyFileSync(src, dest);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 合并 JSON 配置
|
|
26
|
+
*/
|
|
27
|
+
function mergeJsonConfig(src, dest) {
|
|
28
|
+
let existing = {};
|
|
29
|
+
if (fs.existsSync(dest)) {
|
|
30
|
+
try {
|
|
31
|
+
existing = JSON.parse(fs.readFileSync(dest, 'utf-8'));
|
|
32
|
+
} catch (e) {
|
|
33
|
+
existing = {};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const newConfig = JSON.parse(fs.readFileSync(src, 'utf-8'));
|
|
38
|
+
|
|
39
|
+
// 深度合并
|
|
40
|
+
const merged = deepMerge(existing, newConfig);
|
|
41
|
+
|
|
42
|
+
const dir = path.dirname(dest);
|
|
43
|
+
if (!fs.existsSync(dir)) {
|
|
44
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
45
|
+
}
|
|
46
|
+
fs.writeFileSync(dest, JSON.stringify(merged, null, 2));
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* 深度合并对象
|
|
51
|
+
*/
|
|
52
|
+
function deepMerge(target, source) {
|
|
53
|
+
const result = { ...target };
|
|
54
|
+
|
|
55
|
+
for (const key of Object.keys(source)) {
|
|
56
|
+
if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
|
|
57
|
+
result[key] = deepMerge(result[key] || {}, source[key]);
|
|
58
|
+
} else {
|
|
59
|
+
result[key] = source[key];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return result;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 应用单个变更
|
|
68
|
+
*/
|
|
69
|
+
function applyChange(change, projectDir, rules) {
|
|
70
|
+
const rule = rules[change.type];
|
|
71
|
+
if (!rule) return { success: false, reason: 'Unknown change type' };
|
|
72
|
+
|
|
73
|
+
const action = rule[change.action];
|
|
74
|
+
if (!action) return { success: false, reason: 'Unknown action' };
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
switch (change.type) {
|
|
78
|
+
case 'hook':
|
|
79
|
+
return applyHookChange(change, projectDir, action);
|
|
80
|
+
case 'config':
|
|
81
|
+
return applyConfigChange(change, projectDir, action);
|
|
82
|
+
case 'command':
|
|
83
|
+
return applyCommandChange(change, projectDir, action);
|
|
84
|
+
case 'lib':
|
|
85
|
+
return applyLibChange(change, projectDir, action);
|
|
86
|
+
case 'skill':
|
|
87
|
+
return applySkillChange(change, projectDir, action);
|
|
88
|
+
default:
|
|
89
|
+
return { success: true, skipped: true };
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
return { success: false, reason: e.message };
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 应用 hook 变更
|
|
98
|
+
*/
|
|
99
|
+
function applyHookChange(change, projectDir, action) {
|
|
100
|
+
const hookName = change.name;
|
|
101
|
+
const src = path.join(TEMPLATE_DIR, '.claude/hooks', `${hookName}.cjs`);
|
|
102
|
+
const dest = path.join(projectDir, '.claude/hooks', `${hookName}.cjs`);
|
|
103
|
+
|
|
104
|
+
if (action === 'copy') {
|
|
105
|
+
if (fs.existsSync(src)) {
|
|
106
|
+
copyFile(src, dest);
|
|
107
|
+
return { success: true, action: 'copied', file: dest };
|
|
108
|
+
}
|
|
109
|
+
return { success: false, reason: 'Source file not found' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (action === 'delete') {
|
|
113
|
+
if (fs.existsSync(dest)) {
|
|
114
|
+
fs.unlinkSync(dest);
|
|
115
|
+
return { success: true, action: 'deleted', file: dest };
|
|
116
|
+
}
|
|
117
|
+
return { success: true, skipped: true };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { success: true, skipped: true };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* 应用配置变更
|
|
125
|
+
*/
|
|
126
|
+
function applyConfigChange(change, projectDir, action) {
|
|
127
|
+
const configName = change.name;
|
|
128
|
+
let src, dest;
|
|
129
|
+
|
|
130
|
+
if (configName === 'settings.json') {
|
|
131
|
+
src = path.join(TEMPLATE_DIR, '.claude/settings.json');
|
|
132
|
+
dest = path.join(projectDir, '.claude/settings.json');
|
|
133
|
+
} else if (configName === 'hook-registry.json') {
|
|
134
|
+
src = path.join(TEMPLATE_DIR, '.claude/hooks/hook-registry.json');
|
|
135
|
+
dest = path.join(projectDir, '.claude/hooks/hook-registry.json');
|
|
136
|
+
} else {
|
|
137
|
+
src = path.join(TEMPLATE_DIR, '.claude', configName);
|
|
138
|
+
dest = path.join(projectDir, '.claude', configName);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (action === 'copy') {
|
|
142
|
+
if (fs.existsSync(src)) {
|
|
143
|
+
copyFile(src, dest);
|
|
144
|
+
return { success: true, action: 'copied', file: dest };
|
|
145
|
+
}
|
|
146
|
+
return { success: false, reason: 'Source file not found' };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (action === 'merge') {
|
|
150
|
+
if (fs.existsSync(src)) {
|
|
151
|
+
mergeJsonConfig(src, dest);
|
|
152
|
+
return { success: true, action: 'merged', file: dest };
|
|
153
|
+
}
|
|
154
|
+
return { success: false, reason: 'Source file not found' };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return { success: true, skipped: true };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
/**
|
|
161
|
+
* 应用命令变更
|
|
162
|
+
*/
|
|
163
|
+
function applyCommandChange(change, projectDir, action) {
|
|
164
|
+
const commandName = change.name;
|
|
165
|
+
const src = path.join(TEMPLATE_DIR, '.claude/commands', `${commandName}.md`);
|
|
166
|
+
const dest = path.join(projectDir, '.claude/commands', `${commandName}.md`);
|
|
167
|
+
|
|
168
|
+
if (action === 'copy') {
|
|
169
|
+
if (fs.existsSync(src)) {
|
|
170
|
+
copyFile(src, dest);
|
|
171
|
+
return { success: true, action: 'copied', file: dest };
|
|
172
|
+
}
|
|
173
|
+
return { success: false, reason: 'Source file not found' };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
return { success: true, skipped: true };
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* 应用库文件变更
|
|
181
|
+
*/
|
|
182
|
+
function applyLibChange(change, projectDir, action) {
|
|
183
|
+
const libPath = change.name;
|
|
184
|
+
const src = path.join(TEMPLATE_DIR, '.claude', libPath);
|
|
185
|
+
const dest = path.join(projectDir, '.claude', libPath);
|
|
186
|
+
|
|
187
|
+
if (action === 'copy') {
|
|
188
|
+
if (fs.existsSync(src)) {
|
|
189
|
+
copyFile(src, dest);
|
|
190
|
+
return { success: true, action: 'copied', file: dest };
|
|
191
|
+
}
|
|
192
|
+
return { success: false, reason: 'Source file not found' };
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return { success: true, skipped: true };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* 应用技能变更
|
|
200
|
+
*/
|
|
201
|
+
function applySkillChange(change, projectDir, action) {
|
|
202
|
+
// 技能同步由现有的 sync 命令处理
|
|
203
|
+
return { success: true, skipped: true, reason: 'Skills handled by full sync' };
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* 执行增量同步
|
|
208
|
+
*/
|
|
209
|
+
function incrementalSync(projectDir, options = {}) {
|
|
210
|
+
const summary = versionManifest.getUpdateSummary(projectDir);
|
|
211
|
+
|
|
212
|
+
if (!summary.needsUpdate) {
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
message: 'Already up to date',
|
|
216
|
+
changes: []
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (summary.isNewInstall) {
|
|
221
|
+
return {
|
|
222
|
+
success: false,
|
|
223
|
+
message: 'New installation detected. Please run full sync first.',
|
|
224
|
+
needsFullSync: true
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (summary.hasBreakingChanges && !options.force) {
|
|
229
|
+
return {
|
|
230
|
+
success: false,
|
|
231
|
+
message: 'Breaking changes detected. Use --force to continue.',
|
|
232
|
+
hasBreakingChanges: true,
|
|
233
|
+
changes: summary.changes
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
const rules = versionManifest.getSyncRules();
|
|
238
|
+
const results = [];
|
|
239
|
+
|
|
240
|
+
for (const versionChange of summary.changes) {
|
|
241
|
+
for (const change of versionChange.changes) {
|
|
242
|
+
const result = applyChange(change, projectDir, rules);
|
|
243
|
+
results.push({
|
|
244
|
+
version: versionChange.version,
|
|
245
|
+
change,
|
|
246
|
+
result
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// 更新项目版本
|
|
252
|
+
versionManifest.setProjectVersion(projectDir, summary.toVersion);
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
success: true,
|
|
256
|
+
message: `Updated from ${summary.fromVersion} to ${summary.toVersion}`,
|
|
257
|
+
fromVersion: summary.fromVersion,
|
|
258
|
+
toVersion: summary.toVersion,
|
|
259
|
+
results: results
|
|
260
|
+
};
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* 检查更新
|
|
265
|
+
*/
|
|
266
|
+
function checkForUpdates(projectDir) {
|
|
267
|
+
return versionManifest.getUpdateSummary(projectDir);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
module.exports = {
|
|
271
|
+
incrementalSync,
|
|
272
|
+
checkForUpdates,
|
|
273
|
+
applyChange
|
|
274
|
+
};
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Version Manifest Manager
|
|
3
|
+
*
|
|
4
|
+
* 管理版本清单,支持增量同步
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
const MANIFEST_FILE = path.join(__dirname, '../config/version-manifest.json');
|
|
11
|
+
const VERSION_FILE_NAME = '.sumulige-claude-version';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 加载版本清单
|
|
15
|
+
*/
|
|
16
|
+
function loadManifest() {
|
|
17
|
+
if (!fs.existsSync(MANIFEST_FILE)) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
try {
|
|
21
|
+
return JSON.parse(fs.readFileSync(MANIFEST_FILE, 'utf-8'));
|
|
22
|
+
} catch (e) {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* 获取项目当前版本
|
|
29
|
+
*/
|
|
30
|
+
function getProjectVersion(projectDir) {
|
|
31
|
+
const versionFile = path.join(projectDir, '.claude', VERSION_FILE_NAME);
|
|
32
|
+
if (!fs.existsSync(versionFile)) {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
try {
|
|
36
|
+
return fs.readFileSync(versionFile, 'utf-8').trim();
|
|
37
|
+
} catch (e) {
|
|
38
|
+
return null;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* 设置项目版本
|
|
44
|
+
*/
|
|
45
|
+
function setProjectVersion(projectDir, version) {
|
|
46
|
+
const versionFile = path.join(projectDir, '.claude', VERSION_FILE_NAME);
|
|
47
|
+
const dir = path.dirname(versionFile);
|
|
48
|
+
if (!fs.existsSync(dir)) {
|
|
49
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
50
|
+
}
|
|
51
|
+
fs.writeFileSync(versionFile, version);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* 比较版本号
|
|
56
|
+
* @returns -1 if a < b, 0 if a == b, 1 if a > b
|
|
57
|
+
*/
|
|
58
|
+
function compareVersions(a, b) {
|
|
59
|
+
const partsA = a.split('.').map(Number);
|
|
60
|
+
const partsB = b.split('.').map(Number);
|
|
61
|
+
|
|
62
|
+
for (let i = 0; i < Math.max(partsA.length, partsB.length); i++) {
|
|
63
|
+
const numA = partsA[i] || 0;
|
|
64
|
+
const numB = partsB[i] || 0;
|
|
65
|
+
if (numA < numB) return -1;
|
|
66
|
+
if (numA > numB) return 1;
|
|
67
|
+
}
|
|
68
|
+
return 0;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* 获取版本之间的变更
|
|
73
|
+
*/
|
|
74
|
+
function getChangesSince(fromVersion, toVersion = null) {
|
|
75
|
+
const manifest = loadManifest();
|
|
76
|
+
if (!manifest) return [];
|
|
77
|
+
|
|
78
|
+
const targetVersion = toVersion || manifest.current;
|
|
79
|
+
const changes = [];
|
|
80
|
+
|
|
81
|
+
for (const entry of manifest.history) {
|
|
82
|
+
if (compareVersions(entry.version, fromVersion) > 0 &&
|
|
83
|
+
compareVersions(entry.version, targetVersion) <= 0) {
|
|
84
|
+
changes.push({
|
|
85
|
+
version: entry.version,
|
|
86
|
+
date: entry.date,
|
|
87
|
+
breaking: entry.breaking,
|
|
88
|
+
changes: entry.changes
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
return changes.sort((a, b) => compareVersions(a.version, b.version));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* 检测是否有 breaking changes
|
|
98
|
+
*/
|
|
99
|
+
function hasBreakingChanges(fromVersion, toVersion = null) {
|
|
100
|
+
const changes = getChangesSince(fromVersion, toVersion);
|
|
101
|
+
return changes.some(c => c.breaking);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* 获取当前版本
|
|
106
|
+
*/
|
|
107
|
+
function getCurrentVersion() {
|
|
108
|
+
const manifest = loadManifest();
|
|
109
|
+
return manifest ? manifest.current : null;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* 获取同步规则
|
|
114
|
+
*/
|
|
115
|
+
function getSyncRules() {
|
|
116
|
+
const manifest = loadManifest();
|
|
117
|
+
return manifest ? manifest.syncRules : null;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* 检查是否需要更新
|
|
122
|
+
*/
|
|
123
|
+
function needsUpdate(projectDir) {
|
|
124
|
+
const projectVersion = getProjectVersion(projectDir);
|
|
125
|
+
if (!projectVersion) return true;
|
|
126
|
+
|
|
127
|
+
const currentVersion = getCurrentVersion();
|
|
128
|
+
if (!currentVersion) return false;
|
|
129
|
+
|
|
130
|
+
return compareVersions(projectVersion, currentVersion) < 0;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* 获取更新摘要
|
|
135
|
+
*/
|
|
136
|
+
function getUpdateSummary(projectDir) {
|
|
137
|
+
const projectVersion = getProjectVersion(projectDir);
|
|
138
|
+
if (!projectVersion) {
|
|
139
|
+
return {
|
|
140
|
+
needsUpdate: true,
|
|
141
|
+
isNewInstall: true,
|
|
142
|
+
changes: []
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const currentVersion = getCurrentVersion();
|
|
147
|
+
const changes = getChangesSince(projectVersion);
|
|
148
|
+
const hasBreaking = changes.some(c => c.breaking);
|
|
149
|
+
|
|
150
|
+
return {
|
|
151
|
+
needsUpdate: changes.length > 0,
|
|
152
|
+
isNewInstall: false,
|
|
153
|
+
fromVersion: projectVersion,
|
|
154
|
+
toVersion: currentVersion,
|
|
155
|
+
hasBreakingChanges: hasBreaking,
|
|
156
|
+
changes: changes
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = {
|
|
161
|
+
loadManifest,
|
|
162
|
+
getProjectVersion,
|
|
163
|
+
setProjectVersion,
|
|
164
|
+
compareVersions,
|
|
165
|
+
getChangesSince,
|
|
166
|
+
hasBreakingChanges,
|
|
167
|
+
getCurrentVersion,
|
|
168
|
+
getSyncRules,
|
|
169
|
+
needsUpdate,
|
|
170
|
+
getUpdateSummary
|
|
171
|
+
};
|