aidp 0.31.0 → 0.33.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 +4 -4
- data/lib/aidp/analyze/feature_analyzer.rb +322 -320
- data/lib/aidp/auto_update/coordinator.rb +97 -7
- data/lib/aidp/auto_update.rb +0 -12
- data/lib/aidp/cli/devcontainer_commands.rb +0 -5
- data/lib/aidp/cli.rb +2 -1
- data/lib/aidp/comment_consolidator.rb +78 -0
- data/lib/aidp/concurrency.rb +0 -3
- data/lib/aidp/config.rb +0 -1
- data/lib/aidp/config_paths.rb +71 -0
- data/lib/aidp/execute/work_loop_runner.rb +394 -15
- data/lib/aidp/harness/ai_filter_factory.rb +285 -0
- data/lib/aidp/harness/config_schema.rb +97 -1
- data/lib/aidp/harness/config_validator.rb +1 -1
- data/lib/aidp/harness/configuration.rb +61 -5
- data/lib/aidp/harness/filter_definition.rb +212 -0
- data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
- data/lib/aidp/harness/output_filter.rb +50 -25
- data/lib/aidp/harness/output_filter_config.rb +129 -0
- data/lib/aidp/harness/provider_manager.rb +128 -2
- data/lib/aidp/harness/provider_metrics.rb +5 -3
- data/lib/aidp/harness/runner.rb +0 -11
- data/lib/aidp/harness/test_runner.rb +179 -41
- data/lib/aidp/harness/thinking_depth_manager.rb +16 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +0 -2
- data/lib/aidp/loader.rb +195 -0
- data/lib/aidp/metadata/compiler.rb +29 -17
- data/lib/aidp/metadata/query.rb +1 -1
- data/lib/aidp/metadata/scanner.rb +8 -1
- data/lib/aidp/metadata/tool_metadata.rb +13 -13
- data/lib/aidp/metadata/validator.rb +10 -0
- data/lib/aidp/metadata.rb +16 -0
- data/lib/aidp/pr_worktree_manager.rb +582 -0
- data/lib/aidp/provider_manager.rb +1 -7
- data/lib/aidp/setup/wizard.rb +279 -9
- data/lib/aidp/skills.rb +0 -5
- data/lib/aidp/storage/csv_storage.rb +3 -0
- data/lib/aidp/style_guide/selector.rb +360 -0
- data/lib/aidp/tooling_detector.rb +283 -16
- data/lib/aidp/util.rb +11 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/change_request_processor.rb +152 -14
- data/lib/aidp/watch/repository_client.rb +41 -0
- data/lib/aidp/watch/runner.rb +29 -18
- data/lib/aidp/watch.rb +5 -7
- data/lib/aidp/workstream_cleanup.rb +0 -2
- data/lib/aidp/workstream_executor.rb +0 -4
- data/lib/aidp/worktree.rb +0 -1
- data/lib/aidp/worktree_branch_manager.rb +70 -1
- data/lib/aidp.rb +21 -106
- metadata +73 -36
- data/lib/aidp/config/paths.rb +0 -131
|
@@ -7,8 +7,10 @@ require_relative "guard_policy"
|
|
|
7
7
|
require_relative "work_loop_unit_scheduler"
|
|
8
8
|
require_relative "deterministic_unit"
|
|
9
9
|
require_relative "agent_signal_parser"
|
|
10
|
+
require_relative "steps"
|
|
10
11
|
require_relative "../harness/test_runner"
|
|
11
12
|
require_relative "../errors"
|
|
13
|
+
require_relative "../style_guide/selector"
|
|
12
14
|
|
|
13
15
|
module Aidp
|
|
14
16
|
module Execute
|
|
@@ -53,6 +55,7 @@ module Aidp
|
|
|
53
55
|
@checkpoint = Checkpoint.new(project_dir)
|
|
54
56
|
@checkpoint_display = CheckpointDisplay.new(prompt: @prompt)
|
|
55
57
|
@guard_policy = GuardPolicy.new(project_dir, config.guards_config)
|
|
58
|
+
@work_context = {}
|
|
56
59
|
@persistent_tasklist = PersistentTasklist.new(project_dir)
|
|
57
60
|
@iteration_count = 0
|
|
58
61
|
@step_name = nil
|
|
@@ -67,6 +70,9 @@ module Aidp
|
|
|
67
70
|
@thinking_depth_manager = options[:thinking_depth_manager] || Aidp::Harness::ThinkingDepthManager.new(config)
|
|
68
71
|
@consecutive_failures = 0
|
|
69
72
|
@last_tier = nil
|
|
73
|
+
|
|
74
|
+
# Initialize style guide selector for intelligent section selection
|
|
75
|
+
@style_guide_selector = options[:style_guide_selector] || Aidp::StyleGuide::Selector.new(project_dir: project_dir)
|
|
70
76
|
end
|
|
71
77
|
|
|
72
78
|
# Execute a step using fix-forward work loop pattern
|
|
@@ -74,6 +80,7 @@ module Aidp
|
|
|
74
80
|
# Never rolls back - only moves forward through fixes
|
|
75
81
|
def execute_step(step_name, step_spec, context = {})
|
|
76
82
|
@step_name = step_name
|
|
83
|
+
@work_context = context
|
|
77
84
|
@iteration_count = 0
|
|
78
85
|
transition_to(:ready)
|
|
79
86
|
|
|
@@ -81,6 +88,7 @@ module Aidp
|
|
|
81
88
|
|
|
82
89
|
display_message("🔄 Starting hybrid work loop for step: #{step_name}", type: :info)
|
|
83
90
|
display_message(" Flow: Deterministic ↔ Agentic with fix-forward core", type: :info)
|
|
91
|
+
display_work_context(step_name, context)
|
|
84
92
|
|
|
85
93
|
display_guard_policy_status
|
|
86
94
|
display_pending_tasks
|
|
@@ -159,9 +167,20 @@ module Aidp
|
|
|
159
167
|
|
|
160
168
|
transition_to(:apply_patch)
|
|
161
169
|
|
|
170
|
+
# Preview provider/model selection and queued checks for this iteration
|
|
171
|
+
preview_provider, preview_model, _model_data = select_model_for_current_tier
|
|
172
|
+
prompt_length = @prompt_manager.read&.length || 0
|
|
173
|
+
checks_summary = planned_checks_summary
|
|
174
|
+
display_iteration_overview(preview_provider, preview_model, prompt_length, checks_summary)
|
|
175
|
+
log_iteration_status("running",
|
|
176
|
+
provider: preview_provider,
|
|
177
|
+
model: preview_model,
|
|
178
|
+
prompt_length: prompt_length,
|
|
179
|
+
checks: checks_summary)
|
|
180
|
+
|
|
162
181
|
# Wrap agent call in exception handling for true fix-forward
|
|
163
182
|
begin
|
|
164
|
-
agent_result = apply_patch
|
|
183
|
+
agent_result = apply_patch(preview_provider, preview_model)
|
|
165
184
|
rescue Aidp::Errors::ConfigurationError
|
|
166
185
|
# Configuration errors should crash immediately (crash-early principle)
|
|
167
186
|
# Re-raise without catching
|
|
@@ -256,6 +275,12 @@ module Aidp
|
|
|
256
275
|
display_task_summary
|
|
257
276
|
display_message("✅ Step #{@step_name} completed after #{@iteration_count} iterations", type: :success)
|
|
258
277
|
display_state_summary
|
|
278
|
+
log_iteration_status("completed",
|
|
279
|
+
provider: preview_provider,
|
|
280
|
+
model: preview_model,
|
|
281
|
+
prompt_length: prompt_length,
|
|
282
|
+
checks: checks_summary,
|
|
283
|
+
task_status: "complete")
|
|
259
284
|
archive_and_cleanup
|
|
260
285
|
|
|
261
286
|
return build_agentic_payload(
|
|
@@ -270,6 +295,12 @@ module Aidp
|
|
|
270
295
|
display_message(" All checks passed but tasks not complete", type: :warning)
|
|
271
296
|
display_message(" #{task_completion_result[:message]}", type: :warning)
|
|
272
297
|
display_task_summary
|
|
298
|
+
log_iteration_status("checks_passed_tasks_incomplete",
|
|
299
|
+
provider: preview_provider,
|
|
300
|
+
model: preview_model,
|
|
301
|
+
prompt_length: prompt_length,
|
|
302
|
+
checks: checks_summary,
|
|
303
|
+
task_status: "incomplete")
|
|
273
304
|
transition_to(:next_patch)
|
|
274
305
|
|
|
275
306
|
# Append task completion requirement to PROMPT.md
|
|
@@ -277,6 +308,11 @@ module Aidp
|
|
|
277
308
|
end
|
|
278
309
|
else
|
|
279
310
|
display_message(" All checks passed but work not marked complete", type: :info)
|
|
311
|
+
log_iteration_status("checks_passed_waiting_agent_completion",
|
|
312
|
+
provider: preview_provider,
|
|
313
|
+
model: preview_model,
|
|
314
|
+
prompt_length: prompt_length,
|
|
315
|
+
checks: checks_summary)
|
|
280
316
|
transition_to(:next_patch)
|
|
281
317
|
end
|
|
282
318
|
else
|
|
@@ -287,6 +323,12 @@ module Aidp
|
|
|
287
323
|
diagnostic = diagnose_failures(all_results)
|
|
288
324
|
|
|
289
325
|
transition_to(:next_patch)
|
|
326
|
+
log_iteration_status("checks_failed",
|
|
327
|
+
provider: preview_provider,
|
|
328
|
+
model: preview_model,
|
|
329
|
+
prompt_length: prompt_length,
|
|
330
|
+
checks: checks_summary,
|
|
331
|
+
failures: failure_summary_for_log(all_results))
|
|
290
332
|
prepare_next_iteration(all_results, diagnostic)
|
|
291
333
|
end
|
|
292
334
|
end
|
|
@@ -497,8 +539,8 @@ module Aidp
|
|
|
497
539
|
end
|
|
498
540
|
|
|
499
541
|
# Apply patch - send PROMPT.md to agent
|
|
500
|
-
def apply_patch
|
|
501
|
-
send_to_agent
|
|
542
|
+
def apply_patch(selected_provider = nil, selected_model = nil)
|
|
543
|
+
send_to_agent(selected_provider: selected_provider, selected_model: selected_model)
|
|
502
544
|
end
|
|
503
545
|
|
|
504
546
|
# Check if agent marked work complete
|
|
@@ -547,7 +589,9 @@ module Aidp
|
|
|
547
589
|
# Traditional prompt building (fallback or when optimization disabled)
|
|
548
590
|
template_content = load_template(step_spec["templates"]&.first)
|
|
549
591
|
prd_content = load_prd
|
|
550
|
-
|
|
592
|
+
# Use provider-aware style guide loading - skips for Claude/Copilot,
|
|
593
|
+
# selects relevant STYLE_GUIDE sections for other providers
|
|
594
|
+
style_guide = load_style_guide_for_provider(context)
|
|
551
595
|
user_input = format_user_input(context[:user_input])
|
|
552
596
|
deterministic_outputs = Array(context[:deterministic_outputs])
|
|
553
597
|
previous_summary = context[:previous_agent_summary]
|
|
@@ -727,7 +771,7 @@ module Aidp
|
|
|
727
771
|
parts.join("\n")
|
|
728
772
|
end
|
|
729
773
|
|
|
730
|
-
def send_to_agent
|
|
774
|
+
def send_to_agent(selected_provider: nil, selected_model: nil)
|
|
731
775
|
prompt_content = @prompt_manager.read
|
|
732
776
|
return {status: "error", message: "PROMPT.md not found"} unless prompt_content
|
|
733
777
|
|
|
@@ -735,9 +779,11 @@ module Aidp
|
|
|
735
779
|
full_prompt = build_work_loop_header(@step_name, @iteration_count) + "\n\n" + prompt_content
|
|
736
780
|
|
|
737
781
|
# Select model based on thinking depth tier
|
|
738
|
-
provider_name
|
|
782
|
+
provider_name = selected_provider
|
|
783
|
+
model_name = selected_model
|
|
784
|
+
provider_name, model_name, _model_data = select_model_for_current_tier if provider_name.nil? || model_name.nil?
|
|
739
785
|
|
|
740
|
-
if provider_name.nil?
|
|
786
|
+
if provider_name.nil?
|
|
741
787
|
Aidp.logger.error("work_loop", "Failed to select model for tier",
|
|
742
788
|
tier: @thinking_depth_manager.current_tier,
|
|
743
789
|
step: @step_name,
|
|
@@ -748,7 +794,8 @@ module Aidp
|
|
|
748
794
|
# Log model selection
|
|
749
795
|
tier = @thinking_depth_manager.current_tier
|
|
750
796
|
if @last_tier != tier
|
|
751
|
-
|
|
797
|
+
model_label = model_name || "auto"
|
|
798
|
+
display_message(" 💡 Using tier: #{tier} (#{provider_name}/#{model_label})", type: :info)
|
|
752
799
|
@last_tier = tier
|
|
753
800
|
end
|
|
754
801
|
|
|
@@ -770,6 +817,164 @@ module Aidp
|
|
|
770
817
|
end
|
|
771
818
|
end
|
|
772
819
|
|
|
820
|
+
def display_iteration_overview(provider_name, model_name, prompt_length, checks_summary = nil)
|
|
821
|
+
tier = @thinking_depth_manager.current_tier
|
|
822
|
+
checks = checks_summary
|
|
823
|
+
checks ||= summarize_checks(@test_runner.planned_commands) if @test_runner.respond_to?(:planned_commands)
|
|
824
|
+
model_label = model_name || "auto"
|
|
825
|
+
context_labels = iteration_context_labels
|
|
826
|
+
|
|
827
|
+
display_message(" • Step: #{@step_name} | Tier: #{tier} | Model: #{provider_name}/#{model_label}", type: :info)
|
|
828
|
+
display_message(" • Prompt size: #{prompt_length} chars | State: #{STATES[@current_state]}", type: :info)
|
|
829
|
+
display_message(" • Upcoming checks: #{checks}", type: :info) if checks && !checks.empty?
|
|
830
|
+
display_message(" • Context: #{context_labels.join(" | ")}", type: :info) if context_labels.any?
|
|
831
|
+
|
|
832
|
+
# Display output filtering configuration if enabled
|
|
833
|
+
filtering_info = summarize_output_filtering
|
|
834
|
+
display_message(" • Output filtering: #{filtering_info}", type: :info) if filtering_info
|
|
835
|
+
end
|
|
836
|
+
|
|
837
|
+
# Summarize output filtering configuration
|
|
838
|
+
def summarize_output_filtering
|
|
839
|
+
return nil unless @config.respond_to?(:output_filtering_enabled?) && @config.output_filtering_enabled?
|
|
840
|
+
|
|
841
|
+
iteration = @test_runner.respond_to?(:iteration_count) ? @test_runner.iteration_count : 0
|
|
842
|
+
|
|
843
|
+
test_mode = if @config.respond_to?(:test_output_mode)
|
|
844
|
+
@config.test_output_mode
|
|
845
|
+
elsif iteration > 1
|
|
846
|
+
:failures_only
|
|
847
|
+
else
|
|
848
|
+
:full
|
|
849
|
+
end
|
|
850
|
+
|
|
851
|
+
lint_mode = if @config.respond_to?(:lint_output_mode)
|
|
852
|
+
@config.lint_output_mode
|
|
853
|
+
elsif iteration > 1
|
|
854
|
+
:failures_only
|
|
855
|
+
else
|
|
856
|
+
:full
|
|
857
|
+
end
|
|
858
|
+
|
|
859
|
+
if test_mode == :full && lint_mode == :full
|
|
860
|
+
nil # Don't show message when no filtering is active
|
|
861
|
+
else
|
|
862
|
+
"test=#{test_mode}, lint=#{lint_mode}"
|
|
863
|
+
end
|
|
864
|
+
rescue
|
|
865
|
+
nil
|
|
866
|
+
end
|
|
867
|
+
|
|
868
|
+
# Display output filtering statistics after test/lint runs
|
|
869
|
+
def display_filtering_stats
|
|
870
|
+
return unless @test_runner.respond_to?(:filter_stats)
|
|
871
|
+
|
|
872
|
+
stats = @test_runner.filter_stats
|
|
873
|
+
return if stats[:total_input_bytes].zero?
|
|
874
|
+
|
|
875
|
+
reduction = ((stats[:total_input_bytes] - stats[:total_output_bytes]).to_f / stats[:total_input_bytes] * 100).round(1)
|
|
876
|
+
return if reduction <= 0
|
|
877
|
+
|
|
878
|
+
display_message(" 📉 Token optimization: #{reduction}% reduction " \
|
|
879
|
+
"(#{format_bytes(stats[:total_input_bytes])} → #{format_bytes(stats[:total_output_bytes])})", type: :info)
|
|
880
|
+
rescue
|
|
881
|
+
# Silently ignore errors in stats display
|
|
882
|
+
end
|
|
883
|
+
|
|
884
|
+
def format_bytes(bytes)
|
|
885
|
+
if bytes >= 1024 * 1024
|
|
886
|
+
"#{(bytes / 1024.0 / 1024.0).round(1)}MB"
|
|
887
|
+
elsif bytes >= 1024
|
|
888
|
+
"#{(bytes / 1024.0).round(1)}KB"
|
|
889
|
+
else
|
|
890
|
+
"#{bytes}B"
|
|
891
|
+
end
|
|
892
|
+
end
|
|
893
|
+
|
|
894
|
+
def summarize_checks(planned)
|
|
895
|
+
labels = {
|
|
896
|
+
tests: "tests",
|
|
897
|
+
lints: "linters",
|
|
898
|
+
formatters: "formatters",
|
|
899
|
+
builds: "builds",
|
|
900
|
+
docs: "docs"
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
summaries = planned.map do |category, commands|
|
|
904
|
+
count = Array(commands).size
|
|
905
|
+
next if count.zero?
|
|
906
|
+
|
|
907
|
+
label = labels[category] || category.to_s
|
|
908
|
+
cmd_names = Array(commands).map do |cmd|
|
|
909
|
+
cmd.is_a?(Hash) ? cmd[:command] : cmd
|
|
910
|
+
end
|
|
911
|
+
|
|
912
|
+
if cmd_names.size <= 2
|
|
913
|
+
"#{label} (#{cmd_names.join(", ")})"
|
|
914
|
+
else
|
|
915
|
+
"#{label} (#{cmd_names.first(2).join(", ")} +#{cmd_names.size - 2} more)"
|
|
916
|
+
end
|
|
917
|
+
end.compact
|
|
918
|
+
|
|
919
|
+
summaries.join(" | ")
|
|
920
|
+
rescue => e
|
|
921
|
+
Aidp.log_warn("work_loop", "summarize_checks_failed", error: e.message)
|
|
922
|
+
nil
|
|
923
|
+
end
|
|
924
|
+
|
|
925
|
+
def planned_checks_summary
|
|
926
|
+
return nil unless @test_runner.respond_to?(:planned_commands)
|
|
927
|
+
|
|
928
|
+
summarize_checks(@test_runner.planned_commands)
|
|
929
|
+
end
|
|
930
|
+
|
|
931
|
+
def failure_summary_for_log(all_results)
|
|
932
|
+
Array(all_results).each_with_object([]) do |(category, results), summary|
|
|
933
|
+
next if results[:success]
|
|
934
|
+
|
|
935
|
+
failures = results[:required_failures] || results[:failures] || []
|
|
936
|
+
count = failures.size
|
|
937
|
+
commands = Array(failures).map { |f| f[:command] }.compact
|
|
938
|
+
|
|
939
|
+
summary << if commands.any?
|
|
940
|
+
"#{category}: #{count} (#{commands.first(2).join(", ")})"
|
|
941
|
+
else
|
|
942
|
+
"#{category}: #{count}"
|
|
943
|
+
end
|
|
944
|
+
end
|
|
945
|
+
rescue => e
|
|
946
|
+
Aidp.log_warn("work_loop", "failure_summary_for_log_failed", error: e.message)
|
|
947
|
+
[]
|
|
948
|
+
end
|
|
949
|
+
|
|
950
|
+
def log_iteration_status(status, provider:, model:, prompt_length:, checks: nil, failures: nil, task_status: nil)
|
|
951
|
+
context_labels = iteration_context_labels
|
|
952
|
+
metadata = {
|
|
953
|
+
step: @step_name,
|
|
954
|
+
iteration: @iteration_count,
|
|
955
|
+
state: STATES[@current_state],
|
|
956
|
+
tier: @thinking_depth_manager.current_tier,
|
|
957
|
+
provider: provider,
|
|
958
|
+
model: model,
|
|
959
|
+
prompt_length: prompt_length,
|
|
960
|
+
checks: checks,
|
|
961
|
+
failures: failures,
|
|
962
|
+
task_status: task_status
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
metadata.merge!(iteration_context_metadata)
|
|
966
|
+
metadata.delete_if { |_, value| value.nil? || (value.respond_to?(:empty?) && value.empty?) }
|
|
967
|
+
|
|
968
|
+
message = "Iteration #{@iteration_count} for #{@step_name}: #{status}"
|
|
969
|
+
message += " | #{context_labels.join(" | ")}" if context_labels.any?
|
|
970
|
+
|
|
971
|
+
Aidp.log_info("work_loop_iteration",
|
|
972
|
+
message,
|
|
973
|
+
**metadata)
|
|
974
|
+
rescue => e
|
|
975
|
+
Aidp.log_warn("work_loop", "failed_to_log_iteration_status", error: e.message)
|
|
976
|
+
end
|
|
977
|
+
|
|
773
978
|
def build_work_loop_header(step_name, iteration)
|
|
774
979
|
parts = []
|
|
775
980
|
parts << "# Work Loop: #{step_name} (Iteration #{iteration})"
|
|
@@ -830,6 +1035,24 @@ module Aidp
|
|
|
830
1035
|
parts.join("\n")
|
|
831
1036
|
end
|
|
832
1037
|
|
|
1038
|
+
def iteration_context_metadata
|
|
1039
|
+
ctx = (@options || {}).merge(@work_context || {})
|
|
1040
|
+
{
|
|
1041
|
+
issue: issue_context_label(ctx),
|
|
1042
|
+
pr: pr_context_label(ctx),
|
|
1043
|
+
step_position: step_position_label(@step_name, ctx)
|
|
1044
|
+
}.compact
|
|
1045
|
+
end
|
|
1046
|
+
|
|
1047
|
+
def iteration_context_labels
|
|
1048
|
+
meta = iteration_context_metadata
|
|
1049
|
+
labels = []
|
|
1050
|
+
labels << meta[:issue] if meta[:issue]
|
|
1051
|
+
labels << meta[:pr] if meta[:pr]
|
|
1052
|
+
labels << meta[:step_position] if meta[:step_position]
|
|
1053
|
+
labels
|
|
1054
|
+
end
|
|
1055
|
+
|
|
833
1056
|
def prompt_marked_complete?
|
|
834
1057
|
prompt_content = @prompt_manager.read
|
|
835
1058
|
return false unless prompt_content
|
|
@@ -936,30 +1159,50 @@ module Aidp
|
|
|
936
1159
|
|
|
937
1160
|
# Check if we should reinject the style guide at this iteration
|
|
938
1161
|
def should_reinject_style_guide?
|
|
1162
|
+
# Skip reinjection for providers with instruction files (Claude, GitHub Copilot)
|
|
1163
|
+
current_provider = @provider_manager&.current_provider
|
|
1164
|
+
return false unless @style_guide_selector.provider_needs_style_guide?(current_provider)
|
|
1165
|
+
|
|
939
1166
|
# Reinject on intervals (5, 10, 15, etc.) but not on iteration 1
|
|
940
1167
|
@iteration_count > 1 && (@iteration_count % STYLE_GUIDE_REMINDER_INTERVAL == 0)
|
|
941
1168
|
end
|
|
942
1169
|
|
|
943
1170
|
# Create style guide reminder text
|
|
944
1171
|
def reinject_style_guide_reminder
|
|
945
|
-
|
|
1172
|
+
current_provider = @provider_manager&.current_provider
|
|
1173
|
+
|
|
1174
|
+
# Skip for providers with instruction files
|
|
1175
|
+
unless @style_guide_selector.provider_needs_style_guide?(current_provider)
|
|
1176
|
+
Aidp.log_debug("work_loop", "skipping_style_guide_reminder",
|
|
1177
|
+
provider: current_provider,
|
|
1178
|
+
reason: "provider has instruction file")
|
|
1179
|
+
return ""
|
|
1180
|
+
end
|
|
1181
|
+
|
|
946
1182
|
template_content = load_current_template
|
|
947
1183
|
|
|
1184
|
+
# Use provider-aware style guide loading with context-based section selection
|
|
1185
|
+
style_guide = load_style_guide_for_provider(@work_context)
|
|
1186
|
+
|
|
948
1187
|
reminder = []
|
|
949
1188
|
reminder << "### 🔄 Style Guide & Template Reminder (Iteration #{@iteration_count})"
|
|
950
1189
|
reminder << ""
|
|
951
1190
|
reminder << "**IMPORTANT**: To prevent drift from project conventions, please review:"
|
|
952
1191
|
reminder << ""
|
|
953
1192
|
|
|
954
|
-
if style_guide
|
|
955
|
-
reminder << "####
|
|
956
|
-
reminder << "```"
|
|
957
|
-
# Include
|
|
958
|
-
style_guide_preview =
|
|
1193
|
+
if style_guide && !style_guide.empty?
|
|
1194
|
+
reminder << "#### Relevant Style Guide Sections"
|
|
1195
|
+
reminder << "```markdown"
|
|
1196
|
+
# Include selected sections (already limited by selector)
|
|
1197
|
+
style_guide_preview = if style_guide.length > 2000
|
|
1198
|
+
style_guide[0...2000] + "\n...(truncated)"
|
|
1199
|
+
else
|
|
1200
|
+
style_guide
|
|
1201
|
+
end
|
|
959
1202
|
reminder << style_guide_preview
|
|
960
1203
|
reminder << "```"
|
|
961
1204
|
reminder << ""
|
|
962
|
-
display_message(" [STYLE_GUIDE] Re-injecting
|
|
1205
|
+
display_message(" [STYLE_GUIDE] Re-injecting selected STYLE_GUIDE sections at iteration #{@iteration_count}", type: :info)
|
|
963
1206
|
end
|
|
964
1207
|
|
|
965
1208
|
if template_content
|
|
@@ -1049,6 +1292,74 @@ module Aidp
|
|
|
1049
1292
|
File.exist?(style_guide_path) ? File.read(style_guide_path) : nil
|
|
1050
1293
|
end
|
|
1051
1294
|
|
|
1295
|
+
# Load style guide content appropriate for the current provider and context
|
|
1296
|
+
# Returns nil for providers with instruction files (Claude, GitHub Copilot)
|
|
1297
|
+
# Returns selected STYLE_GUIDE sections for other providers
|
|
1298
|
+
#
|
|
1299
|
+
# @param context [Hash] Task context for keyword extraction
|
|
1300
|
+
# @return [String, nil] Style guide content or nil if not needed
|
|
1301
|
+
def load_style_guide_for_provider(context = {})
|
|
1302
|
+
current_provider = @provider_manager&.current_provider
|
|
1303
|
+
|
|
1304
|
+
# Skip style guide for providers with their own instruction files
|
|
1305
|
+
unless @style_guide_selector.provider_needs_style_guide?(current_provider)
|
|
1306
|
+
Aidp.log_debug("work_loop", "skipping_style_guide",
|
|
1307
|
+
provider: current_provider,
|
|
1308
|
+
reason: "provider has instruction file")
|
|
1309
|
+
return nil
|
|
1310
|
+
end
|
|
1311
|
+
|
|
1312
|
+
# Extract keywords from context for intelligent section selection
|
|
1313
|
+
keywords = extract_style_guide_keywords(context)
|
|
1314
|
+
|
|
1315
|
+
# Select relevant sections from STYLE_GUIDE.md
|
|
1316
|
+
content = @style_guide_selector.select_sections(
|
|
1317
|
+
keywords: keywords,
|
|
1318
|
+
include_core: true,
|
|
1319
|
+
max_lines: 500 # Limit to keep prompt size manageable
|
|
1320
|
+
)
|
|
1321
|
+
|
|
1322
|
+
return nil if content.nil? || content.empty?
|
|
1323
|
+
|
|
1324
|
+
Aidp.log_debug("work_loop", "style_guide_selected",
|
|
1325
|
+
provider: current_provider,
|
|
1326
|
+
keywords: keywords,
|
|
1327
|
+
content_lines: content.lines.count)
|
|
1328
|
+
|
|
1329
|
+
content
|
|
1330
|
+
end
|
|
1331
|
+
|
|
1332
|
+
# Extract keywords from task context for style guide section selection
|
|
1333
|
+
#
|
|
1334
|
+
# @param context [Hash] Task context
|
|
1335
|
+
# @return [Array<String>] Keywords for section selection
|
|
1336
|
+
def extract_style_guide_keywords(context)
|
|
1337
|
+
keywords = []
|
|
1338
|
+
|
|
1339
|
+
# Extract from step name
|
|
1340
|
+
step_lower = @step_name.to_s.downcase
|
|
1341
|
+
keywords << "testing" if step_lower.include?("test")
|
|
1342
|
+
keywords << "implementation" if step_lower.include?("implement")
|
|
1343
|
+
keywords << "refactor" if step_lower.include?("refactor")
|
|
1344
|
+
|
|
1345
|
+
# Extract from user input
|
|
1346
|
+
user_input = context[:user_input]
|
|
1347
|
+
if user_input.is_a?(Hash)
|
|
1348
|
+
keywords.concat(@style_guide_selector.extract_keywords(user_input.values.join(" ")))
|
|
1349
|
+
elsif user_input.is_a?(String)
|
|
1350
|
+
keywords.concat(@style_guide_selector.extract_keywords(user_input))
|
|
1351
|
+
end
|
|
1352
|
+
|
|
1353
|
+
# Extract from affected files
|
|
1354
|
+
affected_files = context[:affected_files] || []
|
|
1355
|
+
affected_files.each do |file|
|
|
1356
|
+
keywords << "testing" if file.include?("spec") || file.include?("test")
|
|
1357
|
+
keywords << "tty" if file.include?("cli") || file.include?("tui")
|
|
1358
|
+
end
|
|
1359
|
+
|
|
1360
|
+
keywords.uniq
|
|
1361
|
+
end
|
|
1362
|
+
|
|
1052
1363
|
def format_user_input(user_input)
|
|
1053
1364
|
return nil if user_input.nil? || user_input.empty?
|
|
1054
1365
|
|
|
@@ -1286,6 +1597,74 @@ module Aidp
|
|
|
1286
1597
|
display_message("")
|
|
1287
1598
|
end
|
|
1288
1599
|
|
|
1600
|
+
# Show watch-mode context (issue/PR, step position) to improve situational awareness
|
|
1601
|
+
def display_work_context(step_name, context)
|
|
1602
|
+
parts = work_context_parts(step_name, context)
|
|
1603
|
+
return if parts.empty?
|
|
1604
|
+
|
|
1605
|
+
Aidp.log_debug("work_loop", "work_context", step: step_name, parts: parts)
|
|
1606
|
+
display_message(" 📡 Context: #{parts.join(" | ")}", type: :info)
|
|
1607
|
+
end
|
|
1608
|
+
|
|
1609
|
+
def work_context_parts(step_name, context)
|
|
1610
|
+
ctx = (@options || {}).merge(context || {})
|
|
1611
|
+
parts = []
|
|
1612
|
+
|
|
1613
|
+
if (step_label = step_position_label(step_name, ctx))
|
|
1614
|
+
parts << step_label
|
|
1615
|
+
end
|
|
1616
|
+
|
|
1617
|
+
if (issue_label = issue_context_label(ctx))
|
|
1618
|
+
parts << issue_label
|
|
1619
|
+
end
|
|
1620
|
+
|
|
1621
|
+
if (pr_label = pr_context_label(ctx))
|
|
1622
|
+
parts << pr_label
|
|
1623
|
+
end
|
|
1624
|
+
|
|
1625
|
+
parts << "Watch mode" if ctx[:workflow_type].to_s == "watch_mode"
|
|
1626
|
+
|
|
1627
|
+
parts.compact
|
|
1628
|
+
end
|
|
1629
|
+
|
|
1630
|
+
def step_position_label(step_name, context)
|
|
1631
|
+
steps = Array(context[:selected_steps]).map(&:to_s)
|
|
1632
|
+
steps = Aidp::Execute::Steps::SPEC.keys if steps.empty?
|
|
1633
|
+
steps = [step_name] if steps.empty?
|
|
1634
|
+
steps << step_name unless steps.include?(step_name)
|
|
1635
|
+
|
|
1636
|
+
index = steps.index(step_name)
|
|
1637
|
+
return nil unless index
|
|
1638
|
+
|
|
1639
|
+
"Step #{index + 1}/#{steps.size} (#{step_name})"
|
|
1640
|
+
end
|
|
1641
|
+
|
|
1642
|
+
def issue_context_label(context)
|
|
1643
|
+
issue_number = context[:issue_number] ||
|
|
1644
|
+
context.dig(:issue, :number) ||
|
|
1645
|
+
extract_number_from_url(context[:issue_url] || context.dig(:issue, :url) || context.dig(:user_input, "Issue URL"), /issues\/(\d+)/)
|
|
1646
|
+
|
|
1647
|
+
return nil unless issue_number
|
|
1648
|
+
|
|
1649
|
+
"Issue ##{issue_number}"
|
|
1650
|
+
end
|
|
1651
|
+
|
|
1652
|
+
def pr_context_label(context)
|
|
1653
|
+
pr_number = context[:pr_number] ||
|
|
1654
|
+
context.dig(:pull_request, :number) ||
|
|
1655
|
+
extract_number_from_url(context[:pr_url] || context.dig(:pull_request, :url) || context.dig(:user_input, "PR URL") || context.dig(:user_input, "Pull Request URL"), /pull\/(\d+)/)
|
|
1656
|
+
|
|
1657
|
+
return nil unless pr_number
|
|
1658
|
+
|
|
1659
|
+
"PR ##{pr_number}"
|
|
1660
|
+
end
|
|
1661
|
+
|
|
1662
|
+
def extract_number_from_url(url, pattern)
|
|
1663
|
+
return nil unless url
|
|
1664
|
+
match = url.to_s.match(pattern)
|
|
1665
|
+
match && match[1]
|
|
1666
|
+
end
|
|
1667
|
+
|
|
1289
1668
|
# Append task completion requirement to PROMPT.md
|
|
1290
1669
|
def append_task_requirement_to_prompt(message)
|
|
1291
1670
|
task_requirement = []
|