aios-core 4.2.13 → 4.2.14

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.
Files changed (97) hide show
  1. package/.aios-core/core/code-intel/helpers/dev-helper.js +206 -0
  2. package/.aios-core/core/registry/registry-schema.json +166 -166
  3. package/.aios-core/core/synapse/diagnostics/collectors/hook-collector.js +3 -3
  4. package/.aios-core/data/entity-registry.yaml +27 -0
  5. package/.aios-core/development/scripts/approval-workflow.js +642 -642
  6. package/.aios-core/development/scripts/backup-manager.js +606 -606
  7. package/.aios-core/development/scripts/branch-manager.js +389 -389
  8. package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
  9. package/.aios-core/development/scripts/commit-message-generator.js +849 -849
  10. package/.aios-core/development/scripts/conflict-resolver.js +674 -674
  11. package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
  12. package/.aios-core/development/scripts/diff-generator.js +351 -351
  13. package/.aios-core/development/scripts/elicitation-engine.js +384 -384
  14. package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
  15. package/.aios-core/development/scripts/git-wrapper.js +461 -461
  16. package/.aios-core/development/scripts/manifest-preview.js +244 -244
  17. package/.aios-core/development/scripts/metrics-tracker.js +775 -775
  18. package/.aios-core/development/scripts/modification-validator.js +554 -554
  19. package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
  20. package/.aios-core/development/scripts/performance-analyzer.js +757 -757
  21. package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
  22. package/.aios-core/development/scripts/rollback-handler.js +530 -530
  23. package/.aios-core/development/scripts/security-checker.js +358 -358
  24. package/.aios-core/development/scripts/template-engine.js +239 -239
  25. package/.aios-core/development/scripts/template-validator.js +278 -278
  26. package/.aios-core/development/scripts/test-generator.js +843 -843
  27. package/.aios-core/development/scripts/transaction-manager.js +589 -589
  28. package/.aios-core/development/scripts/usage-tracker.js +673 -673
  29. package/.aios-core/development/scripts/validate-filenames.js +226 -226
  30. package/.aios-core/development/scripts/version-tracker.js +526 -526
  31. package/.aios-core/development/scripts/yaml-validator.js +396 -396
  32. package/.aios-core/development/tasks/build-autonomous.md +10 -4
  33. package/.aios-core/development/tasks/create-service.md +23 -0
  34. package/.aios-core/development/tasks/dev-develop-story.md +12 -6
  35. package/.aios-core/development/tasks/dev-suggest-refactoring.md +7 -1
  36. package/.aios-core/development/tasks/publish-npm.md +3 -3
  37. package/.aios-core/hooks/unified/README.md +1 -1
  38. package/.aios-core/install-manifest.yaml +65 -61
  39. package/.aios-core/manifests/schema/manifest-schema.json +190 -190
  40. package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
  41. package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
  42. package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
  43. package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
  44. package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
  45. package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
  46. package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
  47. package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
  48. package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
  49. package/.aios-core/product/templates/eslintrc-security.json +32 -32
  50. package/.aios-core/product/templates/github-actions-cd.yml +212 -212
  51. package/.aios-core/product/templates/github-actions-ci.yml +172 -172
  52. package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
  53. package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
  54. package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
  55. package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
  56. package/README.en.md +747 -0
  57. package/README.md +4 -2
  58. package/bin/aios.js +7 -4
  59. package/package.json +1 -1
  60. package/packages/aios-pro-cli/src/recover.js +1 -1
  61. package/packages/installer/src/wizard/ide-config-generator.js +6 -6
  62. package/packages/installer/src/wizard/pro-setup.js +3 -3
  63. package/scripts/package-synapse.js +5 -5
  64. package/scripts/validate-package-completeness.js +3 -3
  65. package/.aios-core/.session/current-session.json +0 -14
  66. package/.aios-core/data/registry-update-log.jsonl +0 -191
  67. package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
  68. package/.aios-core/docs/component-creation-guide.md +0 -458
  69. package/.aios-core/docs/session-update-pattern.md +0 -307
  70. package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
  71. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
  72. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
  73. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
  74. package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
  75. package/.aios-core/docs/template-syntax.md +0 -267
  76. package/.aios-core/docs/troubleshooting-guide.md +0 -625
  77. package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
  78. package/.aios-core/manifests/agents.csv +0 -29
  79. package/.aios-core/manifests/tasks.csv +0 -198
  80. package/.aios-core/manifests/workers.csv +0 -204
  81. package/.claude/rules/agent-authority.md +0 -105
  82. package/.claude/rules/coderabbit-integration.md +0 -93
  83. package/.claude/rules/ids-principles.md +0 -112
  84. package/.claude/rules/story-lifecycle.md +0 -139
  85. package/.claude/rules/workflow-execution.md +0 -150
  86. package/pro/README.md +0 -66
  87. package/pro/license/degradation.js +0 -220
  88. package/pro/license/errors.js +0 -450
  89. package/pro/license/feature-gate.js +0 -354
  90. package/pro/license/index.js +0 -181
  91. package/pro/license/license-api.js +0 -651
  92. package/pro/license/license-cache.js +0 -523
  93. package/pro/license/license-crypto.js +0 -303
  94. package/scripts/glue/README.md +0 -355
  95. package/scripts/glue/compose-agent-prompt.cjs +0 -362
  96. /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
  97. /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
@@ -1,531 +1,531 @@
1
- /**
2
- * Rollback Handler for AIOS-FULLSTACK
3
- * Handles undo operations for component transactions
4
- * @module rollback-handler
5
- */
6
-
7
- const TransactionManager = require('./transaction-manager');
8
- const chalk = require('chalk');
9
- const inquirer = require('inquirer');
10
- const path = require('path');
11
- const fs = require('fs').promises;
12
- const ModificationValidator = require('./modification-validator');
13
-
14
- class RollbackHandler {
15
- constructor(options = {}) {
16
- this.rootPath = options.rootPath || process.cwd();
17
- this.transactionManager = new TransactionManager({ rootPath: this.rootPath });
18
- this.modificationValidator = new ModificationValidator();
19
- this.backupPath = path.join(this.rootPath, 'aios-core', '.backups');
20
- }
21
-
22
- /**
23
- * Execute undo-last command
24
- * @param {Object} options - Rollback options
25
- * @returns {Promise<Object>} Rollback result
26
- */
27
- async undoLast(options = {}) {
28
- try {
29
- let transaction;
30
-
31
- if (options.transactionId) {
32
- // Load specific transaction
33
- transaction = await this.transactionManager.loadTransaction(options.transactionId);
34
- if (!transaction) {
35
- throw new Error(`Transaction not found: ${options.transactionId}`);
36
- }
37
- } else {
38
- // Get last transaction
39
- transaction = await this.transactionManager.getLastTransaction();
40
- if (!transaction) {
41
- console.log(chalk.yellow('No transactions found to rollback'));
42
- return { success: false, error: 'No transactions found' };
43
- }
44
- }
45
-
46
- // Display transaction details
47
- console.log(chalk.blue('\n📋 Transaction Details:'));
48
- console.log(chalk.gray(`ID: ${transaction.id}`));
49
- console.log(chalk.gray(`Type: ${transaction.type}`));
50
- console.log(chalk.gray(`Date: ${new Date(transaction.startTime).toLocaleString()}`));
51
- console.log(chalk.gray(`Status: ${transaction.status}`));
52
- console.log(chalk.gray(`Operations: ${transaction.operations.length}`));
53
-
54
- // Show operations
55
- console.log(chalk.blue('\n📝 Operations to rollback:'));
56
- for (const op of transaction.operations) {
57
- const icon = this.getOperationIcon(op.type);
58
- console.log(chalk.gray(` ${icon} ${op.type}: ${path.basename(op.path)}`));
59
- }
60
-
61
- // Confirm rollback
62
- if (!options.force) {
63
- const { confirm } = await inquirer.prompt([{
64
- type: 'confirm',
65
- name: 'confirm',
66
- message: 'Do you want to rollback this transaction?',
67
- default: true
68
- }]);
69
-
70
- if (!confirm) {
71
- console.log(chalk.yellow('Rollback cancelled'));
72
- return { success: false, error: 'User cancelled' };
73
- }
74
- }
75
-
76
- // Execute rollback
77
- console.log(chalk.blue('\n⚙️ Executing rollback...'));
78
-
79
- const rollbackResult = await this.transactionManager.rollbackTransaction(
80
- transaction.id,
81
- {
82
- continueOnError: options.continueOnError !== false
83
- }
84
- );
85
-
86
- // Display results
87
- this.displayRollbackResults(rollbackResult);
88
-
89
- return {
90
- success: rollbackResult.failed.length === 0,
91
- result: rollbackResult
92
- };
93
-
94
- } catch (error) {
95
- console.error(chalk.red(`\n❌ Rollback failed: ${error.message}`));
96
- return {
97
- success: false,
98
- error: error.message
99
- };
100
- }
101
- }
102
-
103
- /**
104
- * List recent transactions
105
- * @param {number} limit - Number of transactions to show
106
- * @returns {Promise<void>}
107
- */
108
- async listTransactions(limit = 10) {
109
- try {
110
- const transactions = await this.transactionManager.listTransactions(limit);
111
-
112
- if (transactions.length === 0) {
113
- console.log(chalk.yellow('No transactions found'));
114
- return;
115
- }
116
-
117
- console.log(chalk.blue('\n📋 Recent Transactions:'));
118
- console.log(chalk.gray('─'.repeat(80)));
119
-
120
- for (const txn of transactions) {
121
- const date = new Date(txn.startTime).toLocaleString();
122
- const duration = txn.endTime
123
- ? `${new Date(txn.endTime) - new Date(txn.startTime)}ms`
124
- : 'active';
125
-
126
- console.log(chalk.white(`\nID: ${txn.id}`));
127
- console.log(chalk.gray(`Type: ${txn.type}`));
128
- console.log(chalk.gray(`Description: ${txn.description}`));
129
- console.log(chalk.gray(`User: ${txn.user}`));
130
- console.log(chalk.gray(`Date: ${date}`));
131
- console.log(chalk.gray(`Status: ${this.getStatusColor(txn.status)}`));
132
- console.log(chalk.gray(`Operations: ${txn.operations}`));
133
- console.log(chalk.gray(`Duration: ${duration}`));
134
- console.log(chalk.gray('─'.repeat(80)));
135
- }
136
-
137
- } catch (error) {
138
- console.error(chalk.red(`Failed to list transactions: ${error.message}`));
139
- }
140
- }
141
-
142
- /**
143
- * Execute selective rollback
144
- * @param {string} transactionId - Transaction ID
145
- * @param {Array<string>} operationIds - Specific operations to rollback
146
- * @returns {Promise<Object>} Rollback result
147
- */
148
- async selectiveRollback(transactionId, operationIds) {
149
- try {
150
- const transaction = await this.transactionManager.loadTransaction(transactionId);
151
- if (!transaction) {
152
- throw new Error(`Transaction not found: ${transactionId}`);
153
- }
154
-
155
- // Filter operations
156
- const selectedOps = transaction.operations.filter(op =>
157
- operationIds.includes(op.id)
158
- );
159
-
160
- if (selectedOps.length === 0) {
161
- throw new Error('No matching operations found');
162
- }
163
-
164
- console.log(chalk.blue(`\n📝 Selective rollback: ${selectedOps.length} operations`));
165
-
166
- // Create a new transaction for selective rollback
167
- const rollbackTxnId = await this.transactionManager.beginTransaction({
168
- type: 'selective_rollback',
169
- description: `Selective rollback of ${transactionId}`,
170
- metadata: {
171
- originalTransaction: transactionId,
172
- selectedOperations: operationIds
173
- }
174
- });
175
-
176
- const results = {
177
- successful: [],
178
- failed: [],
179
- warnings: []
180
- };
181
-
182
- // Rollback selected operations
183
- for (const op of selectedOps) {
184
- try {
185
- await this.transactionManager.rollbackOperation(op, results);
186
- } catch (error) {
187
- results.failed.push({
188
- operation: op.id,
189
- error: error.message
190
- });
191
- }
192
- }
193
-
194
- // Commit rollback transaction
195
- await this.transactionManager.commitTransaction(rollbackTxnId);
196
-
197
- this.displayRollbackResults(results);
198
-
199
- return {
200
- success: results.failed.length === 0,
201
- result: results
202
- };
203
-
204
- } catch (error) {
205
- console.error(chalk.red(`Selective rollback failed: ${error.message}`));
206
- return {
207
- success: false,
208
- error: error.message
209
- };
210
- }
211
- }
212
-
213
- /**
214
- * Clean up old transactions
215
- * @returns {Promise<number>} Number cleaned
216
- */
217
- async cleanup() {
218
- try {
219
- console.log(chalk.blue('🧹 Cleaning up old transactions...'));
220
- const cleaned = await this.transactionManager.cleanupOldTransactions();
221
- console.log(chalk.green(`✅ Cleaned up ${cleaned} old transactions`));
222
- return cleaned;
223
- } catch (error) {
224
- console.error(chalk.red(`Cleanup failed: ${error.message}`));
225
- return 0;
226
- }
227
- }
228
-
229
- /**
230
- * Create backup before modification
231
- * @param {string} componentType - Type of component
232
- * @param {string} componentName - Name of component
233
- * @param {string} content - Content to backup
234
- * @returns {Promise<string>} Backup file path
235
- */
236
- async createBackup(componentType, componentName, content) {
237
- try {
238
- // Ensure backup directory exists
239
- await fs.mkdir(this.backupPath, { recursive: true });
240
-
241
- // Create subdirectory for component type
242
- const typeBackupPath = path.join(this.backupPath, componentType);
243
- await fs.mkdir(typeBackupPath, { recursive: true });
244
-
245
- // Generate backup filename with timestamp
246
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
247
- const backupFilename = `${componentName}.${timestamp}.backup`;
248
- const backupFilePath = path.join(typeBackupPath, backupFilename);
249
-
250
- // Write backup
251
- await fs.writeFile(backupFilePath, content, 'utf8');
252
-
253
- // Record backup in transaction
254
- if (this.currentTransactionId) {
255
- await this.transactionManager.recordOperation({
256
- type: 'backup_created',
257
- path: backupFilePath,
258
- componentType,
259
- componentName,
260
- timestamp
261
- });
262
- }
263
-
264
- return backupFilePath;
265
- } catch (error) {
266
- throw new Error(`Failed to create backup: ${error.message}`);
267
- }
268
- }
269
-
270
- /**
271
- * Restore from backup
272
- * @param {string} backupPath - Path to backup file
273
- * @param {string} targetPath - Path to restore to
274
- * @returns {Promise<boolean>} Success status
275
- */
276
- async restoreFromBackup(backupPath, targetPath) {
277
- try {
278
- // Verify backup exists
279
- await fs.access(backupPath);
280
-
281
- // Read backup content
282
- const content = await fs.readFile(backupPath, 'utf8');
283
-
284
- // Restore to target
285
- await fs.writeFile(targetPath, content, 'utf8');
286
-
287
- console.log(chalk.green(`✅ Restored from backup: ${path.basename(backupPath)}`));
288
- return true;
289
- } catch (error) {
290
- console.error(chalk.red(`Failed to restore from backup: ${error.message}`));
291
- return false;
292
- }
293
- }
294
-
295
- /**
296
- * Validate modification before applying
297
- * @param {string} componentType - Type of component
298
- * @param {string} originalContent - Original content
299
- * @param {string} modifiedContent - Modified content
300
- * @returns {Promise<Object>} Validation result
301
- */
302
- async validateModification(componentType, originalContent, modifiedContent) {
303
- const validation = await this.modificationValidator.validateModification(
304
- componentType,
305
- originalContent,
306
- modifiedContent
307
- );
308
-
309
- if (!validation.valid) {
310
- console.log(chalk.red('\n❌ Modification validation failed:'));
311
- validation.errors.forEach(error => {
312
- console.log(chalk.red(` • ${error}`));
313
- });
314
- }
315
-
316
- if (validation.warnings.length > 0) {
317
- console.log(chalk.yellow('\n⚠️ Warnings:'));
318
- validation.warnings.forEach(warning => {
319
- console.log(chalk.yellow(` • ${warning}`));
320
- });
321
- }
322
-
323
- if (validation.breakingChanges.length > 0) {
324
- console.log(chalk.red('\n🚨 Breaking Changes Detected:'));
325
- validation.breakingChanges.forEach(change => {
326
- console.log(chalk.red(` • ${change.type}: ${change.impact}`));
327
- if (change.items) {
328
- console.log(chalk.red(` Items: ${change.items.join(', ')}`));
329
- }
330
- });
331
- }
332
-
333
- return validation;
334
- }
335
-
336
- /**
337
- * Rollback modification
338
- * @param {Object} modificationData - Modification details
339
- * @returns {Promise<Object>} Rollback result
340
- */
341
- async rollbackModification(modificationData) {
342
- const { componentType, componentName, backupPath, targetPath } = modificationData;
343
-
344
- try {
345
- console.log(chalk.blue(`\n⏪ Rolling back ${componentType}: ${componentName}`));
346
-
347
- // Restore from backup
348
- const restored = await this.restoreFromBackup(backupPath, targetPath);
349
-
350
- if (restored) {
351
- // Record rollback
352
- await this.transactionManager.recordOperation({
353
- type: 'modification_rollback',
354
- componentType,
355
- componentName,
356
- backupPath,
357
- targetPath,
358
- timestamp: new Date().toISOString()
359
- });
360
-
361
- return {
362
- success: true,
363
- message: `Successfully rolled back ${componentType}: ${componentName}`
364
- };
365
- } else {
366
- throw new Error('Restoration failed');
367
- }
368
- } catch (error) {
369
- return {
370
- success: false,
371
- error: error.message
372
- };
373
- }
374
- }
375
-
376
- /**
377
- * List available backups for a component
378
- * @param {string} componentType - Type of component
379
- * @param {string} componentName - Name of component
380
- * @returns {Promise<Array>} List of backups
381
- */
382
- async listBackups(componentType, componentName) {
383
- try {
384
- const typeBackupPath = path.join(this.backupPath, componentType);
385
- const files = await fs.readdir(typeBackupPath);
386
-
387
- const backups = files
388
- .filter(file => file.startsWith(`${componentName}.`) && file.endsWith('.backup'))
389
- .map(file => {
390
- const match = file.match(/\.(\d{4}-\d{2}-\d{2}T[\d-]+Z)\.backup$/);
391
- const timestamp = match ? match[1].replace(/-/g, ':') : 'unknown';
392
-
393
- return {
394
- filename: file,
395
- path: path.join(typeBackupPath, file),
396
- timestamp: new Date(timestamp),
397
- componentName,
398
- componentType
399
- };
400
- })
401
- .sort((a, b) => b.timestamp - a.timestamp);
402
-
403
- return backups;
404
- } catch (error) {
405
- if (error.code === 'ENOENT') {
406
- return [];
407
- }
408
- throw error;
409
- }
410
- }
411
-
412
- /**
413
- * Clean up old backups
414
- * @param {number} daysToKeep - Number of days to keep backups
415
- * @returns {Promise<number>} Number of backups deleted
416
- */
417
- async cleanupBackups(daysToKeep = 30) {
418
- try {
419
- const cutoffDate = new Date();
420
- cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
421
-
422
- let deletedCount = 0;
423
-
424
- // Iterate through component types
425
- const componentTypes = await fs.readdir(this.backupPath);
426
-
427
- for (const componentType of componentTypes) {
428
- const typePath = path.join(this.backupPath, componentType);
429
- const stat = await fs.stat(typePath);
430
-
431
- if (!stat.isDirectory()) continue;
432
-
433
- const files = await fs.readdir(typePath);
434
-
435
- for (const file of files) {
436
- if (!file.endsWith('.backup')) continue;
437
-
438
- const filePath = path.join(typePath, file);
439
- const fileStat = await fs.stat(filePath);
440
-
441
- if (fileStat.mtime < cutoffDate) {
442
- await fs.unlink(filePath);
443
- deletedCount++;
444
- }
445
- }
446
- }
447
-
448
- return deletedCount;
449
- } catch (error) {
450
- console.error(chalk.red(`Backup cleanup failed: ${error.message}`));
451
- return 0;
452
- }
453
- }
454
-
455
- /**
456
- * Get operation icon
457
- * @private
458
- */
459
- getOperationIcon(type) {
460
- const icons = {
461
- create: '➕',
462
- update: '✏️',
463
- delete: '🗑️',
464
- manifest_update: '📋',
465
- metadata_update: '📊',
466
- component_created: '📦',
467
- backup_created: '💾',
468
- modification_rollback: '⏪'
469
- };
470
-
471
- return icons[type] || '•';
472
- }
473
-
474
- /**
475
- * Get status color
476
- * @private
477
- */
478
- getStatusColor(status) {
479
- switch (status) {
480
- case 'active':
481
- return chalk.yellow(status);
482
- case 'committed':
483
- return chalk.green(status);
484
- case 'rolled_back':
485
- return chalk.blue(status);
486
- case 'failed':
487
- return chalk.red(status);
488
- default:
489
- return status;
490
- }
491
- }
492
-
493
- /**
494
- * Display rollback results
495
- * @private
496
- */
497
- displayRollbackResults(results) {
498
- console.log(chalk.blue('\n📊 Rollback Results:'));
499
-
500
- if (results.successful.length > 0) {
501
- console.log(chalk.green(`\n✅ Successful (${results.successful.length}):`));
502
- results.successful.forEach(item => {
503
- console.log(chalk.green(` ✓ ${item.action}: ${path.basename(item.path)}`));
504
- });
505
- }
506
-
507
- if (results.warnings.length > 0) {
508
- console.log(chalk.yellow(`\n⚠️ Warnings (${results.warnings.length}):`));
509
- results.warnings.forEach(item => {
510
- console.log(chalk.yellow(` ⚠ ${item.warning}: ${path.basename(item.path || 'N/A')}`));
511
- });
512
- }
513
-
514
- if (results.failed.length > 0) {
515
- console.log(chalk.red(`\n❌ Failed (${results.failed.length}):`));
516
- results.failed.forEach(item => {
517
- console.log(chalk.red(` ✗ ${item.operation}: ${item.error}`));
518
- });
519
- }
520
-
521
- // Summary
522
- const total = results.successful.length + results.failed.length;
523
- const successRate = total > 0 ? (results.successful.length / total * 100).toFixed(0) : 0;
524
-
525
- console.log(chalk.blue('\n📈 Summary:'));
526
- console.log(chalk.gray(` Total operations: ${total}`));
527
- console.log(chalk.gray(` Success rate: ${successRate}%`));
528
- }
529
- }
530
-
1
+ /**
2
+ * Rollback Handler for AIOS-FULLSTACK
3
+ * Handles undo operations for component transactions
4
+ * @module rollback-handler
5
+ */
6
+
7
+ const TransactionManager = require('./transaction-manager');
8
+ const chalk = require('chalk');
9
+ const inquirer = require('inquirer');
10
+ const path = require('path');
11
+ const fs = require('fs').promises;
12
+ const ModificationValidator = require('./modification-validator');
13
+
14
+ class RollbackHandler {
15
+ constructor(options = {}) {
16
+ this.rootPath = options.rootPath || process.cwd();
17
+ this.transactionManager = new TransactionManager({ rootPath: this.rootPath });
18
+ this.modificationValidator = new ModificationValidator();
19
+ this.backupPath = path.join(this.rootPath, 'aios-core', '.backups');
20
+ }
21
+
22
+ /**
23
+ * Execute undo-last command
24
+ * @param {Object} options - Rollback options
25
+ * @returns {Promise<Object>} Rollback result
26
+ */
27
+ async undoLast(options = {}) {
28
+ try {
29
+ let transaction;
30
+
31
+ if (options.transactionId) {
32
+ // Load specific transaction
33
+ transaction = await this.transactionManager.loadTransaction(options.transactionId);
34
+ if (!transaction) {
35
+ throw new Error(`Transaction not found: ${options.transactionId}`);
36
+ }
37
+ } else {
38
+ // Get last transaction
39
+ transaction = await this.transactionManager.getLastTransaction();
40
+ if (!transaction) {
41
+ console.log(chalk.yellow('No transactions found to rollback'));
42
+ return { success: false, error: 'No transactions found' };
43
+ }
44
+ }
45
+
46
+ // Display transaction details
47
+ console.log(chalk.blue('\n📋 Transaction Details:'));
48
+ console.log(chalk.gray(`ID: ${transaction.id}`));
49
+ console.log(chalk.gray(`Type: ${transaction.type}`));
50
+ console.log(chalk.gray(`Date: ${new Date(transaction.startTime).toLocaleString()}`));
51
+ console.log(chalk.gray(`Status: ${transaction.status}`));
52
+ console.log(chalk.gray(`Operations: ${transaction.operations.length}`));
53
+
54
+ // Show operations
55
+ console.log(chalk.blue('\n📝 Operations to rollback:'));
56
+ for (const op of transaction.operations) {
57
+ const icon = this.getOperationIcon(op.type);
58
+ console.log(chalk.gray(` ${icon} ${op.type}: ${path.basename(op.path)}`));
59
+ }
60
+
61
+ // Confirm rollback
62
+ if (!options.force) {
63
+ const { confirm } = await inquirer.prompt([{
64
+ type: 'confirm',
65
+ name: 'confirm',
66
+ message: 'Do you want to rollback this transaction?',
67
+ default: true
68
+ }]);
69
+
70
+ if (!confirm) {
71
+ console.log(chalk.yellow('Rollback cancelled'));
72
+ return { success: false, error: 'User cancelled' };
73
+ }
74
+ }
75
+
76
+ // Execute rollback
77
+ console.log(chalk.blue('\n⚙️ Executing rollback...'));
78
+
79
+ const rollbackResult = await this.transactionManager.rollbackTransaction(
80
+ transaction.id,
81
+ {
82
+ continueOnError: options.continueOnError !== false
83
+ }
84
+ );
85
+
86
+ // Display results
87
+ this.displayRollbackResults(rollbackResult);
88
+
89
+ return {
90
+ success: rollbackResult.failed.length === 0,
91
+ result: rollbackResult
92
+ };
93
+
94
+ } catch (error) {
95
+ console.error(chalk.red(`\n❌ Rollback failed: ${error.message}`));
96
+ return {
97
+ success: false,
98
+ error: error.message
99
+ };
100
+ }
101
+ }
102
+
103
+ /**
104
+ * List recent transactions
105
+ * @param {number} limit - Number of transactions to show
106
+ * @returns {Promise<void>}
107
+ */
108
+ async listTransactions(limit = 10) {
109
+ try {
110
+ const transactions = await this.transactionManager.listTransactions(limit);
111
+
112
+ if (transactions.length === 0) {
113
+ console.log(chalk.yellow('No transactions found'));
114
+ return;
115
+ }
116
+
117
+ console.log(chalk.blue('\n📋 Recent Transactions:'));
118
+ console.log(chalk.gray('─'.repeat(80)));
119
+
120
+ for (const txn of transactions) {
121
+ const date = new Date(txn.startTime).toLocaleString();
122
+ const duration = txn.endTime
123
+ ? `${new Date(txn.endTime) - new Date(txn.startTime)}ms`
124
+ : 'active';
125
+
126
+ console.log(chalk.white(`\nID: ${txn.id}`));
127
+ console.log(chalk.gray(`Type: ${txn.type}`));
128
+ console.log(chalk.gray(`Description: ${txn.description}`));
129
+ console.log(chalk.gray(`User: ${txn.user}`));
130
+ console.log(chalk.gray(`Date: ${date}`));
131
+ console.log(chalk.gray(`Status: ${this.getStatusColor(txn.status)}`));
132
+ console.log(chalk.gray(`Operations: ${txn.operations}`));
133
+ console.log(chalk.gray(`Duration: ${duration}`));
134
+ console.log(chalk.gray('─'.repeat(80)));
135
+ }
136
+
137
+ } catch (error) {
138
+ console.error(chalk.red(`Failed to list transactions: ${error.message}`));
139
+ }
140
+ }
141
+
142
+ /**
143
+ * Execute selective rollback
144
+ * @param {string} transactionId - Transaction ID
145
+ * @param {Array<string>} operationIds - Specific operations to rollback
146
+ * @returns {Promise<Object>} Rollback result
147
+ */
148
+ async selectiveRollback(transactionId, operationIds) {
149
+ try {
150
+ const transaction = await this.transactionManager.loadTransaction(transactionId);
151
+ if (!transaction) {
152
+ throw new Error(`Transaction not found: ${transactionId}`);
153
+ }
154
+
155
+ // Filter operations
156
+ const selectedOps = transaction.operations.filter(op =>
157
+ operationIds.includes(op.id)
158
+ );
159
+
160
+ if (selectedOps.length === 0) {
161
+ throw new Error('No matching operations found');
162
+ }
163
+
164
+ console.log(chalk.blue(`\n📝 Selective rollback: ${selectedOps.length} operations`));
165
+
166
+ // Create a new transaction for selective rollback
167
+ const rollbackTxnId = await this.transactionManager.beginTransaction({
168
+ type: 'selective_rollback',
169
+ description: `Selective rollback of ${transactionId}`,
170
+ metadata: {
171
+ originalTransaction: transactionId,
172
+ selectedOperations: operationIds
173
+ }
174
+ });
175
+
176
+ const results = {
177
+ successful: [],
178
+ failed: [],
179
+ warnings: []
180
+ };
181
+
182
+ // Rollback selected operations
183
+ for (const op of selectedOps) {
184
+ try {
185
+ await this.transactionManager.rollbackOperation(op, results);
186
+ } catch (error) {
187
+ results.failed.push({
188
+ operation: op.id,
189
+ error: error.message
190
+ });
191
+ }
192
+ }
193
+
194
+ // Commit rollback transaction
195
+ await this.transactionManager.commitTransaction(rollbackTxnId);
196
+
197
+ this.displayRollbackResults(results);
198
+
199
+ return {
200
+ success: results.failed.length === 0,
201
+ result: results
202
+ };
203
+
204
+ } catch (error) {
205
+ console.error(chalk.red(`Selective rollback failed: ${error.message}`));
206
+ return {
207
+ success: false,
208
+ error: error.message
209
+ };
210
+ }
211
+ }
212
+
213
+ /**
214
+ * Clean up old transactions
215
+ * @returns {Promise<number>} Number cleaned
216
+ */
217
+ async cleanup() {
218
+ try {
219
+ console.log(chalk.blue('🧹 Cleaning up old transactions...'));
220
+ const cleaned = await this.transactionManager.cleanupOldTransactions();
221
+ console.log(chalk.green(`✅ Cleaned up ${cleaned} old transactions`));
222
+ return cleaned;
223
+ } catch (error) {
224
+ console.error(chalk.red(`Cleanup failed: ${error.message}`));
225
+ return 0;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * Create backup before modification
231
+ * @param {string} componentType - Type of component
232
+ * @param {string} componentName - Name of component
233
+ * @param {string} content - Content to backup
234
+ * @returns {Promise<string>} Backup file path
235
+ */
236
+ async createBackup(componentType, componentName, content) {
237
+ try {
238
+ // Ensure backup directory exists
239
+ await fs.mkdir(this.backupPath, { recursive: true });
240
+
241
+ // Create subdirectory for component type
242
+ const typeBackupPath = path.join(this.backupPath, componentType);
243
+ await fs.mkdir(typeBackupPath, { recursive: true });
244
+
245
+ // Generate backup filename with timestamp
246
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
247
+ const backupFilename = `${componentName}.${timestamp}.backup`;
248
+ const backupFilePath = path.join(typeBackupPath, backupFilename);
249
+
250
+ // Write backup
251
+ await fs.writeFile(backupFilePath, content, 'utf8');
252
+
253
+ // Record backup in transaction
254
+ if (this.currentTransactionId) {
255
+ await this.transactionManager.recordOperation({
256
+ type: 'backup_created',
257
+ path: backupFilePath,
258
+ componentType,
259
+ componentName,
260
+ timestamp
261
+ });
262
+ }
263
+
264
+ return backupFilePath;
265
+ } catch (error) {
266
+ throw new Error(`Failed to create backup: ${error.message}`);
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Restore from backup
272
+ * @param {string} backupPath - Path to backup file
273
+ * @param {string} targetPath - Path to restore to
274
+ * @returns {Promise<boolean>} Success status
275
+ */
276
+ async restoreFromBackup(backupPath, targetPath) {
277
+ try {
278
+ // Verify backup exists
279
+ await fs.access(backupPath);
280
+
281
+ // Read backup content
282
+ const content = await fs.readFile(backupPath, 'utf8');
283
+
284
+ // Restore to target
285
+ await fs.writeFile(targetPath, content, 'utf8');
286
+
287
+ console.log(chalk.green(`✅ Restored from backup: ${path.basename(backupPath)}`));
288
+ return true;
289
+ } catch (error) {
290
+ console.error(chalk.red(`Failed to restore from backup: ${error.message}`));
291
+ return false;
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Validate modification before applying
297
+ * @param {string} componentType - Type of component
298
+ * @param {string} originalContent - Original content
299
+ * @param {string} modifiedContent - Modified content
300
+ * @returns {Promise<Object>} Validation result
301
+ */
302
+ async validateModification(componentType, originalContent, modifiedContent) {
303
+ const validation = await this.modificationValidator.validateModification(
304
+ componentType,
305
+ originalContent,
306
+ modifiedContent
307
+ );
308
+
309
+ if (!validation.valid) {
310
+ console.log(chalk.red('\n❌ Modification validation failed:'));
311
+ validation.errors.forEach(error => {
312
+ console.log(chalk.red(` • ${error}`));
313
+ });
314
+ }
315
+
316
+ if (validation.warnings.length > 0) {
317
+ console.log(chalk.yellow('\n⚠️ Warnings:'));
318
+ validation.warnings.forEach(warning => {
319
+ console.log(chalk.yellow(` • ${warning}`));
320
+ });
321
+ }
322
+
323
+ if (validation.breakingChanges.length > 0) {
324
+ console.log(chalk.red('\n🚨 Breaking Changes Detected:'));
325
+ validation.breakingChanges.forEach(change => {
326
+ console.log(chalk.red(` • ${change.type}: ${change.impact}`));
327
+ if (change.items) {
328
+ console.log(chalk.red(` Items: ${change.items.join(', ')}`));
329
+ }
330
+ });
331
+ }
332
+
333
+ return validation;
334
+ }
335
+
336
+ /**
337
+ * Rollback modification
338
+ * @param {Object} modificationData - Modification details
339
+ * @returns {Promise<Object>} Rollback result
340
+ */
341
+ async rollbackModification(modificationData) {
342
+ const { componentType, componentName, backupPath, targetPath } = modificationData;
343
+
344
+ try {
345
+ console.log(chalk.blue(`\n⏪ Rolling back ${componentType}: ${componentName}`));
346
+
347
+ // Restore from backup
348
+ const restored = await this.restoreFromBackup(backupPath, targetPath);
349
+
350
+ if (restored) {
351
+ // Record rollback
352
+ await this.transactionManager.recordOperation({
353
+ type: 'modification_rollback',
354
+ componentType,
355
+ componentName,
356
+ backupPath,
357
+ targetPath,
358
+ timestamp: new Date().toISOString()
359
+ });
360
+
361
+ return {
362
+ success: true,
363
+ message: `Successfully rolled back ${componentType}: ${componentName}`
364
+ };
365
+ } else {
366
+ throw new Error('Restoration failed');
367
+ }
368
+ } catch (error) {
369
+ return {
370
+ success: false,
371
+ error: error.message
372
+ };
373
+ }
374
+ }
375
+
376
+ /**
377
+ * List available backups for a component
378
+ * @param {string} componentType - Type of component
379
+ * @param {string} componentName - Name of component
380
+ * @returns {Promise<Array>} List of backups
381
+ */
382
+ async listBackups(componentType, componentName) {
383
+ try {
384
+ const typeBackupPath = path.join(this.backupPath, componentType);
385
+ const files = await fs.readdir(typeBackupPath);
386
+
387
+ const backups = files
388
+ .filter(file => file.startsWith(`${componentName}.`) && file.endsWith('.backup'))
389
+ .map(file => {
390
+ const match = file.match(/\.(\d{4}-\d{2}-\d{2}T[\d-]+Z)\.backup$/);
391
+ const timestamp = match ? match[1].replace(/-/g, ':') : 'unknown';
392
+
393
+ return {
394
+ filename: file,
395
+ path: path.join(typeBackupPath, file),
396
+ timestamp: new Date(timestamp),
397
+ componentName,
398
+ componentType
399
+ };
400
+ })
401
+ .sort((a, b) => b.timestamp - a.timestamp);
402
+
403
+ return backups;
404
+ } catch (error) {
405
+ if (error.code === 'ENOENT') {
406
+ return [];
407
+ }
408
+ throw error;
409
+ }
410
+ }
411
+
412
+ /**
413
+ * Clean up old backups
414
+ * @param {number} daysToKeep - Number of days to keep backups
415
+ * @returns {Promise<number>} Number of backups deleted
416
+ */
417
+ async cleanupBackups(daysToKeep = 30) {
418
+ try {
419
+ const cutoffDate = new Date();
420
+ cutoffDate.setDate(cutoffDate.getDate() - daysToKeep);
421
+
422
+ let deletedCount = 0;
423
+
424
+ // Iterate through component types
425
+ const componentTypes = await fs.readdir(this.backupPath);
426
+
427
+ for (const componentType of componentTypes) {
428
+ const typePath = path.join(this.backupPath, componentType);
429
+ const stat = await fs.stat(typePath);
430
+
431
+ if (!stat.isDirectory()) continue;
432
+
433
+ const files = await fs.readdir(typePath);
434
+
435
+ for (const file of files) {
436
+ if (!file.endsWith('.backup')) continue;
437
+
438
+ const filePath = path.join(typePath, file);
439
+ const fileStat = await fs.stat(filePath);
440
+
441
+ if (fileStat.mtime < cutoffDate) {
442
+ await fs.unlink(filePath);
443
+ deletedCount++;
444
+ }
445
+ }
446
+ }
447
+
448
+ return deletedCount;
449
+ } catch (error) {
450
+ console.error(chalk.red(`Backup cleanup failed: ${error.message}`));
451
+ return 0;
452
+ }
453
+ }
454
+
455
+ /**
456
+ * Get operation icon
457
+ * @private
458
+ */
459
+ getOperationIcon(type) {
460
+ const icons = {
461
+ create: '➕',
462
+ update: '✏️',
463
+ delete: '🗑️',
464
+ manifest_update: '📋',
465
+ metadata_update: '📊',
466
+ component_created: '📦',
467
+ backup_created: '💾',
468
+ modification_rollback: '⏪'
469
+ };
470
+
471
+ return icons[type] || '•';
472
+ }
473
+
474
+ /**
475
+ * Get status color
476
+ * @private
477
+ */
478
+ getStatusColor(status) {
479
+ switch (status) {
480
+ case 'active':
481
+ return chalk.yellow(status);
482
+ case 'committed':
483
+ return chalk.green(status);
484
+ case 'rolled_back':
485
+ return chalk.blue(status);
486
+ case 'failed':
487
+ return chalk.red(status);
488
+ default:
489
+ return status;
490
+ }
491
+ }
492
+
493
+ /**
494
+ * Display rollback results
495
+ * @private
496
+ */
497
+ displayRollbackResults(results) {
498
+ console.log(chalk.blue('\n📊 Rollback Results:'));
499
+
500
+ if (results.successful.length > 0) {
501
+ console.log(chalk.green(`\n✅ Successful (${results.successful.length}):`));
502
+ results.successful.forEach(item => {
503
+ console.log(chalk.green(` ✓ ${item.action}: ${path.basename(item.path)}`));
504
+ });
505
+ }
506
+
507
+ if (results.warnings.length > 0) {
508
+ console.log(chalk.yellow(`\n⚠️ Warnings (${results.warnings.length}):`));
509
+ results.warnings.forEach(item => {
510
+ console.log(chalk.yellow(` ⚠ ${item.warning}: ${path.basename(item.path || 'N/A')}`));
511
+ });
512
+ }
513
+
514
+ if (results.failed.length > 0) {
515
+ console.log(chalk.red(`\n❌ Failed (${results.failed.length}):`));
516
+ results.failed.forEach(item => {
517
+ console.log(chalk.red(` ✗ ${item.operation}: ${item.error}`));
518
+ });
519
+ }
520
+
521
+ // Summary
522
+ const total = results.successful.length + results.failed.length;
523
+ const successRate = total > 0 ? (results.successful.length / total * 100).toFixed(0) : 0;
524
+
525
+ console.log(chalk.blue('\n📈 Summary:'));
526
+ console.log(chalk.gray(` Total operations: ${total}`));
527
+ console.log(chalk.gray(` Success rate: ${successRate}%`));
528
+ }
529
+ }
530
+
531
531
  module.exports = RollbackHandler;