aios-core 4.2.13 → 4.2.15

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 (95) 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/pro/license/degradation.js +220 -220
  64. package/pro/license/errors.js +450 -450
  65. package/pro/license/feature-gate.js +354 -354
  66. package/pro/license/index.js +181 -181
  67. package/pro/license/license-cache.js +523 -523
  68. package/pro/license/license-crypto.js +303 -303
  69. package/scripts/package-synapse.js +5 -5
  70. package/scripts/validate-package-completeness.js +3 -3
  71. package/.aios-core/.session/current-session.json +0 -14
  72. package/.aios-core/data/registry-update-log.jsonl +0 -191
  73. package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +0 -335
  74. package/.aios-core/docs/component-creation-guide.md +0 -458
  75. package/.aios-core/docs/session-update-pattern.md +0 -307
  76. package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +0 -1963
  77. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +0 -1190
  78. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +0 -439
  79. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +0 -5398
  80. package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +0 -523
  81. package/.aios-core/docs/template-syntax.md +0 -267
  82. package/.aios-core/docs/troubleshooting-guide.md +0 -625
  83. package/.aios-core/infrastructure/tests/utilities-audit-results.json +0 -501
  84. package/.aios-core/manifests/agents.csv +0 -29
  85. package/.aios-core/manifests/tasks.csv +0 -198
  86. package/.aios-core/manifests/workers.csv +0 -204
  87. package/.claude/rules/agent-authority.md +0 -105
  88. package/.claude/rules/coderabbit-integration.md +0 -93
  89. package/.claude/rules/ids-principles.md +0 -112
  90. package/.claude/rules/story-lifecycle.md +0 -139
  91. package/.claude/rules/workflow-execution.md +0 -150
  92. package/scripts/glue/README.md +0 -355
  93. package/scripts/glue/compose-agent-prompt.cjs +0 -362
  94. /package/.claude/hooks/{precompact-session-digest.js → precompact-session-digest.cjs} +0 -0
  95. /package/.claude/hooks/{synapse-engine.js → synapse-engine.cjs} +0 -0
@@ -1,590 +1,590 @@
1
- /**
2
- * Transaction Manager for AIOS-FULLSTACK
3
- * Manages component operations with rollback support
4
- * @module transaction-manager
5
- */
6
-
7
- const fs = require('fs-extra');
8
- const path = require('path');
9
- const crypto = require('crypto');
10
- const chalk = require('chalk');
11
- const ComponentMetadata = require('./component-metadata');
12
-
13
- class TransactionManager {
14
- constructor(options = {}) {
15
- this.rootPath = options.rootPath || process.cwd();
16
- this.transactionPath = path.join(this.rootPath, 'aios-core', 'transactions');
17
- this.backupPath = path.join(this.rootPath, 'aios-core', 'backups');
18
- this.componentMetadata = new ComponentMetadata({ rootPath: this.rootPath });
19
-
20
- // Active transactions
21
- this.activeTransactions = new Map();
22
-
23
- // Transaction retention (30 days)
24
- this.retentionDays = options.retentionDays || 30;
25
- }
26
-
27
- /**
28
- * Begin a new transaction
29
- * @param {Object} options - Transaction options
30
- * @returns {Promise<string>} Transaction ID
31
- */
32
- async beginTransaction(options = {}) {
33
- try {
34
- const transactionId = this.generateTransactionId();
35
-
36
- const transaction = {
37
- id: transactionId,
38
- type: options.type || 'component_operation',
39
- description: options.description || 'Component operation',
40
- user: options.user || process.env.USER || 'system',
41
- startTime: new Date().toISOString(),
42
- status: 'active',
43
- operations: [],
44
- backups: [],
45
- metadata: options.metadata || {},
46
- rollbackOnError: options.rollbackOnError !== false
47
- };
48
-
49
- // Save initial transaction state
50
- await this.saveTransaction(transaction);
51
-
52
- // Store in active transactions
53
- this.activeTransactions.set(transactionId, transaction);
54
-
55
- console.log(chalk.blue(`📋 Transaction started: ${transactionId}`));
56
-
57
- return transactionId;
58
-
59
- } catch (error) {
60
- console.error(chalk.red(`Failed to begin transaction: ${error.message}`));
61
- throw error;
62
- }
63
- }
64
-
65
- /**
66
- * Record a file operation in the transaction
67
- * @param {string} transactionId - Transaction ID
68
- * @param {Object} operation - Operation details
69
- * @returns {Promise<void>}
70
- */
71
- async recordOperation(transactionId, operation) {
72
- try {
73
- const transaction = this.activeTransactions.get(transactionId);
74
- if (!transaction) {
75
- throw new Error(`Transaction not found: ${transactionId}`);
76
- }
77
-
78
- const operationRecord = {
79
- id: `op-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
80
- timestamp: new Date().toISOString(),
81
- type: operation.type, // create, update, delete
82
- target: operation.target, // file, manifest, metadata
83
- path: operation.path,
84
- previousState: null,
85
- newState: null,
86
- metadata: operation.metadata || {}
87
- };
88
-
89
- // Backup current state if needed
90
- if (operation.type === 'update' || operation.type === 'delete') {
91
- operationRecord.previousState = await this.backupCurrentState(operation.path);
92
- }
93
-
94
- // Record new state for create/update
95
- if (operation.type === 'create' || operation.type === 'update') {
96
- operationRecord.newState = operation.content || operation.data;
97
- }
98
-
99
- // Add to transaction
100
- transaction.operations.push(operationRecord);
101
-
102
- // Save updated transaction
103
- await this.saveTransaction(transaction);
104
-
105
- } catch (error) {
106
- console.error(chalk.red(`Failed to record operation: ${error.message}`));
107
- throw error;
108
- }
109
- }
110
-
111
- /**
112
- * Commit a transaction
113
- * @param {string} transactionId - Transaction ID
114
- * @returns {Promise<Object>} Commit result
115
- */
116
- async commitTransaction(transactionId) {
117
- try {
118
- const transaction = this.activeTransactions.get(transactionId);
119
- if (!transaction) {
120
- throw new Error(`Transaction not found: ${transactionId}`);
121
- }
122
-
123
- transaction.endTime = new Date().toISOString();
124
- transaction.status = 'committed';
125
- transaction.duration = new Date(transaction.endTime) - new Date(transaction.startTime);
126
-
127
- // Save final transaction state
128
- await this.saveTransaction(transaction);
129
-
130
- // Clean up backups after successful commit (keep for history)
131
- await this.archiveBackups(transaction);
132
-
133
- // Remove from active transactions
134
- this.activeTransactions.delete(transactionId);
135
-
136
- console.log(chalk.green(`✅ Transaction committed: ${transactionId}`));
137
-
138
- return {
139
- transactionId,
140
- operations: transaction.operations.length,
141
- duration: transaction.duration
142
- };
143
-
144
- } catch (error) {
145
- console.error(chalk.red(`Failed to commit transaction: ${error.message}`));
146
- throw error;
147
- }
148
- }
149
-
150
- /**
151
- * Rollback a transaction
152
- * @param {string} transactionId - Transaction ID
153
- * @param {Object} options - Rollback options
154
- * @returns {Promise<Object>} Rollback result
155
- */
156
- async rollbackTransaction(transactionId, options = {}) {
157
- try {
158
- const transaction = this.activeTransactions.get(transactionId) ||
159
- await this.loadTransaction(transactionId);
160
-
161
- if (!transaction) {
162
- throw new Error(`Transaction not found: ${transactionId}`);
163
- }
164
-
165
- console.log(chalk.yellow(`⚙️ Rolling back transaction: ${transactionId}`));
166
-
167
- const rollbackResults = {
168
- transactionId,
169
- successful: [],
170
- failed: [],
171
- warnings: []
172
- };
173
-
174
- // Process operations in reverse order
175
- const operations = [...transaction.operations].reverse();
176
-
177
- for (const operation of operations) {
178
- try {
179
- await this.rollbackOperation(operation, rollbackResults);
180
- } catch (error) {
181
- rollbackResults.failed.push({
182
- operation: operation.id,
183
- error: error.message
184
- });
185
-
186
- if (!options.continueOnError) {
187
- throw error;
188
- }
189
- }
190
- }
191
-
192
- // Update transaction status
193
- transaction.status = 'rolled_back';
194
- transaction.rollbackTime = new Date().toISOString();
195
- transaction.rollbackResults = rollbackResults;
196
-
197
- await this.saveTransaction(transaction);
198
-
199
- // Remove from active transactions
200
- this.activeTransactions.delete(transactionId);
201
-
202
- console.log(chalk.green(`✅ Rollback completed`));
203
- console.log(chalk.gray(` Successful: ${rollbackResults.successful.length}`));
204
- console.log(chalk.gray(` Failed: ${rollbackResults.failed.length}`));
205
- console.log(chalk.gray(` Warnings: ${rollbackResults.warnings.length}`));
206
-
207
- return rollbackResults;
208
-
209
- } catch (error) {
210
- console.error(chalk.red(`Rollback failed: ${error.message}`));
211
- throw error;
212
- }
213
- }
214
-
215
- /**
216
- * Rollback a single operation
217
- * @private
218
- */
219
- async rollbackOperation(operation, results) {
220
- console.log(chalk.gray(` Rolling back: ${operation.type} ${operation.path}`));
221
-
222
- switch (operation.type) {
223
- case 'create':
224
- // Delete created file
225
- if (await fs.pathExists(operation.path)) {
226
- await fs.remove(operation.path);
227
- results.successful.push({
228
- operation: operation.id,
229
- action: 'deleted',
230
- path: operation.path
231
- });
232
- } else {
233
- results.warnings.push({
234
- operation: operation.id,
235
- warning: 'File already removed',
236
- path: operation.path
237
- });
238
- }
239
- break;
240
-
241
- case 'update':
242
- // Restore previous state
243
- if (operation.previousState) {
244
- await this.restoreFromBackup(operation.path, operation.previousState);
245
- results.successful.push({
246
- operation: operation.id,
247
- action: 'restored',
248
- path: operation.path
249
- });
250
- } else {
251
- results.warnings.push({
252
- operation: operation.id,
253
- warning: 'No backup available',
254
- path: operation.path
255
- });
256
- }
257
- break;
258
-
259
- case 'delete':
260
- // Restore deleted file
261
- if (operation.previousState) {
262
- await this.restoreFromBackup(operation.path, operation.previousState);
263
- results.successful.push({
264
- operation: operation.id,
265
- action: 'restored',
266
- path: operation.path
267
- });
268
- } else {
269
- results.warnings.push({
270
- operation: operation.id,
271
- warning: 'No backup available',
272
- path: operation.path
273
- });
274
- }
275
- break;
276
-
277
- case 'manifest_update':
278
- // Special handling for manifest updates
279
- await this.rollbackManifestUpdate(operation, results);
280
- break;
281
-
282
- case 'metadata_update':
283
- // Special handling for metadata updates
284
- await this.rollbackMetadataUpdate(operation, results);
285
- break;
286
-
287
- default:
288
- results.warnings.push({
289
- operation: operation.id,
290
- warning: `Unknown operation type: ${operation.type}`
291
- });
292
- }
293
- }
294
-
295
- /**
296
- * Get the last transaction for selective rollback
297
- * @returns {Promise<Object|null>} Last transaction
298
- */
299
- async getLastTransaction() {
300
- try {
301
- const transactionsDir = this.transactionPath;
302
- if (!await fs.pathExists(transactionsDir)) {
303
- return null;
304
- }
305
-
306
- // Get all transaction files
307
- const files = await fs.readdir(transactionsDir);
308
- const transactionFiles = files.filter(f => f.endsWith('.json'));
309
-
310
- if (transactionFiles.length === 0) {
311
- return null;
312
- }
313
-
314
- // Sort by timestamp (newest first)
315
- const transactions = [];
316
- for (const file of transactionFiles) {
317
- const transaction = await fs.readJson(path.join(transactionsDir, file));
318
- transactions.push(transaction);
319
- }
320
-
321
- transactions.sort((a, b) =>
322
- new Date(b.startTime) - new Date(a.startTime)
323
- );
324
-
325
- return transactions[0];
326
-
327
- } catch (error) {
328
- console.error(chalk.red(`Failed to get last transaction: ${error.message}`));
329
- return null;
330
- }
331
- }
332
-
333
- /**
334
- * List recent transactions
335
- * @param {number} limit - Number of transactions to return
336
- * @returns {Promise<Array>} Recent transactions
337
- */
338
- async listTransactions(limit = 10) {
339
- try {
340
- const transactionsDir = this.transactionPath;
341
- if (!await fs.pathExists(transactionsDir)) {
342
- return [];
343
- }
344
-
345
- const files = await fs.readdir(transactionsDir);
346
- const transactionFiles = files.filter(f => f.endsWith('.json'));
347
-
348
- const transactions = [];
349
- for (const file of transactionFiles) {
350
- const transaction = await fs.readJson(path.join(transactionsDir, file));
351
- transactions.push({
352
- id: transaction.id,
353
- type: transaction.type,
354
- description: transaction.description,
355
- user: transaction.user,
356
- startTime: transaction.startTime,
357
- endTime: transaction.endTime,
358
- status: transaction.status,
359
- operations: transaction.operations.length
360
- });
361
- }
362
-
363
- // Sort by start time (newest first)
364
- transactions.sort((a, b) =>
365
- new Date(b.startTime) - new Date(a.startTime)
366
- );
367
-
368
- return transactions.slice(0, limit);
369
-
370
- } catch (error) {
371
- console.error(chalk.red(`Failed to list transactions: ${error.message}`));
372
- return [];
373
- }
374
- }
375
-
376
- /**
377
- * Clean up old transactions
378
- * @returns {Promise<number>} Number of transactions cleaned
379
- */
380
- async cleanupOldTransactions() {
381
- try {
382
- const transactionsDir = this.transactionPath;
383
- if (!await fs.pathExists(transactionsDir)) {
384
- return 0;
385
- }
386
-
387
- const cutoffDate = new Date();
388
- cutoffDate.setDate(cutoffDate.getDate() - this.retentionDays);
389
-
390
- const files = await fs.readdir(transactionsDir);
391
- let cleaned = 0;
392
-
393
- for (const file of files) {
394
- if (file.endsWith('.json')) {
395
- const filePath = path.join(transactionsDir, file);
396
- const transaction = await fs.readJson(filePath);
397
-
398
- if (new Date(transaction.startTime) < cutoffDate) {
399
- // Clean up transaction and its backups
400
- await fs.remove(filePath);
401
-
402
- // Remove associated backups
403
- if (transaction.backups) {
404
- for (const backup of transaction.backups) {
405
- if (await fs.pathExists(backup.path)) {
406
- await fs.remove(backup.path);
407
- }
408
- }
409
- }
410
-
411
- cleaned++;
412
- }
413
- }
414
- }
415
-
416
- console.log(chalk.gray(`🧹 Cleaned up ${cleaned} old transactions`));
417
- return cleaned;
418
-
419
- } catch (error) {
420
- console.error(chalk.red(`Cleanup failed: ${error.message}`));
421
- return 0;
422
- }
423
- }
424
-
425
- /**
426
- * Backup current state of a file
427
- * @private
428
- */
429
- async backupCurrentState(filePath) {
430
- try {
431
- if (!await fs.pathExists(filePath)) {
432
- return null;
433
- }
434
-
435
- const content = await fs.readFile(filePath, 'utf8');
436
- const hash = crypto.createHash('sha256').update(content).digest('hex');
437
-
438
- const backup = {
439
- path: filePath,
440
- content: content,
441
- hash: hash,
442
- timestamp: new Date().toISOString()
443
- };
444
-
445
- // Save backup
446
- const backupId = `backup-${Date.now()}-${hash.substr(0, 8)}`;
447
- const backupPath = path.join(this.backupPath, backupId);
448
-
449
- await fs.ensureDir(this.backupPath);
450
- await fs.writeJson(backupPath, backup, { spaces: 2 });
451
-
452
- return backupId;
453
-
454
- } catch (error) {
455
- console.error(chalk.red(`Backup failed: ${error.message}`));
456
- return null;
457
- }
458
- }
459
-
460
- /**
461
- * Restore from backup
462
- * @private
463
- */
464
- async restoreFromBackup(targetPath, backupId) {
465
- try {
466
- const backupPath = path.join(this.backupPath, backupId);
467
- const backup = await fs.readJson(backupPath);
468
-
469
- // Ensure directory exists
470
- await fs.ensureDir(path.dirname(targetPath));
471
-
472
- // Restore content
473
- await fs.writeFile(targetPath, backup.content, 'utf8');
474
-
475
- console.log(chalk.green(` ✓ Restored: ${path.basename(targetPath)}`));
476
-
477
- } catch (error) {
478
- console.error(chalk.red(`Restore failed: ${error.message}`));
479
- throw error;
480
- }
481
- }
482
-
483
- /**
484
- * Rollback manifest update
485
- * @private
486
- */
487
- async rollbackManifestUpdate(operation, results) {
488
- try {
489
- const manifestPath = path.join(this.rootPath, 'aios-core', 'team-manifest.yaml');
490
-
491
- if (operation.previousState) {
492
- // Restore previous manifest state
493
- const backupPath = path.join(this.backupPath, operation.previousState);
494
- const backup = await fs.readJson(backupPath);
495
- await fs.writeFile(manifestPath, backup.content, 'utf8');
496
-
497
- results.successful.push({
498
- operation: operation.id,
499
- action: 'manifest_restored',
500
- path: manifestPath
501
- });
502
- } else {
503
- results.warnings.push({
504
- operation: operation.id,
505
- warning: 'No manifest backup available'
506
- });
507
- }
508
-
509
- } catch (error) {
510
- results.failed.push({
511
- operation: operation.id,
512
- error: `Manifest rollback failed: ${error.message}`
513
- });
514
- }
515
- }
516
-
517
- /**
518
- * Rollback metadata update
519
- * @private
520
- */
521
- async rollbackMetadataUpdate(operation, results) {
522
- try {
523
- if (operation.metadata?.componentType && operation.metadata?.componentId) {
524
- // Revert metadata changes
525
- // This would need integration with ComponentMetadata
526
- results.warnings.push({
527
- operation: operation.id,
528
- warning: 'Metadata rollback not fully implemented'
529
- });
530
- }
531
-
532
- } catch (error) {
533
- results.failed.push({
534
- operation: operation.id,
535
- error: `Metadata rollback failed: ${error.message}`
536
- });
537
- }
538
- }
539
-
540
- /**
541
- * Archive backups after successful commit
542
- * @private
543
- */
544
- async archiveBackups(transaction) {
545
- // Move backups to archive directory with transaction reference
546
- const archivePath = path.join(this.backupPath, 'archive', transaction.id);
547
- await fs.ensureDir(archivePath);
548
-
549
- // Archive transaction file
550
- const transactionArchive = path.join(archivePath, 'transaction.json');
551
- await fs.writeJson(transactionArchive, transaction, { spaces: 2 });
552
- }
553
-
554
- /**
555
- * Generate transaction ID
556
- * @private
557
- */
558
- generateTransactionId() {
559
- return `txn-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
560
- }
561
-
562
- /**
563
- * Save transaction to disk
564
- * @private
565
- */
566
- async saveTransaction(transaction) {
567
- await fs.ensureDir(this.transactionPath);
568
- const filePath = path.join(this.transactionPath, `${transaction.id}.json`);
569
- await fs.writeJson(filePath, transaction, { spaces: 2 });
570
- }
571
-
572
- /**
573
- * Load transaction from disk
574
- * @private
575
- */
576
- async loadTransaction(transactionId) {
577
- try {
578
- const filePath = path.join(this.transactionPath, `${transactionId}.json`);
579
- if (await fs.pathExists(filePath)) {
580
- return await fs.readJson(filePath);
581
- }
582
- return null;
583
- } catch (error) {
584
- console.error(chalk.red(`Failed to load transaction: ${error.message}`));
585
- return null;
586
- }
587
- }
588
- }
589
-
1
+ /**
2
+ * Transaction Manager for AIOS-FULLSTACK
3
+ * Manages component operations with rollback support
4
+ * @module transaction-manager
5
+ */
6
+
7
+ const fs = require('fs-extra');
8
+ const path = require('path');
9
+ const crypto = require('crypto');
10
+ const chalk = require('chalk');
11
+ const ComponentMetadata = require('./component-metadata');
12
+
13
+ class TransactionManager {
14
+ constructor(options = {}) {
15
+ this.rootPath = options.rootPath || process.cwd();
16
+ this.transactionPath = path.join(this.rootPath, 'aios-core', 'transactions');
17
+ this.backupPath = path.join(this.rootPath, 'aios-core', 'backups');
18
+ this.componentMetadata = new ComponentMetadata({ rootPath: this.rootPath });
19
+
20
+ // Active transactions
21
+ this.activeTransactions = new Map();
22
+
23
+ // Transaction retention (30 days)
24
+ this.retentionDays = options.retentionDays || 30;
25
+ }
26
+
27
+ /**
28
+ * Begin a new transaction
29
+ * @param {Object} options - Transaction options
30
+ * @returns {Promise<string>} Transaction ID
31
+ */
32
+ async beginTransaction(options = {}) {
33
+ try {
34
+ const transactionId = this.generateTransactionId();
35
+
36
+ const transaction = {
37
+ id: transactionId,
38
+ type: options.type || 'component_operation',
39
+ description: options.description || 'Component operation',
40
+ user: options.user || process.env.USER || 'system',
41
+ startTime: new Date().toISOString(),
42
+ status: 'active',
43
+ operations: [],
44
+ backups: [],
45
+ metadata: options.metadata || {},
46
+ rollbackOnError: options.rollbackOnError !== false
47
+ };
48
+
49
+ // Save initial transaction state
50
+ await this.saveTransaction(transaction);
51
+
52
+ // Store in active transactions
53
+ this.activeTransactions.set(transactionId, transaction);
54
+
55
+ console.log(chalk.blue(`📋 Transaction started: ${transactionId}`));
56
+
57
+ return transactionId;
58
+
59
+ } catch (error) {
60
+ console.error(chalk.red(`Failed to begin transaction: ${error.message}`));
61
+ throw error;
62
+ }
63
+ }
64
+
65
+ /**
66
+ * Record a file operation in the transaction
67
+ * @param {string} transactionId - Transaction ID
68
+ * @param {Object} operation - Operation details
69
+ * @returns {Promise<void>}
70
+ */
71
+ async recordOperation(transactionId, operation) {
72
+ try {
73
+ const transaction = this.activeTransactions.get(transactionId);
74
+ if (!transaction) {
75
+ throw new Error(`Transaction not found: ${transactionId}`);
76
+ }
77
+
78
+ const operationRecord = {
79
+ id: `op-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
80
+ timestamp: new Date().toISOString(),
81
+ type: operation.type, // create, update, delete
82
+ target: operation.target, // file, manifest, metadata
83
+ path: operation.path,
84
+ previousState: null,
85
+ newState: null,
86
+ metadata: operation.metadata || {}
87
+ };
88
+
89
+ // Backup current state if needed
90
+ if (operation.type === 'update' || operation.type === 'delete') {
91
+ operationRecord.previousState = await this.backupCurrentState(operation.path);
92
+ }
93
+
94
+ // Record new state for create/update
95
+ if (operation.type === 'create' || operation.type === 'update') {
96
+ operationRecord.newState = operation.content || operation.data;
97
+ }
98
+
99
+ // Add to transaction
100
+ transaction.operations.push(operationRecord);
101
+
102
+ // Save updated transaction
103
+ await this.saveTransaction(transaction);
104
+
105
+ } catch (error) {
106
+ console.error(chalk.red(`Failed to record operation: ${error.message}`));
107
+ throw error;
108
+ }
109
+ }
110
+
111
+ /**
112
+ * Commit a transaction
113
+ * @param {string} transactionId - Transaction ID
114
+ * @returns {Promise<Object>} Commit result
115
+ */
116
+ async commitTransaction(transactionId) {
117
+ try {
118
+ const transaction = this.activeTransactions.get(transactionId);
119
+ if (!transaction) {
120
+ throw new Error(`Transaction not found: ${transactionId}`);
121
+ }
122
+
123
+ transaction.endTime = new Date().toISOString();
124
+ transaction.status = 'committed';
125
+ transaction.duration = new Date(transaction.endTime) - new Date(transaction.startTime);
126
+
127
+ // Save final transaction state
128
+ await this.saveTransaction(transaction);
129
+
130
+ // Clean up backups after successful commit (keep for history)
131
+ await this.archiveBackups(transaction);
132
+
133
+ // Remove from active transactions
134
+ this.activeTransactions.delete(transactionId);
135
+
136
+ console.log(chalk.green(`✅ Transaction committed: ${transactionId}`));
137
+
138
+ return {
139
+ transactionId,
140
+ operations: transaction.operations.length,
141
+ duration: transaction.duration
142
+ };
143
+
144
+ } catch (error) {
145
+ console.error(chalk.red(`Failed to commit transaction: ${error.message}`));
146
+ throw error;
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Rollback a transaction
152
+ * @param {string} transactionId - Transaction ID
153
+ * @param {Object} options - Rollback options
154
+ * @returns {Promise<Object>} Rollback result
155
+ */
156
+ async rollbackTransaction(transactionId, options = {}) {
157
+ try {
158
+ const transaction = this.activeTransactions.get(transactionId) ||
159
+ await this.loadTransaction(transactionId);
160
+
161
+ if (!transaction) {
162
+ throw new Error(`Transaction not found: ${transactionId}`);
163
+ }
164
+
165
+ console.log(chalk.yellow(`⚙️ Rolling back transaction: ${transactionId}`));
166
+
167
+ const rollbackResults = {
168
+ transactionId,
169
+ successful: [],
170
+ failed: [],
171
+ warnings: []
172
+ };
173
+
174
+ // Process operations in reverse order
175
+ const operations = [...transaction.operations].reverse();
176
+
177
+ for (const operation of operations) {
178
+ try {
179
+ await this.rollbackOperation(operation, rollbackResults);
180
+ } catch (error) {
181
+ rollbackResults.failed.push({
182
+ operation: operation.id,
183
+ error: error.message
184
+ });
185
+
186
+ if (!options.continueOnError) {
187
+ throw error;
188
+ }
189
+ }
190
+ }
191
+
192
+ // Update transaction status
193
+ transaction.status = 'rolled_back';
194
+ transaction.rollbackTime = new Date().toISOString();
195
+ transaction.rollbackResults = rollbackResults;
196
+
197
+ await this.saveTransaction(transaction);
198
+
199
+ // Remove from active transactions
200
+ this.activeTransactions.delete(transactionId);
201
+
202
+ console.log(chalk.green(`✅ Rollback completed`));
203
+ console.log(chalk.gray(` Successful: ${rollbackResults.successful.length}`));
204
+ console.log(chalk.gray(` Failed: ${rollbackResults.failed.length}`));
205
+ console.log(chalk.gray(` Warnings: ${rollbackResults.warnings.length}`));
206
+
207
+ return rollbackResults;
208
+
209
+ } catch (error) {
210
+ console.error(chalk.red(`Rollback failed: ${error.message}`));
211
+ throw error;
212
+ }
213
+ }
214
+
215
+ /**
216
+ * Rollback a single operation
217
+ * @private
218
+ */
219
+ async rollbackOperation(operation, results) {
220
+ console.log(chalk.gray(` Rolling back: ${operation.type} ${operation.path}`));
221
+
222
+ switch (operation.type) {
223
+ case 'create':
224
+ // Delete created file
225
+ if (await fs.pathExists(operation.path)) {
226
+ await fs.remove(operation.path);
227
+ results.successful.push({
228
+ operation: operation.id,
229
+ action: 'deleted',
230
+ path: operation.path
231
+ });
232
+ } else {
233
+ results.warnings.push({
234
+ operation: operation.id,
235
+ warning: 'File already removed',
236
+ path: operation.path
237
+ });
238
+ }
239
+ break;
240
+
241
+ case 'update':
242
+ // Restore previous state
243
+ if (operation.previousState) {
244
+ await this.restoreFromBackup(operation.path, operation.previousState);
245
+ results.successful.push({
246
+ operation: operation.id,
247
+ action: 'restored',
248
+ path: operation.path
249
+ });
250
+ } else {
251
+ results.warnings.push({
252
+ operation: operation.id,
253
+ warning: 'No backup available',
254
+ path: operation.path
255
+ });
256
+ }
257
+ break;
258
+
259
+ case 'delete':
260
+ // Restore deleted file
261
+ if (operation.previousState) {
262
+ await this.restoreFromBackup(operation.path, operation.previousState);
263
+ results.successful.push({
264
+ operation: operation.id,
265
+ action: 'restored',
266
+ path: operation.path
267
+ });
268
+ } else {
269
+ results.warnings.push({
270
+ operation: operation.id,
271
+ warning: 'No backup available',
272
+ path: operation.path
273
+ });
274
+ }
275
+ break;
276
+
277
+ case 'manifest_update':
278
+ // Special handling for manifest updates
279
+ await this.rollbackManifestUpdate(operation, results);
280
+ break;
281
+
282
+ case 'metadata_update':
283
+ // Special handling for metadata updates
284
+ await this.rollbackMetadataUpdate(operation, results);
285
+ break;
286
+
287
+ default:
288
+ results.warnings.push({
289
+ operation: operation.id,
290
+ warning: `Unknown operation type: ${operation.type}`
291
+ });
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Get the last transaction for selective rollback
297
+ * @returns {Promise<Object|null>} Last transaction
298
+ */
299
+ async getLastTransaction() {
300
+ try {
301
+ const transactionsDir = this.transactionPath;
302
+ if (!await fs.pathExists(transactionsDir)) {
303
+ return null;
304
+ }
305
+
306
+ // Get all transaction files
307
+ const files = await fs.readdir(transactionsDir);
308
+ const transactionFiles = files.filter(f => f.endsWith('.json'));
309
+
310
+ if (transactionFiles.length === 0) {
311
+ return null;
312
+ }
313
+
314
+ // Sort by timestamp (newest first)
315
+ const transactions = [];
316
+ for (const file of transactionFiles) {
317
+ const transaction = await fs.readJson(path.join(transactionsDir, file));
318
+ transactions.push(transaction);
319
+ }
320
+
321
+ transactions.sort((a, b) =>
322
+ new Date(b.startTime) - new Date(a.startTime)
323
+ );
324
+
325
+ return transactions[0];
326
+
327
+ } catch (error) {
328
+ console.error(chalk.red(`Failed to get last transaction: ${error.message}`));
329
+ return null;
330
+ }
331
+ }
332
+
333
+ /**
334
+ * List recent transactions
335
+ * @param {number} limit - Number of transactions to return
336
+ * @returns {Promise<Array>} Recent transactions
337
+ */
338
+ async listTransactions(limit = 10) {
339
+ try {
340
+ const transactionsDir = this.transactionPath;
341
+ if (!await fs.pathExists(transactionsDir)) {
342
+ return [];
343
+ }
344
+
345
+ const files = await fs.readdir(transactionsDir);
346
+ const transactionFiles = files.filter(f => f.endsWith('.json'));
347
+
348
+ const transactions = [];
349
+ for (const file of transactionFiles) {
350
+ const transaction = await fs.readJson(path.join(transactionsDir, file));
351
+ transactions.push({
352
+ id: transaction.id,
353
+ type: transaction.type,
354
+ description: transaction.description,
355
+ user: transaction.user,
356
+ startTime: transaction.startTime,
357
+ endTime: transaction.endTime,
358
+ status: transaction.status,
359
+ operations: transaction.operations.length
360
+ });
361
+ }
362
+
363
+ // Sort by start time (newest first)
364
+ transactions.sort((a, b) =>
365
+ new Date(b.startTime) - new Date(a.startTime)
366
+ );
367
+
368
+ return transactions.slice(0, limit);
369
+
370
+ } catch (error) {
371
+ console.error(chalk.red(`Failed to list transactions: ${error.message}`));
372
+ return [];
373
+ }
374
+ }
375
+
376
+ /**
377
+ * Clean up old transactions
378
+ * @returns {Promise<number>} Number of transactions cleaned
379
+ */
380
+ async cleanupOldTransactions() {
381
+ try {
382
+ const transactionsDir = this.transactionPath;
383
+ if (!await fs.pathExists(transactionsDir)) {
384
+ return 0;
385
+ }
386
+
387
+ const cutoffDate = new Date();
388
+ cutoffDate.setDate(cutoffDate.getDate() - this.retentionDays);
389
+
390
+ const files = await fs.readdir(transactionsDir);
391
+ let cleaned = 0;
392
+
393
+ for (const file of files) {
394
+ if (file.endsWith('.json')) {
395
+ const filePath = path.join(transactionsDir, file);
396
+ const transaction = await fs.readJson(filePath);
397
+
398
+ if (new Date(transaction.startTime) < cutoffDate) {
399
+ // Clean up transaction and its backups
400
+ await fs.remove(filePath);
401
+
402
+ // Remove associated backups
403
+ if (transaction.backups) {
404
+ for (const backup of transaction.backups) {
405
+ if (await fs.pathExists(backup.path)) {
406
+ await fs.remove(backup.path);
407
+ }
408
+ }
409
+ }
410
+
411
+ cleaned++;
412
+ }
413
+ }
414
+ }
415
+
416
+ console.log(chalk.gray(`🧹 Cleaned up ${cleaned} old transactions`));
417
+ return cleaned;
418
+
419
+ } catch (error) {
420
+ console.error(chalk.red(`Cleanup failed: ${error.message}`));
421
+ return 0;
422
+ }
423
+ }
424
+
425
+ /**
426
+ * Backup current state of a file
427
+ * @private
428
+ */
429
+ async backupCurrentState(filePath) {
430
+ try {
431
+ if (!await fs.pathExists(filePath)) {
432
+ return null;
433
+ }
434
+
435
+ const content = await fs.readFile(filePath, 'utf8');
436
+ const hash = crypto.createHash('sha256').update(content).digest('hex');
437
+
438
+ const backup = {
439
+ path: filePath,
440
+ content: content,
441
+ hash: hash,
442
+ timestamp: new Date().toISOString()
443
+ };
444
+
445
+ // Save backup
446
+ const backupId = `backup-${Date.now()}-${hash.substr(0, 8)}`;
447
+ const backupPath = path.join(this.backupPath, backupId);
448
+
449
+ await fs.ensureDir(this.backupPath);
450
+ await fs.writeJson(backupPath, backup, { spaces: 2 });
451
+
452
+ return backupId;
453
+
454
+ } catch (error) {
455
+ console.error(chalk.red(`Backup failed: ${error.message}`));
456
+ return null;
457
+ }
458
+ }
459
+
460
+ /**
461
+ * Restore from backup
462
+ * @private
463
+ */
464
+ async restoreFromBackup(targetPath, backupId) {
465
+ try {
466
+ const backupPath = path.join(this.backupPath, backupId);
467
+ const backup = await fs.readJson(backupPath);
468
+
469
+ // Ensure directory exists
470
+ await fs.ensureDir(path.dirname(targetPath));
471
+
472
+ // Restore content
473
+ await fs.writeFile(targetPath, backup.content, 'utf8');
474
+
475
+ console.log(chalk.green(` ✓ Restored: ${path.basename(targetPath)}`));
476
+
477
+ } catch (error) {
478
+ console.error(chalk.red(`Restore failed: ${error.message}`));
479
+ throw error;
480
+ }
481
+ }
482
+
483
+ /**
484
+ * Rollback manifest update
485
+ * @private
486
+ */
487
+ async rollbackManifestUpdate(operation, results) {
488
+ try {
489
+ const manifestPath = path.join(this.rootPath, 'aios-core', 'team-manifest.yaml');
490
+
491
+ if (operation.previousState) {
492
+ // Restore previous manifest state
493
+ const backupPath = path.join(this.backupPath, operation.previousState);
494
+ const backup = await fs.readJson(backupPath);
495
+ await fs.writeFile(manifestPath, backup.content, 'utf8');
496
+
497
+ results.successful.push({
498
+ operation: operation.id,
499
+ action: 'manifest_restored',
500
+ path: manifestPath
501
+ });
502
+ } else {
503
+ results.warnings.push({
504
+ operation: operation.id,
505
+ warning: 'No manifest backup available'
506
+ });
507
+ }
508
+
509
+ } catch (error) {
510
+ results.failed.push({
511
+ operation: operation.id,
512
+ error: `Manifest rollback failed: ${error.message}`
513
+ });
514
+ }
515
+ }
516
+
517
+ /**
518
+ * Rollback metadata update
519
+ * @private
520
+ */
521
+ async rollbackMetadataUpdate(operation, results) {
522
+ try {
523
+ if (operation.metadata?.componentType && operation.metadata?.componentId) {
524
+ // Revert metadata changes
525
+ // This would need integration with ComponentMetadata
526
+ results.warnings.push({
527
+ operation: operation.id,
528
+ warning: 'Metadata rollback not fully implemented'
529
+ });
530
+ }
531
+
532
+ } catch (error) {
533
+ results.failed.push({
534
+ operation: operation.id,
535
+ error: `Metadata rollback failed: ${error.message}`
536
+ });
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Archive backups after successful commit
542
+ * @private
543
+ */
544
+ async archiveBackups(transaction) {
545
+ // Move backups to archive directory with transaction reference
546
+ const archivePath = path.join(this.backupPath, 'archive', transaction.id);
547
+ await fs.ensureDir(archivePath);
548
+
549
+ // Archive transaction file
550
+ const transactionArchive = path.join(archivePath, 'transaction.json');
551
+ await fs.writeJson(transactionArchive, transaction, { spaces: 2 });
552
+ }
553
+
554
+ /**
555
+ * Generate transaction ID
556
+ * @private
557
+ */
558
+ generateTransactionId() {
559
+ return `txn-${Date.now()}-${crypto.randomBytes(4).toString('hex')}`;
560
+ }
561
+
562
+ /**
563
+ * Save transaction to disk
564
+ * @private
565
+ */
566
+ async saveTransaction(transaction) {
567
+ await fs.ensureDir(this.transactionPath);
568
+ const filePath = path.join(this.transactionPath, `${transaction.id}.json`);
569
+ await fs.writeJson(filePath, transaction, { spaces: 2 });
570
+ }
571
+
572
+ /**
573
+ * Load transaction from disk
574
+ * @private
575
+ */
576
+ async loadTransaction(transactionId) {
577
+ try {
578
+ const filePath = path.join(this.transactionPath, `${transactionId}.json`);
579
+ if (await fs.pathExists(filePath)) {
580
+ return await fs.readJson(filePath);
581
+ }
582
+ return null;
583
+ } catch (error) {
584
+ console.error(chalk.red(`Failed to load transaction: ${error.message}`));
585
+ return null;
586
+ }
587
+ }
588
+ }
589
+
590
590
  module.exports = TransactionManager;