aidp 0.5.0 → 0.8.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 (122) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +128 -151
  3. data/bin/aidp +1 -1
  4. data/lib/aidp/analysis/kb_inspector.rb +471 -0
  5. data/lib/aidp/analysis/seams.rb +159 -0
  6. data/lib/aidp/analysis/tree_sitter_grammar_loader.rb +480 -0
  7. data/lib/aidp/analysis/tree_sitter_scan.rb +686 -0
  8. data/lib/aidp/analyze/error_handler.rb +2 -78
  9. data/lib/aidp/analyze/json_file_storage.rb +292 -0
  10. data/lib/aidp/analyze/progress.rb +12 -0
  11. data/lib/aidp/analyze/progress_visualizer.rb +12 -17
  12. data/lib/aidp/analyze/ruby_maat_integration.rb +13 -31
  13. data/lib/aidp/analyze/runner.rb +256 -87
  14. data/lib/aidp/analyze/steps.rb +6 -0
  15. data/lib/aidp/cli/jobs_command.rb +103 -435
  16. data/lib/aidp/cli.rb +317 -191
  17. data/lib/aidp/config.rb +298 -10
  18. data/lib/aidp/debug_logger.rb +195 -0
  19. data/lib/aidp/debug_mixin.rb +187 -0
  20. data/lib/aidp/execute/progress.rb +9 -0
  21. data/lib/aidp/execute/runner.rb +221 -40
  22. data/lib/aidp/execute/steps.rb +17 -7
  23. data/lib/aidp/execute/workflow_selector.rb +211 -0
  24. data/lib/aidp/harness/completion_checker.rb +268 -0
  25. data/lib/aidp/harness/condition_detector.rb +1526 -0
  26. data/lib/aidp/harness/config_loader.rb +373 -0
  27. data/lib/aidp/harness/config_manager.rb +382 -0
  28. data/lib/aidp/harness/config_schema.rb +1006 -0
  29. data/lib/aidp/harness/config_validator.rb +355 -0
  30. data/lib/aidp/harness/configuration.rb +477 -0
  31. data/lib/aidp/harness/enhanced_runner.rb +494 -0
  32. data/lib/aidp/harness/error_handler.rb +616 -0
  33. data/lib/aidp/harness/provider_config.rb +423 -0
  34. data/lib/aidp/harness/provider_factory.rb +306 -0
  35. data/lib/aidp/harness/provider_manager.rb +1269 -0
  36. data/lib/aidp/harness/provider_type_checker.rb +88 -0
  37. data/lib/aidp/harness/runner.rb +411 -0
  38. data/lib/aidp/harness/state/errors.rb +28 -0
  39. data/lib/aidp/harness/state/metrics.rb +219 -0
  40. data/lib/aidp/harness/state/persistence.rb +128 -0
  41. data/lib/aidp/harness/state/provider_state.rb +132 -0
  42. data/lib/aidp/harness/state/ui_state.rb +68 -0
  43. data/lib/aidp/harness/state/workflow_state.rb +123 -0
  44. data/lib/aidp/harness/state_manager.rb +586 -0
  45. data/lib/aidp/harness/status_display.rb +888 -0
  46. data/lib/aidp/harness/ui/base.rb +16 -0
  47. data/lib/aidp/harness/ui/enhanced_tui.rb +545 -0
  48. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +252 -0
  49. data/lib/aidp/harness/ui/error_handler.rb +132 -0
  50. data/lib/aidp/harness/ui/frame_manager.rb +361 -0
  51. data/lib/aidp/harness/ui/job_monitor.rb +500 -0
  52. data/lib/aidp/harness/ui/navigation/main_menu.rb +311 -0
  53. data/lib/aidp/harness/ui/navigation/menu_formatter.rb +120 -0
  54. data/lib/aidp/harness/ui/navigation/menu_item.rb +142 -0
  55. data/lib/aidp/harness/ui/navigation/menu_state.rb +139 -0
  56. data/lib/aidp/harness/ui/navigation/submenu.rb +202 -0
  57. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +176 -0
  58. data/lib/aidp/harness/ui/progress_display.rb +280 -0
  59. data/lib/aidp/harness/ui/question_collector.rb +141 -0
  60. data/lib/aidp/harness/ui/spinner_group.rb +184 -0
  61. data/lib/aidp/harness/ui/spinner_helper.rb +152 -0
  62. data/lib/aidp/harness/ui/status_manager.rb +312 -0
  63. data/lib/aidp/harness/ui/status_widget.rb +280 -0
  64. data/lib/aidp/harness/ui/workflow_controller.rb +312 -0
  65. data/lib/aidp/harness/user_interface.rb +2381 -0
  66. data/lib/aidp/provider_manager.rb +131 -7
  67. data/lib/aidp/providers/anthropic.rb +28 -109
  68. data/lib/aidp/providers/base.rb +170 -0
  69. data/lib/aidp/providers/cursor.rb +52 -183
  70. data/lib/aidp/providers/gemini.rb +24 -109
  71. data/lib/aidp/providers/macos_ui.rb +99 -5
  72. data/lib/aidp/providers/opencode.rb +194 -0
  73. data/lib/aidp/storage/csv_storage.rb +172 -0
  74. data/lib/aidp/storage/file_manager.rb +214 -0
  75. data/lib/aidp/storage/json_storage.rb +140 -0
  76. data/lib/aidp/version.rb +1 -1
  77. data/lib/aidp.rb +56 -35
  78. data/templates/ANALYZE/06a_tree_sitter_scan.md +217 -0
  79. data/templates/COMMON/AGENT_BASE.md +11 -0
  80. data/templates/EXECUTE/00_PRD.md +4 -4
  81. data/templates/EXECUTE/02_ARCHITECTURE.md +5 -4
  82. data/templates/EXECUTE/07_TEST_PLAN.md +4 -1
  83. data/templates/EXECUTE/08_TASKS.md +4 -4
  84. data/templates/EXECUTE/10_IMPLEMENTATION_AGENT.md +4 -4
  85. data/templates/README.md +279 -0
  86. data/templates/aidp-development.yml.example +373 -0
  87. data/templates/aidp-minimal.yml.example +48 -0
  88. data/templates/aidp-production.yml.example +475 -0
  89. data/templates/aidp.yml.example +598 -0
  90. metadata +106 -64
  91. data/lib/aidp/analyze/agent_personas.rb +0 -71
  92. data/lib/aidp/analyze/agent_tool_executor.rb +0 -445
  93. data/lib/aidp/analyze/data_retention_manager.rb +0 -426
  94. data/lib/aidp/analyze/database.rb +0 -260
  95. data/lib/aidp/analyze/dependencies.rb +0 -335
  96. data/lib/aidp/analyze/export_manager.rb +0 -425
  97. data/lib/aidp/analyze/focus_guidance.rb +0 -517
  98. data/lib/aidp/analyze/incremental_analyzer.rb +0 -543
  99. data/lib/aidp/analyze/language_analysis_strategies.rb +0 -897
  100. data/lib/aidp/analyze/large_analysis_progress.rb +0 -504
  101. data/lib/aidp/analyze/memory_manager.rb +0 -365
  102. data/lib/aidp/analyze/metrics_storage.rb +0 -336
  103. data/lib/aidp/analyze/parallel_processor.rb +0 -460
  104. data/lib/aidp/analyze/performance_optimizer.rb +0 -694
  105. data/lib/aidp/analyze/repository_chunker.rb +0 -704
  106. data/lib/aidp/analyze/static_analysis_detector.rb +0 -577
  107. data/lib/aidp/analyze/storage.rb +0 -662
  108. data/lib/aidp/analyze/tool_configuration.rb +0 -456
  109. data/lib/aidp/analyze/tool_modernization.rb +0 -750
  110. data/lib/aidp/database/pg_adapter.rb +0 -148
  111. data/lib/aidp/database_config.rb +0 -69
  112. data/lib/aidp/database_connection.rb +0 -72
  113. data/lib/aidp/database_migration.rb +0 -158
  114. data/lib/aidp/job_manager.rb +0 -41
  115. data/lib/aidp/jobs/base_job.rb +0 -47
  116. data/lib/aidp/jobs/provider_execution_job.rb +0 -96
  117. data/lib/aidp/project_detector.rb +0 -117
  118. data/lib/aidp/providers/agent_supervisor.rb +0 -348
  119. data/lib/aidp/providers/supervised_base.rb +0 -317
  120. data/lib/aidp/providers/supervised_cursor.rb +0 -22
  121. data/lib/aidp/sync.rb +0 -13
  122. data/lib/aidp/workspace.rb +0 -19
@@ -0,0 +1,1526 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Aidp
4
+ module Harness
5
+ # Detects run conditions (rate limits, user feedback, completion, errors)
6
+ class ConditionDetector
7
+ def initialize
8
+ # Enhanced rate limit patterns for different providers
9
+ @rate_limit_patterns = {
10
+ # Common patterns
11
+ common: [
12
+ /rate limit/i,
13
+ /too many requests/i,
14
+ /quota exceeded/i,
15
+ /429/i,
16
+ /rate.{0,20}exceeded/i,
17
+ /throttled/i,
18
+ /limit.{0,20}exceeded/i
19
+ ],
20
+ # Anthropic/Claude specific
21
+ anthropic: [
22
+ /rate limit exceeded/i,
23
+ /too many requests/i,
24
+ /quota.{0,20}exceeded/i,
25
+ /anthropic.{0,20}rate.{0,20}limit/i
26
+ ],
27
+ # OpenAI specific
28
+ openai: [
29
+ /rate limit exceeded/i,
30
+ /requests per minute/i,
31
+ /tokens per minute/i,
32
+ /openai.{0,20}rate.{0,20}limit/i
33
+ ],
34
+ # Google/Gemini specific
35
+ google: [
36
+ /quota exceeded/i,
37
+ /rate limit exceeded/i,
38
+ /google.{0,20}api.{0,20}limit/i,
39
+ /gemini.{0,20}rate.{0,20}limit/i
40
+ ],
41
+ # Cursor specific
42
+ cursor: [
43
+ /cursor.{0,20}rate.{0,20}limit/i,
44
+ /package.{0,20}limit/i,
45
+ /usage.{0,20}limit/i
46
+ ]
47
+ }
48
+
49
+ # Enhanced user feedback patterns
50
+ @user_feedback_patterns = {
51
+ # Direct requests for input
52
+ direct_requests: [
53
+ /please provide/i,
54
+ /can you provide/i,
55
+ /could you provide/i,
56
+ /i need.{0,20}input/i,
57
+ /i require.{0,20}input/i,
58
+ /please give me/i,
59
+ /can you give me/i
60
+ ],
61
+ # Clarification requests
62
+ clarification: [
63
+ /can you clarify/i,
64
+ /could you clarify/i,
65
+ /please clarify/i,
66
+ /i need clarification/i,
67
+ /can you explain/i,
68
+ /could you explain/i
69
+ ],
70
+ # Choice/decision requests
71
+ choices: [
72
+ /what would you like/i,
73
+ /what do you prefer/i,
74
+ /which.{0,20}would you prefer/i,
75
+ /which.{0,20}do you want/i,
76
+ /do you want/i,
77
+ /should i/i,
78
+ /would you like/i,
79
+ /which option/i,
80
+ /choose between/i,
81
+ /select.{0,20}from/i
82
+ ],
83
+ # Confirmation requests
84
+ confirmation: [
85
+ /is this correct/i,
86
+ /does this look right/i,
87
+ /should i proceed/i,
88
+ /can i continue/i,
89
+ /is this what you want/i,
90
+ /confirm.{0,20}this/i,
91
+ /approve.{0,20}this/i
92
+ ],
93
+ # File/input requests
94
+ file_requests: [
95
+ /please upload/i,
96
+ /can you upload/i,
97
+ /i need.{0,20}file/i,
98
+ /please provide.{0,20}file/i,
99
+ /attach.{0,20}file/i,
100
+ /send.{0,20}file/i
101
+ ],
102
+ # Specific information requests
103
+ information: [
104
+ /what is.{0,20}name/i,
105
+ /what is.{0,20}email/i,
106
+ /what is.{0,20}url/i,
107
+ /what is.{0,20}path/i,
108
+ /enter.{0,20}name/i,
109
+ /enter.{0,20}email/i,
110
+ /enter.{0,20}url/i,
111
+ /enter.{0,20}path/i
112
+ ]
113
+ }
114
+
115
+ # Enhanced question patterns
116
+ @question_patterns = [
117
+ # Numbered questions
118
+ /^\d+\.\s+(.+)\?/,
119
+ /^(\d+)\)\s+(.+)\?/,
120
+ /^(\d+\.\s+.+)\?$/m,
121
+ # Bullet point questions
122
+ /^[-*]\s+(.+)\?/,
123
+ # Lettered questions
124
+ /^[a-z]\)\s+(.+)\?/i,
125
+ /^[A-Z]\)\s+(.+)\?/,
126
+ # Questions with colons
127
+ /^(\d+):\s+(.+)\?/,
128
+ # Questions in quotes
129
+ /"([^"]+\?)"/,
130
+ /'([^']+\?)'/
131
+ ]
132
+
133
+ # Context patterns that indicate user interaction is needed
134
+ @context_patterns = [
135
+ /waiting for.{0,20}input/i,
136
+ /awaiting.{0,20}response/i,
137
+ /need.{0,20}feedback/i,
138
+ /require.{0,20}confirmation/i,
139
+ /pending.{0,20}approval/i,
140
+ /user.{0,20}interaction.{0,20}required/i,
141
+ /manual.{0,20}intervention/i
142
+ ]
143
+
144
+ # Rate limit reset time patterns
145
+ @reset_time_patterns = [
146
+ /reset.{0,20}in.{0,20}(\d+).{0,20}seconds/i,
147
+ /retry.{0,20}after.{0,20}(\d+).{0,20}seconds/i,
148
+ /wait[^\d]*(\d+)[^\d]*seconds/i,
149
+ /(\d+).{0,20}seconds.{0,20}until.{0,20}reset/i,
150
+ /reset.{0,20}at.{0,20}(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i,
151
+ /retry.{0,20}after.{0,20}(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i
152
+ ]
153
+ end
154
+
155
+ # Check if result indicates rate limiting
156
+ def is_rate_limited?(result, provider = nil)
157
+ return false unless result.is_a?(Hash)
158
+
159
+ # Check HTTP status codes
160
+ if result[:status_code] == 429 || result[:http_status] == 429
161
+ return true
162
+ end
163
+
164
+ # Get all text content to check
165
+ text_content = [
166
+ result[:error],
167
+ result[:message],
168
+ result[:output],
169
+ result[:response],
170
+ result[:body]
171
+ ].compact.join(" ")
172
+
173
+ return false if text_content.empty?
174
+
175
+ # Check provider-specific patterns first
176
+ if provider && @rate_limit_patterns[provider.to_sym]
177
+ return true if @rate_limit_patterns[provider.to_sym].any? { |pattern| text_content.match?(pattern) }
178
+ end
179
+
180
+ # Check common patterns
181
+ @rate_limit_patterns[:common].any? { |pattern| text_content.match?(pattern) }
182
+ end
183
+
184
+ # Extract rate limit information from result
185
+ def extract_rate_limit_info(result, provider = nil)
186
+ return nil unless is_rate_limited?(result, provider)
187
+
188
+ text_content = [
189
+ result[:error],
190
+ result[:message],
191
+ result[:output],
192
+ result[:response],
193
+ result[:body]
194
+ ].compact.join(" ")
195
+
196
+ {
197
+ provider: provider,
198
+ detected_at: Time.now,
199
+ reset_time: extract_reset_time(text_content),
200
+ retry_after: extract_retry_after(text_content),
201
+ limit_type: detect_limit_type(text_content, provider),
202
+ message: text_content
203
+ }
204
+ end
205
+
206
+ # Extract reset time from rate limit message
207
+ def extract_reset_time(text_content)
208
+ @reset_time_patterns.each do |pattern|
209
+ match = text_content.match(pattern)
210
+ next unless match
211
+
212
+ if match[1].match?(/^\d+$/)
213
+ # Seconds from now
214
+ Time.now + match[1].to_i
215
+ else
216
+ # Specific timestamp
217
+ begin
218
+ Time.parse(match[1])
219
+ rescue ArgumentError
220
+ nil
221
+ end
222
+ end
223
+ end
224
+
225
+ # Default to 60 seconds if no specific time found
226
+ Time.now + 60
227
+ end
228
+
229
+ # Extract retry-after value
230
+ def extract_retry_after(text_content)
231
+ # Look for retry-after header or similar
232
+ retry_patterns = [
233
+ /retry\s+after\s+(\d{1,6})/i,
234
+ /wait\s+(\d{1,6})\s+seconds/i,
235
+ /(\d{1,6})\s+seconds\s+until/i
236
+ ]
237
+
238
+ retry_patterns.each do |pattern|
239
+ match = text_content.match(pattern)
240
+ return match[1].to_i if match
241
+ end
242
+
243
+ # Default retry time
244
+ 60
245
+ end
246
+
247
+ # Detect the type of rate limit
248
+ def detect_limit_type(text_content, provider)
249
+ case provider&.to_s&.downcase
250
+ when "anthropic", "claude"
251
+ if text_content.match?(/requests per minute/i)
252
+ "requests_per_minute"
253
+ elsif text_content.match?(/tokens per minute/i)
254
+ "tokens_per_minute"
255
+ else
256
+ "general_rate_limit"
257
+ end
258
+ when "openai"
259
+ if text_content.match?(/requests per minute/i)
260
+ "requests_per_minute"
261
+ elsif text_content.match?(/tokens per minute/i)
262
+ "tokens_per_minute"
263
+ else
264
+ "general_rate_limit"
265
+ end
266
+ when "google", "gemini"
267
+ if text_content.match?(/quota exceeded/i)
268
+ "quota_exceeded"
269
+ else
270
+ "general_rate_limit"
271
+ end
272
+ when "cursor"
273
+ if text_content.match?(/package\s+limit/i)
274
+ "package_limit"
275
+ elsif text_content.match?(/usage\s+limit/i)
276
+ "usage_limit"
277
+ else
278
+ "general_rate_limit"
279
+ end
280
+ else
281
+ "general_rate_limit"
282
+ end
283
+ end
284
+
285
+ # Check if result needs user feedback
286
+ def needs_user_feedback?(result)
287
+ return false unless result.is_a?(Hash)
288
+
289
+ # Get all text content to check
290
+ text_content = [
291
+ result[:output],
292
+ result[:message],
293
+ result[:response],
294
+ result[:body]
295
+ ].compact.join(" ")
296
+
297
+ return false if text_content.empty?
298
+
299
+ # Check for context patterns first
300
+ return true if @context_patterns.any? { |pattern| text_content.match?(pattern) }
301
+
302
+ # Check for any user feedback patterns
303
+ @user_feedback_patterns.values.flatten.any? { |pattern| text_content.match?(pattern) }
304
+ end
305
+
306
+ # Get detailed user feedback information
307
+ def extract_user_feedback_info(result)
308
+ return nil unless needs_user_feedback?(result)
309
+
310
+ # Get all text content to analyze
311
+ text_content = [
312
+ result[:output],
313
+ result[:message],
314
+ result[:response],
315
+ result[:body]
316
+ ].compact.join(" ")
317
+
318
+ {
319
+ detected_at: Time.now,
320
+ feedback_type: detect_feedback_type(text_content),
321
+ questions: extract_questions(result),
322
+ context: extract_context(text_content),
323
+ urgency: detect_urgency(text_content),
324
+ input_type: detect_input_type(text_content)
325
+ }
326
+ end
327
+
328
+ # Detect the type of feedback needed
329
+ def detect_feedback_type(text_content)
330
+ @user_feedback_patterns.each do |type, patterns|
331
+ if patterns.any? { |pattern| text_content.match?(pattern) }
332
+ return type.to_s
333
+ end
334
+ end
335
+
336
+ "general"
337
+ end
338
+
339
+ # Extract context information
340
+ def extract_context(text_content)
341
+ context_matches = []
342
+ @context_patterns.each do |pattern|
343
+ matches = text_content.scan(pattern)
344
+ context_matches.concat(matches)
345
+ end
346
+ context_matches.uniq
347
+ end
348
+
349
+ # Detect urgency level
350
+ def detect_urgency(text_content)
351
+ urgent_patterns = [
352
+ /urgent/i,
353
+ /asap/i,
354
+ /immediately/i,
355
+ /right now/i,
356
+ /critical/i,
357
+ /important/i
358
+ ]
359
+
360
+ low_urgency_patterns = [
361
+ /when you have time/i,
362
+ /when convenient/i,
363
+ /at your convenience/i,
364
+ /when possible/i,
365
+ /no rush/i,
366
+ /take your time/i
367
+ ]
368
+
369
+ if urgent_patterns.any? { |pattern| text_content.match?(pattern) }
370
+ "high"
371
+ elsif low_urgency_patterns.any? { |pattern| text_content.match?(pattern) }
372
+ "low"
373
+ elsif text_content.match?(/please/i) || text_content.match?(/can you/i)
374
+ "medium"
375
+ else
376
+ "low"
377
+ end
378
+ end
379
+
380
+ # Detect the type of input expected
381
+ def detect_input_type(text_content)
382
+ if text_content.match?(/file/i) || text_content.match?(/upload/i) || text_content.match?(/attach/i)
383
+ "file"
384
+ elsif text_content.match?(/email/i)
385
+ "email"
386
+ elsif text_content.match?(/url/i) || text_content.match?(/link/i)
387
+ "url"
388
+ elsif text_content.match?(/path/i) || text_content.match?(/directory/i)
389
+ "path"
390
+ elsif text_content.match?(/number/i) || text_content.match?(/\d+/) ||
391
+ text_content.match?(/how many/i) || text_content.match?(/how much/i) ||
392
+ text_content.match?(/count/i) || text_content.match?(/quantity/i) ||
393
+ text_content.match?(/amount/i)
394
+ "number"
395
+ elsif text_content.match?(/yes/i) || text_content.match?(/no/i) || text_content.match?(/confirm/i) ||
396
+ text_content.match?(/should i/i) || text_content.match?(/can i/i) || text_content.match?(/may i/i) ||
397
+ text_content.match?(/proceed/i) || text_content.match?(/continue/i) || text_content.match?(/approve/i)
398
+ "boolean"
399
+ else
400
+ "text"
401
+ end
402
+ end
403
+
404
+ # Extract questions from result output
405
+ def extract_questions(result)
406
+ return [] unless result.is_a?(Hash)
407
+
408
+ # Get all text content to analyze
409
+ text_content = [
410
+ result[:output],
411
+ result[:message],
412
+ result[:response],
413
+ result[:body]
414
+ ].compact.join(" ")
415
+
416
+ return [] if text_content.empty?
417
+
418
+ questions = []
419
+
420
+ # Extract structured questions using patterns
421
+ @question_patterns.each do |pattern|
422
+ matches = text_content.scan(pattern)
423
+ matches.each do |match|
424
+ if match.is_a?(Array)
425
+ if match.length == 2
426
+ # Pattern with two capture groups (number and question)
427
+ number = match[0]
428
+ question = match[1]
429
+ next if number.nil? || question.nil?
430
+
431
+ questions << {
432
+ number: number,
433
+ question: question.strip,
434
+ type: detect_question_type(question),
435
+ input_type: detect_input_type(question)
436
+ }
437
+ elsif match.length == 1
438
+ # Pattern with one capture group (just question)
439
+ question = match[0]
440
+ next if question.nil?
441
+
442
+ questions << {
443
+ question: question.strip,
444
+ type: detect_question_type(question),
445
+ input_type: detect_input_type(question)
446
+ }
447
+ end
448
+ else
449
+ # Single capture group
450
+ next if match.nil?
451
+
452
+ questions << {
453
+ question: match.strip,
454
+ type: detect_question_type(match),
455
+ input_type: detect_input_type(match)
456
+ }
457
+ end
458
+ end
459
+ end
460
+
461
+ # If no structured questions found, look for general questions
462
+ if questions.empty?
463
+ general_questions = text_content.scan(/([^.!?]{1,500}\?)/)
464
+ general_questions.each_with_index do |match, index|
465
+ question_text = match[0].strip
466
+ next if question_text.length < 10 # Skip very short questions
467
+
468
+ questions << {
469
+ number: index + 1,
470
+ question: question_text,
471
+ type: detect_question_type(question_text),
472
+ input_type: detect_input_type(question_text)
473
+ }
474
+ end
475
+ end
476
+
477
+ # Remove duplicates and clean up
478
+ questions.uniq { |q| q[:question] }
479
+ end
480
+
481
+ # Detect the type of question
482
+ def detect_question_type(question_text)
483
+ question_lower = question_text.downcase
484
+
485
+ if question_lower.match?(/what.{0,20}name/i) || question_lower.match?(/what.{0,20}email/i)
486
+ "information"
487
+ elsif question_lower.match?(/which.{0,20}prefer/i) || question_lower.match?(/which.{0,20}want/i)
488
+ "choice"
489
+ elsif question_lower.match?(/should.{0,20}i/i) || question_lower.match?(/can.{0,20}i/i)
490
+ "permission"
491
+ elsif question_lower.match?(/is.{0,20}correct/i) || question_lower.match?(/does.{0,20}look/i)
492
+ "confirmation"
493
+ elsif question_lower.match?(/can.{0,20}you/i) || question_lower.match?(/could.{0,20}you/i)
494
+ "request"
495
+ elsif question_lower.match?(/how.{0,20}many/i) || question_lower.match?(/how.{0,20}much/i)
496
+ "quantity"
497
+ elsif question_lower.match?(/when/i)
498
+ "time"
499
+ elsif question_lower.match?(/where/i)
500
+ "location"
501
+ elsif question_lower.match?(/why/i)
502
+ "explanation"
503
+ else
504
+ "general"
505
+ end
506
+ end
507
+
508
+ # Check if work is complete
509
+ def is_work_complete?(result, progress)
510
+ return false unless result.is_a?(Hash)
511
+
512
+ # Check if all steps are completed
513
+ if progress&.completed_steps && progress.total_steps &&
514
+ progress.completed_steps.size == progress.total_steps
515
+ return true
516
+ end
517
+
518
+ # Get all text content to analyze
519
+ text_content = [
520
+ result[:output],
521
+ result[:message],
522
+ result[:response],
523
+ result[:body]
524
+ ].compact.join(" ")
525
+
526
+ return false if text_content.empty?
527
+
528
+ # Check for completion indicators
529
+ completion_info = extract_completion_info(result, progress)
530
+ completion_info[:is_complete]
531
+ end
532
+
533
+ # Extract comprehensive completion information
534
+ def extract_completion_info(result, progress)
535
+ # Get all text content to analyze
536
+ text_content = [
537
+ result[:output],
538
+ result[:message],
539
+ result[:response],
540
+ result[:body]
541
+ ].compact.join(" ")
542
+
543
+ completion_info = {
544
+ is_complete: false,
545
+ completion_type: nil,
546
+ confidence: 0.0,
547
+ indicators: [],
548
+ progress_status: nil,
549
+ next_actions: []
550
+ }
551
+
552
+ # Check progress-based completion
553
+ if progress&.completed_steps && progress.total_steps &&
554
+ progress.completed_steps.size == progress.total_steps
555
+ completion_info[:is_complete] = true
556
+ completion_info[:completion_type] = "all_steps_completed"
557
+ completion_info[:confidence] = 1.0
558
+ completion_info[:progress_status] = "all_steps_completed"
559
+ return completion_info
560
+ end
561
+
562
+ # Check for explicit completion indicators
563
+ explicit_completion = detect_explicit_completion(text_content)
564
+ if explicit_completion[:found]
565
+ completion_info[:is_complete] = true
566
+ completion_info[:completion_type] = explicit_completion[:type]
567
+ completion_info[:confidence] = explicit_completion[:confidence]
568
+ completion_info[:indicators] = explicit_completion[:indicators]
569
+ return completion_info
570
+ end
571
+
572
+ # Check for implicit completion indicators
573
+ implicit_completion = detect_implicit_completion(text_content, progress)
574
+ if implicit_completion[:found]
575
+ completion_info[:is_complete] = true
576
+ completion_info[:completion_type] = implicit_completion[:type]
577
+ completion_info[:confidence] = implicit_completion[:confidence]
578
+ completion_info[:indicators] = implicit_completion[:indicators]
579
+ return completion_info
580
+ end
581
+
582
+ # If no completion indicators found, check for partial completion
583
+ partial_completion = detect_partial_completion(text_content, progress)
584
+ completion_info[:progress_status] = partial_completion[:status]
585
+ completion_info[:next_actions] = partial_completion[:next_actions]
586
+
587
+ # Only consider work complete if we have explicit or implicit completion indicators
588
+ completion_info[:is_complete] = false
589
+
590
+ completion_info
591
+ end
592
+
593
+ # Detect explicit completion indicators
594
+ def detect_explicit_completion(text_content)
595
+ completion_patterns = {
596
+ # High confidence completion indicators
597
+ high_confidence: [
598
+ /all steps completed/i,
599
+ /workflow complete/i,
600
+ /analysis complete/i,
601
+ /execution finished/i,
602
+ /task completed/i,
603
+ /all done/i,
604
+ /finished successfully/i,
605
+ /completed successfully/i,
606
+ /workflow finished/i,
607
+ /analysis finished/i,
608
+ /execution completed/i
609
+ ],
610
+ # Medium confidence completion indicators
611
+ medium_confidence: [
612
+ /complete/i,
613
+ /finished/i,
614
+ /done/i,
615
+ /success/i,
616
+ /ready/i,
617
+ /final/i
618
+ ],
619
+ # Low confidence completion indicators
620
+ low_confidence: [
621
+ /end/i,
622
+ /stop/i,
623
+ /close/i,
624
+ /finalize/i
625
+ ]
626
+ }
627
+
628
+ found_indicators = []
629
+ max_confidence = 0.0
630
+ completion_type = nil
631
+
632
+ completion_patterns.each do |confidence_level, patterns|
633
+ patterns.each do |pattern|
634
+ if text_content.match?(pattern)
635
+ found_indicators << pattern.source
636
+ case confidence_level
637
+ when :high_confidence
638
+ if max_confidence < 0.9
639
+ max_confidence = 0.9
640
+ completion_type = "explicit_high_confidence"
641
+ end
642
+ when :medium_confidence
643
+ if max_confidence < 0.7
644
+ max_confidence = 0.7
645
+ completion_type = "explicit_medium_confidence"
646
+ end
647
+ when :low_confidence
648
+ if max_confidence < 0.5
649
+ max_confidence = 0.5
650
+ completion_type = "explicit_low_confidence"
651
+ end
652
+ end
653
+ end
654
+ end
655
+ end
656
+
657
+ {
658
+ found: max_confidence > 0.0,
659
+ type: completion_type,
660
+ confidence: max_confidence,
661
+ indicators: found_indicators
662
+ }
663
+ end
664
+
665
+ # Detect implicit completion indicators
666
+ def detect_implicit_completion(text_content, progress)
667
+ # Check for summary or conclusion patterns
668
+ summary_patterns = [
669
+ /summary/i,
670
+ /conclusion/i,
671
+ /overview/i,
672
+ /results/i,
673
+ /findings/i,
674
+ /recommendations/i,
675
+ /next steps/i
676
+ ]
677
+
678
+ # Check for deliverable patterns
679
+ deliverable_patterns = [
680
+ /report generated/i,
681
+ /document created/i,
682
+ /file created/i,
683
+ /output generated/i,
684
+ /result saved/i,
685
+ /analysis saved/i
686
+ ]
687
+
688
+ # Check for status patterns
689
+ status_patterns = [
690
+ /status: complete/i,
691
+ /status: finished/i,
692
+ /status: done/i,
693
+ /phase.{0,20}complete/i,
694
+ /stage.{0,20}complete/i
695
+ ]
696
+
697
+ found_indicators = []
698
+ max_confidence = 0.0
699
+ completion_type = nil
700
+
701
+ # Check summary patterns
702
+ if summary_patterns.any? { |pattern| text_content.match?(pattern) }
703
+ found_indicators << "summary_patterns"
704
+ max_confidence = [max_confidence, 0.8].max
705
+ completion_type = "implicit_summary"
706
+ end
707
+
708
+ # Check deliverable patterns
709
+ if deliverable_patterns.any? { |pattern| text_content.match?(pattern) }
710
+ found_indicators << "deliverable_patterns"
711
+ max_confidence = [max_confidence, 0.8].max
712
+ completion_type = "implicit_deliverable"
713
+ end
714
+
715
+ # Check status patterns
716
+ if status_patterns.any? { |pattern| text_content.match?(pattern) }
717
+ found_indicators << "status_patterns"
718
+ max_confidence = [max_confidence, 0.7].max
719
+ completion_type = "implicit_status"
720
+ end
721
+
722
+ # Check for progress completion
723
+ if progress && progress.completed_steps.size > 0
724
+ completion_ratio = progress.completed_steps.size.to_f / progress.total_steps
725
+ if completion_ratio >= 0.8 # 80% or more complete
726
+ found_indicators << "high_progress_ratio"
727
+ max_confidence = [max_confidence, 0.6].max
728
+ completion_type = "implicit_high_progress"
729
+ end
730
+ end
731
+
732
+ {
733
+ found: max_confidence > 0.0,
734
+ type: completion_type,
735
+ confidence: max_confidence,
736
+ indicators: found_indicators
737
+ }
738
+ end
739
+
740
+ # Detect partial completion and next actions
741
+ def detect_partial_completion(text_content, progress)
742
+ next_actions = []
743
+ status = "in_progress"
744
+
745
+ # Check for next action indicators
746
+ next_action_patterns = [
747
+ /next step/i,
748
+ /next action/i,
749
+ /continue with/i,
750
+ /proceed to/i,
751
+ /move to/i,
752
+ /now\s+will/i,
753
+ /next\s+will/i
754
+ ]
755
+
756
+ if next_action_patterns.any? { |pattern| text_content.match?(pattern) }
757
+ status = "has_next_actions"
758
+ next_actions << "continue_execution"
759
+ end
760
+
761
+ # Check for waiting patterns
762
+ waiting_patterns = [
763
+ /waiting for/i,
764
+ /pending/i,
765
+ /awaiting/i,
766
+ /need\s+input/i,
767
+ /require\s+input/i
768
+ ]
769
+
770
+ if waiting_patterns.any? { |pattern| text_content.match?(pattern) }
771
+ status = "waiting_for_input"
772
+ next_actions << "collect_user_input"
773
+ end
774
+
775
+ # Check for error patterns
776
+ error_patterns = [
777
+ /error/i,
778
+ /failed/i,
779
+ /issue/i,
780
+ /problem/i,
781
+ /exception/i
782
+ ]
783
+
784
+ if error_patterns.any? { |pattern| text_content.match?(pattern) }
785
+ status = "has_errors"
786
+ next_actions << "handle_errors"
787
+ end
788
+
789
+ # Check progress status only if no specific status was detected from text
790
+ if status == "in_progress" && progress
791
+ completion_ratio = progress.completed_steps.size.to_f / progress.total_steps
792
+ if completion_ratio >= 0.8
793
+ status = "near_completion"
794
+ next_actions << "continue_to_completion"
795
+ elsif completion_ratio >= 0.5
796
+ status = "half_complete"
797
+ next_actions << "continue_execution"
798
+ elsif completion_ratio >= 0.2
799
+ status = "early_stage"
800
+ next_actions << "continue_execution"
801
+ else
802
+ status = "just_started"
803
+ next_actions << "continue_execution"
804
+ end
805
+ end
806
+
807
+ {
808
+ status: status,
809
+ next_actions: next_actions
810
+ }
811
+ end
812
+
813
+ # Classify error type with comprehensive analysis
814
+ def classify_error(error)
815
+ return :unknown unless error.is_a?(StandardError)
816
+
817
+ error_info = extract_error_info(error)
818
+ error_info[:type]
819
+ end
820
+
821
+ # Extract comprehensive error information
822
+ def extract_error_info(error)
823
+ return {type: :unknown, severity: :low, recoverable: true} unless error.is_a?(StandardError)
824
+
825
+ error_message = error.message.downcase
826
+ error_class = error.class.name.downcase
827
+
828
+ # Get error classification
829
+ error_type = classify_error_type(error_message, error_class)
830
+ severity = determine_error_severity(error_type, error_message)
831
+ recoverable = determine_recoverability(error_type, error_message)
832
+ retry_strategy = determine_retry_strategy(error_type, error_message)
833
+
834
+ {
835
+ type: error_type,
836
+ severity: severity,
837
+ recoverable: recoverable,
838
+ retry_strategy: retry_strategy,
839
+ message: error.message,
840
+ class: error.class.name,
841
+ backtrace: error.backtrace&.first(5)
842
+ }
843
+ end
844
+
845
+ # Classify error type based on message and class
846
+ def classify_error_type(error_message, error_class)
847
+ # Network and connectivity errors
848
+ if error_message.match?(/timeout/i) || error_class.include?("timeout")
849
+ :timeout
850
+ elsif error_message.match?(/connection/i) || error_message.match?(/network/i) ||
851
+ error_class.include?("connection") || error_class.include?("network")
852
+ :network
853
+ elsif error_message.match?(/dns/i) || error_message.match?(/resolve/i)
854
+ :dns_resolution
855
+ elsif error_message.match?(/ssl/i) || error_message.match?(/tls/i) || error_message.match?(/certificate/i)
856
+ :ssl_tls
857
+
858
+ # Authentication and authorization errors
859
+ elsif error_message.match?(/authentication/i) || error_message.match?(/unauthorized/i) ||
860
+ error_message.match?(/401/i) || error_class.include?("authentication")
861
+ :authentication
862
+ elsif error_message.match?(/permission/i) || error_message.match?(/forbidden/i) ||
863
+ error_message.match?(/403/i) || error_class.include?("permission")
864
+ :permission
865
+ elsif error_message.match?(/access.{0,20}denied/i) || error_message.match?(/insufficient.{0,20}privileges/i)
866
+ :access_denied
867
+
868
+ # File and I/O errors (check these first as they're more specific)
869
+ elsif error_message.match?(/file.{0,20}not.{0,20}found/i) || error_message.match?(/no.{0,20}such.{0,20}file/i)
870
+ :file_not_found
871
+
872
+ # HTTP and API errors
873
+ elsif error_message.match?(/not found/i) || error_message.match?(/404/i)
874
+ :not_found
875
+ elsif error_message.match?(/server error/i) || error_message.match?(/500/i) ||
876
+ error_message.match?(/internal.{0,20}error/i)
877
+ :server_error
878
+ elsif error_message.match?(/bad request/i) || error_message.match?(/400/i) ||
879
+ error_message.match?(/invalid.{0,20}request/i)
880
+ :bad_request
881
+ elsif error_message.match?(/rate limit/i) || error_message.match?(/429/i) ||
882
+ error_message.match?(/too many requests/i)
883
+ :rate_limit
884
+ elsif error_message.match?(/quota.{0,20}exceeded/i) || error_message.match?(/usage.{0,20}limit/i)
885
+ :quota_exceeded
886
+ elsif error_message.match?(/permission.{0,20}denied/i) || error_message.match?(/eacces/i)
887
+ :file_permission
888
+ elsif error_message.match?(/disk.{0,20}full/i) || error_message.match?(/no.{0,20}space/i)
889
+ :disk_full
890
+ elsif error_message.match?(/read.{0,20}only/i) || error_message.match?(/eacces/i)
891
+ :read_only_filesystem
892
+
893
+ # Memory and resource errors
894
+ elsif error_message.match?(/memory/i) || error_message.match?(/out of memory/i) ||
895
+ error_class.include?("memory")
896
+ :memory_error
897
+ elsif error_message.match?(/resource.{0,20}unavailable/i) || error_message.match?(/resource.{0,20}exhausted/i)
898
+ :resource_exhausted
899
+
900
+ # Configuration and setup errors
901
+ elsif error_message.match?(/configuration/i) || error_message.match?(/config/i) ||
902
+ error_class.include?("configuration")
903
+ :configuration
904
+ elsif error_message.match?(/missing.{0,20}dependency/i) || error_message.match?(/gem.{0,20}not found/i)
905
+ :missing_dependency
906
+ elsif error_message.match?(/environment/i) || error_message.match?(/env/i)
907
+ :environment
908
+
909
+ # Provider-specific errors
910
+ elsif error_message.match?(/anthropic/i) || error_message.match?(/claude/i)
911
+ :anthropic_error
912
+ elsif error_message.match?(/openai/i) || error_message.match?(/gpt/i)
913
+ :openai_error
914
+ elsif error_message.match?(/google/i) || error_message.match?(/gemini/i)
915
+ :google_error
916
+ elsif error_message.match?(/cursor/i)
917
+ :cursor_error
918
+
919
+ # Parsing and format errors
920
+ elsif error_message.match?(/parse/i) || error_message.match?(/json/i) ||
921
+ error_message.match?(/syntax/i) || error_class.include?("parse")
922
+ :parsing_error
923
+ elsif error_message.match?(/format/i) || error_message.match?(/invalid.{0,20}format/i)
924
+ :format_error
925
+
926
+ # Validation errors
927
+ elsif error_message.match?(/validation/i) || error_message.match?(/invalid.{0,20}input/i) ||
928
+ error_class.include?("validation")
929
+ :validation_error
930
+ elsif error_message.match?(/argument/i) || error_message.match?(/parameter/i)
931
+ :argument_error
932
+
933
+ # System errors
934
+ elsif error_message.match?(/system/i) || error_class.include?("system")
935
+ :system_error
936
+ elsif error_message.match?(/interrupt/i) || error_message.match?(/sigint/i) ||
937
+ error_class.include?("interrupt")
938
+ :interrupted
939
+
940
+ else
941
+ :unknown
942
+ end
943
+ end
944
+
945
+ # Determine error severity
946
+ def determine_error_severity(error_type, _error_message)
947
+ case error_type
948
+ when :authentication, :permission, :access_denied, :configuration, :missing_dependency
949
+ :critical
950
+ when :rate_limit, :quota_exceeded, :disk_full, :memory_error, :resource_exhausted
951
+ :high
952
+ when :timeout, :network, :dns_resolution, :ssl_tls, :server_error, :bad_request
953
+ :medium
954
+ when :not_found, :file_not_found, :file_permission, :read_only_filesystem
955
+ :medium
956
+ when :parsing_error, :format_error, :validation_error, :argument_error
957
+ :low
958
+ when :interrupted, :system_error
959
+ :high
960
+ else
961
+ :medium
962
+ end
963
+ end
964
+
965
+ # Determine if error is recoverable
966
+ def determine_recoverability(error_type, _error_message)
967
+ case error_type
968
+ when :authentication, :permission, :access_denied, :configuration, :missing_dependency
969
+ false
970
+ when :rate_limit, :quota_exceeded, :timeout, :network, :dns_resolution, :ssl_tls
971
+ true
972
+ when :server_error, :bad_request, :not_found, :file_not_found
973
+ true
974
+ when :disk_full, :memory_error, :resource_exhausted
975
+ false
976
+ when :file_permission, :read_only_filesystem
977
+ false
978
+ when :parsing_error, :format_error, :validation_error, :argument_error
979
+ true
980
+ when :interrupted, :system_error
981
+ false
982
+ else
983
+ # Unknown errors are considered recoverable with caution
984
+ true
985
+ end
986
+ end
987
+
988
+ # Determine retry strategy
989
+ def determine_retry_strategy(error_type, _error_message)
990
+ case error_type
991
+ when :timeout, :network, :dns_resolution, :ssl_tls
992
+ {strategy: :exponential_backoff, max_retries: 3, base_delay: 5}
993
+ when :rate_limit, :quota_exceeded
994
+ {strategy: :fixed_delay, max_retries: 2, delay: 60}
995
+ when :server_error, :bad_request
996
+ {strategy: :exponential_backoff, max_retries: 2, base_delay: 10}
997
+ when :not_found, :file_not_found
998
+ {strategy: :no_retry, max_retries: 0, delay: 0}
999
+ when :parsing_error, :format_error, :validation_error, :argument_error
1000
+ {strategy: :no_retry, max_retries: 0, delay: 0}
1001
+ when :authentication, :permission, :access_denied, :configuration, :missing_dependency
1002
+ {strategy: :no_retry, max_retries: 0, delay: 0}
1003
+ when :disk_full, :memory_error, :resource_exhausted
1004
+ {strategy: :no_retry, max_retries: 0, delay: 0}
1005
+ when :interrupted, :system_error
1006
+ {strategy: :no_retry, max_retries: 0, delay: 0}
1007
+ else
1008
+ {strategy: :exponential_backoff, max_retries: 1, base_delay: 5}
1009
+ end
1010
+ end
1011
+
1012
+ # Check if error is recoverable
1013
+ def recoverable_error?(error)
1014
+ error_info = extract_error_info(error)
1015
+ error_info[:recoverable]
1016
+ end
1017
+
1018
+ # Get retry delay for error type
1019
+ def retry_delay_for_error(error, attempt_number)
1020
+ error_info = extract_error_info(error)
1021
+ retry_strategy = error_info[:retry_strategy]
1022
+
1023
+ case retry_strategy[:strategy]
1024
+ when :exponential_backoff
1025
+ retry_strategy[:base_delay] * (2**(attempt_number - 1))
1026
+ when :fixed_delay
1027
+ retry_strategy[:delay]
1028
+ when :no_retry
1029
+ 0
1030
+ else
1031
+ 5 # Default delay
1032
+ end
1033
+ end
1034
+
1035
+ # Get maximum retries for error type
1036
+ def max_retries_for_error(error)
1037
+ error_info = extract_error_info(error)
1038
+ error_info[:retry_strategy][:max_retries]
1039
+ end
1040
+
1041
+ # Get error severity
1042
+ def get_error_severity(error)
1043
+ error_info = extract_error_info(error)
1044
+ error_info[:severity]
1045
+ end
1046
+
1047
+ # Check if error is high severity
1048
+ def high_severity_error?(error)
1049
+ [:critical, :high].include?(get_error_severity(error))
1050
+ end
1051
+
1052
+ # Get error description
1053
+ def get_error_description(error)
1054
+ error_info = extract_error_info(error)
1055
+ error_type = error_info[:type]
1056
+
1057
+ case error_type
1058
+ when :timeout
1059
+ "Request timed out"
1060
+ when :network
1061
+ "Network connection error"
1062
+ when :dns_resolution
1063
+ "DNS resolution failed"
1064
+ when :ssl_tls
1065
+ "SSL/TLS connection error"
1066
+ when :authentication
1067
+ "Authentication failed"
1068
+ when :permission
1069
+ "Permission denied"
1070
+ when :access_denied
1071
+ "Access denied"
1072
+ when :not_found
1073
+ "Resource not found"
1074
+ when :server_error
1075
+ "Server error"
1076
+ when :bad_request
1077
+ "Bad request"
1078
+ when :rate_limit
1079
+ "Rate limit exceeded"
1080
+ when :quota_exceeded
1081
+ "Quota exceeded"
1082
+ when :file_not_found
1083
+ "File not found"
1084
+ when :file_permission
1085
+ "File permission denied"
1086
+ when :disk_full
1087
+ "Disk full"
1088
+ when :read_only_filesystem
1089
+ "Read-only filesystem"
1090
+ when :memory_error
1091
+ "Memory error"
1092
+ when :resource_exhausted
1093
+ "Resource exhausted"
1094
+ when :configuration
1095
+ "Configuration error"
1096
+ when :missing_dependency
1097
+ "Missing dependency"
1098
+ when :environment
1099
+ "Environment error"
1100
+ when :anthropic_error
1101
+ "Anthropic API error"
1102
+ when :openai_error
1103
+ "OpenAI API error"
1104
+ when :google_error
1105
+ "Google API error"
1106
+ when :cursor_error
1107
+ "Cursor API error"
1108
+ when :parsing_error
1109
+ "Parsing error"
1110
+ when :format_error
1111
+ "Format error"
1112
+ when :validation_error
1113
+ "Validation error"
1114
+ when :argument_error
1115
+ "Argument error"
1116
+ when :system_error
1117
+ "System error"
1118
+ when :interrupted
1119
+ "Operation interrupted"
1120
+ else
1121
+ "Unknown error"
1122
+ end
1123
+ end
1124
+
1125
+ # Get error recovery suggestions
1126
+ def get_error_recovery_suggestions(error)
1127
+ error_info = extract_error_info(error)
1128
+ error_type = error_info[:type]
1129
+
1130
+ case error_type
1131
+ when :timeout, :network, :dns_resolution, :ssl_tls
1132
+ ["Check network connection", "Retry the operation", "Check firewall settings"]
1133
+ when :authentication, :permission, :access_denied
1134
+ ["Check credentials", "Verify permissions", "Contact administrator"]
1135
+ when :rate_limit, :quota_exceeded
1136
+ ["Wait before retrying", "Check usage limits", "Consider upgrading plan"]
1137
+ when :file_not_found, :file_permission
1138
+ ["Check file path", "Verify file permissions", "Ensure file exists"]
1139
+ when :disk_full, :memory_error, :resource_exhausted
1140
+ ["Free up disk space", "Increase memory", "Check system resources"]
1141
+ when :configuration, :missing_dependency, :environment
1142
+ ["Check configuration", "Install missing dependencies", "Verify environment setup"]
1143
+ when :parsing_error, :format_error, :validation_error, :argument_error
1144
+ ["Check input format", "Validate parameters", "Review data structure"]
1145
+ when :server_error, :bad_request
1146
+ ["Retry the operation", "Check request format", "Contact service provider"]
1147
+ else
1148
+ ["Review error details", "Check logs", "Contact support"]
1149
+ end
1150
+ end
1151
+
1152
+ # Check if a provider is currently rate limited
1153
+ def is_provider_rate_limited?(provider, rate_limit_info)
1154
+ return false unless rate_limit_info && rate_limit_info[:provider] == provider
1155
+
1156
+ # Check if the rate limit has expired
1157
+ if rate_limit_info[:reset_time] && rate_limit_info[:reset_time] > Time.now
1158
+ return true
1159
+ end
1160
+
1161
+ false
1162
+ end
1163
+
1164
+ # Get time until rate limit resets
1165
+ def time_until_reset(rate_limit_info)
1166
+ return 0 unless rate_limit_info && rate_limit_info[:reset_time]
1167
+
1168
+ reset_time = rate_limit_info[:reset_time]
1169
+ remaining = reset_time - Time.now
1170
+ [remaining, 0].max
1171
+ end
1172
+
1173
+ # Check if rate limit has expired
1174
+ def rate_limit_expired?(rate_limit_info)
1175
+ return true unless rate_limit_info && rate_limit_info[:reset_time]
1176
+
1177
+ rate_limit_info[:reset_time] <= Time.now
1178
+ end
1179
+
1180
+ # Get all available rate limit patterns for a provider
1181
+ def get_rate_limit_patterns(provider = nil)
1182
+ if provider && @rate_limit_patterns[provider.to_sym]
1183
+ @rate_limit_patterns[:common] + @rate_limit_patterns[provider.to_sym]
1184
+ else
1185
+ @rate_limit_patterns[:common]
1186
+ end
1187
+ end
1188
+
1189
+ # Check if operation has timed out
1190
+ def is_timeout?(result, start_time, timeout_duration = nil)
1191
+ return false unless result.is_a?(Hash) && start_time.is_a?(Time)
1192
+
1193
+ # Check for explicit timeout indicators in result
1194
+ if has_timeout_indicators?(result)
1195
+ return true
1196
+ end
1197
+
1198
+ # Check if operation duration exceeds timeout
1199
+ if timeout_duration && (Time.now - start_time) > timeout_duration
1200
+ return true
1201
+ end
1202
+
1203
+ false
1204
+ end
1205
+
1206
+ # Check for timeout indicators in result
1207
+ def has_timeout_indicators?(result)
1208
+ return false unless result.is_a?(Hash)
1209
+
1210
+ # Get all text content to analyze
1211
+ text_content = [
1212
+ result[:output],
1213
+ result[:message],
1214
+ result[:response],
1215
+ result[:body],
1216
+ result[:error]
1217
+ ].compact.join(" ")
1218
+
1219
+ return false if text_content.empty?
1220
+
1221
+ # Check for timeout patterns
1222
+ timeout_patterns = [
1223
+ /timeout/i,
1224
+ /timed out/i,
1225
+ /time.{0,20}out/i,
1226
+ /request.{0,20}timeout/i,
1227
+ /connection.{0,20}timeout/i,
1228
+ /read.{0,20}timeout/i,
1229
+ /write.{0,20}timeout/i,
1230
+ /operation.{0,20}timeout/i,
1231
+ /execution.{0,20}timeout/i,
1232
+ /deadline.{0,20}exceeded/i,
1233
+ /time.{0,20}limit.{0,20}exceeded/i,
1234
+ /time.{0,20}expired/i
1235
+ ]
1236
+
1237
+ timeout_patterns.any? { |pattern| text_content.match?(pattern) }
1238
+ end
1239
+
1240
+ # Extract timeout information from result
1241
+ def extract_timeout_info(result, start_time, timeout_duration = nil)
1242
+ timeout_info = {
1243
+ is_timeout: false,
1244
+ timeout_type: nil,
1245
+ duration: nil,
1246
+ timeout_duration: timeout_duration,
1247
+ exceeded_by: nil,
1248
+ indicators: []
1249
+ }
1250
+
1251
+ return timeout_info unless result.is_a?(Hash) && start_time.is_a?(Time)
1252
+
1253
+ # Check for explicit timeout indicators
1254
+ if has_timeout_indicators?(result)
1255
+ timeout_info[:is_timeout] = true
1256
+ timeout_info[:timeout_type] = "explicit"
1257
+ timeout_info[:indicators] = extract_timeout_indicators(result)
1258
+ end
1259
+
1260
+ # Check for duration-based timeout
1261
+ if timeout_duration
1262
+ duration = Time.now - start_time
1263
+ timeout_info[:duration] = duration
1264
+
1265
+ if duration > timeout_duration
1266
+ timeout_info[:is_timeout] = true
1267
+ timeout_info[:timeout_type] = "duration"
1268
+ timeout_info[:exceeded_by] = duration - timeout_duration
1269
+ end
1270
+ end
1271
+
1272
+ timeout_info
1273
+ end
1274
+
1275
+ # Extract timeout indicators from result
1276
+ def extract_timeout_indicators(result)
1277
+ text_content = [
1278
+ result[:output],
1279
+ result[:message],
1280
+ result[:response],
1281
+ result[:body],
1282
+ result[:error]
1283
+ ].compact.join(" ")
1284
+
1285
+ timeout_patterns = [
1286
+ /timeout/i,
1287
+ /timed out/i,
1288
+ /time.{0,20}out/i,
1289
+ /request.{0,20}timeout/i,
1290
+ /connection.{0,20}timeout/i,
1291
+ /read.{0,20}timeout/i,
1292
+ /write.{0,20}timeout/i,
1293
+ /operation.{0,20}timeout/i,
1294
+ /execution.{0,20}timeout/i,
1295
+ /deadline.{0,20}exceeded/i,
1296
+ /time.{0,20}limit.{0,20}exceeded/i,
1297
+ /time.{0,20}expired/i
1298
+ ]
1299
+
1300
+ found_indicators = []
1301
+ timeout_patterns.each do |pattern|
1302
+ if text_content.match?(pattern)
1303
+ found_indicators << pattern.source
1304
+ end
1305
+ end
1306
+
1307
+ found_indicators
1308
+ end
1309
+
1310
+ # Get timeout duration for operation type
1311
+ def get_timeout_duration(operation_type, configuration = nil)
1312
+ default_timeouts = {
1313
+ analyze: 300, # 5 minutes
1314
+ execute: 600, # 10 minutes
1315
+ provider_call: 120, # 2 minutes
1316
+ file_operation: 30, # 30 seconds
1317
+ network_request: 60, # 1 minute
1318
+ user_input: 300, # 5 minutes
1319
+ default: 120 # 2 minutes
1320
+ }
1321
+
1322
+ # Get timeout from configuration if available
1323
+ if configuration && configuration[:timeouts] && configuration[:timeouts][operation_type]
1324
+ return configuration[:timeouts][operation_type]
1325
+ end
1326
+
1327
+ # Return default timeout for operation type
1328
+ default_timeouts[operation_type] || default_timeouts[:default]
1329
+ end
1330
+
1331
+ # Get time remaining until timeout
1332
+ def time_until_timeout(start_time, timeout_duration)
1333
+ return 0 unless start_time.is_a?(Time) && timeout_duration
1334
+
1335
+ elapsed = Time.now - start_time
1336
+ remaining = timeout_duration - elapsed
1337
+ [remaining, 0].max
1338
+ end
1339
+
1340
+ # Get timeout status description
1341
+ def get_timeout_status_description(timeout_info)
1342
+ return "No timeout" unless timeout_info && timeout_info[:is_timeout]
1343
+
1344
+ case timeout_info[:timeout_type]
1345
+ when "explicit"
1346
+ "Operation timed out (explicit timeout detected)"
1347
+ when "duration"
1348
+ if timeout_info[:exceeded_by]
1349
+ "Operation timed out (exceeded by #{timeout_info[:exceeded_by].round(2)}s)"
1350
+ else
1351
+ "Operation timed out (duration exceeded)"
1352
+ end
1353
+ else
1354
+ "Operation timed out"
1355
+ end
1356
+ end
1357
+
1358
+ # Get timeout recovery suggestions
1359
+ def get_timeout_recovery_suggestions(timeout_info, operation_type = nil)
1360
+ suggestions = []
1361
+
1362
+ case timeout_info[:timeout_type]
1363
+ when "explicit"
1364
+ suggestions << "Check network connection"
1365
+ suggestions << "Verify service availability"
1366
+ suggestions << "Retry with longer timeout"
1367
+ when "duration"
1368
+ suggestions << "Increase timeout duration"
1369
+ suggestions << "Optimize operation performance"
1370
+ suggestions << "Break operation into smaller chunks"
1371
+ end
1372
+
1373
+ # Add operation-specific suggestions
1374
+ case operation_type
1375
+ when :analyze
1376
+ suggestions << "Reduce analysis scope"
1377
+ suggestions << "Use incremental analysis"
1378
+ when :execute
1379
+ suggestions << "Break execution into smaller steps"
1380
+ suggestions << "Optimize execution performance"
1381
+ when :provider_call
1382
+ suggestions << "Check provider status"
1383
+ suggestions << "Try different provider"
1384
+ when :file_operation
1385
+ suggestions << "Check file system performance"
1386
+ suggestions << "Verify file permissions"
1387
+ when :network_request
1388
+ suggestions << "Check network connectivity"
1389
+ suggestions << "Verify endpoint availability"
1390
+ end
1391
+
1392
+ suggestions.uniq
1393
+ end
1394
+
1395
+ # Validate user response based on expected input type
1396
+ def validate_user_response(response, expected_input_type)
1397
+ return false if response.nil? || response.strip.empty?
1398
+
1399
+ case expected_input_type
1400
+ when "email"
1401
+ response.match?(/\A[\w+\-.]+@[a-z\d-]+(\.[a-z\d-]+)*\.[a-z]+\z/i)
1402
+ when "url"
1403
+ response.match?(/\Ahttps?:\/\/.+/i)
1404
+ when "number"
1405
+ response.match?(/^\d+$/)
1406
+ when "boolean"
1407
+ response.match?(/^(yes|no|true|false|y|n|1|0)$/i)
1408
+ when "file"
1409
+ response.match?(/^@/) || File.exist?(response)
1410
+ when "path"
1411
+ File.exist?(response) || Dir.exist?(response)
1412
+ else
1413
+ # For text input, just check it's not empty
1414
+ !response.strip.empty?
1415
+ end
1416
+ end
1417
+
1418
+ # Get user feedback patterns for a specific type
1419
+ def get_user_feedback_patterns(feedback_type = nil)
1420
+ if feedback_type && @user_feedback_patterns[feedback_type.to_sym]
1421
+ @user_feedback_patterns[feedback_type.to_sym]
1422
+ else
1423
+ @user_feedback_patterns.values.flatten
1424
+ end
1425
+ end
1426
+
1427
+ # Get completion confidence level
1428
+ def get_completion_confidence(completion_info)
1429
+ return 0.0 unless completion_info && completion_info[:confidence]
1430
+
1431
+ completion_info[:confidence]
1432
+ end
1433
+
1434
+ # Check if completion is high confidence
1435
+ def high_confidence_completion?(completion_info)
1436
+ get_completion_confidence(completion_info) >= 0.8
1437
+ end
1438
+
1439
+ # Check if completion is medium confidence
1440
+ def medium_confidence_completion?(completion_info)
1441
+ confidence = get_completion_confidence(completion_info)
1442
+ confidence >= 0.5 && confidence < 0.8
1443
+ end
1444
+
1445
+ # Check if completion is low confidence
1446
+ def low_confidence_completion?(completion_info)
1447
+ confidence = get_completion_confidence(completion_info)
1448
+ confidence > 0.0 && confidence < 0.5
1449
+ end
1450
+
1451
+ # Get next actions from completion info
1452
+ def get_next_actions(completion_info)
1453
+ return [] unless completion_info && completion_info[:next_actions]
1454
+
1455
+ completion_info[:next_actions]
1456
+ end
1457
+
1458
+ # Check if work is in progress
1459
+ def is_work_in_progress?(completion_info)
1460
+ return false unless completion_info
1461
+
1462
+ !completion_info[:is_complete] &&
1463
+ completion_info[:progress_status] != "waiting_for_input" &&
1464
+ completion_info[:progress_status] != "has_errors"
1465
+ end
1466
+
1467
+ # Check if work is waiting for input
1468
+ def is_waiting_for_input?(completion_info)
1469
+ return false unless completion_info
1470
+
1471
+ if completion_info[:progress_status] == "waiting_for_input"
1472
+ return true
1473
+ end
1474
+
1475
+ if completion_info[:next_actions]&.include?("collect_user_input")
1476
+ return true
1477
+ end
1478
+
1479
+ false
1480
+ end
1481
+
1482
+ # Check if work has errors
1483
+ def has_errors?(completion_info)
1484
+ return false unless completion_info
1485
+
1486
+ if completion_info[:progress_status] == "has_errors"
1487
+ return true
1488
+ end
1489
+
1490
+ if completion_info[:next_actions]&.include?("handle_errors")
1491
+ return true
1492
+ end
1493
+
1494
+ false
1495
+ end
1496
+
1497
+ # Get progress status description
1498
+ def get_progress_status_description(completion_info)
1499
+ return "unknown" unless completion_info
1500
+
1501
+ case completion_info[:progress_status]
1502
+ when "all_steps_completed"
1503
+ "All steps completed successfully"
1504
+ when "near_completion"
1505
+ "Near completion (80%+ done)"
1506
+ when "half_complete"
1507
+ "Half complete (50%+ done)"
1508
+ when "early_stage"
1509
+ "Early stage (20%+ done)"
1510
+ when "just_started"
1511
+ "Just started (0-20% done)"
1512
+ when "has_next_actions"
1513
+ "Has next actions to perform"
1514
+ when "waiting_for_input"
1515
+ "Waiting for user input"
1516
+ when "has_errors"
1517
+ "Has errors that need attention"
1518
+ when "in_progress"
1519
+ "Work in progress"
1520
+ else
1521
+ "Status unknown"
1522
+ end
1523
+ end
1524
+ end
1525
+ end
1526
+ end