aidp 0.23.0 → 0.24.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 (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/aidp/cli.rb +3 -0
  3. data/lib/aidp/execute/work_loop_runner.rb +252 -45
  4. data/lib/aidp/execute/work_loop_unit_scheduler.rb +27 -2
  5. data/lib/aidp/harness/condition_detector.rb +42 -8
  6. data/lib/aidp/harness/config_manager.rb +7 -0
  7. data/lib/aidp/harness/config_schema.rb +25 -0
  8. data/lib/aidp/harness/configuration.rb +69 -6
  9. data/lib/aidp/harness/error_handler.rb +117 -44
  10. data/lib/aidp/harness/provider_manager.rb +64 -0
  11. data/lib/aidp/harness/provider_metrics.rb +138 -0
  12. data/lib/aidp/harness/runner.rb +90 -29
  13. data/lib/aidp/harness/simple_user_interface.rb +4 -0
  14. data/lib/aidp/harness/state/ui_state.rb +0 -10
  15. data/lib/aidp/harness/state_manager.rb +1 -15
  16. data/lib/aidp/harness/test_runner.rb +39 -2
  17. data/lib/aidp/logger.rb +34 -4
  18. data/lib/aidp/providers/adapter.rb +241 -0
  19. data/lib/aidp/providers/anthropic.rb +75 -7
  20. data/lib/aidp/providers/base.rb +29 -1
  21. data/lib/aidp/providers/capability_registry.rb +205 -0
  22. data/lib/aidp/providers/codex.rb +14 -0
  23. data/lib/aidp/providers/error_taxonomy.rb +195 -0
  24. data/lib/aidp/providers/gemini.rb +3 -2
  25. data/lib/aidp/setup/provider_registry.rb +107 -0
  26. data/lib/aidp/setup/wizard.rb +115 -31
  27. data/lib/aidp/version.rb +1 -1
  28. data/lib/aidp/watch/build_processor.rb +263 -23
  29. data/lib/aidp/watch/repository_client.rb +4 -4
  30. data/lib/aidp/watch/runner.rb +37 -5
  31. data/lib/aidp/workflows/guided_agent.rb +53 -0
  32. data/lib/aidp/worktree.rb +67 -10
  33. data/templates/work_loop/decide_whats_next.md +21 -0
  34. data/templates/work_loop/diagnose_failures.md +21 -0
  35. metadata +10 -3
  36. /data/{bin → exe}/aidp +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1dedfe324bb5255c43e42a4ac3f716bb99f9db453ae436a1e2aa57b68754a28f
4
- data.tar.gz: 24b5a225e474c14dabad326455e4d5286e175dc6a929a1bffab4ffb29c1c063c
3
+ metadata.gz: e2ec07f1c36212b7ace8e467e098fc3bcd917dc0738e27125bbddcef9bbcc30e
4
+ data.tar.gz: edbc40f6c50b581729d185186eae3510e8c23885f12655c46868117af361f31a
5
5
  SHA512:
6
- metadata.gz: b842c15a0169d3b3c7361cbdf633b5bb66d0189f40fb16e12099436b3db7eda3e983fb624326b2160dd018d63090feae2b92042f46758990c31cef9172252d36
7
- data.tar.gz: 1c614a680d74d56fff4561a62eee471ab1d8f7b8b2cc801786da25a63c12b142948f8e418477a4b479b3b119abe2275b954d9202662f7acbb36cdd82401a68eb
6
+ metadata.gz: f6472e33b88cf45f0c0abc5131dd608ba20d73480074552275d6d886222357c435023845fa8762b7f527268ba34fc2d3841d504309c8c068adbc38d6ac41f12b
7
+ data.tar.gz: 4283ba15ee02985603adbdba0be476adec35efc9fa6730a01beef1eac50d068647f3560aa03d75fcdb24ffc102e742bc1c3d9ff53fe95662a20241c711b54ae7
data/lib/aidp/cli.rb CHANGED
@@ -1264,6 +1264,9 @@ module Aidp
1264
1264
  end
1265
1265
  end
1266
1266
 
1267
+ # Initialize logger for watch mode
1268
+ setup_logging(Dir.pwd)
1269
+
1267
1270
  # Load watch safety configuration
1268
1271
  config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
1269
1272
  config = config_manager.config || {}
@@ -77,7 +77,7 @@ module Aidp
77
77
  display_guard_policy_status
78
78
  display_pending_tasks
79
79
 
80
- @unit_scheduler = WorkLoopUnitScheduler.new(units_config)
80
+ @unit_scheduler = WorkLoopUnitScheduler.new(units_config, project_dir: @project_dir)
81
81
  base_context = context.dup
82
82
 
83
83
  loop do
@@ -97,6 +97,8 @@ module Aidp
97
97
 
98
98
  agentic_payload = if unit.name == :decide_whats_next
99
99
  run_decider_agentic_unit(enriched_context)
100
+ elsif unit.name == :diagnose_failures
101
+ run_diagnose_agentic_unit(enriched_context)
100
102
  else
101
103
  run_primary_agentic_unit(step_spec, enriched_context)
102
104
  end
@@ -148,7 +150,27 @@ module Aidp
148
150
  transition_to(:ready) unless @current_state == :ready
149
151
 
150
152
  transition_to(:apply_patch)
151
- agent_result = apply_patch
153
+
154
+ # Wrap agent call in exception handling for true fix-forward
155
+ begin
156
+ agent_result = apply_patch
157
+ rescue => e
158
+ # Convert exception to error result for fix-forward handling
159
+ Aidp.logger.error("work_loop", "Exception during agent call",
160
+ step: @step_name,
161
+ iteration: @iteration_count,
162
+ error: e.message,
163
+ error_class: e.class.name,
164
+ backtrace: e.backtrace&.first(5))
165
+
166
+ display_message(" ⚠️ Exception during agent call: #{e.class.name}: #{e.message}", type: :error)
167
+
168
+ # Append exception to PROMPT.md so agent can see and fix it
169
+ append_exception_to_prompt(e)
170
+
171
+ # Continue to next iteration with fix-forward pattern
172
+ next
173
+ end
152
174
 
153
175
  # Process agent output for task filing signals
154
176
  process_task_filing(agent_result)
@@ -223,6 +245,34 @@ module Aidp
223
245
  )
224
246
  end
225
247
 
248
+ def run_diagnose_agentic_unit(context)
249
+ Aidp.logger.info("work_loop", "Running diagnose_failures agentic unit", step: @step_name)
250
+
251
+ prompt = build_diagnose_prompt(context)
252
+
253
+ agent_result = @provider_manager.execute_with_provider(
254
+ @provider_manager.current_provider,
255
+ prompt,
256
+ {
257
+ step_name: @step_name,
258
+ iteration: @iteration_count,
259
+ project_dir: @project_dir,
260
+ mode: :diagnose_failures
261
+ }
262
+ )
263
+
264
+ requested = AgentSignalParser.extract_next_unit(agent_result[:output])
265
+
266
+ build_agentic_payload(
267
+ agent_result: agent_result,
268
+ response: agent_result,
269
+ summary: agent_result[:output],
270
+ completed: false,
271
+ terminate: false,
272
+ requested_next: requested
273
+ )
274
+ end
275
+
226
276
  def units_config
227
277
  if @config.respond_to?(:work_loop_units_config)
228
278
  @config.work_loop_units_config
@@ -243,41 +293,94 @@ module Aidp
243
293
  end
244
294
 
245
295
  def build_decider_prompt(context)
246
- outputs = Array(context[:deterministic_outputs])
247
- summary = context[:previous_agent_summary]
248
-
249
- sections = []
250
- sections << "# Decide Next Work Loop Unit"
251
- sections << ""
252
- sections << "You are operating in the Aidp work loop. Determine what should happen next."
253
- sections << ""
254
- sections << "## Recent Deterministic Outputs"
255
-
256
- if outputs.empty?
257
- sections << "- None recorded yet."
258
- else
259
- outputs.each do |entry|
260
- sections << "- #{entry[:name]} (status: #{entry[:status]}, finished_at: #{entry[:finished_at]})"
261
- sections << " Output: #{entry[:output_path] || "n/a"}"
262
- end
263
- end
296
+ template = load_work_loop_template("decide_whats_next.md", default_decider_template)
297
+ replacements = {
298
+ "{{DETERMINISTIC_OUTPUTS}}" => format_deterministic_outputs(context[:deterministic_outputs]),
299
+ "{{PREVIOUS_AGENT_SUMMARY}}" => format_previous_agent_summary(context[:previous_agent_summary])
300
+ }
301
+ replacements.reduce(template) { |body, (token, value)| body.gsub(token, value) }
302
+ end
264
303
 
265
- if summary
266
- sections << ""
267
- sections << "## Previous Agent Summary"
268
- sections << summary
269
- end
304
+ def build_diagnose_prompt(context)
305
+ template = load_work_loop_template("diagnose_failures.md", default_diagnose_template)
306
+ replacements = {
307
+ "{{DETERMINISTIC_OUTPUTS}}" => format_deterministic_outputs(context[:deterministic_outputs]),
308
+ "{{PREVIOUS_AGENT_SUMMARY}}" => format_previous_agent_summary(context[:previous_agent_summary])
309
+ }
310
+ replacements.reduce(template) { |body, (token, value)| body.gsub(token, value) }
311
+ end
312
+
313
+ def load_work_loop_template(relative_path, fallback)
314
+ template_path = File.join(@project_dir, "templates", "work_loop", relative_path)
315
+ return File.read(template_path) if File.exist?(template_path)
270
316
 
271
- sections << ""
272
- sections << "## Instructions"
273
- sections << "- Decide whether to run another deterministic unit or resume agentic editing."
274
- sections << "- Announce your decision with `NEXT_UNIT: <unit_name>`."
275
- sections << "- Valid values: names defined in configuration, `agentic`, or `wait_for_github`."
276
- sections << "- Provide a concise rationale below."
277
- sections << ""
278
- sections << "## Rationale"
317
+ fallback
318
+ rescue => e
319
+ Aidp.logger.warn("work_loop", "Unable to load #{relative_path}", error: e.message)
320
+ fallback
321
+ end
322
+
323
+ def default_decider_template
324
+ <<~TEMPLATE
325
+ # Decide Next Work Loop Unit
326
+
327
+ ## Deterministic Outputs
328
+
329
+ {{DETERMINISTIC_OUTPUTS}}
330
+
331
+ ## Previous Agent Summary
332
+
333
+ {{PREVIOUS_AGENT_SUMMARY}}
334
+
335
+ ## Guidance
336
+ - Decide whether to run another deterministic unit or resume agentic editing.
337
+ - Announce your decision with `NEXT_UNIT: <unit_name>`.
338
+ - Valid values: names defined in configuration, `agentic`, or `wait_for_github`.
339
+ - Provide a concise rationale below.
279
340
 
280
- sections.join("\n")
341
+ ## Rationale
342
+ TEMPLATE
343
+ end
344
+
345
+ def default_diagnose_template
346
+ <<~TEMPLATE
347
+ # Diagnose Failures
348
+
349
+ ## Recent Deterministic Outputs
350
+
351
+ {{DETERMINISTIC_OUTPUTS}}
352
+
353
+ ## Previous Agent Summary
354
+
355
+ {{PREVIOUS_AGENT_SUMMARY}}
356
+
357
+ ## Instructions
358
+ - Identify the root cause of the failures above.
359
+ - Recommend the next concrete action (another deterministic unit, agentic editing, or waiting).
360
+ - Emit `NEXT_UNIT: <unit_name>` on its own line.
361
+
362
+ ## Analysis
363
+ TEMPLATE
364
+ end
365
+
366
+ def format_deterministic_outputs(entries)
367
+ data = Array(entries)
368
+ return "- None recorded yet." if data.empty?
369
+
370
+ data.map do |entry|
371
+ name = entry[:name] || "unknown_unit"
372
+ status = entry[:status] || "unknown"
373
+ finished_at = entry[:finished_at]&.to_s || "unknown"
374
+ output = entry[:output_path] || "n/a"
375
+ "- #{name} (status: #{status}, finished_at: #{finished_at})\n Output: #{output}"
376
+ end.join("\n")
377
+ end
378
+
379
+ def format_previous_agent_summary(summary)
380
+ content = summary.to_s.strip
381
+ return "_No previous agent summary._" if content.empty?
382
+
383
+ content
281
384
  end
282
385
 
283
386
  # Transition to a new state in the fix-forward state machine
@@ -544,16 +647,50 @@ module Aidp
544
647
  prompt_content = @prompt_manager.read
545
648
  return {status: "error", message: "PROMPT.md not found"} unless prompt_content
546
649
 
547
- # Send to provider via provider_manager
548
- @provider_manager.execute_with_provider(
549
- @provider_manager.current_provider,
550
- prompt_content,
551
- {
552
- step_name: @step_name,
553
- iteration: @iteration_count,
554
- project_dir: @project_dir
555
- }
556
- )
650
+ # Prepend work loop instructions to every iteration
651
+ full_prompt = build_work_loop_header(@step_name, @iteration_count) + "\n\n" + prompt_content
652
+
653
+ # CRITICAL: Change to project directory before calling provider
654
+ # This ensures Claude CLI runs in the correct directory and can create files
655
+ Dir.chdir(@project_dir) do
656
+ # Send to provider via provider_manager
657
+ @provider_manager.execute_with_provider(
658
+ @provider_manager.current_provider,
659
+ full_prompt,
660
+ {
661
+ step_name: @step_name,
662
+ iteration: @iteration_count,
663
+ project_dir: @project_dir
664
+ }
665
+ )
666
+ end
667
+ end
668
+
669
+ def build_work_loop_header(step_name, iteration)
670
+ parts = []
671
+ parts << "# Work Loop: #{step_name} (Iteration #{iteration})"
672
+ parts << ""
673
+ parts << "## Instructions"
674
+ parts << "You are working in a work loop. Your responsibilities:"
675
+ parts << "1. Read the task description below to understand what needs to be done"
676
+ parts << "2. **Write/edit code files** to implement the required changes"
677
+ parts << "3. Run tests to verify your changes work correctly"
678
+ parts << "4. Update the task list in PROMPT.md as you complete items"
679
+ parts << "5. When ALL tasks are complete and tests pass, mark the step COMPLETE"
680
+ parts << ""
681
+ parts << "## Important Notes"
682
+ parts << "- You have full file system access - create and edit files as needed"
683
+ parts << "- The working directory is: #{@project_dir}"
684
+ parts << "- After you finish, tests and linters will run automatically"
685
+ parts << "- If tests/linters fail, you'll see the errors in the next iteration and can fix them"
686
+ parts << ""
687
+ parts << "## Completion Criteria"
688
+ parts << "Mark this step COMPLETE by adding this line to PROMPT.md:"
689
+ parts << "```"
690
+ parts << "STATUS: COMPLETE"
691
+ parts << "```"
692
+ parts << ""
693
+ parts.join("\n")
557
694
  end
558
695
 
559
696
  def prompt_marked_complete?
@@ -598,6 +735,9 @@ module Aidp
598
735
  failures << ""
599
736
  end
600
737
 
738
+ strategy = build_failure_strategy(test_results, lint_results)
739
+ failures.concat(strategy) unless strategy.empty?
740
+
601
741
  failures << "**Fix-forward instructions**: Do not rollback changes. Build on what exists and fix the failures above."
602
742
  failures << ""
603
743
 
@@ -608,7 +748,48 @@ module Aidp
608
748
  updated_prompt = current_prompt + "\n\n---\n\n" + failures.join("\n")
609
749
  @prompt_manager.write(updated_prompt, step_name: @step_name)
610
750
 
611
- display_message(" [NEXT_PATCH] Added failure reports and diagnostic to PROMPT.md", type: :warning)
751
+ display_message(" [NEXT_PATCH] Added failure reports, strategy, and diagnostic to PROMPT.md", type: :warning)
752
+ end
753
+
754
+ # Append exception details to PROMPT.md for fix-forward handling
755
+ # This allows the agent to see and fix errors that occur during execution
756
+ def append_exception_to_prompt(exception)
757
+ error_report = []
758
+
759
+ error_report << "## Fix-Forward Exception in Iteration #{@iteration_count}"
760
+ error_report << ""
761
+ error_report << "**CRITICAL**: An exception occurred during this iteration. Please analyze and fix the underlying issue."
762
+ error_report << ""
763
+ error_report << "### Exception Details"
764
+ error_report << "- **Type**: `#{exception.class.name}`"
765
+ error_report << "- **Message**: #{exception.message}"
766
+ error_report << ""
767
+
768
+ if exception.backtrace && !exception.backtrace.empty?
769
+ error_report << "### Stack Trace (First 10 lines)"
770
+ error_report << "```"
771
+ exception.backtrace.first(10).each do |line|
772
+ error_report << line
773
+ end
774
+ error_report << "```"
775
+ error_report << ""
776
+ end
777
+
778
+ error_report << "### Required Action"
779
+ error_report << "1. Analyze the exception type and message"
780
+ error_report << "2. Review the stack trace to identify the source"
781
+ error_report << "3. Fix the underlying code issue"
782
+ error_report << "4. Ensure the fix doesn't break existing functionality"
783
+ error_report << ""
784
+ error_report << "**Fix-forward instructions**: Do not rollback changes. Identify the root cause and fix it in the next iteration."
785
+ error_report << ""
786
+
787
+ # Append to PROMPT.md
788
+ current_prompt = @prompt_manager.read
789
+ updated_prompt = current_prompt + "\n\n---\n\n" + error_report.join("\n")
790
+ @prompt_manager.write(updated_prompt, step_name: @step_name)
791
+
792
+ display_message(" [EXCEPTION] Added exception details to PROMPT.md for fix-forward", type: :error)
612
793
  end
613
794
 
614
795
  # Check if we should reinject the style guide at this iteration
@@ -651,6 +832,32 @@ module Aidp
651
832
  reminder.join("\n")
652
833
  end
653
834
 
835
+ def build_failure_strategy(test_results, lint_results)
836
+ return [] if test_results[:success] && lint_results[:success]
837
+
838
+ lines = ["### Recovery Strategy", ""]
839
+
840
+ unless test_results[:success]
841
+ commands = format_command_list(test_results[:failures])
842
+ lines << "- Re-run #{commands} locally to reproduce the failing specs listed above."
843
+ lines << "- Triage the exact failures before moving on to new work."
844
+ end
845
+
846
+ unless lint_results[:success]
847
+ commands = format_command_list(lint_results[:failures])
848
+ lines << "- Execute #{commands} and fix each reported offense."
849
+ end
850
+
851
+ lines << ""
852
+ lines
853
+ end
854
+
855
+ def format_command_list(failures)
856
+ commands = Array(failures).map { |failure| failure[:command] }.compact
857
+ commands = ["the configured command"] if commands.empty?
858
+ commands.map { |cmd| "`#{cmd}`" }.join(" or ")
859
+ end
860
+
654
861
  # Load current step's template content
655
862
  def load_current_template
656
863
  return nil unless @step_name
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "fileutils"
3
4
  require_relative "deterministic_unit"
4
5
  require_relative "../logger"
5
6
 
@@ -18,18 +19,21 @@ module Aidp
18
19
 
19
20
  attr_reader :last_agentic_summary
20
21
 
21
- def initialize(units_config, clock: Time)
22
+ def initialize(units_config, project_dir:, clock: Time)
22
23
  @clock = clock
24
+ @project_dir = project_dir
23
25
  @deterministic_definitions = build_deterministic_definitions(units_config[:deterministic])
24
26
  @defaults = default_options.merge(units_config[:defaults] || {})
25
27
  @pending_units = []
28
+ @initial_unit_requests = read_initial_unit_requests
26
29
  @deterministic_history = []
27
30
  @deterministic_state = Hash.new { |h, key| h[key] = default_deterministic_state }
28
31
  @agentic_runs = []
29
32
  @last_agentic_summary = nil
30
33
  @consecutive_deciders = 0
31
34
  @completed = false
32
- @started = false
35
+ apply_initial_requests
36
+ @started = @pending_units.any?
33
37
  end
34
38
 
35
39
  def next_unit
@@ -173,6 +177,27 @@ module Aidp
173
177
  end
174
178
  end
175
179
 
180
+ def read_initial_unit_requests
181
+ return [] unless @project_dir
182
+
183
+ path = File.join(@project_dir, ".aidp", "work_loop", "initial_units.txt")
184
+ return [] unless File.exist?(path)
185
+
186
+ requests = File.readlines(path, chomp: true).map(&:strip).reject(&:empty?)
187
+ File.delete(path)
188
+ requests
189
+ rescue => e
190
+ Aidp.logger.warn("work_loop", "Failed to read initial work loop requests", error: e.message)
191
+ []
192
+ end
193
+
194
+ def apply_initial_requests
195
+ Array(@initial_unit_requests).each do |request|
196
+ queue_requested_unit(request.to_sym)
197
+ end
198
+ @initial_unit_requests = []
199
+ end
200
+
176
201
  def default_deterministic_state
177
202
  {last_run_at: nil, current_backoff: nil}
178
203
  end
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "time"
4
+
3
5
  module Aidp
4
6
  module Harness
5
7
  # Detects run conditions (rate limits, user feedback, completion, errors)
@@ -15,14 +17,16 @@ module Aidp
15
17
  /429/i,
16
18
  /rate.{0,20}exceeded/i,
17
19
  /throttled/i,
18
- /limit.{0,20}exceeded/i
20
+ /limit.{0,20}exceeded/i,
21
+ /session limit/i
19
22
  ],
20
23
  # Anthropic/Claude specific
21
24
  anthropic: [
22
25
  /rate limit exceeded/i,
23
26
  /too many requests/i,
24
27
  /quota.{0,20}exceeded/i,
25
- /anthropic.{0,20}rate.{0,20}limit/i
28
+ /anthropic.{0,20}rate.{0,20}limit/i,
29
+ /session limit reached/i
26
30
  ],
27
31
  # OpenAI specific
28
32
  openai: [
@@ -143,10 +147,10 @@ module Aidp
143
147
 
144
148
  # Rate limit reset time patterns
145
149
  @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,
150
+ /reset(?:s)?\s+in\s+(\d+)\s+seconds/i,
151
+ /retry\s+after\s+(\d+)\s+seconds/i,
148
152
  /wait[^\d]*(\d+)[^\d]*seconds/i,
149
- /(\d+).{0,20}seconds.{0,20}until.{0,20}reset/i,
153
+ /(\d+)\s+seconds\s+until\s+reset/i,
150
154
  /reset.{0,20}at.{0,20}(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i,
151
155
  /retry.{0,20}after.{0,20}(\d{4}-\d{2}-\d{2}\s+\d{2}:\d{2}:\d{2})/i
152
156
  ]
@@ -168,7 +172,18 @@ module Aidp
168
172
  result[:output],
169
173
  result[:response],
170
174
  result[:body]
171
- ].compact.join(" ")
175
+ ].compact.join(" ").strip
176
+
177
+ return nil if text_content.empty?
178
+
179
+ {
180
+ provider: provider,
181
+ detected_at: Time.now,
182
+ reset_time: extract_reset_time(text_content),
183
+ retry_after: extract_retry_after(text_content),
184
+ limit_type: detect_limit_type(text_content, provider),
185
+ message: text_content
186
+ }
172
187
 
173
188
  return false if text_content.empty?
174
189
 
@@ -205,17 +220,34 @@ module Aidp
205
220
 
206
221
  # Extract reset time from rate limit message
207
222
  def extract_reset_time(text_content)
223
+ # Handle expressions like "resets 4am" or "reset at 4:30pm"
224
+ time_of_day_match = text_content.match(/reset(?:s)?(?:\s+at)?\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)/i)
225
+ if time_of_day_match
226
+ hour = time_of_day_match[1].to_i
227
+ minute = time_of_day_match[2] ? time_of_day_match[2].to_i : 0
228
+ meridiem = time_of_day_match[3].downcase
229
+
230
+ hour %= 12
231
+ hour += 12 if meridiem == "pm"
232
+
233
+ now = Time.now
234
+ reset_time = Time.new(now.year, now.month, now.day, hour, minute, 0, now.utc_offset)
235
+ reset_time += 86_400 if reset_time <= now
236
+ return reset_time
237
+ end
238
+
208
239
  @reset_time_patterns.each do |pattern|
209
240
  match = text_content.match(pattern)
210
241
  next unless match
211
242
 
212
243
  if match[1].match?(/^\d+$/)
213
244
  # Seconds from now
214
- Time.now + match[1].to_i
245
+ return Time.now + match[1].to_i
215
246
  else
216
247
  # Specific timestamp
217
248
  begin
218
- Time.parse(match[1])
249
+ parsed_time = Time.parse(match[1])
250
+ return parsed_time if parsed_time
219
251
  rescue ArgumentError
220
252
  nil
221
253
  end
@@ -246,6 +278,8 @@ module Aidp
246
278
 
247
279
  # Detect the type of rate limit
248
280
  def detect_limit_type(text_content, provider)
281
+ return "session_limit" if text_content.match?(/session limit/i)
282
+
249
283
  case provider&.to_s&.downcase
250
284
  when "anthropic", "claude"
251
285
  if text_content.match?(/requests per minute/i)
@@ -10,6 +10,8 @@ module Aidp
10
10
  class ConfigManager
11
11
  include ProviderTypeChecker
12
12
 
13
+ attr_reader :project_dir
14
+
13
15
  def initialize(project_dir = Dir.pwd)
14
16
  @project_dir = project_dir
15
17
  @loader = ConfigLoader.new(project_dir)
@@ -101,6 +103,11 @@ module Aidp
101
103
  }
102
104
  end
103
105
 
106
+ # Get max retries (alias for backward compatibility with ErrorHandler)
107
+ def max_retries(options = {})
108
+ retry_config(options)[:max_attempts]
109
+ end
110
+
104
111
  # Get circuit breaker configuration
105
112
  def circuit_breaker_config(options = {})
106
113
  harness_config = harness_config(options)
@@ -886,6 +886,31 @@ module Aidp
886
886
  type: :string
887
887
  }
888
888
  },
889
+ dangerous_mode: {
890
+ type: :hash,
891
+ required: false,
892
+ default: {},
893
+ properties: {
894
+ enabled: {
895
+ type: :boolean,
896
+ required: false,
897
+ default: false
898
+ },
899
+ flags: {
900
+ type: :array,
901
+ required: false,
902
+ default: [],
903
+ items: {
904
+ type: :string
905
+ }
906
+ },
907
+ auto_enable_in_devcontainer: {
908
+ type: :boolean,
909
+ required: false,
910
+ default: true
911
+ }
912
+ }
913
+ },
889
914
  models: {
890
915
  type: :array,
891
916
  required: false,