kiro-spec-engine 1.8.1 → 1.9.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/CHANGELOG.md +96 -0
- package/bin/kiro-spec-engine.js +7 -3
- package/lib/adoption/adoption-logger.js +487 -0
- package/lib/adoption/backup-manager.js +420 -0
- package/lib/adoption/conflict-resolver.js +162 -2
- package/lib/adoption/error-formatter.js +509 -0
- package/lib/adoption/file-classifier.js +385 -0
- package/lib/adoption/progress-reporter.js +534 -0
- package/lib/adoption/smart-orchestrator.js +443 -0
- package/lib/adoption/strategy-selector.js +218 -0
- package/lib/adoption/summary-generator.js +493 -0
- package/lib/adoption/template-sync.js +605 -0
- package/lib/commands/adopt.js +183 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,102 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [1.9.0] - 2026-01-28
|
|
9
|
+
|
|
10
|
+
### Added - Adopt Command UX Improvement 🎉
|
|
11
|
+
|
|
12
|
+
**Spec 14-00**: Complete UX overhaul for the `kse adopt` command with zero-interaction smart adoption
|
|
13
|
+
|
|
14
|
+
**Phase 1: Core Smart Adoption**
|
|
15
|
+
- **Smart Orchestrator**: Zero-interaction adoption coordinator
|
|
16
|
+
- Automatic project state detection
|
|
17
|
+
- Intelligent strategy selection (fresh, smart-update, smart-adopt, skip, warning)
|
|
18
|
+
- Mandatory backup integration with validation
|
|
19
|
+
- Comprehensive error handling
|
|
20
|
+
- **Strategy Selector**: Automatic adoption mode selection
|
|
21
|
+
- Version comparison and compatibility checking
|
|
22
|
+
- Project state analysis
|
|
23
|
+
- Optimal strategy recommendation
|
|
24
|
+
- **File Classifier**: Intelligent file categorization
|
|
25
|
+
- Template files (steering/, tools/, README.md)
|
|
26
|
+
- User content (specs/, custom files)
|
|
27
|
+
- Config files (version.json, adoption-config.json)
|
|
28
|
+
- Generated files (backups/, logs/)
|
|
29
|
+
- **Conflict Resolver**: Automatic conflict resolution
|
|
30
|
+
- Rule-based resolution (update, preserve, merge, skip)
|
|
31
|
+
- Context-aware decisions
|
|
32
|
+
- Special case handling (CURRENT_CONTEXT.md)
|
|
33
|
+
- **Backup Manager**: Enhanced backup system
|
|
34
|
+
- Mandatory backup before modifications
|
|
35
|
+
- Integrity validation (file count, size, hash)
|
|
36
|
+
- Selective backup support
|
|
37
|
+
- Automatic rollback on failure
|
|
38
|
+
|
|
39
|
+
**Phase 2: User Experience**
|
|
40
|
+
- **Progress Reporter**: Real-time progress feedback
|
|
41
|
+
- 8 progress stages with clear status icons (🔄 ✅ ❌ ⏭️)
|
|
42
|
+
- File operation tracking (create, update, delete, preserve)
|
|
43
|
+
- Batch operation support
|
|
44
|
+
- Verbose mode with timing information
|
|
45
|
+
- Quiet mode for silent operation
|
|
46
|
+
- **Summary Generator**: Comprehensive adoption summaries
|
|
47
|
+
- Mode and backup information
|
|
48
|
+
- Complete change lists (created, updated, deleted, preserved)
|
|
49
|
+
- Statistics and analysis
|
|
50
|
+
- Rollback instructions
|
|
51
|
+
- Context-aware next steps
|
|
52
|
+
- Text and object output formats
|
|
53
|
+
- **Error Formatter**: Enhanced error messages
|
|
54
|
+
- 9 error categories with specialized templates
|
|
55
|
+
- Clear problem descriptions (non-technical language)
|
|
56
|
+
- Possible causes listing
|
|
57
|
+
- Actionable solutions
|
|
58
|
+
- Help references (kse doctor, documentation)
|
|
59
|
+
- Consistent formatting across all errors
|
|
60
|
+
|
|
61
|
+
**Phase 3: Advanced Features**
|
|
62
|
+
- **Command-Line Options**: Full integration of advanced options
|
|
63
|
+
- `--dry-run`: Preview without executing
|
|
64
|
+
- `--no-backup`: Skip backup with warning
|
|
65
|
+
- `--skip-update`: Skip template updates
|
|
66
|
+
- `--verbose`: Show detailed logs
|
|
67
|
+
- `--interactive`: Enable legacy mode
|
|
68
|
+
- `--force`: Force overwrite with backup
|
|
69
|
+
- **Verbose Logging**: Detailed debugging system
|
|
70
|
+
- 5 log levels (ERROR, WARN, INFO, DEBUG, VERBOSE)
|
|
71
|
+
- File-based logging (`.kiro/logs/adopt-{timestamp}.log`)
|
|
72
|
+
- Timestamps and elapsed time tracking
|
|
73
|
+
- Domain-specific logging methods
|
|
74
|
+
- Buffer management
|
|
75
|
+
- Runtime log level changes
|
|
76
|
+
- **Template Sync System**: Automatic template synchronization
|
|
77
|
+
- Content-based file comparison (SHA-256 hashes)
|
|
78
|
+
- Binary file detection and handling
|
|
79
|
+
- Line ending normalization (CRLF vs LF)
|
|
80
|
+
- Selective sync (only changed files)
|
|
81
|
+
- CURRENT_CONTEXT.md preservation
|
|
82
|
+
- Progress callbacks and dry-run support
|
|
83
|
+
|
|
84
|
+
**Key Benefits**:
|
|
85
|
+
- **Zero Questions**: No user interaction required by default
|
|
86
|
+
- **Smart Decisions**: Automatic mode selection and conflict resolution
|
|
87
|
+
- **Safety First**: Mandatory backups with validation
|
|
88
|
+
- **Clear Feedback**: Real-time progress and detailed summaries
|
|
89
|
+
- **Easy Rollback**: Simple undo with clear instructions
|
|
90
|
+
- **Power User Support**: Advanced options for fine control
|
|
91
|
+
|
|
92
|
+
**Test Coverage**:
|
|
93
|
+
- 200+ new unit tests
|
|
94
|
+
- 100% coverage for all new components
|
|
95
|
+
- All tests passing (1173+ tests)
|
|
96
|
+
- Zero regressions
|
|
97
|
+
|
|
98
|
+
**Migration**:
|
|
99
|
+
- Default behavior is now non-interactive
|
|
100
|
+
- Use `--interactive` flag for legacy behavior
|
|
101
|
+
- All existing flags still work
|
|
102
|
+
- Backward compatible
|
|
103
|
+
|
|
8
104
|
## [1.8.1] - 2026-01-27
|
|
9
105
|
|
|
10
106
|
### Fixed - Test Suite Hotfix 🔧
|
package/bin/kiro-spec-engine.js
CHANGED
|
@@ -182,10 +182,14 @@ program
|
|
|
182
182
|
program
|
|
183
183
|
.command('adopt')
|
|
184
184
|
.description('Adopt existing project into Kiro Spec Engine')
|
|
185
|
-
.option('--
|
|
185
|
+
.option('--interactive', 'Enable interactive mode (legacy behavior with prompts)')
|
|
186
186
|
.option('--dry-run', 'Show what would change without making changes')
|
|
187
|
-
.option('--
|
|
188
|
-
.option('--
|
|
187
|
+
.option('--verbose', 'Show detailed logs')
|
|
188
|
+
.option('--no-backup', 'Skip backup creation (dangerous, not recommended)')
|
|
189
|
+
.option('--skip-update', 'Skip template file updates')
|
|
190
|
+
.option('--force', 'Force overwrite conflicting files (legacy, creates backup first)')
|
|
191
|
+
.option('--auto', 'Skip confirmations (legacy, use --interactive for old behavior)')
|
|
192
|
+
.option('--mode <mode>', 'Force specific adoption mode (legacy: fresh/partial/full)')
|
|
189
193
|
.action((options) => {
|
|
190
194
|
adoptCommand(options);
|
|
191
195
|
});
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adoption Logger
|
|
3
|
+
*
|
|
4
|
+
* Provides detailed logging for debugging and troubleshooting adoption process.
|
|
5
|
+
* Logs internal operations not shown in progress display.
|
|
6
|
+
*
|
|
7
|
+
* Core Responsibilities:
|
|
8
|
+
* - Log all operations when verbose mode is enabled
|
|
9
|
+
* - Include timestamps and operation details
|
|
10
|
+
* - Write logs to file for later review
|
|
11
|
+
* - Support different log levels (info, debug, verbose)
|
|
12
|
+
* - Don't clutter normal output
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const path = require('path');
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Log levels
|
|
20
|
+
*/
|
|
21
|
+
const LogLevel = {
|
|
22
|
+
ERROR: 0,
|
|
23
|
+
WARN: 1,
|
|
24
|
+
INFO: 2,
|
|
25
|
+
DEBUG: 3,
|
|
26
|
+
VERBOSE: 4
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Log level names
|
|
31
|
+
*/
|
|
32
|
+
const LogLevelNames = {
|
|
33
|
+
0: 'ERROR',
|
|
34
|
+
1: 'WARN',
|
|
35
|
+
2: 'INFO',
|
|
36
|
+
3: 'DEBUG',
|
|
37
|
+
4: 'VERBOSE'
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Adoption Logger for detailed operation logging
|
|
42
|
+
*/
|
|
43
|
+
class AdoptionLogger {
|
|
44
|
+
constructor(options = {}) {
|
|
45
|
+
this.enabled = options.enabled !== false;
|
|
46
|
+
this.level = this._parseLogLevel(options.level || 'info');
|
|
47
|
+
this.logToFile = options.logToFile !== false;
|
|
48
|
+
this.logToConsole = options.logToConsole || false;
|
|
49
|
+
this.logFilePath = options.logFilePath || null;
|
|
50
|
+
this.logBuffer = [];
|
|
51
|
+
this.maxBufferSize = options.maxBufferSize || 1000;
|
|
52
|
+
this.startTime = Date.now();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Parses log level from string or number
|
|
57
|
+
*
|
|
58
|
+
* @param {string|number} level - Log level
|
|
59
|
+
* @returns {number}
|
|
60
|
+
* @private
|
|
61
|
+
*/
|
|
62
|
+
_parseLogLevel(level) {
|
|
63
|
+
if (typeof level === 'number') {
|
|
64
|
+
return level;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const levelMap = {
|
|
68
|
+
'error': 0, // LogLevel.ERROR
|
|
69
|
+
'warn': 1, // LogLevel.WARN
|
|
70
|
+
'info': 2, // LogLevel.INFO
|
|
71
|
+
'debug': 3, // LogLevel.DEBUG
|
|
72
|
+
'verbose': 4 // LogLevel.VERBOSE
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
return levelMap[level.toLowerCase()] !== undefined ? levelMap[level.toLowerCase()] : 2; // Default to INFO
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Initializes log file
|
|
80
|
+
*
|
|
81
|
+
* @param {string} projectPath - Project path
|
|
82
|
+
* @param {string} adoptionId - Adoption ID (timestamp)
|
|
83
|
+
*/
|
|
84
|
+
initialize(projectPath, adoptionId) {
|
|
85
|
+
if (!this.logToFile) return;
|
|
86
|
+
|
|
87
|
+
try {
|
|
88
|
+
// Create logs directory if it doesn't exist
|
|
89
|
+
const logsDir = path.join(projectPath, '.kiro', 'logs');
|
|
90
|
+
if (!fs.existsSync(logsDir)) {
|
|
91
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Set log file path
|
|
95
|
+
this.logFilePath = path.join(logsDir, `adopt-${adoptionId}.log`);
|
|
96
|
+
|
|
97
|
+
// Write header
|
|
98
|
+
const header = [
|
|
99
|
+
'='.repeat(80),
|
|
100
|
+
`Kiro Spec Engine - Adoption Log`,
|
|
101
|
+
`Adoption ID: ${adoptionId}`,
|
|
102
|
+
`Start Time: ${new Date().toISOString()}`,
|
|
103
|
+
`Log Level: ${LogLevelNames[this.level]}`,
|
|
104
|
+
'='.repeat(80),
|
|
105
|
+
''
|
|
106
|
+
].join('\n');
|
|
107
|
+
|
|
108
|
+
fs.writeFileSync(this.logFilePath, header, 'utf8');
|
|
109
|
+
|
|
110
|
+
this.info('Logger initialized', { logFile: this.logFilePath });
|
|
111
|
+
} catch (error) {
|
|
112
|
+
// Silently fail - logging is not critical
|
|
113
|
+
this.logToFile = false;
|
|
114
|
+
this.logFilePath = null;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Logs a message at specified level
|
|
120
|
+
*
|
|
121
|
+
* @param {number} level - Log level
|
|
122
|
+
* @param {string} message - Log message
|
|
123
|
+
* @param {Object} data - Additional data
|
|
124
|
+
* @private
|
|
125
|
+
*/
|
|
126
|
+
_log(level, message, data = null) {
|
|
127
|
+
if (!this.enabled || level > this.level) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const timestamp = new Date().toISOString();
|
|
132
|
+
const elapsed = Date.now() - this.startTime;
|
|
133
|
+
const levelName = LogLevelNames[level];
|
|
134
|
+
|
|
135
|
+
// Format log entry
|
|
136
|
+
const logEntry = {
|
|
137
|
+
timestamp,
|
|
138
|
+
elapsed,
|
|
139
|
+
level: levelName,
|
|
140
|
+
message,
|
|
141
|
+
data
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
// Add to buffer
|
|
145
|
+
this.logBuffer.push(logEntry);
|
|
146
|
+
if (this.logBuffer.length > this.maxBufferSize) {
|
|
147
|
+
this.logBuffer.shift();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Format for output
|
|
151
|
+
const formattedMessage = this._formatLogEntry(logEntry);
|
|
152
|
+
|
|
153
|
+
// Write to console if enabled
|
|
154
|
+
if (this.logToConsole) {
|
|
155
|
+
console.log(formattedMessage);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Write to file if enabled
|
|
159
|
+
if (this.logToFile && this.logFilePath) {
|
|
160
|
+
try {
|
|
161
|
+
fs.appendFileSync(this.logFilePath, formattedMessage + '\n', 'utf8');
|
|
162
|
+
} catch (error) {
|
|
163
|
+
// Silently fail - logging is not critical
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Formats log entry for output
|
|
170
|
+
*
|
|
171
|
+
* @param {Object} entry - Log entry
|
|
172
|
+
* @returns {string}
|
|
173
|
+
* @private
|
|
174
|
+
*/
|
|
175
|
+
_formatLogEntry(entry) {
|
|
176
|
+
const parts = [
|
|
177
|
+
`[${entry.timestamp}]`,
|
|
178
|
+
`[+${this._formatElapsed(entry.elapsed)}]`,
|
|
179
|
+
`[${entry.level}]`,
|
|
180
|
+
entry.message
|
|
181
|
+
];
|
|
182
|
+
|
|
183
|
+
if (entry.data) {
|
|
184
|
+
parts.push(JSON.stringify(entry.data, null, 2));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return parts.join(' ');
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Formats elapsed time
|
|
192
|
+
*
|
|
193
|
+
* @param {number} ms - Milliseconds
|
|
194
|
+
* @returns {string}
|
|
195
|
+
* @private
|
|
196
|
+
*/
|
|
197
|
+
_formatElapsed(ms) {
|
|
198
|
+
if (ms < 1000) {
|
|
199
|
+
return `${ms}ms`;
|
|
200
|
+
} else if (ms < 60000) {
|
|
201
|
+
return `${(ms / 1000).toFixed(2)}s`;
|
|
202
|
+
} else {
|
|
203
|
+
const minutes = Math.floor(ms / 60000);
|
|
204
|
+
const seconds = ((ms % 60000) / 1000).toFixed(2);
|
|
205
|
+
return `${minutes}m${seconds}s`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Logs error message
|
|
211
|
+
*
|
|
212
|
+
* @param {string} message - Error message
|
|
213
|
+
* @param {Object} data - Additional data
|
|
214
|
+
*/
|
|
215
|
+
error(message, data = null) {
|
|
216
|
+
this._log(LogLevel.ERROR, message, data);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Logs warning message
|
|
221
|
+
*
|
|
222
|
+
* @param {string} message - Warning message
|
|
223
|
+
* @param {Object} data - Additional data
|
|
224
|
+
*/
|
|
225
|
+
warn(message, data = null) {
|
|
226
|
+
this._log(LogLevel.WARN, message, data);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Logs info message
|
|
231
|
+
*
|
|
232
|
+
* @param {string} message - Info message
|
|
233
|
+
* @param {Object} data - Additional data
|
|
234
|
+
*/
|
|
235
|
+
info(message, data = null) {
|
|
236
|
+
this._log(LogLevel.INFO, message, data);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Logs debug message
|
|
241
|
+
*
|
|
242
|
+
* @param {string} message - Debug message
|
|
243
|
+
* @param {Object} data - Additional data
|
|
244
|
+
*/
|
|
245
|
+
debug(message, data = null) {
|
|
246
|
+
this._log(LogLevel.DEBUG, message, data);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Logs verbose message
|
|
251
|
+
*
|
|
252
|
+
* @param {string} message - Verbose message
|
|
253
|
+
* @param {Object} data - Additional data
|
|
254
|
+
*/
|
|
255
|
+
verbose(message, data = null) {
|
|
256
|
+
this._log(LogLevel.VERBOSE, message, data);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Logs operation start
|
|
261
|
+
*
|
|
262
|
+
* @param {string} operation - Operation name
|
|
263
|
+
* @param {Object} params - Operation parameters
|
|
264
|
+
*/
|
|
265
|
+
startOperation(operation, params = null) {
|
|
266
|
+
this.info(`Starting operation: ${operation}`, params);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Logs operation end
|
|
271
|
+
*
|
|
272
|
+
* @param {string} operation - Operation name
|
|
273
|
+
* @param {Object} result - Operation result
|
|
274
|
+
*/
|
|
275
|
+
endOperation(operation, result = null) {
|
|
276
|
+
this.info(`Completed operation: ${operation}`, result);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Logs operation error
|
|
281
|
+
*
|
|
282
|
+
* @param {string} operation - Operation name
|
|
283
|
+
* @param {Error} error - Error object
|
|
284
|
+
*/
|
|
285
|
+
operationError(operation, error) {
|
|
286
|
+
this.error(`Operation failed: ${operation}`, {
|
|
287
|
+
message: error.message,
|
|
288
|
+
stack: error.stack
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Logs file operation
|
|
294
|
+
*
|
|
295
|
+
* @param {string} operation - Operation type (create, update, delete, preserve)
|
|
296
|
+
* @param {string} filePath - File path
|
|
297
|
+
* @param {Object} details - Additional details
|
|
298
|
+
*/
|
|
299
|
+
fileOperation(operation, filePath, details = null) {
|
|
300
|
+
this.debug(`File ${operation}: ${filePath}`, details);
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Logs detection result
|
|
305
|
+
*
|
|
306
|
+
* @param {Object} state - Project state
|
|
307
|
+
*/
|
|
308
|
+
detectionResult(state) {
|
|
309
|
+
this.info('Project state detected', {
|
|
310
|
+
hasKiroDir: state.hasKiroDir,
|
|
311
|
+
hasVersionFile: state.hasVersionFile,
|
|
312
|
+
currentVersion: state.currentVersion,
|
|
313
|
+
targetVersion: state.targetVersion,
|
|
314
|
+
conflictsCount: state.conflicts ? state.conflicts.length : 0
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Logs strategy selection
|
|
320
|
+
*
|
|
321
|
+
* @param {string} mode - Selected mode
|
|
322
|
+
* @param {string} reason - Selection reason
|
|
323
|
+
*/
|
|
324
|
+
strategySelected(mode, reason) {
|
|
325
|
+
this.info('Strategy selected', { mode, reason });
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Logs conflict resolution
|
|
330
|
+
*
|
|
331
|
+
* @param {string} filePath - File path
|
|
332
|
+
* @param {string} resolution - Resolution action
|
|
333
|
+
* @param {string} reason - Resolution reason
|
|
334
|
+
*/
|
|
335
|
+
conflictResolved(filePath, resolution, reason) {
|
|
336
|
+
this.debug('Conflict resolved', { filePath, resolution, reason });
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Logs backup creation
|
|
341
|
+
*
|
|
342
|
+
* @param {Object} backup - Backup result
|
|
343
|
+
*/
|
|
344
|
+
backupCreated(backup) {
|
|
345
|
+
this.info('Backup created', {
|
|
346
|
+
id: backup.id,
|
|
347
|
+
location: backup.location,
|
|
348
|
+
filesCount: backup.filesCount,
|
|
349
|
+
totalSize: backup.totalSize
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Logs validation result
|
|
355
|
+
*
|
|
356
|
+
* @param {Object} validation - Validation result
|
|
357
|
+
*/
|
|
358
|
+
validationResult(validation) {
|
|
359
|
+
if (validation.success) {
|
|
360
|
+
this.info('Validation successful', {
|
|
361
|
+
filesVerified: validation.filesVerified
|
|
362
|
+
});
|
|
363
|
+
} else {
|
|
364
|
+
this.error('Validation failed', {
|
|
365
|
+
error: validation.error
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Logs adoption plan
|
|
372
|
+
*
|
|
373
|
+
* @param {Object} plan - Adoption plan
|
|
374
|
+
*/
|
|
375
|
+
adoptionPlan(plan) {
|
|
376
|
+
this.info('Adoption plan created', {
|
|
377
|
+
mode: plan.mode,
|
|
378
|
+
requiresBackup: plan.requiresBackup,
|
|
379
|
+
changesCount: {
|
|
380
|
+
created: plan.changes.created.length,
|
|
381
|
+
updated: plan.changes.updated.length,
|
|
382
|
+
deleted: plan.changes.deleted ? plan.changes.deleted.length : 0,
|
|
383
|
+
preserved: plan.changes.preserved.length
|
|
384
|
+
}
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Logs adoption result
|
|
390
|
+
*
|
|
391
|
+
* @param {Object} result - Adoption result
|
|
392
|
+
*/
|
|
393
|
+
adoptionResult(result) {
|
|
394
|
+
if (result.success) {
|
|
395
|
+
this.info('Adoption completed successfully', {
|
|
396
|
+
mode: result.mode,
|
|
397
|
+
backupId: result.backup ? result.backup.id : null,
|
|
398
|
+
changesCount: {
|
|
399
|
+
created: result.changes.created.length,
|
|
400
|
+
updated: result.changes.updated.length,
|
|
401
|
+
deleted: result.changes.deleted ? result.changes.deleted.length : 0,
|
|
402
|
+
preserved: result.changes.preserved.length
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
} else {
|
|
406
|
+
this.error('Adoption failed', {
|
|
407
|
+
errors: result.errors
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Flushes log buffer to file
|
|
414
|
+
*/
|
|
415
|
+
flush() {
|
|
416
|
+
if (!this.logToFile || !this.logFilePath) return;
|
|
417
|
+
|
|
418
|
+
try {
|
|
419
|
+
// Write footer
|
|
420
|
+
const footer = [
|
|
421
|
+
'',
|
|
422
|
+
'='.repeat(80),
|
|
423
|
+
`End Time: ${new Date().toISOString()}`,
|
|
424
|
+
`Total Duration: ${this._formatElapsed(Date.now() - this.startTime)}`,
|
|
425
|
+
`Total Log Entries: ${this.logBuffer.length}`,
|
|
426
|
+
'='.repeat(80)
|
|
427
|
+
].join('\n');
|
|
428
|
+
|
|
429
|
+
fs.appendFileSync(this.logFilePath, footer + '\n', 'utf8');
|
|
430
|
+
} catch (error) {
|
|
431
|
+
// Silently fail - logging is not critical
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* Gets log file path
|
|
437
|
+
*
|
|
438
|
+
* @returns {string|null}
|
|
439
|
+
*/
|
|
440
|
+
getLogFilePath() {
|
|
441
|
+
return this.logFilePath;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
/**
|
|
445
|
+
* Gets log buffer
|
|
446
|
+
*
|
|
447
|
+
* @returns {Array}
|
|
448
|
+
*/
|
|
449
|
+
getLogBuffer() {
|
|
450
|
+
return this.logBuffer;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
/**
|
|
454
|
+
* Clears log buffer
|
|
455
|
+
*/
|
|
456
|
+
clearBuffer() {
|
|
457
|
+
this.logBuffer = [];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* Enables logging
|
|
462
|
+
*/
|
|
463
|
+
enable() {
|
|
464
|
+
this.enabled = true;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Disables logging
|
|
469
|
+
*/
|
|
470
|
+
disable() {
|
|
471
|
+
this.enabled = false;
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Sets log level
|
|
476
|
+
*
|
|
477
|
+
* @param {string|number} level - Log level
|
|
478
|
+
*/
|
|
479
|
+
setLevel(level) {
|
|
480
|
+
this.level = this._parseLogLevel(level);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
// Export class and constants
|
|
485
|
+
module.exports = AdoptionLogger;
|
|
486
|
+
module.exports.LogLevel = LogLevel;
|
|
487
|
+
module.exports.LogLevelNames = LogLevelNames;
|