vibecodingmachine-core 2026.2.20-438 → 2026.2.26-1739
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +240 -0
- package/package.json +10 -2
- package/src/agents/Agent.js +300 -0
- package/src/agents/AgentAdditionService.js +311 -0
- package/src/agents/AgentCheckService.js +690 -0
- package/src/agents/AgentInstallationService.js +140 -0
- package/src/agents/AgentSetupService.js +467 -0
- package/src/agents/AgentStatus.js +183 -0
- package/src/agents/AgentVerificationService.js +634 -0
- package/src/agents/ConfigurationSchemaValidator.js +543 -0
- package/src/agents/EnvironmentConfigurationManager.js +602 -0
- package/src/agents/InstallationErrorHandler.js +372 -0
- package/src/agents/InstallationLog.js +363 -0
- package/src/agents/InstallationMethod.js +510 -0
- package/src/agents/InstallationOrchestrator.js +352 -0
- package/src/agents/InstallationProgressReporter.js +372 -0
- package/src/agents/InstallationRetryManager.js +322 -0
- package/src/agents/InstallationType.js +254 -0
- package/src/agents/OperationTypes.js +310 -0
- package/src/agents/PerformanceMetricsCollector.js +493 -0
- package/src/agents/SecurityValidationService.js +534 -0
- package/src/agents/VerificationTest.js +354 -0
- package/src/agents/VerificationType.js +226 -0
- package/src/agents/WindowsPermissionHandler.js +518 -0
- package/src/agents/config/AgentConfigManager.js +393 -0
- package/src/agents/config/AgentDefaultsRegistry.js +373 -0
- package/src/agents/config/ConfigValidator.js +281 -0
- package/src/agents/discovery/AgentDiscoveryService.js +707 -0
- package/src/agents/logging/AgentLogger.js +511 -0
- package/src/agents/status/AgentStatusManager.js +481 -0
- package/src/agents/storage/FileManager.js +454 -0
- package/src/agents/verification/AgentCommunicationTester.js +474 -0
- package/src/agents/verification/BaseVerifier.js +430 -0
- package/src/agents/verification/CommandVerifier.js +480 -0
- package/src/agents/verification/FileOperationVerifier.js +453 -0
- package/src/agents/verification/ResultAnalyzer.js +707 -0
- package/src/agents/verification/TestRequirementManager.js +495 -0
- package/src/agents/verification/VerificationRunner.js +433 -0
- package/src/agents/windows/BaseWindowsInstaller.js +441 -0
- package/src/agents/windows/ChocolateyInstaller.js +509 -0
- package/src/agents/windows/DirectInstaller.js +443 -0
- package/src/agents/windows/InstallerFactory.js +391 -0
- package/src/agents/windows/NpmInstaller.js +505 -0
- package/src/agents/windows/PowerShellInstaller.js +458 -0
- package/src/agents/windows/WinGetInstaller.js +390 -0
- package/src/analysis/analysis-reporter.js +132 -0
- package/src/analysis/boundary-detector.js +712 -0
- package/src/analysis/categorizer.js +340 -0
- package/src/analysis/codebase-scanner.js +384 -0
- package/src/analysis/line-counter.js +513 -0
- package/src/analysis/priority-calculator.js +679 -0
- package/src/analysis/report/analysis-report.js +250 -0
- package/src/analysis/report/package-analyzer.js +278 -0
- package/src/analysis/report/recommendation-generator.js +382 -0
- package/src/analysis/report/statistics-generator.js +515 -0
- package/src/analysis/reports/analysis-report-model.js +101 -0
- package/src/analysis/reports/recommendation-generator.js +283 -0
- package/src/analysis/reports/report-generators.js +191 -0
- package/src/analysis/reports/statistics-calculator.js +231 -0
- package/src/analysis/reports/trend-analyzer.js +219 -0
- package/src/analysis/strategy-generator.js +814 -0
- package/src/auto-mode/AutoModeBusinessLogic.js +836 -0
- package/src/config/refactoring-config.js +307 -0
- package/src/health-tracking/json-storage.js +38 -2
- package/src/ide-integration/applescript-manager-core.js +233 -0
- package/src/ide-integration/applescript-manager.cjs +357 -28
- package/src/ide-integration/applescript-manager.js +89 -3599
- package/src/ide-integration/cdp-manager.js +306 -0
- package/src/ide-integration/claude-code-cli-manager.cjs +1 -1
- package/src/ide-integration/continuation-handler.js +337 -0
- package/src/ide-integration/ide-status-checker.js +292 -0
- package/src/ide-integration/macos-ide-manager.js +627 -0
- package/src/ide-integration/macos-text-sender.js +528 -0
- package/src/ide-integration/response-reader.js +548 -0
- package/src/ide-integration/windows-automation-manager.js +121 -0
- package/src/ide-integration/windows-ide-manager.js +373 -0
- package/src/index.cjs +25 -3
- package/src/index.js +15 -1
- package/src/llm/direct-llm-manager.cjs +90 -2
- package/src/models/compliance-report.js +538 -0
- package/src/models/file-analysis.js +681 -0
- package/src/models/refactoring-plan.js +770 -0
- package/src/monitoring/alert-system.js +834 -0
- package/src/monitoring/compliance-progress-tracker.js +437 -0
- package/src/monitoring/continuous-scan-notifications.js +661 -0
- package/src/monitoring/continuous-scanner.js +279 -0
- package/src/monitoring/file-monitor/file-analyzer.js +262 -0
- package/src/monitoring/file-monitor/file-monitor.js +237 -0
- package/src/monitoring/file-monitor/watcher.js +194 -0
- package/src/monitoring/file-monitor.js +17 -0
- package/src/monitoring/notification-manager.js +437 -0
- package/src/monitoring/scanner-core.js +368 -0
- package/src/monitoring/scanner-events.js +214 -0
- package/src/monitoring/violation-notification-system.js +515 -0
- package/src/refactoring/boundaries/cohesion-analyzer.js +316 -0
- package/src/refactoring/boundaries/extraction-result.js +285 -0
- package/src/refactoring/boundaries/extraction-strategies.js +392 -0
- package/src/refactoring/boundaries/module-boundary.js +209 -0
- package/src/refactoring/boundary/boundary-detector.js +741 -0
- package/src/refactoring/boundary/boundary-types.js +405 -0
- package/src/refactoring/boundary/extraction-strategies.js +554 -0
- package/src/refactoring/boundary-extraction-result.js +77 -0
- package/src/refactoring/boundary-extraction-strategies.js +330 -0
- package/src/refactoring/boundary-extractor.js +384 -0
- package/src/refactoring/boundary-types.js +46 -0
- package/src/refactoring/circular/circular-dependency.js +88 -0
- package/src/refactoring/circular/cycle-detection.js +147 -0
- package/src/refactoring/circular/dependency-node.js +82 -0
- package/src/refactoring/circular/dependency-result.js +107 -0
- package/src/refactoring/circular/dependency-types.js +58 -0
- package/src/refactoring/circular/graph-builder.js +213 -0
- package/src/refactoring/circular/resolution-strategy.js +72 -0
- package/src/refactoring/circular/strategy-generator.js +229 -0
- package/src/refactoring/circular-dependency-resolver-original.js +809 -0
- package/src/refactoring/circular-dependency-resolver.js +200 -0
- package/src/refactoring/code-mover.js +761 -0
- package/src/refactoring/file-splitter.js +696 -0
- package/src/refactoring/functionality-validator.js +816 -0
- package/src/refactoring/import-manager.js +774 -0
- package/src/refactoring/module-boundary.js +107 -0
- package/src/refactoring/refactoring-executor.js +672 -0
- package/src/refactoring/refactoring-rollback.js +614 -0
- package/src/refactoring/test-validator.js +631 -0
- package/src/requirement-management/default-requirement-manager.js +321 -0
- package/src/requirement-management/requirement-file-parser.js +159 -0
- package/src/requirement-management/requirement-sequencer.js +221 -0
- package/src/rui/commands/AgentCommandParser.js +600 -0
- package/src/rui/commands/AgentCommands.js +487 -0
- package/src/rui/commands/AgentResponseFormatter.js +832 -0
- package/src/scripts/verify-full-compliance.js +269 -0
- package/src/sync/sync-engine-core.js +1 -0
- package/src/sync/sync-engine-remote-handlers.js +135 -0
- package/src/task-generation/automated-task-generator.js +351 -0
- package/src/task-generation/prioritizer.js +287 -0
- package/src/task-generation/task-list-updater.js +215 -0
- package/src/task-generation/task-management-integration.js +480 -0
- package/src/task-generation/task-manager-integration.js +270 -0
- package/src/task-generation/violation-task-generator.js +474 -0
- package/src/task-management/continuous-scan-integration.js +342 -0
- package/src/timeout-management/index.js +12 -3
- package/src/timeout-management/response-time-tracker.js +167 -0
- package/src/timeout-management/timeout-calculator.js +159 -0
- package/src/timeout-management/timeout-config-manager.js +172 -0
- package/src/utils/ast-analyzer.js +417 -0
- package/src/utils/current-requirement-manager.js +276 -0
- package/src/utils/current-requirement-operations.js +472 -0
- package/src/utils/dependency-mapper.js +456 -0
- package/src/utils/download-with-progress.js +4 -2
- package/src/utils/electron-update-checker.js +4 -1
- package/src/utils/file-size-analyzer.js +272 -0
- package/src/utils/import-updater.js +280 -0
- package/src/utils/refactoring-tools.js +512 -0
- package/src/utils/report-generator.js +569 -0
- package/src/utils/reports/report-analysis.js +218 -0
- package/src/utils/reports/report-types.js +55 -0
- package/src/utils/reports/summary-generators.js +102 -0
- package/src/utils/requirement-file-management.js +157 -0
- package/src/utils/requirement-helpers/requirement-file-ops.js +392 -0
- package/src/utils/requirement-helpers/requirement-mover.js +414 -0
- package/src/utils/requirement-helpers/requirement-parser.js +326 -0
- package/src/utils/requirement-helpers/requirement-status.js +320 -0
- package/src/utils/requirement-helpers-new.js +55 -0
- package/src/utils/requirement-helpers-refactored.js +367 -0
- package/src/utils/requirement-helpers.js +291 -1191
- package/src/utils/requirement-movement-operations.js +450 -0
- package/src/utils/requirement-movement.js +312 -0
- package/src/utils/requirement-parsing-helpers.js +56 -0
- package/src/utils/requirement-statistics.js +200 -0
- package/src/utils/requirement-text-utils.js +58 -0
- package/src/utils/rollback/rollback-handlers.js +125 -0
- package/src/utils/rollback/rollback-operation.js +63 -0
- package/src/utils/rollback/rollback-recorder.js +166 -0
- package/src/utils/rollback/rollback-state-manager.js +175 -0
- package/src/utils/rollback/rollback-types.js +33 -0
- package/src/utils/rollback/rollback-utils.js +110 -0
- package/src/utils/rollback-manager-original.js +569 -0
- package/src/utils/rollback-manager.js +202 -0
- package/src/utils/smoke-test-cli.js +362 -0
- package/src/utils/smoke-test-gui.js +351 -0
- package/src/utils/smoke-test-orchestrator.js +321 -0
- package/src/utils/smoke-test-runner.js +60 -0
- package/src/utils/smoke-test-web.js +347 -0
- package/src/utils/specification-helpers.js +39 -13
- package/src/utils/specification-migration.js +97 -0
- package/src/utils/test-runner.js +579 -0
- package/src/utils/validation-framework.js +518 -0
- package/src/validation/compliance-analyzer.js +197 -0
- package/src/validation/compliance-report-generator.js +343 -0
- package/src/validation/compliance-reporter.js +711 -0
- package/src/validation/compliance-rules.js +127 -0
- package/src/validation/constitution-validator-new.js +196 -0
- package/src/validation/constitution-validator.js +17 -0
- package/src/validation/file-validators.js +170 -0
- package/src/validation/line-limit/file-analyzer.js +201 -0
- package/src/validation/line-limit/line-limit-validator.js +208 -0
- package/src/validation/line-limit/validation-result.js +144 -0
- package/src/validation/line-limit-core.js +225 -0
- package/src/validation/line-limit-reporter.js +134 -0
- package/src/validation/line-limit-result.js +125 -0
- package/src/validation/line-limit-validator.js +41 -0
- package/src/validation/metrics-calculator.js +660 -0
- package/src/sync/sync-engine-backup.js +0 -559
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installation Progress Reporter
|
|
3
|
+
*
|
|
4
|
+
* Provides progress reporting for long-running installations.
|
|
5
|
+
* Follows constitutional requirements: <555 lines, test-first approach.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Installation Progress Reporter class
|
|
10
|
+
*/
|
|
11
|
+
class InstallationProgressReporter {
|
|
12
|
+
/**
|
|
13
|
+
* Create progress reporter instance
|
|
14
|
+
* @param {Object} options - Reporter options
|
|
15
|
+
*/
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.logger = options.logger || null;
|
|
18
|
+
this.updateInterval = options.updateInterval || 2000; // 2 seconds
|
|
19
|
+
this.verbose = options.verbose || false;
|
|
20
|
+
this.progressCallbacks = options.progressCallbacks || [];
|
|
21
|
+
|
|
22
|
+
// Progress tracking
|
|
23
|
+
this.activeOperations = new Map();
|
|
24
|
+
this.timers = new Map();
|
|
25
|
+
this.lastUpdate = new Map();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Start tracking installation progress
|
|
30
|
+
* @param {string} operationId - Unique operation identifier
|
|
31
|
+
* @param {Object} context - Operation context
|
|
32
|
+
* @returns {Object} - Progress tracker
|
|
33
|
+
*/
|
|
34
|
+
startProgress(operationId, context = {}) {
|
|
35
|
+
const { agentId, installerType, totalSteps = 5, estimatedDuration = 300000 } = context;
|
|
36
|
+
|
|
37
|
+
const progress = {
|
|
38
|
+
operationId,
|
|
39
|
+
agentId,
|
|
40
|
+
installerType,
|
|
41
|
+
startTime: Date.now(),
|
|
42
|
+
totalSteps,
|
|
43
|
+
currentStep: 0,
|
|
44
|
+
currentStepName: 'Initializing',
|
|
45
|
+
progressPercent: 0,
|
|
46
|
+
estimatedDuration,
|
|
47
|
+
elapsedMs: 0,
|
|
48
|
+
remainingMs: estimatedDuration,
|
|
49
|
+
status: 'RUNNING',
|
|
50
|
+
details: {},
|
|
51
|
+
logs: []
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
this.activeOperations.set(operationId, progress);
|
|
55
|
+
|
|
56
|
+
// Start periodic updates
|
|
57
|
+
this.startPeriodicUpdate(operationId);
|
|
58
|
+
|
|
59
|
+
// Log start
|
|
60
|
+
if (this.logger) {
|
|
61
|
+
this.logger.info(`Started installation: ${agentId}`, {
|
|
62
|
+
operationId,
|
|
63
|
+
agentId,
|
|
64
|
+
installerType,
|
|
65
|
+
estimatedDuration
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Notify callbacks
|
|
70
|
+
this.notifyProgress(operationId, progress);
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
updateStep: (stepName, details = {}) => this.updateStep(operationId, stepName, details),
|
|
74
|
+
setProgress: (percent, details = {}) => this.setProgress(operationId, percent, details),
|
|
75
|
+
complete: (result = {}) => this.completeProgress(operationId, result),
|
|
76
|
+
fail: (error) => this.failProgress(operationId, error),
|
|
77
|
+
getProgress: () => this.getProgress(operationId)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Update current step
|
|
83
|
+
* @param {string} operationId - Operation identifier
|
|
84
|
+
* @param {string} stepName - Name of current step
|
|
85
|
+
* @param {Object} details - Step details
|
|
86
|
+
*/
|
|
87
|
+
updateStep(operationId, stepName, details = {}) {
|
|
88
|
+
const progress = this.activeOperations.get(operationId);
|
|
89
|
+
if (!progress) return;
|
|
90
|
+
|
|
91
|
+
progress.currentStep++;
|
|
92
|
+
progress.currentStepName = stepName;
|
|
93
|
+
progress.progressPercent = Math.round((progress.currentStep / progress.totalSteps) * 100);
|
|
94
|
+
progress.details = { ...progress.details, ...details };
|
|
95
|
+
progress.elapsedMs = Date.now() - progress.startTime;
|
|
96
|
+
|
|
97
|
+
// Estimate remaining time
|
|
98
|
+
const avgStepTime = progress.elapsedMs / progress.currentStep;
|
|
99
|
+
const remainingSteps = progress.totalSteps - progress.currentStep;
|
|
100
|
+
progress.remainingMs = remainingSteps * avgStepTime;
|
|
101
|
+
|
|
102
|
+
// Add to log
|
|
103
|
+
progress.logs.push({
|
|
104
|
+
timestamp: new Date().toISOString(),
|
|
105
|
+
step: stepName,
|
|
106
|
+
details,
|
|
107
|
+
elapsedMs: progress.elapsedMs
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
this.notifyProgress(operationId, progress);
|
|
111
|
+
|
|
112
|
+
if (this.verbose && this.logger) {
|
|
113
|
+
this.logger.info(`Progress: ${progress.agentId} - ${stepName}`, {
|
|
114
|
+
operationId,
|
|
115
|
+
step: progress.currentStep,
|
|
116
|
+
totalSteps: progress.totalSteps,
|
|
117
|
+
progressPercent: progress.progressPercent,
|
|
118
|
+
elapsedMs: progress.elapsedMs
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Set progress percentage directly
|
|
125
|
+
* @param {string} operationId - Operation identifier
|
|
126
|
+
* @param {number} percent - Progress percentage (0-100)
|
|
127
|
+
* @param {Object} details - Progress details
|
|
128
|
+
*/
|
|
129
|
+
setProgress(operationId, percent, details = {}) {
|
|
130
|
+
const progress = this.activeOperations.get(operationId);
|
|
131
|
+
if (!progress) return;
|
|
132
|
+
|
|
133
|
+
progress.progressPercent = Math.max(0, Math.min(100, percent));
|
|
134
|
+
progress.details = { ...progress.details, ...details };
|
|
135
|
+
progress.elapsedMs = Date.now() - progress.startTime;
|
|
136
|
+
|
|
137
|
+
// Estimate remaining time
|
|
138
|
+
if (percent > 0) {
|
|
139
|
+
const totalTime = (progress.elapsedMs / percent) * 100;
|
|
140
|
+
progress.remainingMs = Math.max(0, totalTime - progress.elapsedMs);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
this.notifyProgress(operationId, progress);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Complete progress tracking
|
|
148
|
+
* @param {string} operationId - Operation identifier
|
|
149
|
+
* @param {Object} result - Completion result
|
|
150
|
+
*/
|
|
151
|
+
completeProgress(operationId, result = {}) {
|
|
152
|
+
const progress = this.activeOperations.get(operationId);
|
|
153
|
+
if (!progress) return;
|
|
154
|
+
|
|
155
|
+
progress.status = 'COMPLETED';
|
|
156
|
+
progress.progressPercent = 100;
|
|
157
|
+
progress.currentStepName = 'Completed';
|
|
158
|
+
progress.elapsedMs = Date.now() - progress.startTime;
|
|
159
|
+
progress.remainingMs = 0;
|
|
160
|
+
progress.result = result;
|
|
161
|
+
|
|
162
|
+
// Add completion log
|
|
163
|
+
progress.logs.push({
|
|
164
|
+
timestamp: new Date().toISOString(),
|
|
165
|
+
step: 'Completed',
|
|
166
|
+
details: result,
|
|
167
|
+
elapsedMs: progress.elapsedMs
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
// Stop periodic updates
|
|
171
|
+
this.stopPeriodicUpdate(operationId);
|
|
172
|
+
|
|
173
|
+
// Log completion
|
|
174
|
+
if (this.logger) {
|
|
175
|
+
this.logger.info(`Completed installation: ${progress.agentId}`, {
|
|
176
|
+
operationId,
|
|
177
|
+
agentId: progress.agentId,
|
|
178
|
+
duration: progress.elapsedMs,
|
|
179
|
+
result
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
this.notifyProgress(operationId, progress);
|
|
184
|
+
|
|
185
|
+
// Remove from active operations after delay
|
|
186
|
+
setTimeout(() => {
|
|
187
|
+
this.activeOperations.delete(operationId);
|
|
188
|
+
}, 5000);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Mark progress as failed
|
|
193
|
+
* @param {string} operationId - Operation identifier
|
|
194
|
+
* @param {Error} error - Error that caused failure
|
|
195
|
+
*/
|
|
196
|
+
failProgress(operationId, error) {
|
|
197
|
+
const progress = this.activeOperations.get(operationId);
|
|
198
|
+
if (!progress) return;
|
|
199
|
+
|
|
200
|
+
progress.status = 'FAILED';
|
|
201
|
+
progress.currentStepName = 'Failed';
|
|
202
|
+
progress.elapsedMs = Date.now() - progress.startTime;
|
|
203
|
+
progress.error = error.message;
|
|
204
|
+
|
|
205
|
+
// Add failure log
|
|
206
|
+
progress.logs.push({
|
|
207
|
+
timestamp: new Date().toISOString(),
|
|
208
|
+
step: 'Failed',
|
|
209
|
+
details: { error: error.message },
|
|
210
|
+
elapsedMs: progress.elapsedMs
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
// Stop periodic updates
|
|
214
|
+
this.stopPeriodicUpdate(operationId);
|
|
215
|
+
|
|
216
|
+
// Log failure
|
|
217
|
+
if (this.logger) {
|
|
218
|
+
this.logger.error(`Failed installation: ${progress.agentId}`, {
|
|
219
|
+
operationId,
|
|
220
|
+
agentId: progress.agentId,
|
|
221
|
+
error: error.message,
|
|
222
|
+
elapsedMs: progress.elapsedMs
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
this.notifyProgress(operationId, progress);
|
|
227
|
+
|
|
228
|
+
// Remove from active operations after delay
|
|
229
|
+
setTimeout(() => {
|
|
230
|
+
this.activeOperations.delete(operationId);
|
|
231
|
+
}, 5000);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get current progress
|
|
236
|
+
* @param {string} operationId - Operation identifier
|
|
237
|
+
* @returns {Object|null} - Current progress or null
|
|
238
|
+
*/
|
|
239
|
+
getProgress(operationId) {
|
|
240
|
+
return this.activeOperations.get(operationId) || null;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Get all active operations
|
|
245
|
+
* @returns {Array} - Array of active operations
|
|
246
|
+
*/
|
|
247
|
+
getActiveOperations() {
|
|
248
|
+
return Array.from(this.activeOperations.values());
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Add progress callback
|
|
253
|
+
* @param {Function} callback - Progress callback function
|
|
254
|
+
*/
|
|
255
|
+
addProgressCallback(callback) {
|
|
256
|
+
this.progressCallbacks.push(callback);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Remove progress callback
|
|
261
|
+
* @param {Function} callback - Progress callback function to remove
|
|
262
|
+
*/
|
|
263
|
+
removeProgressCallback(callback) {
|
|
264
|
+
const index = this.progressCallbacks.indexOf(callback);
|
|
265
|
+
if (index > -1) {
|
|
266
|
+
this.progressCallbacks.splice(index, 1);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Notify all progress callbacks
|
|
272
|
+
* @param {string} operationId - Operation identifier
|
|
273
|
+
* @param {Object} progress - Progress data
|
|
274
|
+
*/
|
|
275
|
+
notifyProgress(operationId, progress) {
|
|
276
|
+
const progressData = {
|
|
277
|
+
...progress,
|
|
278
|
+
formattedTime: this.formatDuration(progress.elapsedMs),
|
|
279
|
+
formattedRemaining: this.formatDuration(progress.remainingMs),
|
|
280
|
+
progressBar: this.generateProgressBar(progress.progressPercent)
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
this.progressCallbacks.forEach(callback => {
|
|
284
|
+
try {
|
|
285
|
+
callback(operationId, progressData);
|
|
286
|
+
} catch (error) {
|
|
287
|
+
if (this.logger) {
|
|
288
|
+
this.logger.warn('Progress callback error', { error: error.message });
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Start periodic updates
|
|
296
|
+
* @param {string} operationId - Operation identifier
|
|
297
|
+
*/
|
|
298
|
+
startPeriodicUpdate(operationId) {
|
|
299
|
+
const timer = setInterval(() => {
|
|
300
|
+
const progress = this.activeOperations.get(operationId);
|
|
301
|
+
if (!progress) {
|
|
302
|
+
this.stopPeriodicUpdate(operationId);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
progress.elapsedMs = Date.now() - progress.startTime;
|
|
307
|
+
|
|
308
|
+
// Update remaining time estimate
|
|
309
|
+
if (progress.progressPercent > 0) {
|
|
310
|
+
const totalTime = (progress.elapsedMs / progress.progressPercent) * 100;
|
|
311
|
+
progress.remainingMs = Math.max(0, totalTime - progress.elapsedMs);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
this.notifyProgress(operationId, progress);
|
|
315
|
+
}, this.updateInterval);
|
|
316
|
+
|
|
317
|
+
this.timers.set(operationId, timer);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Stop periodic updates
|
|
322
|
+
* @param {string} operationId - Operation identifier
|
|
323
|
+
*/
|
|
324
|
+
stopPeriodicUpdate(operationId) {
|
|
325
|
+
const timer = this.timers.get(operationId);
|
|
326
|
+
if (timer) {
|
|
327
|
+
clearInterval(timer);
|
|
328
|
+
this.timers.delete(operationId);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Format duration in human readable format
|
|
334
|
+
* @param {number} ms - Duration in milliseconds
|
|
335
|
+
* @returns {string} - Formatted duration
|
|
336
|
+
*/
|
|
337
|
+
formatDuration(ms) {
|
|
338
|
+
if (ms < 1000) return `${ms}ms`;
|
|
339
|
+
if (ms < 60000) return `${Math.round(ms / 1000)}s`;
|
|
340
|
+
if (ms < 3600000) return `${Math.round(ms / 60000)}m ${Math.round((ms % 60000) / 1000)}s`;
|
|
341
|
+
return `${Math.round(ms / 3600000)}h ${Math.round((ms % 3600000) / 60000)}m`;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Generate progress bar
|
|
346
|
+
* @param {number} percent - Progress percentage
|
|
347
|
+
* @param {number} width - Progress bar width
|
|
348
|
+
* @returns {string} - Progress bar string
|
|
349
|
+
*/
|
|
350
|
+
generateProgressBar(percent, width = 20) {
|
|
351
|
+
const filled = Math.round((percent / 100) * width);
|
|
352
|
+
const empty = width - filled;
|
|
353
|
+
return `[${'='.repeat(filled)}${' '.repeat(empty)}] ${percent}%`;
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Cleanup all active operations and timers
|
|
358
|
+
*/
|
|
359
|
+
cleanup() {
|
|
360
|
+
// Clear all timers
|
|
361
|
+
for (const [operationId, timer] of this.timers) {
|
|
362
|
+
clearInterval(timer);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// Clear collections
|
|
366
|
+
this.timers.clear();
|
|
367
|
+
this.activeOperations.clear();
|
|
368
|
+
this.lastUpdate.clear();
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
module.exports = InstallationProgressReporter;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Installation Retry Manager
|
|
3
|
+
*
|
|
4
|
+
* Handles retry logic for transient network failures during installation.
|
|
5
|
+
* Follows constitutional requirements: <555 lines, test-first approach.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Installation Retry Manager class
|
|
10
|
+
*/
|
|
11
|
+
class InstallationRetryManager {
|
|
12
|
+
/**
|
|
13
|
+
* Create retry manager instance
|
|
14
|
+
* @param {Object} options - Retry options
|
|
15
|
+
*/
|
|
16
|
+
constructor(options = {}) {
|
|
17
|
+
this.logger = options.logger || null;
|
|
18
|
+
this.maxRetries = options.maxRetries || 3;
|
|
19
|
+
this.baseDelay = options.baseDelay || 1000; // 1 second
|
|
20
|
+
this.maxDelay = options.maxDelay || 30000; // 30 seconds
|
|
21
|
+
this.backoffMultiplier = options.backoffMultiplier || 2;
|
|
22
|
+
this.jitter = options.jitter || true;
|
|
23
|
+
|
|
24
|
+
// Network error patterns
|
|
25
|
+
this.networkErrorPatterns = [
|
|
26
|
+
/network/i,
|
|
27
|
+
/connection/i,
|
|
28
|
+
/timeout/i,
|
|
29
|
+
/unreachable/i,
|
|
30
|
+
/dns/i,
|
|
31
|
+
/proxy/i,
|
|
32
|
+
/firewall/i,
|
|
33
|
+
/etimedout/i,
|
|
34
|
+
/enotfound/i,
|
|
35
|
+
/econnrefused/i,
|
|
36
|
+
/econnreset/i,
|
|
37
|
+
/socket/i,
|
|
38
|
+
/host not found/i,
|
|
39
|
+
/connection refused/i,
|
|
40
|
+
/no internet/i,
|
|
41
|
+
/offline/i
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
// Transient HTTP status codes
|
|
45
|
+
this.transientStatusCodes = [
|
|
46
|
+
408, // Request Timeout
|
|
47
|
+
429, // Too Many Requests
|
|
48
|
+
500, // Internal Server Error
|
|
49
|
+
502, // Bad Gateway
|
|
50
|
+
503, // Service Unavailable
|
|
51
|
+
504, // Gateway Timeout
|
|
52
|
+
507, // Insufficient Storage
|
|
53
|
+
509, // Bandwidth Limit Exceeded
|
|
54
|
+
520, // Unknown Error
|
|
55
|
+
521, // Web Server Is Down
|
|
56
|
+
522, // Connection Timed Out
|
|
57
|
+
523, // Origin Is Unreachable
|
|
58
|
+
524 // A Timeout Occurred
|
|
59
|
+
];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Execute operation with retry logic
|
|
64
|
+
* @param {Function} operation - Operation to execute
|
|
65
|
+
* @param {Object} context - Operation context
|
|
66
|
+
* @returns {Promise<Object>} - Operation result
|
|
67
|
+
*/
|
|
68
|
+
async executeWithRetry(operation, context = {}) {
|
|
69
|
+
const { operationId, agentId, operationType = 'INSTALLATION' } = context;
|
|
70
|
+
let lastError = null;
|
|
71
|
+
let attempt = 0;
|
|
72
|
+
|
|
73
|
+
if (this.logger) {
|
|
74
|
+
this.logger.info(`Starting operation with retry logic`, {
|
|
75
|
+
operationId,
|
|
76
|
+
agentId,
|
|
77
|
+
operationType,
|
|
78
|
+
maxRetries: this.maxRetries
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
while (attempt <= this.maxRetries) {
|
|
83
|
+
attempt++;
|
|
84
|
+
|
|
85
|
+
try {
|
|
86
|
+
if (this.logger) {
|
|
87
|
+
this.logger.info(`Executing operation attempt ${attempt}`, {
|
|
88
|
+
operationId,
|
|
89
|
+
agentId,
|
|
90
|
+
attempt,
|
|
91
|
+
maxRetries: this.maxRetries + 1
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const result = await operation();
|
|
96
|
+
|
|
97
|
+
// Success - log and return
|
|
98
|
+
if (this.logger) {
|
|
99
|
+
this.logger.info(`Operation succeeded on attempt ${attempt}`, {
|
|
100
|
+
operationId,
|
|
101
|
+
agentId,
|
|
102
|
+
attempt,
|
|
103
|
+
duration: result.duration
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return {
|
|
108
|
+
success: true,
|
|
109
|
+
result,
|
|
110
|
+
attempts: attempt,
|
|
111
|
+
operationId,
|
|
112
|
+
agentId
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
} catch (error) {
|
|
116
|
+
lastError = error;
|
|
117
|
+
|
|
118
|
+
// Check if error is retryable
|
|
119
|
+
const isRetryable = this.isRetryableError(error);
|
|
120
|
+
|
|
121
|
+
if (this.logger) {
|
|
122
|
+
this.logger.warn(`Operation attempt ${attempt} failed`, {
|
|
123
|
+
operationId,
|
|
124
|
+
agentId,
|
|
125
|
+
attempt,
|
|
126
|
+
error: error.message,
|
|
127
|
+
isRetryable,
|
|
128
|
+
remainingRetries: this.maxRetries - attempt + 1
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// If not retryable or no more attempts, throw error
|
|
133
|
+
if (!isRetryable || attempt > this.maxRetries) {
|
|
134
|
+
const failureMessage = !isRetryable ?
|
|
135
|
+
`Non-retryable error: ${error.message}` :
|
|
136
|
+
`Operation failed after ${attempt} attempts: ${error.message}`;
|
|
137
|
+
|
|
138
|
+
if (this.logger) {
|
|
139
|
+
this.logger.error(`Operation failed permanently`, {
|
|
140
|
+
operationId,
|
|
141
|
+
agentId,
|
|
142
|
+
attempts: attempt,
|
|
143
|
+
finalError: error.message,
|
|
144
|
+
isRetryable
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
throw new Error(failureMessage);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// Calculate delay for next attempt
|
|
152
|
+
const delay = this.calculateDelay(attempt);
|
|
153
|
+
|
|
154
|
+
if (this.logger) {
|
|
155
|
+
this.logger.info(`Waiting ${delay}ms before retry`, {
|
|
156
|
+
operationId,
|
|
157
|
+
agentId,
|
|
158
|
+
attempt,
|
|
159
|
+
delay,
|
|
160
|
+
nextAttempt: attempt + 1
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Wait before retry
|
|
165
|
+
await this.sleep(delay);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Should not reach here, but just in case
|
|
170
|
+
throw lastError || new Error('Operation failed after all retry attempts');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* Check if error is retryable
|
|
175
|
+
* @param {Error} error - Error to check
|
|
176
|
+
* @returns {boolean} - Whether error is retryable
|
|
177
|
+
*/
|
|
178
|
+
isRetryableError(error) {
|
|
179
|
+
const errorMessage = error.message.toLowerCase();
|
|
180
|
+
|
|
181
|
+
// Check network error patterns
|
|
182
|
+
const isNetworkError = this.networkErrorPatterns.some(pattern =>
|
|
183
|
+
pattern.test(errorMessage)
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
if (isNetworkError) {
|
|
187
|
+
return true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check HTTP status codes
|
|
191
|
+
const statusCodeMatch = errorMessage.match(/status code:? (\d{3})/i);
|
|
192
|
+
if (statusCodeMatch) {
|
|
193
|
+
const statusCode = parseInt(statusCodeMatch[1]);
|
|
194
|
+
return this.transientStatusCodes.includes(statusCode);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// Check for specific retryable error messages
|
|
198
|
+
const retryablePatterns = [
|
|
199
|
+
/rate limit/i,
|
|
200
|
+
/too many requests/i,
|
|
201
|
+
/temporarily unavailable/i,
|
|
202
|
+
/service unavailable/i,
|
|
203
|
+
/try again later/i,
|
|
204
|
+
/quota exceeded/i,
|
|
205
|
+
/bandwidth limit/i,
|
|
206
|
+
/connection pool/i,
|
|
207
|
+
/max connections/i
|
|
208
|
+
];
|
|
209
|
+
|
|
210
|
+
return retryablePatterns.some(pattern => pattern.test(errorMessage));
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Calculate delay before next retry
|
|
215
|
+
* @param {number} attempt - Current attempt number (1-based)
|
|
216
|
+
* @returns {number} - Delay in milliseconds
|
|
217
|
+
*/
|
|
218
|
+
calculateDelay(attempt) {
|
|
219
|
+
// Exponential backoff: delay = baseDelay * (multiplier ^ (attempt - 1))
|
|
220
|
+
let delay = this.baseDelay * Math.pow(this.backoffMultiplier, attempt - 1);
|
|
221
|
+
|
|
222
|
+
// Cap at maximum delay
|
|
223
|
+
delay = Math.min(delay, this.maxDelay);
|
|
224
|
+
|
|
225
|
+
// Add jitter to prevent thundering herd
|
|
226
|
+
if (this.jitter) {
|
|
227
|
+
const jitterRange = delay * 0.1; // 10% jitter
|
|
228
|
+
delay = delay + (Math.random() - 0.5) * jitterRange;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Ensure minimum delay
|
|
232
|
+
delay = Math.max(delay, 100);
|
|
233
|
+
|
|
234
|
+
return Math.round(delay);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Sleep for specified milliseconds
|
|
239
|
+
* @param {number} ms - Milliseconds to sleep
|
|
240
|
+
* @returns {Promise<void>} - Sleep promise
|
|
241
|
+
*/
|
|
242
|
+
sleep(ms) {
|
|
243
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Create retry context for operation
|
|
248
|
+
* @param {Object} context - Base context
|
|
249
|
+
* @returns {Object} - Retry context
|
|
250
|
+
*/
|
|
251
|
+
createRetryContext(context = {}) {
|
|
252
|
+
return {
|
|
253
|
+
operationId: context.operationId || `retry-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
|
254
|
+
agentId: context.agentId,
|
|
255
|
+
operationType: context.operationType || 'INSTALLATION',
|
|
256
|
+
startTime: Date.now(),
|
|
257
|
+
...context
|
|
258
|
+
};
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Get retry statistics
|
|
263
|
+
* @param {Array} attempts - Array of attempt results
|
|
264
|
+
* @returns {Object} - Retry statistics
|
|
265
|
+
*/
|
|
266
|
+
getRetryStatistics(attempts) {
|
|
267
|
+
const successful = attempts.filter(a => a.success).length;
|
|
268
|
+
const failed = attempts.length - successful;
|
|
269
|
+
const totalDuration = attempts.reduce((sum, a) => sum + (a.duration || 0), 0);
|
|
270
|
+
const averageDuration = attempts.length > 0 ? totalDuration / attempts.length : 0;
|
|
271
|
+
|
|
272
|
+
return {
|
|
273
|
+
totalAttempts: attempts.length,
|
|
274
|
+
successful,
|
|
275
|
+
failed,
|
|
276
|
+
successRate: attempts.length > 0 ? (successful / attempts.length) * 100 : 0,
|
|
277
|
+
totalDuration,
|
|
278
|
+
averageDuration,
|
|
279
|
+
maxRetriesReached: failed > 0 && attempts[attempts.length - 1].attempts > this.maxRetries
|
|
280
|
+
};
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Configure retry settings
|
|
285
|
+
* @param {Object} config - New configuration
|
|
286
|
+
*/
|
|
287
|
+
configure(config = {}) {
|
|
288
|
+
if (config.maxRetries !== undefined) this.maxRetries = config.maxRetries;
|
|
289
|
+
if (config.baseDelay !== undefined) this.baseDelay = config.baseDelay;
|
|
290
|
+
if (config.maxDelay !== undefined) this.maxDelay = config.maxDelay;
|
|
291
|
+
if (config.backoffMultiplier !== undefined) this.backoffMultiplier = config.backoffMultiplier;
|
|
292
|
+
if (config.jitter !== undefined) this.jitter = config.jitter;
|
|
293
|
+
|
|
294
|
+
if (this.logger) {
|
|
295
|
+
this.logger.info('Retry configuration updated', {
|
|
296
|
+
maxRetries: this.maxRetries,
|
|
297
|
+
baseDelay: this.baseDelay,
|
|
298
|
+
maxDelay: this.maxDelay,
|
|
299
|
+
backoffMultiplier: this.backoffMultiplier,
|
|
300
|
+
jitter: this.jitter
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Get current configuration
|
|
307
|
+
* @returns {Object} - Current configuration
|
|
308
|
+
*/
|
|
309
|
+
getConfiguration() {
|
|
310
|
+
return {
|
|
311
|
+
maxRetries: this.maxRetries,
|
|
312
|
+
baseDelay: this.baseDelay,
|
|
313
|
+
maxDelay: this.maxDelay,
|
|
314
|
+
backoffMultiplier: this.backoffMultiplier,
|
|
315
|
+
jitter: this.jitter,
|
|
316
|
+
networkErrorPatterns: this.networkErrorPatterns.length,
|
|
317
|
+
transientStatusCodes: this.transientStatusCodes.length
|
|
318
|
+
};
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
module.exports = InstallationRetryManager;
|