scene-capability-engine 3.6.39 → 3.6.44
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/CHANGELOG.md +45 -0
- package/bin/scene-capability-engine.js +42 -2
- package/docs/developer-guide.md +1 -1
- package/docs/releases/README.md +5 -0
- package/docs/releases/v3.6.40.md +19 -0
- package/docs/releases/v3.6.41.md +20 -0
- package/docs/releases/v3.6.42.md +19 -0
- package/docs/releases/v3.6.43.md +17 -0
- package/docs/releases/v3.6.44.md +17 -0
- package/docs/spec-collaboration-guide.md +1 -1
- package/docs/zh/releases/README.md +5 -0
- package/docs/zh/releases/v3.6.40.md +19 -0
- package/docs/zh/releases/v3.6.41.md +20 -0
- package/docs/zh/releases/v3.6.42.md +19 -0
- package/docs/zh/releases/v3.6.43.md +17 -0
- package/docs/zh/releases/v3.6.44.md +17 -0
- package/lib/adoption/adoption-logger.js +1 -1
- package/lib/adoption/adoption-strategy.js +29 -29
- package/lib/adoption/detection-engine.js +16 -13
- package/lib/adoption/smart-orchestrator.js +3 -3
- package/lib/adoption/strategy-selector.js +19 -15
- package/lib/adoption/template-sync.js +3 -3
- package/lib/auto/autonomous-engine.js +5 -5
- package/lib/auto/handoff-release-gate-history-loaders-service.js +24 -4
- package/lib/auto/handoff-run-service.js +37 -0
- package/lib/backup/backup-system.js +10 -10
- package/lib/collab/collab-manager.js +8 -5
- package/lib/collab/dependency-manager.js +1 -1
- package/lib/commands/adopt.js +2 -2
- package/lib/commands/auto.js +239 -97
- package/lib/commands/collab.js +10 -4
- package/lib/commands/status.js +3 -3
- package/lib/commands/studio.js +8 -0
- package/lib/repo/config-manager.js +2 -2
- package/lib/spec/bootstrap/context-collector.js +5 -4
- package/lib/spec-gate/rules/default-rules.js +8 -8
- package/lib/upgrade/migration-engine.js +5 -5
- package/lib/upgrade/migrations/1.0.0-to-1.1.0.js +3 -3
- package/lib/utils/tool-detector.js +4 -4
- package/lib/utils/validation.js +6 -6
- package/lib/workspace/multi/workspace-context-resolver.js +3 -3
- package/lib/workspace/multi/workspace-registry.js +3 -3
- package/lib/workspace/multi/workspace-state-manager.js +3 -3
- package/lib/workspace/spec-delivery-audit.js +553 -0
- package/package.json +1 -1
|
@@ -31,7 +31,11 @@ const AdoptionMode = {
|
|
|
31
31
|
*/
|
|
32
32
|
class ProjectState {
|
|
33
33
|
constructor(data = {}) {
|
|
34
|
-
this.
|
|
34
|
+
this.hasSceDir = data.hasSceDir !== undefined
|
|
35
|
+
? data.hasSceDir
|
|
36
|
+
: (data.hasKiroDir !== undefined ? data.hasKiroDir : false);
|
|
37
|
+
// Backward-compatible alias for older callers/tests.
|
|
38
|
+
this.hasKiroDir = this.hasSceDir;
|
|
35
39
|
this.hasVersionFile = data.hasVersionFile !== undefined ? data.hasVersionFile : false;
|
|
36
40
|
this.currentVersion = data.currentVersion !== undefined ? data.currentVersion : null;
|
|
37
41
|
this.targetVersion = data.targetVersion !== undefined ? data.targetVersion : null;
|
|
@@ -61,22 +65,22 @@ class StrategySelector {
|
|
|
61
65
|
* @returns {Promise<ProjectState>} - Comprehensive project state
|
|
62
66
|
*/
|
|
63
67
|
async detectProjectState(projectPath) {
|
|
64
|
-
const
|
|
65
|
-
const versionPath = path.join(
|
|
66
|
-
const specsPath = path.join(
|
|
67
|
-
const steeringPath = path.join(
|
|
68
|
-
const toolsPath = path.join(
|
|
68
|
+
const scePath = path.join(projectPath, '.sce');
|
|
69
|
+
const versionPath = path.join(scePath, 'version.json');
|
|
70
|
+
const specsPath = path.join(scePath, 'specs');
|
|
71
|
+
const steeringPath = path.join(scePath, 'steering');
|
|
72
|
+
const toolsPath = path.join(scePath, 'tools');
|
|
69
73
|
|
|
70
74
|
// Get target version from package.json
|
|
71
75
|
const packageJson = require('../../package.json');
|
|
72
76
|
const targetVersion = packageJson.version;
|
|
73
77
|
|
|
74
78
|
// Check directory existence
|
|
75
|
-
const
|
|
76
|
-
const hasVersionFile =
|
|
77
|
-
const hasSpecs =
|
|
78
|
-
const hasSteering =
|
|
79
|
-
const hasTools =
|
|
79
|
+
const hasSceDir = await this.fs.pathExists(scePath);
|
|
80
|
+
const hasVersionFile = hasSceDir && await this.fs.pathExists(versionPath);
|
|
81
|
+
const hasSpecs = hasSceDir && await this.fs.pathExists(specsPath);
|
|
82
|
+
const hasSteering = hasSceDir && await this.fs.pathExists(steeringPath);
|
|
83
|
+
const hasTools = hasSceDir && await this.fs.pathExists(toolsPath);
|
|
80
84
|
|
|
81
85
|
// Read current version if available
|
|
82
86
|
let currentVersion = null;
|
|
@@ -103,7 +107,7 @@ class StrategySelector {
|
|
|
103
107
|
|
|
104
108
|
// Detect potential conflicts (files that exist and might be overwritten)
|
|
105
109
|
const conflicts = [];
|
|
106
|
-
if (
|
|
110
|
+
if (hasSceDir) {
|
|
107
111
|
const templateFiles = [
|
|
108
112
|
'steering/CORE_PRINCIPLES.md',
|
|
109
113
|
'steering/ENVIRONMENT.md',
|
|
@@ -116,7 +120,7 @@ class StrategySelector {
|
|
|
116
120
|
];
|
|
117
121
|
|
|
118
122
|
for (const templateFile of templateFiles) {
|
|
119
|
-
const filePath = path.join(
|
|
123
|
+
const filePath = path.join(scePath, templateFile);
|
|
120
124
|
if (await this.fs.pathExists(filePath)) {
|
|
121
125
|
conflicts.push(templateFile);
|
|
122
126
|
}
|
|
@@ -124,7 +128,7 @@ class StrategySelector {
|
|
|
124
128
|
}
|
|
125
129
|
|
|
126
130
|
return new ProjectState({
|
|
127
|
-
|
|
131
|
+
hasSceDir,
|
|
128
132
|
hasVersionFile,
|
|
129
133
|
currentVersion,
|
|
130
134
|
targetVersion,
|
|
@@ -144,7 +148,7 @@ class StrategySelector {
|
|
|
144
148
|
*/
|
|
145
149
|
selectMode(state) {
|
|
146
150
|
// No .sce/ directory - fresh adoption
|
|
147
|
-
if (!state.
|
|
151
|
+
if (!state.hasSceDir) {
|
|
148
152
|
return AdoptionMode.FRESH;
|
|
149
153
|
}
|
|
150
154
|
|
|
@@ -59,7 +59,7 @@ class TemplateSync {
|
|
|
59
59
|
errors: [] // Files that couldn't be compared
|
|
60
60
|
};
|
|
61
61
|
|
|
62
|
-
const
|
|
62
|
+
const scePath = path.join(projectPath, '.sce');
|
|
63
63
|
|
|
64
64
|
for (const templateFile of this.templateFiles) {
|
|
65
65
|
try {
|
|
@@ -72,7 +72,7 @@ class TemplateSync {
|
|
|
72
72
|
continue;
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
-
const projectFilePath = path.join(
|
|
75
|
+
const projectFilePath = path.join(scePath, templateFile);
|
|
76
76
|
const templateFilePath = path.join(templatePath, templateFile);
|
|
77
77
|
|
|
78
78
|
// Check if template file exists
|
|
@@ -323,7 +323,7 @@ class TemplateSync {
|
|
|
323
323
|
|
|
324
324
|
const synced = [];
|
|
325
325
|
const errors = [];
|
|
326
|
-
const
|
|
326
|
+
const scePath = path.join(projectPath, '.sce');
|
|
327
327
|
|
|
328
328
|
// Sync missing files
|
|
329
329
|
for (const missing of report.differences.missing) {
|
|
@@ -69,25 +69,25 @@ class AutonomousEngine {
|
|
|
69
69
|
const fs = require('fs-extra');
|
|
70
70
|
|
|
71
71
|
// Check for .sce directory (接管标志)
|
|
72
|
-
const
|
|
73
|
-
if (!await fs.pathExists(
|
|
72
|
+
const sceDir = path.join(process.cwd(), '.sce');
|
|
73
|
+
if (!await fs.pathExists(sceDir)) {
|
|
74
74
|
throw new Error('CORE_PRINCIPLES violation: .sce directory not found. Project not adopted by sce.');
|
|
75
75
|
}
|
|
76
76
|
|
|
77
77
|
// Check for version.json
|
|
78
|
-
const versionFile = path.join(
|
|
78
|
+
const versionFile = path.join(sceDir, 'version.json');
|
|
79
79
|
if (!await fs.pathExists(versionFile)) {
|
|
80
80
|
console.warn('Warning: version.json not found. Consider running sce adoption.');
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
// Check for specs directory
|
|
84
|
-
const specsDir = path.join(
|
|
84
|
+
const specsDir = path.join(sceDir, 'specs');
|
|
85
85
|
if (!await fs.pathExists(specsDir)) {
|
|
86
86
|
await fs.ensureDir(specsDir);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
// Check for steering directory
|
|
90
|
-
const steeringDir = path.join(
|
|
90
|
+
const steeringDir = path.join(sceDir, 'steering');
|
|
91
91
|
if (!await fs.pathExists(steeringDir)) {
|
|
92
92
|
console.warn('Warning: steering directory not found.');
|
|
93
93
|
}
|
|
@@ -426,7 +426,19 @@ async function loadAutoHandoffReleaseGateReports(projectPath, dirCandidate = nul
|
|
|
426
426
|
warnings.push(`skip invalid release gate payload: ${reportFile}`);
|
|
427
427
|
continue;
|
|
428
428
|
}
|
|
429
|
-
|
|
429
|
+
try {
|
|
430
|
+
entries.push(buildAutoHandoffReleaseGateHistoryEntry(
|
|
431
|
+
payload,
|
|
432
|
+
{
|
|
433
|
+
projectPath,
|
|
434
|
+
file: reportFile,
|
|
435
|
+
tag: parseAutoHandoffReleaseGateTag(path.basename(reportFile))
|
|
436
|
+
},
|
|
437
|
+
dependencies
|
|
438
|
+
));
|
|
439
|
+
} catch (error) {
|
|
440
|
+
warnings.push(`skip invalid release gate report entry: ${reportFile} (${error.message})`);
|
|
441
|
+
}
|
|
430
442
|
}
|
|
431
443
|
|
|
432
444
|
return {
|
|
@@ -459,13 +471,21 @@ async function loadAutoHandoffReleaseGateHistorySeed(projectPath, fileCandidate
|
|
|
459
471
|
};
|
|
460
472
|
}
|
|
461
473
|
const list = Array.isArray(payload && payload.entries) ? payload.entries : [];
|
|
462
|
-
const
|
|
474
|
+
const warnings = [];
|
|
475
|
+
const entries = [];
|
|
476
|
+
list
|
|
463
477
|
.filter(item => item && typeof item === 'object' && !Array.isArray(item))
|
|
464
|
-
.
|
|
478
|
+
.forEach((item, index) => {
|
|
479
|
+
try {
|
|
480
|
+
entries.push(buildAutoHandoffReleaseGateHistoryEntry(item, { projectPath }, dependencies));
|
|
481
|
+
} catch (error) {
|
|
482
|
+
warnings.push(`skip invalid gate history entry #${index + 1}: ${error.message}`);
|
|
483
|
+
}
|
|
484
|
+
});
|
|
465
485
|
return {
|
|
466
486
|
file: filePath,
|
|
467
487
|
entries,
|
|
468
|
-
warnings
|
|
488
|
+
warnings
|
|
469
489
|
};
|
|
470
490
|
}
|
|
471
491
|
|
|
@@ -13,6 +13,8 @@ async function runAutoHandoff(projectPath, options = {}, dependencies = {}) {
|
|
|
13
13
|
loadGovernanceReleaseGateSignals,
|
|
14
14
|
completeAutoHandoffRunPhase,
|
|
15
15
|
evaluateAutoHandoffOntologyGateReasons,
|
|
16
|
+
auditSpecDeliverySync,
|
|
17
|
+
evaluateAutoHandoffSpecDeliveryGateReasons,
|
|
16
18
|
evaluateAutoHandoffReleaseGatePreflightGateReasons,
|
|
17
19
|
failAutoHandoffRunPhase,
|
|
18
20
|
buildAutoHandoffMoquiBaselineSnapshot,
|
|
@@ -63,6 +65,7 @@ async function runAutoHandoff(projectPath, options = {}, dependencies = {}) {
|
|
|
63
65
|
observability_snapshot: null,
|
|
64
66
|
spec_status: null,
|
|
65
67
|
ontology_validation: null,
|
|
68
|
+
spec_delivery_sync: null,
|
|
66
69
|
moqui_baseline: null,
|
|
67
70
|
scene_package_batch: null,
|
|
68
71
|
moqui_capability_coverage: null,
|
|
@@ -92,6 +95,9 @@ async function runAutoHandoff(projectPath, options = {}, dependencies = {}) {
|
|
|
92
95
|
result.ontology_validation = evaluateHandoffOntologyValidation(
|
|
93
96
|
plan && plan.handoff ? plan.handoff.ontology_validation : null
|
|
94
97
|
);
|
|
98
|
+
result.spec_delivery_sync = await auditSpecDeliverySync(projectPath, {
|
|
99
|
+
requireManifest: false
|
|
100
|
+
});
|
|
95
101
|
result.template_diff = await buildAutoHandoffTemplateDiff(projectPath, { manifest: options.manifest });
|
|
96
102
|
result.release_gate_preflight = buildAutoHandoffReleaseGatePreflight(
|
|
97
103
|
await loadGovernanceReleaseGateSignals(projectPath)
|
|
@@ -110,6 +116,15 @@ async function runAutoHandoff(projectPath, options = {}, dependencies = {}) {
|
|
|
110
116
|
completeAutoHandoffRunPhase(precheckPhase, {
|
|
111
117
|
validation: plan.validation,
|
|
112
118
|
phase_count: Array.isArray(plan.phases) ? plan.phases.length : 0,
|
|
119
|
+
spec_delivery_sync: result.spec_delivery_sync
|
|
120
|
+
? {
|
|
121
|
+
manifest_count: result.spec_delivery_sync.summary
|
|
122
|
+
? result.spec_delivery_sync.summary.manifest_count
|
|
123
|
+
: 0,
|
|
124
|
+
passed: result.spec_delivery_sync.passed === true,
|
|
125
|
+
reason: result.spec_delivery_sync.reason || null
|
|
126
|
+
}
|
|
127
|
+
: null,
|
|
113
128
|
template_compatibility: result.template_diff.compatibility,
|
|
114
129
|
release_gate_preflight: {
|
|
115
130
|
available: result.release_gate_preflight.available,
|
|
@@ -139,6 +154,28 @@ async function runAutoHandoff(projectPath, options = {}, dependencies = {}) {
|
|
|
139
154
|
if (ontologyGateReasons.length > 0) {
|
|
140
155
|
throw new Error(`handoff ontology validation gate failed: ${ontologyGateReasons.join('; ')}`);
|
|
141
156
|
}
|
|
157
|
+
const specDeliveryGateReasons = evaluateAutoHandoffSpecDeliveryGateReasons(
|
|
158
|
+
result.policy,
|
|
159
|
+
result.spec_delivery_sync
|
|
160
|
+
);
|
|
161
|
+
if (specDeliveryGateReasons.length > 0) {
|
|
162
|
+
throw new Error(`handoff spec delivery sync gate failed: ${specDeliveryGateReasons.join('; ')}`);
|
|
163
|
+
}
|
|
164
|
+
if (
|
|
165
|
+
result.policy.require_spec_delivery_sync !== true &&
|
|
166
|
+
result.spec_delivery_sync &&
|
|
167
|
+
result.spec_delivery_sync.reason !== 'no-manifests' &&
|
|
168
|
+
result.spec_delivery_sync.passed !== true
|
|
169
|
+
) {
|
|
170
|
+
const advisoryReasons = Array.isArray(result.spec_delivery_sync.violations)
|
|
171
|
+
? result.spec_delivery_sync.violations
|
|
172
|
+
: [];
|
|
173
|
+
if (advisoryReasons.length > 0) {
|
|
174
|
+
result.warnings.push(
|
|
175
|
+
`spec delivery sync advisory: ${advisoryReasons.join('; ')}`
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
142
179
|
const releaseGatePreflightReasons = evaluateAutoHandoffReleaseGatePreflightGateReasons(
|
|
143
180
|
result.policy,
|
|
144
181
|
result.release_gate_preflight
|
|
@@ -40,7 +40,7 @@ class BackupSystem {
|
|
|
40
40
|
* @param {string} projectPath - Absolute path to project root
|
|
41
41
|
* @returns {string} - Absolute path to .sce directory
|
|
42
42
|
*/
|
|
43
|
-
|
|
43
|
+
getSceDir(projectPath) {
|
|
44
44
|
return path.join(projectPath, '.sce');
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -74,11 +74,11 @@ class BackupSystem {
|
|
|
74
74
|
const { type = 'manual' } = options;
|
|
75
75
|
|
|
76
76
|
try {
|
|
77
|
-
const
|
|
77
|
+
const sceDir = this.getSceDir(projectPath);
|
|
78
78
|
|
|
79
79
|
// Check if .sce/ exists
|
|
80
|
-
const
|
|
81
|
-
if (!
|
|
80
|
+
const sceExists = await pathExists(sceDir);
|
|
81
|
+
if (!sceExists) {
|
|
82
82
|
throw new Error('.sce/ directory does not exist');
|
|
83
83
|
}
|
|
84
84
|
|
|
@@ -100,7 +100,7 @@ class BackupSystem {
|
|
|
100
100
|
await ensureDirectory(backupPath);
|
|
101
101
|
|
|
102
102
|
// Copy .sce/ contents to backup (excluding backups/ itself)
|
|
103
|
-
const items = await listFiles(
|
|
103
|
+
const items = await listFiles(sceDir);
|
|
104
104
|
|
|
105
105
|
for (const item of items) {
|
|
106
106
|
// Skip the backups directory itself
|
|
@@ -108,7 +108,7 @@ class BackupSystem {
|
|
|
108
108
|
continue;
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
const sourcePath = path.join(
|
|
111
|
+
const sourcePath = path.join(sceDir, item);
|
|
112
112
|
const destPath = path.join(backupPath, item);
|
|
113
113
|
|
|
114
114
|
await copyDirectory(sourcePath, destPath, { overwrite: false });
|
|
@@ -243,20 +243,20 @@ class BackupSystem {
|
|
|
243
243
|
throw new Error(`Backup validation failed: ${backupId}`);
|
|
244
244
|
}
|
|
245
245
|
|
|
246
|
-
const
|
|
246
|
+
const sceDir = this.getSceDir(projectPath);
|
|
247
247
|
|
|
248
248
|
// Get list of items to restore (excluding metadata.json)
|
|
249
249
|
const items = await listFiles(backupPath);
|
|
250
250
|
const itemsToRestore = items.filter(item => item !== 'metadata.json');
|
|
251
251
|
|
|
252
252
|
// Remove existing .sce/ contents (except backups/)
|
|
253
|
-
const existingItems = await listFiles(
|
|
253
|
+
const existingItems = await listFiles(sceDir);
|
|
254
254
|
for (const item of existingItems) {
|
|
255
255
|
if (item === this.backupDirName) {
|
|
256
256
|
continue;
|
|
257
257
|
}
|
|
258
258
|
|
|
259
|
-
const itemPath = path.join(
|
|
259
|
+
const itemPath = path.join(sceDir, item);
|
|
260
260
|
await remove(itemPath);
|
|
261
261
|
}
|
|
262
262
|
|
|
@@ -264,7 +264,7 @@ class BackupSystem {
|
|
|
264
264
|
const restoredFiles = [];
|
|
265
265
|
for (const item of itemsToRestore) {
|
|
266
266
|
const sourcePath = path.join(backupPath, item);
|
|
267
|
-
const destPath = path.join(
|
|
267
|
+
const destPath = path.join(sceDir, item);
|
|
268
268
|
|
|
269
269
|
await copyDirectory(sourcePath, destPath, { overwrite: true });
|
|
270
270
|
restoredFiles.push(item);
|
|
@@ -125,10 +125,10 @@ class CollaborationManager {
|
|
|
125
125
|
/**
|
|
126
126
|
* Assign a spec to a SCE instance
|
|
127
127
|
* @param {string} specName - Name of the spec
|
|
128
|
-
* @param {string}
|
|
128
|
+
* @param {string} sceInstance - SCE instance identifier
|
|
129
129
|
* @returns {Promise<Object>} Assignment result
|
|
130
130
|
*/
|
|
131
|
-
async assignSpec(specName,
|
|
131
|
+
async assignSpec(specName, sceInstance) {
|
|
132
132
|
const metadata = await this.metadataManager.readMetadata(specName);
|
|
133
133
|
|
|
134
134
|
if (!metadata) {
|
|
@@ -149,17 +149,20 @@ class CollaborationManager {
|
|
|
149
149
|
// Update assignment
|
|
150
150
|
const updated = await this.metadataManager.atomicUpdate(specName, (meta) => {
|
|
151
151
|
meta.assignment = {
|
|
152
|
-
|
|
152
|
+
sceInstance,
|
|
153
153
|
assignedAt: new Date().toISOString()
|
|
154
154
|
};
|
|
155
|
+
if (meta.assignment && Object.prototype.hasOwnProperty.call(meta.assignment, 'kiroInstance')) {
|
|
156
|
+
delete meta.assignment.kiroInstance;
|
|
157
|
+
}
|
|
155
158
|
return meta;
|
|
156
159
|
});
|
|
157
160
|
|
|
158
161
|
return {
|
|
159
162
|
success: true,
|
|
160
163
|
spec: specName,
|
|
161
|
-
|
|
162
|
-
message: `Assigned '${specName}' to '${
|
|
164
|
+
sceInstance,
|
|
165
|
+
message: `Assigned '${specName}' to '${sceInstance}'`
|
|
163
166
|
};
|
|
164
167
|
}
|
|
165
168
|
|
|
@@ -25,7 +25,7 @@ class DependencyManager {
|
|
|
25
25
|
nodes.push({
|
|
26
26
|
id: name,
|
|
27
27
|
status: metadata.status?.current || 'not-started',
|
|
28
|
-
|
|
28
|
+
sceInstance: metadata.assignment?.sceInstance || metadata.assignment?.kiroInstance || null,
|
|
29
29
|
type: metadata.type
|
|
30
30
|
});
|
|
31
31
|
|
package/lib/commands/adopt.js
CHANGED
|
@@ -220,7 +220,7 @@ async function adoptInteractive(projectPath, options) {
|
|
|
220
220
|
console.log(' - Preserve existing specs/ and steering/');
|
|
221
221
|
console.log(' - Add missing components');
|
|
222
222
|
console.log(' - Create/update version.json');
|
|
223
|
-
if (detection.
|
|
223
|
+
if (detection.hasSceDir) {
|
|
224
224
|
console.log(' - Create backup before changes');
|
|
225
225
|
}
|
|
226
226
|
} else if (strategy === 'full') {
|
|
@@ -487,7 +487,7 @@ async function adoptInteractive(projectPath, options) {
|
|
|
487
487
|
|
|
488
488
|
// 9. Create backup if needed (for non-conflict scenarios)
|
|
489
489
|
let backupId = null;
|
|
490
|
-
if (detection.
|
|
490
|
+
if (detection.hasSceDir && (strategy === 'partial' || strategy === 'full')) {
|
|
491
491
|
console.log(chalk.blue('📦 Creating backup...'));
|
|
492
492
|
const backupSystem = new BackupSystem();
|
|
493
493
|
|