enhance_swarm 1.0.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 (64) hide show
  1. checksums.yaml +7 -0
  2. data/.enhance_swarm/agent_scripts/frontend_agent.md +39 -0
  3. data/.enhance_swarm/user_patterns.json +37 -0
  4. data/CHANGELOG.md +184 -0
  5. data/LICENSE +21 -0
  6. data/PRODUCTION_TEST_LOG.md +502 -0
  7. data/README.md +905 -0
  8. data/Rakefile +28 -0
  9. data/USAGE_EXAMPLES.md +477 -0
  10. data/examples/enhance_workflow.md +346 -0
  11. data/examples/rails_project.md +253 -0
  12. data/exe/enhance-swarm +30 -0
  13. data/lib/enhance_swarm/additional_commands.rb +299 -0
  14. data/lib/enhance_swarm/agent_communicator.rb +460 -0
  15. data/lib/enhance_swarm/agent_reviewer.rb +283 -0
  16. data/lib/enhance_swarm/agent_spawner.rb +462 -0
  17. data/lib/enhance_swarm/cleanup_manager.rb +245 -0
  18. data/lib/enhance_swarm/cli.rb +1592 -0
  19. data/lib/enhance_swarm/command_executor.rb +78 -0
  20. data/lib/enhance_swarm/configuration.rb +324 -0
  21. data/lib/enhance_swarm/control_agent.rb +307 -0
  22. data/lib/enhance_swarm/dependency_validator.rb +195 -0
  23. data/lib/enhance_swarm/error_recovery.rb +785 -0
  24. data/lib/enhance_swarm/generator.rb +194 -0
  25. data/lib/enhance_swarm/interrupt_handler.rb +512 -0
  26. data/lib/enhance_swarm/logger.rb +106 -0
  27. data/lib/enhance_swarm/mcp_integration.rb +85 -0
  28. data/lib/enhance_swarm/monitor.rb +28 -0
  29. data/lib/enhance_swarm/notification_manager.rb +444 -0
  30. data/lib/enhance_swarm/orchestrator.rb +313 -0
  31. data/lib/enhance_swarm/output_streamer.rb +281 -0
  32. data/lib/enhance_swarm/process_monitor.rb +266 -0
  33. data/lib/enhance_swarm/progress_tracker.rb +215 -0
  34. data/lib/enhance_swarm/project_analyzer.rb +612 -0
  35. data/lib/enhance_swarm/resource_manager.rb +177 -0
  36. data/lib/enhance_swarm/retry_handler.rb +40 -0
  37. data/lib/enhance_swarm/session_manager.rb +247 -0
  38. data/lib/enhance_swarm/signal_handler.rb +95 -0
  39. data/lib/enhance_swarm/smart_defaults.rb +708 -0
  40. data/lib/enhance_swarm/task_integration.rb +150 -0
  41. data/lib/enhance_swarm/task_manager.rb +174 -0
  42. data/lib/enhance_swarm/version.rb +5 -0
  43. data/lib/enhance_swarm/visual_dashboard.rb +555 -0
  44. data/lib/enhance_swarm/web_ui.rb +211 -0
  45. data/lib/enhance_swarm.rb +69 -0
  46. data/setup.sh +86 -0
  47. data/sig/enhance_swarm.rbs +4 -0
  48. data/templates/claude/CLAUDE.md +160 -0
  49. data/templates/claude/MCP.md +117 -0
  50. data/templates/claude/PERSONAS.md +114 -0
  51. data/templates/claude/RULES.md +221 -0
  52. data/test_builtin_functionality.rb +121 -0
  53. data/test_core_components.rb +156 -0
  54. data/test_real_claude_integration.rb +285 -0
  55. data/test_security.rb +150 -0
  56. data/test_smart_defaults.rb +155 -0
  57. data/test_task_integration.rb +173 -0
  58. data/test_web_ui.rb +245 -0
  59. data/web/assets/css/main.css +645 -0
  60. data/web/assets/js/kanban.js +499 -0
  61. data/web/assets/js/main.js +525 -0
  62. data/web/templates/dashboard.html.erb +226 -0
  63. data/web/templates/kanban.html.erb +193 -0
  64. metadata +293 -0
@@ -0,0 +1,785 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'singleton'
4
+ require 'json'
5
+ require 'digest'
6
+ require 'timeout'
7
+
8
+ module EnhanceSwarm
9
+ class ErrorRecovery
10
+ include Singleton
11
+
12
+ RECOVERY_STRATEGIES_FILE = '.enhance_swarm/error_recovery_strategies.json'
13
+ ERROR_PATTERNS_FILE = '.enhance_swarm/error_patterns.json'
14
+
15
+ def initialize
16
+ ensure_recovery_directory
17
+ @recovery_strategies = load_recovery_strategies
18
+ @error_patterns = load_error_patterns
19
+ @recovery_history = []
20
+ end
21
+
22
+ # Analyze error and suggest recovery actions
23
+ def analyze_error(error, context = {})
24
+ error_info = {
25
+ message: error.message,
26
+ type: error.class.name,
27
+ context: context,
28
+ timestamp: Time.now.iso8601
29
+ }
30
+
31
+ # Find matching patterns
32
+ matching_patterns = find_matching_patterns(error_info)
33
+
34
+ # Generate recovery suggestions
35
+ suggestions = generate_recovery_suggestions(error_info, matching_patterns)
36
+
37
+ # Log error for pattern learning
38
+ log_error_occurrence(error_info)
39
+
40
+ {
41
+ error: error_info,
42
+ patterns: matching_patterns,
43
+ suggestions: suggestions,
44
+ auto_recoverable: auto_recoverable?(error_info, matching_patterns)
45
+ }
46
+ end
47
+
48
+ # Attempt automatic recovery
49
+ def attempt_recovery(error_analysis, agent_context = {})
50
+ return false unless error_analysis[:auto_recoverable]
51
+
52
+ recovery_attempts = []
53
+
54
+ error_analysis[:suggestions].each do |suggestion|
55
+ auto_executable = suggestion['auto_executable'] || suggestion[:auto_executable]
56
+ next unless auto_executable
57
+
58
+ begin
59
+ description = suggestion['description'] || suggestion[:description]
60
+ Logger.info("Attempting automatic recovery: #{description}")
61
+
62
+ result = execute_recovery_action(suggestion, agent_context)
63
+
64
+ recovery_attempts << {
65
+ suggestion: suggestion,
66
+ result: result,
67
+ success: result[:success],
68
+ timestamp: Time.now.iso8601
69
+ }
70
+
71
+ # If recovery succeeds, stop trying other strategies
72
+ if result[:success]
73
+ log_successful_recovery(error_analysis[:error], suggestion)
74
+ return result
75
+ end
76
+
77
+ rescue StandardError => recovery_error
78
+ Logger.error("Recovery attempt failed: #{recovery_error.message}")
79
+ recovery_attempts << {
80
+ suggestion: suggestion,
81
+ result: { success: false, error: recovery_error.message },
82
+ success: false,
83
+ timestamp: Time.now.iso8601
84
+ }
85
+ end
86
+ end
87
+
88
+ # Log all recovery attempts for learning
89
+ log_recovery_attempts(error_analysis[:error], recovery_attempts)
90
+
91
+ { success: false, attempts: recovery_attempts }
92
+ end
93
+
94
+ # Get human-readable error explanation
95
+ def explain_error(error, context = {})
96
+ error_info = {
97
+ message: error.message,
98
+ type: error.class.name,
99
+ context: context
100
+ }
101
+
102
+ matching_patterns = find_matching_patterns(error_info)
103
+
104
+ if matching_patterns.any?
105
+ primary_pattern = matching_patterns.first
106
+ {
107
+ explanation: primary_pattern[:explanation],
108
+ likely_cause: primary_pattern[:likely_cause],
109
+ prevention_tips: primary_pattern[:prevention_tips] || []
110
+ }
111
+ else
112
+ generate_generic_explanation(error_info)
113
+ end
114
+ end
115
+
116
+ # Learn from successful manual recovery
117
+ def learn_from_manual_recovery(error, recovery_steps, context = {})
118
+ error_info = {
119
+ message: error.message,
120
+ type: error.class.name,
121
+ context: context,
122
+ timestamp: Time.now.iso8601
123
+ }
124
+
125
+ # Create or update pattern
126
+ pattern_key = generate_pattern_key(error_info)
127
+
128
+ @error_patterns[pattern_key] ||= {
129
+ 'error_signatures' => [],
130
+ 'successful_recoveries' => [],
131
+ 'failure_rate' => 0.0,
132
+ 'last_seen' => nil
133
+ }
134
+
135
+ pattern = @error_patterns[pattern_key]
136
+
137
+ # Add error signature if not already present
138
+ signature = extract_error_signature(error_info)
139
+ unless pattern['error_signatures'].any? { |sig| sig['message_pattern'] == signature[:message_pattern] }
140
+ pattern['error_signatures'] << signature
141
+ end
142
+
143
+ # Add successful recovery
144
+ pattern['successful_recoveries'] << {
145
+ 'steps' => recovery_steps,
146
+ 'context' => context,
147
+ 'timestamp' => Time.now.iso8601
148
+ }
149
+
150
+ pattern['last_seen'] = Time.now.iso8601
151
+
152
+ save_error_patterns
153
+
154
+ Logger.info("Learned new recovery pattern for #{error.class.name}")
155
+ end
156
+
157
+ # Get recovery statistics
158
+ def recovery_statistics
159
+ total_errors = @recovery_history.count
160
+ successful_recoveries = @recovery_history.count { |h| h[:recovery_successful] }
161
+
162
+ {
163
+ total_errors_processed: total_errors,
164
+ successful_automatic_recoveries: successful_recoveries,
165
+ recovery_success_rate: total_errors > 0 ? (successful_recoveries.to_f / total_errors * 100).round(1) : 0.0,
166
+ most_common_errors: most_common_error_types,
167
+ recovery_patterns_learned: @error_patterns.count
168
+ }
169
+ end
170
+
171
+ # Clear old recovery data
172
+ def cleanup_old_data(days_to_keep = 30)
173
+ cutoff_time = Time.now - (days_to_keep * 24 * 60 * 60)
174
+
175
+ # Clean recovery history
176
+ @recovery_history.reject! { |h| Time.parse(h[:timestamp]) < cutoff_time }
177
+
178
+ # Clean old error patterns that haven't been seen recently
179
+ @error_patterns.reject! do |_, pattern|
180
+ last_seen = pattern['last_seen'] || pattern[:last_seen]
181
+ last_seen && Time.parse(last_seen) < cutoff_time
182
+ end
183
+
184
+ save_error_patterns
185
+
186
+ Logger.info("Cleaned up error recovery data older than #{days_to_keep} days")
187
+ end
188
+
189
+ private
190
+
191
+ def load_recovery_strategies
192
+ return default_recovery_strategies unless File.exist?(RECOVERY_STRATEGIES_FILE)
193
+
194
+ JSON.parse(File.read(RECOVERY_STRATEGIES_FILE))
195
+ rescue StandardError
196
+ default_recovery_strategies
197
+ end
198
+
199
+ def load_error_patterns
200
+ return {} unless File.exist?(ERROR_PATTERNS_FILE)
201
+
202
+ JSON.parse(File.read(ERROR_PATTERNS_FILE))
203
+ rescue StandardError
204
+ {}
205
+ end
206
+
207
+ def save_error_patterns
208
+ ensure_recovery_directory
209
+ File.write(ERROR_PATTERNS_FILE, JSON.pretty_generate(@error_patterns))
210
+ end
211
+
212
+ def ensure_recovery_directory
213
+ dir = File.dirname(RECOVERY_STRATEGIES_FILE)
214
+ FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
215
+ end
216
+
217
+ def default_recovery_strategies
218
+ {
219
+ 'network_errors' => [
220
+ {
221
+ 'description' => 'Retry with exponential backoff',
222
+ 'auto_executable' => true,
223
+ 'action' => 'retry_with_backoff',
224
+ 'max_attempts' => 3,
225
+ 'base_delay' => 1
226
+ },
227
+ {
228
+ 'description' => 'Check network connectivity',
229
+ 'auto_executable' => false,
230
+ 'action' => 'check_network',
231
+ 'command' => 'ping -c 1 8.8.8.8'
232
+ }
233
+ ],
234
+ 'file_not_found' => [
235
+ {
236
+ 'description' => 'Create missing file with default content',
237
+ 'auto_executable' => true,
238
+ 'action' => 'create_default_file'
239
+ },
240
+ {
241
+ 'description' => 'Search for similar files in project',
242
+ 'auto_executable' => true,
243
+ 'action' => 'find_similar_files'
244
+ }
245
+ ],
246
+ 'permission_denied' => [
247
+ {
248
+ 'description' => 'Fix file permissions',
249
+ 'auto_executable' => false,
250
+ 'action' => 'fix_permissions',
251
+ 'command' => 'chmod +x {file_path}'
252
+ },
253
+ {
254
+ 'description' => 'Run with elevated privileges',
255
+ 'auto_executable' => false,
256
+ 'action' => 'elevate_privileges'
257
+ }
258
+ ],
259
+ 'dependency_missing' => [
260
+ {
261
+ 'description' => 'Install missing dependencies',
262
+ 'auto_executable' => true,
263
+ 'action' => 'install_dependencies'
264
+ },
265
+ {
266
+ 'description' => 'Update package manager',
267
+ 'auto_executable' => false,
268
+ 'action' => 'update_package_manager'
269
+ }
270
+ ],
271
+ 'timeout_error' => [
272
+ {
273
+ 'description' => 'Increase timeout and retry',
274
+ 'auto_executable' => true,
275
+ 'action' => 'retry_with_longer_timeout',
276
+ 'timeout_multiplier' => 2.0
277
+ },
278
+ {
279
+ 'description' => 'Break task into smaller chunks',
280
+ 'auto_executable' => false,
281
+ 'action' => 'split_task'
282
+ }
283
+ ],
284
+ 'memory_error' => [
285
+ {
286
+ 'description' => 'Reduce memory usage and retry',
287
+ 'auto_executable' => true,
288
+ 'action' => 'retry_with_reduced_memory'
289
+ },
290
+ {
291
+ 'description' => 'Clear system memory cache',
292
+ 'auto_executable' => false,
293
+ 'action' => 'clear_memory_cache'
294
+ }
295
+ ]
296
+ }
297
+ end
298
+
299
+ def find_matching_patterns(error_info)
300
+ matches = []
301
+
302
+ @error_patterns.each do |pattern_key, pattern|
303
+ confidence = calculate_pattern_match_confidence(error_info, pattern)
304
+
305
+ if confidence > 0.5 # 50% confidence threshold
306
+ matches << {
307
+ pattern_key: pattern_key,
308
+ confidence: confidence,
309
+ explanation: pattern['explanation'] || pattern[:explanation] || "Similar error pattern detected",
310
+ likely_cause: pattern['likely_cause'] || pattern[:likely_cause] || "Unknown cause",
311
+ prevention_tips: pattern['prevention_tips'] || pattern[:prevention_tips] || [],
312
+ successful_recoveries: pattern['successful_recoveries'] || pattern[:successful_recoveries] || []
313
+ }
314
+ end
315
+ end
316
+
317
+ # Sort by confidence
318
+ matches.sort_by { |m| -m[:confidence] }
319
+ end
320
+
321
+ def calculate_pattern_match_confidence(error_info, pattern)
322
+ error_signatures = pattern['error_signatures'] || pattern[:error_signatures]
323
+ return 0.0 if error_signatures.nil? || error_signatures.empty?
324
+
325
+ max_confidence = 0.0
326
+
327
+ error_signatures.each do |signature|
328
+ confidence = 0.0
329
+
330
+ # Match error type
331
+ error_type = signature['error_type'] || signature[:error_type]
332
+ if error_type == error_info[:type]
333
+ confidence += 0.4
334
+ end
335
+
336
+ # Match message patterns
337
+ message_pattern = signature['message_pattern'] || signature[:message_pattern]
338
+ if message_pattern && error_info[:message].downcase.include?(message_pattern.downcase)
339
+ confidence += 0.3
340
+ end
341
+
342
+ # Match context patterns
343
+ context_patterns = signature['context_patterns'] || signature[:context_patterns]
344
+ if context_patterns && context_patterns.any?
345
+ context_matches = context_patterns.count do |pattern|
346
+ error_info[:context].to_s.downcase.include?(pattern.downcase)
347
+ end
348
+
349
+ confidence += (context_matches.to_f / context_patterns.count) * 0.3
350
+ end
351
+
352
+ max_confidence = [max_confidence, confidence].max
353
+ end
354
+
355
+ max_confidence
356
+ end
357
+
358
+ def generate_recovery_suggestions(error_info, matching_patterns)
359
+ suggestions = []
360
+
361
+ # Add suggestions from matching patterns
362
+ matching_patterns.each do |pattern|
363
+ successful_recoveries = pattern['successful_recoveries'] || pattern[:successful_recoveries] || []
364
+ successful_recoveries.each do |recovery|
365
+ steps = recovery['steps'] || recovery[:steps] || []
366
+ suggestions << {
367
+ description: "Apply previously successful recovery: #{steps.join(' → ')}",
368
+ auto_executable: false,
369
+ action: 'manual_recovery',
370
+ steps: steps,
371
+ confidence: pattern[:confidence],
372
+ source: 'learned_pattern'
373
+ }
374
+ end
375
+ end
376
+
377
+ # Add generic recovery strategies
378
+ generic_strategies = get_generic_strategies_for_error(error_info)
379
+ suggestions.concat(generic_strategies)
380
+
381
+ # Sort by confidence and auto-executability
382
+ suggestions.sort_by { |s| [-s[:confidence], s[:auto_executable] ? 1 : 0] }
383
+ end
384
+
385
+ def get_generic_strategies_for_error(error_info)
386
+ strategies = []
387
+
388
+ # Network-related errors
389
+ if network_error?(error_info)
390
+ @recovery_strategies['network_errors'].each do |strategy|
391
+ strategies << strategy.merge(confidence: 0.7, source: 'generic')
392
+ end
393
+ end
394
+
395
+ # File system errors
396
+ if file_system_error?(error_info)
397
+ if error_info[:message].include?('No such file')
398
+ @recovery_strategies['file_not_found'].each do |strategy|
399
+ strategies << strategy.merge(confidence: 0.8, source: 'generic')
400
+ end
401
+ elsif error_info[:message].include?('Permission denied')
402
+ @recovery_strategies['permission_denied'].each do |strategy|
403
+ strategies << strategy.merge(confidence: 0.8, source: 'generic')
404
+ end
405
+ end
406
+ end
407
+
408
+ # Dependency errors
409
+ if dependency_error?(error_info)
410
+ @recovery_strategies['dependency_missing'].each do |strategy|
411
+ strategies << strategy.merge(confidence: 0.7, source: 'generic')
412
+ end
413
+ end
414
+
415
+ # Timeout errors
416
+ if timeout_error?(error_info)
417
+ @recovery_strategies['timeout_error'].each do |strategy|
418
+ strategies << strategy.merge(confidence: 0.6, source: 'generic')
419
+ end
420
+ end
421
+
422
+ # Memory errors
423
+ if memory_error?(error_info)
424
+ @recovery_strategies['memory_error'].each do |strategy|
425
+ strategies << strategy.merge(confidence: 0.6, source: 'generic')
426
+ end
427
+ end
428
+
429
+ strategies
430
+ end
431
+
432
+ def auto_recoverable?(error_info, matching_patterns)
433
+ # Don't auto-recover critical system errors
434
+ return false if critical_error?(error_info)
435
+
436
+ # Check if any generic strategies are auto-executable
437
+ get_generic_strategies_for_error(error_info).any? { |s| s['auto_executable'] }
438
+ end
439
+
440
+ def execute_recovery_action(suggestion, agent_context)
441
+ action = suggestion['action'] || suggestion[:action]
442
+
443
+ case action
444
+ when 'retry_with_backoff'
445
+ retry_with_backoff(suggestion, agent_context)
446
+ when 'create_default_file'
447
+ create_default_file(suggestion, agent_context)
448
+ when 'find_similar_files'
449
+ find_similar_files(suggestion, agent_context)
450
+ when 'install_dependencies'
451
+ install_dependencies(suggestion, agent_context)
452
+ when 'retry_with_longer_timeout'
453
+ retry_with_longer_timeout(suggestion, agent_context)
454
+ when 'retry_with_reduced_memory'
455
+ retry_with_reduced_memory(suggestion, agent_context)
456
+ else
457
+ { success: false, error: "Unknown recovery action: #{action}" }
458
+ end
459
+ end
460
+
461
+ def retry_with_backoff(suggestion, agent_context)
462
+ max_attempts = suggestion['max_attempts'] || 3
463
+ base_delay = suggestion['base_delay'] || 1
464
+
465
+ attempt = 1
466
+
467
+ while attempt <= max_attempts
468
+ begin
469
+ # Re-execute the original operation
470
+ if agent_context[:retry_block]
471
+ result = agent_context[:retry_block].call
472
+ return { success: true, result: result, attempts: attempt }
473
+ else
474
+ return { success: false, error: 'No retry block provided' }
475
+ end
476
+ rescue StandardError => e
477
+ if attempt == max_attempts
478
+ return { success: false, error: e.message, attempts: attempt }
479
+ end
480
+
481
+ delay = base_delay * (2 ** (attempt - 1))
482
+ sleep(delay)
483
+ attempt += 1
484
+ end
485
+ end
486
+ end
487
+
488
+ def create_default_file(suggestion, agent_context)
489
+ file_path = agent_context[:file_path]
490
+ return { success: false, error: 'No file path provided' } unless file_path
491
+
492
+ begin
493
+ # Create directory if it doesn't exist
494
+ FileUtils.mkdir_p(File.dirname(file_path))
495
+
496
+ # Create file with default content based on extension
497
+ default_content = generate_default_file_content(file_path)
498
+ File.write(file_path, default_content)
499
+
500
+ { success: true, file_created: file_path, content: default_content }
501
+ rescue StandardError => e
502
+ { success: false, error: e.message }
503
+ end
504
+ end
505
+
506
+ def find_similar_files(suggestion, agent_context)
507
+ file_path = agent_context[:file_path]
508
+ return { success: false, error: 'No file path provided' } unless file_path
509
+
510
+ begin
511
+ filename = File.basename(file_path)
512
+ directory = File.dirname(file_path)
513
+ extension = File.extname(file_path)
514
+ basename_without_ext = File.basename(filename, extension)
515
+
516
+ # Search for similar files
517
+ similar_files = Dir.glob("#{directory}/**/*#{extension}").select do |f|
518
+ existing_basename = File.basename(f, extension)
519
+ # Check if either file contains parts of the other's name
520
+ basename_without_ext.include?(existing_basename) ||
521
+ existing_basename.include?(basename_without_ext) ||
522
+ # Also check for common prefixes (e.g., "test" in both "test_new" and "test_file")
523
+ common_prefix_length(basename_without_ext, existing_basename) >= 3
524
+ end
525
+
526
+ { success: true, similar_files: similar_files }
527
+ rescue StandardError => e
528
+ { success: false, error: e.message }
529
+ end
530
+ end
531
+
532
+ def install_dependencies(suggestion, agent_context)
533
+ begin
534
+ if File.exist?('package.json')
535
+ system('npm install')
536
+ { success: true, package_manager: 'npm' }
537
+ elsif File.exist?('Gemfile')
538
+ system('bundle install')
539
+ { success: true, package_manager: 'bundler' }
540
+ elsif File.exist?('requirements.txt')
541
+ system('pip install -r requirements.txt')
542
+ { success: true, package_manager: 'pip' }
543
+ else
544
+ { success: false, error: 'No recognized dependency file found' }
545
+ end
546
+ rescue StandardError => e
547
+ { success: false, error: e.message }
548
+ end
549
+ end
550
+
551
+ def retry_with_longer_timeout(suggestion, agent_context)
552
+ multiplier = suggestion['timeout_multiplier'] || 2.0
553
+ original_timeout = agent_context[:timeout] || 30
554
+ new_timeout = (original_timeout * multiplier).to_i
555
+
556
+ begin
557
+ if agent_context[:retry_block]
558
+ result = Timeout.timeout(new_timeout) do
559
+ agent_context[:retry_block].call
560
+ end
561
+ { success: true, result: result, new_timeout: new_timeout }
562
+ else
563
+ { success: false, error: 'No retry block provided' }
564
+ end
565
+ rescue StandardError => e
566
+ { success: false, error: e.message }
567
+ end
568
+ end
569
+
570
+ def retry_with_reduced_memory(suggestion, agent_context)
571
+ begin
572
+ # Force garbage collection
573
+ GC.start
574
+
575
+ # Re-execute with reduced memory profile
576
+ if agent_context[:retry_block]
577
+ result = agent_context[:retry_block].call
578
+ { success: true, result: result }
579
+ else
580
+ { success: false, error: 'No retry block provided' }
581
+ end
582
+ rescue StandardError => e
583
+ { success: false, error: e.message }
584
+ end
585
+ end
586
+
587
+ def generate_default_file_content(file_path)
588
+ extension = File.extname(file_path).downcase
589
+
590
+ case extension
591
+ when '.rb'
592
+ "# frozen_string_literal: true\n\n# Default Ruby file\n"
593
+ when '.js'
594
+ "// Default JavaScript file\n"
595
+ when '.ts'
596
+ "// Default TypeScript file\nexport {};\n"
597
+ when '.py'
598
+ "#!/usr/bin/env python3\n# Default Python file\n"
599
+ when '.yml', '.yaml'
600
+ "# Default YAML configuration\n"
601
+ when '.json'
602
+ "{}\n"
603
+ when '.md'
604
+ "# #{File.basename(file_path, extension).gsub(/[_-]/, ' ').split.map(&:capitalize).join(' ')}\n\nDefault content.\n"
605
+ else
606
+ "# Default content for #{file_path}\n"
607
+ end
608
+ end
609
+
610
+ def network_error?(error_info)
611
+ network_patterns = [
612
+ 'connection', 'network', 'timeout', 'unreachable', 'dns',
613
+ 'socket', 'ssl', 'certificate', 'refused', 'reset'
614
+ ]
615
+
616
+ message_lower = error_info[:message].downcase
617
+ network_patterns.any? { |pattern| message_lower.include?(pattern) }
618
+ end
619
+
620
+ def file_system_error?(error_info)
621
+ fs_patterns = [
622
+ 'no such file', 'permission denied', 'file not found',
623
+ 'directory not found', 'access denied', 'file exists'
624
+ ]
625
+
626
+ message_lower = error_info[:message].downcase
627
+ fs_patterns.any? { |pattern| message_lower.include?(pattern) }
628
+ end
629
+
630
+ def dependency_error?(error_info)
631
+ dep_patterns = [
632
+ 'cannot load', 'not found', 'missing', 'uninitialized constant',
633
+ 'module not found', 'import error', 'no such gem'
634
+ ]
635
+
636
+ message_lower = error_info[:message].downcase
637
+ dep_patterns.any? { |pattern| message_lower.include?(pattern) }
638
+ end
639
+
640
+ def timeout_error?(error_info)
641
+ error_info[:type].include?('Timeout') ||
642
+ error_info[:message].downcase.include?('timeout')
643
+ end
644
+
645
+ def memory_error?(error_info)
646
+ memory_patterns = ['memory', 'out of memory', 'cannot allocate']
647
+
648
+ message_lower = error_info[:message].downcase
649
+ memory_patterns.any? { |pattern| message_lower.include?(pattern) }
650
+ end
651
+
652
+ def critical_error?(error_info)
653
+ critical_patterns = [
654
+ 'system', 'kernel', 'segmentation fault', 'access violation',
655
+ 'stack overflow', 'fatal'
656
+ ]
657
+
658
+ message_lower = error_info[:message].downcase
659
+ critical_patterns.any? { |pattern| message_lower.include?(pattern) }
660
+ end
661
+
662
+ def generate_generic_explanation(error_info)
663
+ {
664
+ explanation: "An error of type #{error_info[:type]} occurred",
665
+ likely_cause: "The specific cause is unclear from the available information",
666
+ prevention_tips: [
667
+ "Check the error message for specific details",
668
+ "Verify that all dependencies are properly installed",
669
+ "Ensure file permissions are correct",
670
+ "Check for typos in file paths or commands"
671
+ ]
672
+ }
673
+ end
674
+
675
+ def log_error_occurrence(error_info)
676
+ @recovery_history << {
677
+ error: error_info,
678
+ timestamp: Time.now.iso8601,
679
+ recovery_attempted: false,
680
+ recovery_successful: false
681
+ }
682
+ end
683
+
684
+ def log_successful_recovery(error_info, recovery_strategy)
685
+ # Update the most recent entry
686
+ if @recovery_history.last && @recovery_history.last[:error] == error_info
687
+ @recovery_history.last[:recovery_attempted] = true
688
+ @recovery_history.last[:recovery_successful] = true
689
+ @recovery_history.last[:recovery_strategy] = recovery_strategy
690
+ end
691
+ end
692
+
693
+ def log_recovery_attempts(error_info, attempts)
694
+ # Update the most recent entry
695
+ if @recovery_history.last && @recovery_history.last[:error] == error_info
696
+ @recovery_history.last[:recovery_attempted] = true
697
+ @recovery_history.last[:recovery_successful] = attempts.any? { |a| a[:success] }
698
+ @recovery_history.last[:recovery_attempts] = attempts
699
+ end
700
+ end
701
+
702
+ def most_common_error_types
703
+ error_counts = @recovery_history.group_by { |h| h[:error][:type] }
704
+ .transform_values(&:count)
705
+
706
+ error_counts.sort_by { |_, count| -count }.first(5).to_h
707
+ end
708
+
709
+ def extract_error_signature(error_info)
710
+ {
711
+ 'error_type' => error_info[:type],
712
+ 'message_pattern' => extract_message_pattern(error_info[:message]),
713
+ 'context_patterns' => extract_context_patterns(error_info[:context])
714
+ }
715
+ end
716
+
717
+ def extract_message_pattern(message)
718
+ # Extract key words and remove specific paths/values
719
+ pattern = message.gsub(/\/[\/\w.-]+/, '<PATH>')
720
+ .gsub(/\d+/, '<NUMBER>')
721
+ .gsub(/[a-f0-9]{8,}/, '<HASH>')
722
+ .strip
723
+
724
+ # Take first meaningful part
725
+ pattern.split(/[:.!]/).first&.strip || pattern
726
+ end
727
+
728
+ def extract_context_patterns(context)
729
+ return [] unless context.is_a?(Hash)
730
+
731
+ patterns = []
732
+ context.each do |key, value|
733
+ patterns << "#{key}:#{value.class.name}" if value
734
+ end
735
+ patterns
736
+ end
737
+
738
+ def generate_pattern_key(error_info)
739
+ # Generate a consistent key for grouping similar errors
740
+ key_parts = [
741
+ error_info[:type],
742
+ extract_message_pattern(error_info[:message])
743
+ ]
744
+
745
+ Digest::SHA256.hexdigest(key_parts.join('|'))[0, 16]
746
+ end
747
+
748
+ def common_prefix_length(str1, str2)
749
+ return 0 if str1.nil? || str2.nil?
750
+
751
+ min_length = [str1.length, str2.length].min
752
+ (0...min_length).each do |i|
753
+ return i if str1[i].downcase != str2[i].downcase
754
+ end
755
+ min_length
756
+ end
757
+
758
+ # Class methods for singleton access
759
+ class << self
760
+ def analyze_error(*args)
761
+ instance.analyze_error(*args)
762
+ end
763
+
764
+ def attempt_recovery(*args)
765
+ instance.attempt_recovery(*args)
766
+ end
767
+
768
+ def explain_error(*args)
769
+ instance.explain_error(*args)
770
+ end
771
+
772
+ def learn_from_manual_recovery(*args)
773
+ instance.learn_from_manual_recovery(*args)
774
+ end
775
+
776
+ def recovery_statistics
777
+ instance.recovery_statistics
778
+ end
779
+
780
+ def cleanup_old_data(*args)
781
+ instance.cleanup_old_data(*args)
782
+ end
783
+ end
784
+ end
785
+ end