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 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 🔧
@@ -182,10 +182,14 @@ program
182
182
  program
183
183
  .command('adopt')
184
184
  .description('Adopt existing project into Kiro Spec Engine')
185
- .option('--auto', 'Skip confirmations (use with caution)')
185
+ .option('--interactive', 'Enable interactive mode (legacy behavior with prompts)')
186
186
  .option('--dry-run', 'Show what would change without making changes')
187
- .option('--mode <mode>', 'Force specific adoption mode (fresh/partial/full)')
188
- .option('--force', 'Force overwrite conflicting files (creates backup first)')
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;