aios-core 4.1.0 → 4.2.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.
Files changed (145) hide show
  1. package/.aios-core/.session/current-session.json +14 -0
  2. package/.aios-core/core/registry/registry-schema.json +166 -166
  3. package/.aios-core/core/registry/service-registry.json +6585 -6585
  4. package/.aios-core/data/entity-registry.yaml +208 -8
  5. package/.aios-core/data/registry-update-log.jsonl +165 -0
  6. package/.aios-core/development/scripts/approval-workflow.js +642 -642
  7. package/.aios-core/development/scripts/backup-manager.js +606 -606
  8. package/.aios-core/development/scripts/branch-manager.js +389 -389
  9. package/.aios-core/development/scripts/code-quality-improver.js +1311 -1311
  10. package/.aios-core/development/scripts/commit-message-generator.js +849 -849
  11. package/.aios-core/development/scripts/conflict-resolver.js +674 -674
  12. package/.aios-core/development/scripts/dependency-analyzer.js +637 -637
  13. package/.aios-core/development/scripts/diff-generator.js +351 -351
  14. package/.aios-core/development/scripts/elicitation-engine.js +384 -384
  15. package/.aios-core/development/scripts/elicitation-session-manager.js +299 -299
  16. package/.aios-core/development/scripts/git-wrapper.js +461 -461
  17. package/.aios-core/development/scripts/manifest-preview.js +244 -244
  18. package/.aios-core/development/scripts/metrics-tracker.js +775 -775
  19. package/.aios-core/development/scripts/modification-validator.js +554 -554
  20. package/.aios-core/development/scripts/pattern-learner.js +1224 -1224
  21. package/.aios-core/development/scripts/performance-analyzer.js +757 -757
  22. package/.aios-core/development/scripts/refactoring-suggester.js +1138 -1138
  23. package/.aios-core/development/scripts/rollback-handler.js +530 -530
  24. package/.aios-core/development/scripts/security-checker.js +358 -358
  25. package/.aios-core/development/scripts/template-engine.js +239 -239
  26. package/.aios-core/development/scripts/template-validator.js +278 -278
  27. package/.aios-core/development/scripts/test-generator.js +843 -843
  28. package/.aios-core/development/scripts/transaction-manager.js +589 -589
  29. package/.aios-core/development/scripts/usage-tracker.js +673 -673
  30. package/.aios-core/development/scripts/validate-filenames.js +226 -226
  31. package/.aios-core/development/scripts/version-tracker.js +526 -526
  32. package/.aios-core/development/scripts/yaml-validator.js +396 -396
  33. package/.aios-core/development/tasks/validate-next-story.md +99 -2
  34. package/.aios-core/development/templates/service-template/README.md.hbs +158 -158
  35. package/.aios-core/development/templates/service-template/__tests__/index.test.ts.hbs +237 -237
  36. package/.aios-core/development/templates/service-template/client.ts.hbs +403 -403
  37. package/.aios-core/development/templates/service-template/errors.ts.hbs +182 -182
  38. package/.aios-core/development/templates/service-template/index.ts.hbs +120 -120
  39. package/.aios-core/development/templates/service-template/package.json.hbs +87 -87
  40. package/.aios-core/development/templates/service-template/types.ts.hbs +145 -145
  41. package/.aios-core/development/templates/squad-template/LICENSE +21 -21
  42. package/.aios-core/docs/SHARD-TRANSLATION-GUIDE.md +335 -0
  43. package/.aios-core/docs/component-creation-guide.md +458 -0
  44. package/.aios-core/docs/session-update-pattern.md +307 -0
  45. package/.aios-core/docs/standards/AIOS-FRAMEWORK-MASTER.md +1963 -0
  46. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1-SUMMARY.md +1190 -0
  47. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO-V2.1.md +439 -0
  48. package/.aios-core/docs/standards/AIOS-LIVRO-DE-OURO.md +5398 -0
  49. package/.aios-core/docs/standards/V3-ARCHITECTURAL-DECISIONS.md +523 -0
  50. package/.aios-core/docs/template-syntax.md +267 -0
  51. package/.aios-core/docs/troubleshooting-guide.md +625 -0
  52. package/.aios-core/infrastructure/templates/aios-sync.yaml.template +193 -193
  53. package/.aios-core/infrastructure/templates/coderabbit.yaml.template +279 -279
  54. package/.aios-core/infrastructure/templates/github-workflows/ci.yml.template +169 -169
  55. package/.aios-core/infrastructure/templates/github-workflows/pr-automation.yml.template +330 -330
  56. package/.aios-core/infrastructure/templates/github-workflows/release.yml.template +196 -196
  57. package/.aios-core/infrastructure/templates/gitignore/gitignore-aios-base.tmpl +63 -63
  58. package/.aios-core/infrastructure/templates/gitignore/gitignore-brownfield-merge.tmpl +18 -18
  59. package/.aios-core/infrastructure/templates/gitignore/gitignore-node.tmpl +85 -85
  60. package/.aios-core/infrastructure/templates/gitignore/gitignore-python.tmpl +145 -145
  61. package/.aios-core/infrastructure/tests/utilities-audit-results.json +501 -0
  62. package/.aios-core/install-manifest.yaml +101 -101
  63. package/.aios-core/local-config.yaml.template +70 -70
  64. package/.aios-core/manifests/agents.csv +29 -0
  65. package/.aios-core/manifests/schema/manifest-schema.json +190 -190
  66. package/.aios-core/manifests/tasks.csv +198 -0
  67. package/.aios-core/manifests/workers.csv +204 -0
  68. package/.aios-core/monitor/hooks/lib/__init__.py +1 -1
  69. package/.aios-core/monitor/hooks/lib/enrich.py +58 -58
  70. package/.aios-core/monitor/hooks/lib/send_event.py +47 -47
  71. package/.aios-core/monitor/hooks/notification.py +29 -29
  72. package/.aios-core/monitor/hooks/post_tool_use.py +45 -45
  73. package/.aios-core/monitor/hooks/pre_compact.py +29 -29
  74. package/.aios-core/monitor/hooks/pre_tool_use.py +40 -40
  75. package/.aios-core/monitor/hooks/stop.py +29 -29
  76. package/.aios-core/monitor/hooks/subagent_stop.py +29 -29
  77. package/.aios-core/monitor/hooks/user_prompt_submit.py +38 -38
  78. package/.aios-core/product/templates/adr.hbs +125 -125
  79. package/.aios-core/product/templates/component-react-tmpl.tsx +98 -98
  80. package/.aios-core/product/templates/dbdr.hbs +241 -241
  81. package/.aios-core/product/templates/engine/schemas/adr.schema.json +102 -102
  82. package/.aios-core/product/templates/engine/schemas/dbdr.schema.json +205 -205
  83. package/.aios-core/product/templates/engine/schemas/epic.schema.json +175 -175
  84. package/.aios-core/product/templates/engine/schemas/pmdr.schema.json +175 -175
  85. package/.aios-core/product/templates/engine/schemas/prd-v2.schema.json +300 -300
  86. package/.aios-core/product/templates/engine/schemas/prd.schema.json +152 -152
  87. package/.aios-core/product/templates/engine/schemas/story.schema.json +222 -222
  88. package/.aios-core/product/templates/engine/schemas/task.schema.json +154 -154
  89. package/.aios-core/product/templates/epic.hbs +212 -212
  90. package/.aios-core/product/templates/eslintrc-security.json +32 -32
  91. package/.aios-core/product/templates/github-actions-cd.yml +212 -212
  92. package/.aios-core/product/templates/github-actions-ci.yml +172 -172
  93. package/.aios-core/product/templates/pmdr.hbs +186 -186
  94. package/.aios-core/product/templates/prd-v2.0.hbs +216 -216
  95. package/.aios-core/product/templates/prd.hbs +201 -201
  96. package/.aios-core/product/templates/shock-report-tmpl.html +502 -502
  97. package/.aios-core/product/templates/story.hbs +263 -263
  98. package/.aios-core/product/templates/task.hbs +170 -170
  99. package/.aios-core/product/templates/tmpl-comment-on-examples.sql +158 -158
  100. package/.aios-core/product/templates/tmpl-migration-script.sql +91 -91
  101. package/.aios-core/product/templates/tmpl-rls-granular-policies.sql +104 -104
  102. package/.aios-core/product/templates/tmpl-rls-kiss-policy.sql +10 -10
  103. package/.aios-core/product/templates/tmpl-rls-roles.sql +135 -135
  104. package/.aios-core/product/templates/tmpl-rls-simple.sql +77 -77
  105. package/.aios-core/product/templates/tmpl-rls-tenant.sql +152 -152
  106. package/.aios-core/product/templates/tmpl-rollback-script.sql +77 -77
  107. package/.aios-core/product/templates/tmpl-seed-data.sql +140 -140
  108. package/.aios-core/product/templates/tmpl-smoke-test.sql +16 -16
  109. package/.aios-core/product/templates/tmpl-staging-copy-merge.sql +139 -139
  110. package/.aios-core/product/templates/tmpl-stored-proc.sql +140 -140
  111. package/.aios-core/product/templates/tmpl-trigger.sql +152 -152
  112. package/.aios-core/product/templates/tmpl-view-materialized.sql +133 -133
  113. package/.aios-core/product/templates/tmpl-view.sql +177 -177
  114. package/.aios-core/product/templates/token-exports-css-tmpl.css +240 -240
  115. package/.aios-core/quality/schemas/quality-metrics.schema.json +233 -233
  116. package/.aios-core/scripts/migrate-framework-docs.sh +300 -300
  117. package/.aios-core/scripts/pm.sh +0 -0
  118. package/.claude/hooks/enforce-architecture-first.py +196 -196
  119. package/.claude/hooks/mind-clone-governance.py +192 -192
  120. package/.claude/hooks/read-protection.py +151 -151
  121. package/.claude/hooks/slug-validation.py +176 -176
  122. package/.claude/hooks/sql-governance.py +182 -182
  123. package/.claude/hooks/write-path-validation.py +194 -194
  124. package/.claude/rules/agent-authority.md +105 -0
  125. package/.claude/rules/coderabbit-integration.md +93 -0
  126. package/.claude/rules/ids-principles.md +112 -0
  127. package/.claude/rules/story-lifecycle.md +139 -0
  128. package/.claude/rules/workflow-execution.md +150 -0
  129. package/LICENSE +48 -48
  130. package/bin/aios-minimal.js +0 -0
  131. package/bin/aios.js +0 -0
  132. package/package.json +1 -1
  133. package/packages/aios-install/bin/aios-install.js +0 -0
  134. package/packages/aios-install/bin/edmcp.js +0 -0
  135. package/packages/aios-pro-cli/bin/aios-pro.js +0 -0
  136. package/packages/installer/src/wizard/pro-setup.js +433 -49
  137. package/scripts/check-markdown-links.py +352 -352
  138. package/scripts/code-intel-health-check.js +343 -0
  139. package/scripts/dashboard-parallel-dev.sh +0 -0
  140. package/scripts/dashboard-parallel-phase3.sh +0 -0
  141. package/scripts/dashboard-parallel-phase4.sh +0 -0
  142. package/scripts/glue/README.md +355 -0
  143. package/scripts/glue/compose-agent-prompt.cjs +362 -0
  144. package/scripts/install-monitor-hooks.sh +0 -0
  145. package/.aios-core/lib/build.json +0 -1
@@ -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;