aidp 0.25.0 → 0.26.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 348238e998f8d75ef9f23fcb5a8f4517b6fde0dd9612a4520583b03ab692cc23
4
- data.tar.gz: ed8db8dbd0459211e65b76784b6074b65f2689cdb76f7b7c74bcb029d4c9f74d
3
+ metadata.gz: 0b4770edc850e936b3201ec532cdd369f7fd881ff79dd10e24407c6453b8cb22
4
+ data.tar.gz: d7d2640aa63d2c25afbf795bbf77d48741bdd99d6a074765fa5595f3982b96e6
5
5
  SHA512:
6
- metadata.gz: ac5628709db6248036d75e5873252089e497b96bac785a610bcc9b153b350714d4a5e441610501b3897cf6994d9081fa36e990da67678b86c167d5e58b048f64
7
- data.tar.gz: dd660df245ffd1bb28048e4b76b231efce515e880fc8cfa088af6a25b1fcb42b98562d592880d256ade4474a3db6879567f3fb2670e750da0ee94ea5347b2df7
6
+ metadata.gz: 1841e0b35478d65d382d13dd138955c30655002b91c2a83d96ec2c83925eec4f954892aa710ded867b50554dbc9653bddfad5c5aa915890e674bb49afed347bc
7
+ data.tar.gz: 1fc1443566c4e01e0c6ff25f1ae62c890ddcd5373257265ee7119ea8db359c20282cf646d61b955204e4dc538ceb9c6313725cb4fed21cda231b97321cc2c030
data/README.md CHANGED
@@ -218,7 +218,9 @@ aidp watch owner/repo --once
218
218
 
219
219
  **Label Workflow:**
220
220
 
221
- AIDP uses a smart label-based workflow to manage the lifecycle of automated issue resolution:
221
+ AIDP uses a smart label-based workflow to manage both issues and pull requests:
222
+
223
+ #### Issue Workflow (Plan & Build)
222
224
 
223
225
  1. **Planning Phase** (`aidp-plan` label):
224
226
  - Add this label to an issue to trigger plan generation
@@ -247,6 +249,32 @@ AIDP uses a smart label-based workflow to manage the lifecycle of automated issu
247
249
  - Posts completion comment with summary
248
250
  - Automatically removes the `aidp-build` label
249
251
 
252
+ #### Pull Request Workflow (Review, CI Fix, Change Requests)
253
+
254
+ <!-- markdownlint-disable-next-line MD029 -->
255
+ 1. **Code Review** (`aidp-review` label):
256
+ - Add this label to any PR to trigger automated code review
257
+ - AIDP analyzes code from three expert perspectives (Senior Developer, Security Specialist, Performance Analyst)
258
+ - Posts a comprehensive review comment with severity-categorized findings (High Priority, Major, Minor, Nit)
259
+ - Automatically removes the label after posting review
260
+ - No commits are made - review only
261
+
262
+ 2. **CI Fix** (`aidp-fix-ci` label):
263
+ - Add this label to a PR with failing CI checks
264
+ - AIDP analyzes CI failure logs and identifies root causes
265
+ - Automatically fixes issues like linting errors, simple test failures, and dependency problems
266
+ - Commits and pushes fixes to the PR branch
267
+ - Posts a summary of what was fixed
268
+ - Automatically removes the label after completion
269
+
270
+ 3. **Change Requests** (`aidp-request-changes` label):
271
+ - Comment on your own PR describing desired changes, then add this label
272
+ - AIDP implements the requested changes on the PR branch
273
+ - Runs tests/linters and commits changes
274
+ - **If clarification needed**: Replaces label with `aidp-needs-input` and posts questions
275
+ - User responds to questions and re-applies the label to continue
276
+ - Automatically removes the label after completion
277
+
250
278
  **Customizable Labels:**
251
279
 
252
280
  All label names are configurable to match your repository's existing label scheme. Configure via the interactive wizard or manually in `aidp.yml`:
@@ -255,10 +283,16 @@ All label names are configurable to match your repository's existing label schem
255
283
  # .aidp/aidp.yml
256
284
  watch:
257
285
  labels:
258
- plan_trigger: aidp-plan # Label to trigger plan generation
259
- needs_input: aidp-needs-input # Label when plan needs user input
260
- ready_to_build: aidp-ready # Label when plan is ready to build
261
- build_trigger: aidp-build # Label to trigger implementation
286
+ # Issue-based automation
287
+ plan_trigger: aidp-plan # Trigger plan generation
288
+ needs_input: aidp-needs-input # Needs user input/clarification
289
+ ready_to_build: aidp-ready # Plan ready for implementation
290
+ build_trigger: aidp-build # Trigger implementation
291
+
292
+ # PR-based automation
293
+ review_trigger: aidp-review # Trigger code review
294
+ ci_fix_trigger: aidp-fix-ci # Trigger CI auto-fix
295
+ change_request_trigger: aidp-request-changes # Trigger PR change implementation
262
296
  ```
263
297
 
264
298
  Run `aidp config --interactive` and enable watch mode to configure labels interactively.
@@ -294,7 +328,12 @@ AIDP can automatically request clarification when it needs more information duri
294
328
 
295
329
  This ensures AIDP never gets stuck - if it needs more information, it will ask for it rather than making incorrect assumptions or failing silently.
296
330
 
297
- See [Watch Mode Guide](docs/FULLY_AUTOMATIC_MODE.md) and [Watch Mode Safety](docs/WATCH_MODE_SAFETY.md) for complete documentation.
331
+ **Additional Documentation:**
332
+
333
+ - [Watch Mode Guide](docs/FULLY_AUTOMATIC_MODE.md) - Complete guide to watch mode setup and operation
334
+ - [Watch Mode Safety](docs/WATCH_MODE_SAFETY.md) - Security features and best practices
335
+ - [PR Automation Guide](docs/PR_AUTOMATION.md) - Detailed guide for code review, CI fixes, and PR changes
336
+ - [PR Change Requests](docs/PR_CHANGE_REQUESTS.md) - Comprehensive documentation for automated PR modifications
298
337
 
299
338
  ## Command Reference
300
339
 
@@ -99,6 +99,13 @@ module Aidp
99
99
  private
100
100
 
101
101
  def setup_logger(log_file, verbose)
102
+ # Suppress logger output in test/CI environments
103
+ if suppress_error_logs?
104
+ logger = ::Logger.new(IO::NULL)
105
+ logger.level = ::Logger::FATAL
106
+ return logger
107
+ end
108
+
102
109
  output_stream = log_file || @output || $stdout
103
110
  logger = ::Logger.new(output_stream)
104
111
  logger.level = verbose ? ::Logger::DEBUG : ::Logger::INFO
@@ -108,6 +115,10 @@ module Aidp
108
115
  logger
109
116
  end
110
117
 
118
+ def suppress_error_logs?
119
+ ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
120
+ end
121
+
111
122
  def setup_recovery_strategies
112
123
  strategies = {
113
124
  Errno::ENOENT => :skip_step_with_warning,
@@ -59,6 +59,12 @@ module Aidp
59
59
  @state_history = []
60
60
  @deterministic_runner = DeterministicUnits::Runner.new(project_dir)
61
61
  @unit_scheduler = nil
62
+
63
+ # Initialize thinking depth manager for intelligent model selection
64
+ require_relative "../harness/thinking_depth_manager"
65
+ @thinking_depth_manager = Aidp::Harness::ThinkingDepthManager.new(config)
66
+ @consecutive_failures = 0
67
+ @last_tier = nil
62
68
  end
63
69
 
64
70
  # Execute a step using fix-forward work loop pattern
@@ -176,19 +182,45 @@ module Aidp
176
182
  process_task_filing(agent_result)
177
183
 
178
184
  transition_to(:test)
185
+ # Run all configured checks
179
186
  test_results = @test_runner.run_tests
180
187
  lint_results = @test_runner.run_linters
188
+ build_results = @test_runner.run_builds
189
+ doc_results = @test_runner.run_documentation
190
+
191
+ # Run formatters only if agent marked work complete (per issue #234)
192
+ formatter_results = if agent_marked_complete?(agent_result)
193
+ @test_runner.run_formatters
194
+ else
195
+ {success: true, output: "Formatters: Skipped (work not complete)", failures: [], required_failures: []}
196
+ end
197
+
198
+ all_results = {
199
+ tests: test_results,
200
+ lints: lint_results,
201
+ formatters: formatter_results,
202
+ builds: build_results,
203
+ docs: doc_results
204
+ }
205
+
206
+ record_periodic_checkpoint(all_results)
181
207
 
182
- record_periodic_checkpoint(test_results, lint_results)
208
+ # Track failures and escalate thinking tier if needed
209
+ track_failures_and_escalate(all_results)
183
210
 
184
- tests_pass = test_results[:success] && lint_results[:success]
211
+ # All required checks must pass for completion
212
+ all_checks_pass = test_results[:success] &&
213
+ lint_results[:success] &&
214
+ formatter_results[:success] &&
215
+ build_results[:success] &&
216
+ doc_results[:success]
185
217
 
186
- if tests_pass
218
+ if all_checks_pass
187
219
  transition_to(:pass)
188
220
 
189
221
  if agent_marked_complete?(agent_result)
190
222
  transition_to(:done)
191
- record_final_checkpoint(test_results, lint_results)
223
+ record_final_checkpoint(all_results)
192
224
  display_message("✅ Step #{@step_name} completed after #{@iteration_count} iterations", type: :success)
193
225
  display_state_summary
194
226
  archive_and_cleanup
@@ -201,18 +233,18 @@ module Aidp
201
233
  terminate: true
202
234
  )
203
235
  else
204
- display_message(" Tests passed but work not marked complete", type: :info)
236
+ display_message(" All checks passed but work not marked complete", type: :info)
205
237
  transition_to(:next_patch)
206
238
  end
207
239
  else
208
240
  transition_to(:fail)
209
- display_message(" Tests or linters failed", type: :warning)
241
+ display_message(" Required checks failed", type: :warning)
210
242
 
211
243
  transition_to(:diagnose)
212
- diagnostic = diagnose_failures(test_results, lint_results)
244
+ diagnostic = diagnose_failures(all_results)
213
245
 
214
246
  transition_to(:next_patch)
215
- prepare_next_iteration(test_results, lint_results, diagnostic)
247
+ prepare_next_iteration(all_results, diagnostic)
216
248
  end
217
249
  end
218
250
  end
@@ -222,14 +254,18 @@ module Aidp
222
254
 
223
255
  prompt = build_decider_prompt(context)
224
256
 
257
+ # Select model based on thinking depth tier
258
+ provider_name, model_name, _model_data = select_model_for_current_tier
259
+
225
260
  agent_result = @provider_manager.execute_with_provider(
226
- @provider_manager.current_provider,
261
+ provider_name,
227
262
  prompt,
228
263
  {
229
264
  step_name: @step_name,
230
265
  iteration: @iteration_count,
231
266
  project_dir: @project_dir,
232
- mode: :decide_whats_next
267
+ mode: :decide_whats_next,
268
+ model: model_name
233
269
  }
234
270
  )
235
271
 
@@ -250,14 +286,18 @@ module Aidp
250
286
 
251
287
  prompt = build_diagnose_prompt(context)
252
288
 
289
+ # Select model based on thinking depth tier
290
+ provider_name, model_name, _model_data = select_model_for_current_tier
291
+
253
292
  agent_result = @provider_manager.execute_with_provider(
254
- @provider_manager.current_provider,
293
+ provider_name,
255
294
  prompt,
256
295
  {
257
296
  step_name: @step_name,
258
297
  iteration: @iteration_count,
259
298
  project_dir: @project_dir,
260
- mode: :diagnose_failures
299
+ mode: :diagnose_failures,
300
+ model: model_name
261
301
  }
262
302
  )
263
303
 
@@ -421,27 +461,26 @@ module Aidp
421
461
  result[:status] == "completed" || prompt_marked_complete?
422
462
  end
423
463
 
424
- # Diagnose test/lint failures
464
+ # Diagnose all failures (tests, lints, formatters, builds, docs)
425
465
  # Returns diagnostic information to help agent understand what went wrong
426
- def diagnose_failures(test_results, lint_results)
466
+ def diagnose_failures(all_results)
427
467
  diagnostic = {
428
468
  iteration: @iteration_count,
429
469
  failures: []
430
470
  }
431
471
 
432
- unless test_results[:success]
433
- diagnostic[:failures] << {
434
- type: "tests",
435
- count: test_results[:failures]&.size || 0,
436
- commands: test_results[:failures]&.map { |f| f[:command] } || []
437
- }
438
- end
472
+ # Check each result type for failures
473
+ all_results.each do |category, results|
474
+ next if results[:success]
475
+
476
+ # Only include required failures in diagnostic
477
+ required_failures = results[:required_failures] || results[:failures] || []
478
+ next if required_failures.empty?
439
479
 
440
- unless lint_results[:success]
441
480
  diagnostic[:failures] << {
442
- type: "linters",
443
- count: lint_results[:failures]&.size || 0,
444
- commands: lint_results[:failures]&.map { |f| f[:command] } || []
481
+ type: category.to_s,
482
+ count: required_failures.size,
483
+ commands: required_failures.map { |f| f[:command] }
445
484
  }
446
485
  end
447
486
 
@@ -650,17 +689,36 @@ module Aidp
650
689
  # Prepend work loop instructions to every iteration
651
690
  full_prompt = build_work_loop_header(@step_name, @iteration_count) + "\n\n" + prompt_content
652
691
 
692
+ # Select model based on thinking depth tier
693
+ provider_name, model_name, _model_data = select_model_for_current_tier
694
+
695
+ if provider_name.nil? || model_name.nil?
696
+ Aidp.logger.error("work_loop", "Failed to select model for tier",
697
+ tier: @thinking_depth_manager.current_tier,
698
+ step: @step_name,
699
+ iteration: @iteration_count)
700
+ return {status: "error", message: "No model available for tier #{@thinking_depth_manager.current_tier}"}
701
+ end
702
+
703
+ # Log model selection
704
+ tier = @thinking_depth_manager.current_tier
705
+ if @last_tier != tier
706
+ display_message(" 💡 Using tier: #{tier} (#{provider_name}/#{model_name})", type: :info)
707
+ @last_tier = tier
708
+ end
709
+
653
710
  # CRITICAL: Change to project directory before calling provider
654
711
  # This ensures Claude CLI runs in the correct directory and can create files
655
712
  Dir.chdir(@project_dir) do
656
- # Send to provider via provider_manager
713
+ # Send to provider via provider_manager with selected model
657
714
  @provider_manager.execute_with_provider(
658
- @provider_manager.current_provider,
715
+ provider_name,
659
716
  full_prompt,
660
717
  {
661
718
  step_name: @step_name,
662
719
  iteration: @iteration_count,
663
- project_dir: @project_dir
720
+ project_dir: @project_dir,
721
+ model: model_name
664
722
  }
665
723
  )
666
724
  end
@@ -701,7 +759,7 @@ module Aidp
701
759
  prompt_content.match?(/^STATUS:\s*COMPLETE/i)
702
760
  end
703
761
 
704
- def prepare_next_iteration(test_results, lint_results, diagnostic = nil)
762
+ def prepare_next_iteration(all_results, diagnostic = nil)
705
763
  # Only append failures to PROMPT.md for agent to see
706
764
  # This follows fix-forward: never rollback, only add information for next patch
707
765
  failures = []
@@ -723,25 +781,30 @@ module Aidp
723
781
  failures << ""
724
782
  end
725
783
 
726
- unless test_results[:success]
727
- failures << "### Test Failures"
728
- failures << test_results[:output]
729
- failures << ""
730
- end
784
+ # Add failure output for each category that has failures
785
+ category_labels = {
786
+ tests: "Test",
787
+ lints: "Linter",
788
+ formatters: "Formatter",
789
+ builds: "Build",
790
+ docs: "Documentation"
791
+ }
731
792
 
732
- unless lint_results[:success]
733
- failures << "### Linter Failures"
734
- failures << lint_results[:output]
793
+ all_results.each do |category, results|
794
+ next if results[:success]
795
+
796
+ failures << "### #{category_labels[category]} Failures"
797
+ failures << results[:output]
735
798
  failures << ""
736
799
  end
737
800
 
738
- strategy = build_failure_strategy(test_results, lint_results)
801
+ strategy = build_failure_strategy(all_results)
739
802
  failures.concat(strategy) unless strategy.empty?
740
803
 
741
804
  failures << "**Fix-forward instructions**: Do not rollback changes. Build on what exists and fix the failures above."
742
805
  failures << ""
743
806
 
744
- return if test_results[:success] && lint_results[:success]
807
+ return if all_results.values.all? { |result| result[:success] }
745
808
 
746
809
  # Append failures to PROMPT.md and archive immediately (issue #224)
747
810
  current_prompt = @prompt_manager.read
@@ -832,20 +895,27 @@ module Aidp
832
895
  reminder.join("\n")
833
896
  end
834
897
 
835
- def build_failure_strategy(test_results, lint_results)
836
- return [] if test_results[:success] && lint_results[:success]
898
+ def build_failure_strategy(all_results)
899
+ return [] if all_results.values.all? { |result| result[:success] }
837
900
 
838
901
  lines = ["### Recovery Strategy", ""]
839
902
 
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
903
+ category_strategies = {
904
+ tests: "Re-run %s locally to reproduce the failing specs listed above. Triage the exact failures before moving on to new work.",
905
+ lints: "Execute %s and fix each reported offense.",
906
+ formatters: "Run %s to fix formatting issues.",
907
+ builds: "Run %s to diagnose and fix build errors.",
908
+ docs: "Review and update documentation using %s to meet requirements."
909
+ }
845
910
 
846
- unless lint_results[:success]
847
- commands = format_command_list(lint_results[:failures])
848
- lines << "- Execute #{commands} and fix each reported offense."
911
+ all_results.each do |category, results|
912
+ next if results[:success]
913
+
914
+ strategy_template = category_strategies[category]
915
+ next unless strategy_template
916
+
917
+ commands = format_command_list(results[:failures])
918
+ lines << "- #{strategy_template % commands}"
849
919
  end
850
920
 
851
921
  lines << ""
@@ -926,13 +996,16 @@ module Aidp
926
996
  end
927
997
 
928
998
  # Record checkpoint at regular intervals
929
- def record_periodic_checkpoint(test_results, lint_results)
999
+ def record_periodic_checkpoint(all_results)
930
1000
  # Record every CHECKPOINT_INTERVAL iterations or on iteration 1
931
1001
  return unless @iteration_count == 1 || (@iteration_count % CHECKPOINT_INTERVAL == 0)
932
1002
 
933
1003
  metrics = {
934
- tests_passing: test_results[:success],
935
- linters_passing: lint_results[:success]
1004
+ tests_passing: all_results[:tests][:success],
1005
+ linters_passing: all_results[:lints][:success],
1006
+ formatters_passing: all_results[:formatters][:success],
1007
+ builds_passing: all_results[:builds][:success],
1008
+ docs_passing: all_results[:docs][:success]
936
1009
  }
937
1010
 
938
1011
  checkpoint_data = @checkpoint.record_checkpoint(@step_name, @iteration_count, metrics)
@@ -947,10 +1020,13 @@ module Aidp
947
1020
  end
948
1021
 
949
1022
  # Record final checkpoint when step completes
950
- def record_final_checkpoint(test_results, lint_results)
1023
+ def record_final_checkpoint(all_results)
951
1024
  metrics = {
952
- tests_passing: test_results[:success],
953
- linters_passing: lint_results[:success],
1025
+ tests_passing: all_results[:tests][:success],
1026
+ linters_passing: all_results[:lints][:success],
1027
+ formatters_passing: all_results[:formatters][:success],
1028
+ builds_passing: all_results[:builds][:success],
1029
+ docs_passing: all_results[:docs][:success],
954
1030
  completed: true
955
1031
  }
956
1032
 
@@ -1114,6 +1190,100 @@ module Aidp
1114
1190
  display_message(" ✓ Confirmed", type: :success)
1115
1191
  end
1116
1192
  end
1193
+
1194
+ # Select model based on current thinking depth tier
1195
+ # Returns [provider_name, model_name, model_data]
1196
+ def select_model_for_current_tier
1197
+ current_tier = @thinking_depth_manager.current_tier
1198
+ provider_name, model_name, model_data = @thinking_depth_manager.select_model_for_tier(
1199
+ current_tier,
1200
+ provider: @provider_manager.current_provider
1201
+ )
1202
+
1203
+ Aidp.logger.debug("work_loop", "Selected model for tier",
1204
+ tier: current_tier,
1205
+ provider: provider_name,
1206
+ model: model_name,
1207
+ step: @step_name,
1208
+ iteration: @iteration_count)
1209
+
1210
+ [provider_name, model_name, model_data]
1211
+ end
1212
+
1213
+ # Track test/lint/formatter/build/doc failures and escalate tier if needed
1214
+ def track_failures_and_escalate(all_results)
1215
+ all_pass = all_results.values.all? { |result| result[:success] }
1216
+
1217
+ if all_pass
1218
+ # Reset failure count on success
1219
+ @consecutive_failures = 0
1220
+ else
1221
+ # Increment failure count
1222
+ @consecutive_failures += 1
1223
+
1224
+ # Check if we should escalate based on consecutive failures
1225
+ if @thinking_depth_manager.should_escalate_on_failures?(@consecutive_failures)
1226
+ escalate_thinking_tier("consecutive_failures")
1227
+ end
1228
+ end
1229
+
1230
+ # Check complexity-based escalation
1231
+ changed_files = get_changed_files
1232
+ if @thinking_depth_manager.should_escalate_on_complexity?(
1233
+ files_changed: changed_files.size,
1234
+ modules_touched: estimate_modules_touched(changed_files)
1235
+ )
1236
+ escalate_thinking_tier("complexity_threshold")
1237
+ end
1238
+ end
1239
+
1240
+ # Escalate to next thinking tier
1241
+ def escalate_thinking_tier(reason)
1242
+ old_tier = @thinking_depth_manager.current_tier
1243
+ new_tier = @thinking_depth_manager.escalate_tier(reason: reason)
1244
+
1245
+ if new_tier
1246
+ display_message(" ⬆️ Escalated thinking tier: #{old_tier} → #{new_tier} (#{reason})", type: :warning)
1247
+ Aidp.logger.info("work_loop", "Escalated thinking tier",
1248
+ from: old_tier,
1249
+ to: new_tier,
1250
+ reason: reason,
1251
+ step: @step_name,
1252
+ iteration: @iteration_count,
1253
+ consecutive_failures: @consecutive_failures)
1254
+
1255
+ # Reset last tier to trigger display of new tier
1256
+ @last_tier = nil
1257
+ else
1258
+ Aidp.logger.debug("work_loop", "Cannot escalate tier further",
1259
+ current: old_tier,
1260
+ max: @thinking_depth_manager.max_tier,
1261
+ reason: reason)
1262
+ end
1263
+ end
1264
+
1265
+ # Estimate number of modules touched based on file paths
1266
+ def estimate_modules_touched(files)
1267
+ # Group files by their top-level directory or module
1268
+ modules = files.map do |file|
1269
+ parts = file.split("/")
1270
+ # Consider top 2 levels as module identifier
1271
+ parts.take(2).join("/")
1272
+ end.uniq
1273
+
1274
+ modules.size
1275
+ end
1276
+
1277
+ # Get thinking depth status for display
1278
+ def thinking_depth_status
1279
+ {
1280
+ current_tier: @thinking_depth_manager.current_tier,
1281
+ max_tier: @thinking_depth_manager.max_tier,
1282
+ can_escalate: @thinking_depth_manager.can_escalate?,
1283
+ consecutive_failures: @consecutive_failures,
1284
+ escalation_count: @thinking_depth_manager.escalation_count
1285
+ }
1286
+ end
1117
1287
  end
1118
1288
  end
1119
1289
  end
@@ -316,17 +316,19 @@ module Aidp
316
316
  def handle_validation_errors(errors)
317
317
  error_message = "Configuration validation failed:\n" + errors.join("\n")
318
318
 
319
- # Log error
320
- if defined?(Rails) && Rails.logger
321
- Rails.logger.error(error_message)
322
- else
323
- warn(error_message)
319
+ # Log error (suppress in test/CI environments)
320
+ unless suppress_config_warnings?
321
+ if defined?(Rails) && Rails.logger
322
+ Rails.logger.error(error_message)
323
+ else
324
+ warn(error_message)
325
+ end
324
326
  end
325
327
 
326
328
  # In development, try to fix common issues
327
329
  if ENV["AIDP_ENV"] == "development" || ENV["RACK_ENV"] == "development"
328
330
  if @validator.fix_common_issues
329
- warn("Attempted to fix configuration issues. Please review the updated configuration file.")
331
+ warn("Attempted to fix configuration issues. Please review the updated configuration file.") unless suppress_config_warnings?
330
332
  end
331
333
  end
332
334
  end
@@ -334,11 +336,13 @@ module Aidp
334
336
  def log_warnings(warnings)
335
337
  warning_message = "Configuration warnings:\n" + warnings.join("\n")
336
338
 
337
- # Log warnings
338
- if defined?(Rails) && Rails.logger
339
- Rails.logger.warn(warning_message)
340
- else
341
- warn(warning_message)
339
+ # Log warnings (suppress in test/CI environments)
340
+ unless suppress_config_warnings?
341
+ if defined?(Rails) && Rails.logger
342
+ Rails.logger.warn(warning_message)
343
+ else
344
+ warn(warning_message)
345
+ end
342
346
  end
343
347
  end
344
348
 
@@ -390,6 +394,11 @@ module Aidp
390
394
  false
391
395
  end
392
396
  end
397
+
398
+ # Suppress configuration warnings in test/CI environments
399
+ def suppress_config_warnings?
400
+ ENV["RSPEC_RUNNING"] || ENV["CI"] || ENV["RAILS_ENV"] == "test" || ENV["RACK_ENV"] == "test"
401
+ end
393
402
  end
394
403
  end
395
404
  end
@@ -375,6 +375,9 @@ module Aidp
375
375
  max_iterations: 50,
376
376
  test_commands: [],
377
377
  lint_commands: [],
378
+ formatter_commands: [],
379
+ build_commands: [],
380
+ documentation_commands: [],
378
381
  units: {},
379
382
  guards: {enabled: false},
380
383
  version_control: {tool: "git", behavior: "nothing", conventional_commits: false},
@@ -397,18 +400,37 @@ module Aidp
397
400
  test_commands: {
398
401
  type: :array,
399
402
  required: false,
400
- default: [],
401
- items: {
402
- type: :string
403
- }
403
+ default: []
404
+ # Items can be strings or {command: string, required: boolean}
405
+ # Validation handled in Configuration class for flexibility
404
406
  },
405
407
  lint_commands: {
406
408
  type: :array,
407
409
  required: false,
408
- default: [],
409
- items: {
410
- type: :string
411
- }
410
+ default: []
411
+ # Items can be strings or {command: string, required: boolean}
412
+ # Validation handled in Configuration class for flexibility
413
+ },
414
+ formatter_commands: {
415
+ type: :array,
416
+ required: false,
417
+ default: []
418
+ # Items can be strings or {command: string, required: boolean}
419
+ # Validation handled in Configuration class for flexibility
420
+ },
421
+ build_commands: {
422
+ type: :array,
423
+ required: false,
424
+ default: []
425
+ # Items can be strings or {command: string, required: boolean}
426
+ # Validation handled in Configuration class for flexibility
427
+ },
428
+ documentation_commands: {
429
+ type: :array,
430
+ required: false,
431
+ default: []
432
+ # Items can be strings or {command: string, required: boolean}
433
+ # Validation handled in Configuration class for flexibility
412
434
  },
413
435
  units: {
414
436
  type: :hash,