aidp 0.19.1 → 0.21.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/cli/issue_importer.rb +130 -3
- data/lib/aidp/cli.rb +12 -0
- data/lib/aidp/harness/config_schema.rb +3 -0
- data/lib/aidp/harness/config_validator.rb +1 -0
- data/lib/aidp/harness/provider_manager.rb +50 -6
- data/lib/aidp/harness/state/persistence.rb +36 -19
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -1
- data/lib/aidp/init/doc_generator.rb +6 -0
- data/lib/aidp/logger.rb +13 -1
- data/lib/aidp/rescue_logging.rb +20 -9
- data/lib/aidp/setup/wizard.rb +277 -56
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/repository_client.rb +2 -0
- data/lib/aidp/watch/repository_safety_checker.rb +214 -0
- data/lib/aidp/watch/runner.rb +25 -1
- data/lib/aidp/workflows/guided_agent.rb +115 -15
- data/lib/aidp.rb +1 -0
- data/templates/planning/generate_llm_style_guide.md +97 -14
- metadata +2 -1
data/lib/aidp/setup/wizard.rb
CHANGED
|
@@ -30,6 +30,8 @@ module Aidp
|
|
|
30
30
|
|
|
31
31
|
def run
|
|
32
32
|
display_welcome
|
|
33
|
+
# Normalize any legacy or label-based model_family entries before prompting
|
|
34
|
+
normalize_existing_model_families!
|
|
33
35
|
return @saved if skip_wizard?
|
|
34
36
|
|
|
35
37
|
configure_providers
|
|
@@ -150,11 +152,30 @@ module Aidp
|
|
|
150
152
|
fallback_choices = available_providers.reject { |_, name| name == provider_choice }
|
|
151
153
|
fallback_default_names = existing_fallbacks.filter_map { |provider_name| fallback_choices.key(provider_name) }
|
|
152
154
|
|
|
155
|
+
prompt.say("\n💡 Use ↑/↓ arrows to navigate, SPACE to select/deselect, ENTER to confirm")
|
|
153
156
|
fallback_selected = prompt.multi_select("Select fallback providers (used if primary fails):", default: fallback_default_names) do |menu|
|
|
154
157
|
fallback_choices.each do |display_name, provider_name|
|
|
155
158
|
menu.choice display_name, provider_name
|
|
156
159
|
end
|
|
157
160
|
end
|
|
161
|
+
if ENV["AIDP_FALLBACK_DEBUG"] == "1"
|
|
162
|
+
prompt.say("[debug] raw multi_select fallback_selected=#{fallback_selected.inspect}")
|
|
163
|
+
end
|
|
164
|
+
# Recovery: if multi_select unexpectedly returns empty and there were no existing fallbacks, offer a single-select
|
|
165
|
+
if fallback_selected.empty? && existing_fallbacks.empty? && !fallback_choices.empty?
|
|
166
|
+
if ENV["AIDP_FALLBACK_DEBUG"] == "1"
|
|
167
|
+
prompt.say("[debug] invoking recovery single-select for first fallback")
|
|
168
|
+
end
|
|
169
|
+
if prompt.yes?("No fallback selected. Add one?", default: true)
|
|
170
|
+
recovery_choice = prompt.select("Select a fallback provider:") do |menu|
|
|
171
|
+
fallback_choices.each do |display_name, provider_name|
|
|
172
|
+
menu.choice display_name, provider_name
|
|
173
|
+
end
|
|
174
|
+
menu.choice "Skip", :skip
|
|
175
|
+
end
|
|
176
|
+
fallback_selected = [recovery_choice] unless recovery_choice == :skip
|
|
177
|
+
end
|
|
178
|
+
end
|
|
158
179
|
|
|
159
180
|
# If user selected none but we had existing fallbacks, confirm removal
|
|
160
181
|
if fallback_selected.empty? && existing_fallbacks.any?
|
|
@@ -167,10 +188,81 @@ module Aidp
|
|
|
167
188
|
set([:harness, :fallback_providers], cleaned_fallbacks)
|
|
168
189
|
|
|
169
190
|
# Auto-create minimal provider configs for fallbacks if missing
|
|
170
|
-
cleaned_fallbacks.each
|
|
191
|
+
cleaned_fallbacks.each do |fp|
|
|
192
|
+
prompt.say("[debug] ensuring billing config for fallback '#{fp}'") if ENV["AIDP_FALLBACK_DEBUG"] == "1"
|
|
193
|
+
ensure_provider_billing_config(fp, force: true)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Offer editing of existing provider configurations (primary + fallbacks)
|
|
197
|
+
# (editable will be recomputed after any additional fallback additions)
|
|
198
|
+
([provider_choice] + cleaned_fallbacks).uniq.reject { |p| p == "custom" }
|
|
199
|
+
|
|
200
|
+
# Optional: allow adding more fallbacks iteratively
|
|
201
|
+
if prompt.yes?("Add another fallback provider?", default: false)
|
|
202
|
+
loop do
|
|
203
|
+
remaining = available_providers.reject { |_, name| ([provider_choice] + cleaned_fallbacks).include?(name) }
|
|
204
|
+
break if remaining.empty?
|
|
205
|
+
add_choice = prompt.select("Select additional fallback provider:") do |menu|
|
|
206
|
+
remaining.each { |display, name| menu.choice display, name }
|
|
207
|
+
menu.choice "Done", :done
|
|
208
|
+
end
|
|
209
|
+
break if add_choice == :done
|
|
210
|
+
unless cleaned_fallbacks.include?(add_choice)
|
|
211
|
+
cleaned_fallbacks << add_choice
|
|
212
|
+
set([:harness, :fallback_providers], cleaned_fallbacks)
|
|
213
|
+
ensure_provider_billing_config(add_choice, force: true)
|
|
214
|
+
end
|
|
215
|
+
end
|
|
216
|
+
end
|
|
217
|
+
# Recompute editable after additions
|
|
218
|
+
editable = ([provider_choice] + cleaned_fallbacks).uniq.reject { |p| p == "custom" }
|
|
219
|
+
if editable.any? && prompt.yes?("Edit provider configuration details (billing/model family)?", default: false)
|
|
220
|
+
loop do
|
|
221
|
+
# Build dynamic mapping of display names -> internal names for edit menu
|
|
222
|
+
available_map = discover_available_providers # {display_name => internal_name}
|
|
223
|
+
display_name_for = available_map.invert # {internal_name => display_name}
|
|
224
|
+
to_edit = prompt.select("Select a provider to edit or add:") do |menu|
|
|
225
|
+
editable.each do |prov|
|
|
226
|
+
display_label = display_name_for.fetch(prov, prov.capitalize)
|
|
227
|
+
menu.choice display_label, prov
|
|
228
|
+
end
|
|
229
|
+
# Sentinel option: add a new fallback provider that isn't yet in editable list
|
|
230
|
+
remaining = available_map.values - editable
|
|
231
|
+
if remaining.any?
|
|
232
|
+
menu.choice "➕ Add fallback provider…", :add_fallback
|
|
233
|
+
end
|
|
234
|
+
menu.choice "Done", :done
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
case to_edit
|
|
238
|
+
when :done
|
|
239
|
+
break
|
|
240
|
+
when :add_fallback
|
|
241
|
+
# Allow user to pick from remaining providers by display name
|
|
242
|
+
remaining_map = available_map.select { |disp, internal| !editable.include?(internal) && internal != provider_choice }
|
|
243
|
+
add_choice = prompt.select("Select provider to add as fallback:") do |menu|
|
|
244
|
+
remaining_map.each { |disp, internal| menu.choice disp, internal }
|
|
245
|
+
menu.choice "Cancel", :cancel
|
|
246
|
+
end
|
|
247
|
+
next if add_choice == :cancel
|
|
248
|
+
unless cleaned_fallbacks.include?(add_choice)
|
|
249
|
+
cleaned_fallbacks << add_choice
|
|
250
|
+
set([:harness, :fallback_providers], cleaned_fallbacks)
|
|
251
|
+
prompt.say("[debug] ensuring billing config for newly added fallback '#{add_choice}'") if ENV["AIDP_FALLBACK_DEBUG"] == "1"
|
|
252
|
+
ensure_provider_billing_config(add_choice, force: true)
|
|
253
|
+
editable = ([provider_choice] + cleaned_fallbacks).uniq.reject { |p| p == "custom" }
|
|
254
|
+
end
|
|
255
|
+
else
|
|
256
|
+
edit_provider_configuration(to_edit)
|
|
257
|
+
end
|
|
258
|
+
end
|
|
259
|
+
end
|
|
171
260
|
|
|
172
261
|
# Provide informational note (no secret handling stored)
|
|
173
262
|
show_provider_info_note(provider_choice) unless provider_choice == "custom"
|
|
263
|
+
|
|
264
|
+
# Show summary of configured providers (replaces the earlier inline summary)
|
|
265
|
+
show_provider_summary(provider_choice, cleaned_fallbacks) unless provider_choice == "custom"
|
|
174
266
|
end
|
|
175
267
|
|
|
176
268
|
# Removed MCP configuration step (MCP now expected to be provider-specific if used)
|
|
@@ -262,13 +354,19 @@ module Aidp
|
|
|
262
354
|
enabled = prompt.yes?("Enable coverage tracking?", default: existing.fetch(:enabled, false))
|
|
263
355
|
return set([:work_loop, :coverage], {enabled: false}) unless enabled
|
|
264
356
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
357
|
+
coverage_tool_choices = [
|
|
358
|
+
["SimpleCov (Ruby)", "simplecov"],
|
|
359
|
+
["NYC/Istanbul (JavaScript)", "nyc"],
|
|
360
|
+
["Coverage.py (Python)", "coverage.py"],
|
|
361
|
+
["go test -cover (Go)", "go-cover"],
|
|
362
|
+
["Jest (JavaScript)", "jest"],
|
|
363
|
+
["Other", "other"]
|
|
364
|
+
]
|
|
365
|
+
coverage_tool_default = existing[:tool]
|
|
366
|
+
coverage_tool_default_label = coverage_tool_choices.find { |label, value| value == coverage_tool_default }&.first
|
|
367
|
+
|
|
368
|
+
tool = prompt.select("Which coverage tool do you use?", default: coverage_tool_default_label) do |menu|
|
|
369
|
+
coverage_tool_choices.each { |label, value| menu.choice label, value }
|
|
272
370
|
end
|
|
273
371
|
|
|
274
372
|
run_command = ask_with_default("Coverage run command", existing[:run_command] || detect_coverage_command(tool))
|
|
@@ -300,10 +398,16 @@ module Aidp
|
|
|
300
398
|
enabled = prompt.yes?("Enable interactive testing tools?", default: existing.fetch(:enabled, false))
|
|
301
399
|
return set([:work_loop, :interactive_testing], {enabled: false}) unless enabled
|
|
302
400
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
401
|
+
app_type_choices = [
|
|
402
|
+
["Web application", "web"],
|
|
403
|
+
["CLI application", "cli"],
|
|
404
|
+
["Desktop application", "desktop"]
|
|
405
|
+
]
|
|
406
|
+
app_type_default = existing[:app_type]
|
|
407
|
+
app_type_default_label = app_type_choices.find { |label, value| value == app_type_default }&.first
|
|
408
|
+
|
|
409
|
+
app_type = prompt.select("What type of application are you testing?", default: app_type_default_label) do |menu|
|
|
410
|
+
app_type_choices.each { |label, value| menu.choice label, value }
|
|
307
411
|
end
|
|
308
412
|
|
|
309
413
|
tools = {}
|
|
@@ -382,17 +486,21 @@ module Aidp
|
|
|
382
486
|
|
|
383
487
|
# Detect VCS
|
|
384
488
|
detected_vcs = detect_vcs_tool
|
|
489
|
+
vcs_choices = [
|
|
490
|
+
["git", "git"],
|
|
491
|
+
["svn", "svn"],
|
|
492
|
+
["none (no VCS)", "none"]
|
|
493
|
+
]
|
|
494
|
+
vcs_default = existing[:tool] || detected_vcs || "git"
|
|
495
|
+
vcs_default_label = vcs_choices.find { |label, value| value == vcs_default }&.first
|
|
496
|
+
|
|
385
497
|
vcs_tool = if detected_vcs
|
|
386
|
-
prompt.select("Detected #{detected_vcs}. Use this version control system?", default:
|
|
387
|
-
menu.choice
|
|
388
|
-
menu.choice "svn", "svn"
|
|
389
|
-
menu.choice "none (no VCS)", "none"
|
|
498
|
+
prompt.select("Detected #{detected_vcs}. Use this version control system?", default: vcs_default_label) do |menu|
|
|
499
|
+
vcs_choices.each { |label, value| menu.choice label, value }
|
|
390
500
|
end
|
|
391
501
|
else
|
|
392
|
-
prompt.select("Which version control system do you use?", default:
|
|
393
|
-
menu.choice
|
|
394
|
-
menu.choice "svn", "svn"
|
|
395
|
-
menu.choice "none (no VCS)", "none"
|
|
502
|
+
prompt.select("Which version control system do you use?", default: vcs_default_label) do |menu|
|
|
503
|
+
vcs_choices.each { |label, value| menu.choice label, value }
|
|
396
504
|
end
|
|
397
505
|
end
|
|
398
506
|
|
|
@@ -400,10 +508,18 @@ module Aidp
|
|
|
400
508
|
|
|
401
509
|
prompt.say("\n📋 Commit Behavior (applies to copilot/interactive mode only)")
|
|
402
510
|
prompt.say("Note: Watch mode and fully automatic daemon mode will always commit changes.")
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
511
|
+
|
|
512
|
+
# Map value defaults to choice labels for TTY::Prompt validation
|
|
513
|
+
behavior_choices = [
|
|
514
|
+
["Do nothing (manual git operations)", "nothing"],
|
|
515
|
+
["Stage changes only", "stage"],
|
|
516
|
+
["Stage and commit changes", "commit"]
|
|
517
|
+
]
|
|
518
|
+
behavior_default = existing[:behavior] || "nothing"
|
|
519
|
+
behavior_default_label = behavior_choices.find { |label, value| value == behavior_default }&.first
|
|
520
|
+
|
|
521
|
+
behavior = prompt.select("In copilot mode, should aidp:", default: behavior_default_label) do |menu|
|
|
522
|
+
behavior_choices.each { |label, value| menu.choice label, value }
|
|
407
523
|
end
|
|
408
524
|
|
|
409
525
|
# Commit message configuration
|
|
@@ -437,10 +553,16 @@ module Aidp
|
|
|
437
553
|
|
|
438
554
|
# Commit message style
|
|
439
555
|
commit_style = if conventional_commits
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
556
|
+
commit_style_choices = [
|
|
557
|
+
["Default (e.g., 'feat: add user authentication')", "default"],
|
|
558
|
+
["Angular (with scope: 'feat(auth): add login')", "angular"],
|
|
559
|
+
["Emoji (e.g., '✨ feat: add user authentication')", "emoji"]
|
|
560
|
+
]
|
|
561
|
+
commit_style_default = existing[:commit_style] || "default"
|
|
562
|
+
commit_style_default_label = commit_style_choices.find { |label, value| value == commit_style_default }&.first
|
|
563
|
+
|
|
564
|
+
prompt.select("Conventional commit style:", default: commit_style_default_label) do |menu|
|
|
565
|
+
commit_style_choices.each { |label, value| menu.choice label, value }
|
|
444
566
|
end
|
|
445
567
|
else
|
|
446
568
|
"default"
|
|
@@ -476,10 +598,16 @@ module Aidp
|
|
|
476
598
|
)
|
|
477
599
|
|
|
478
600
|
if auto_create_pr
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
601
|
+
pr_strategy_choices = [
|
|
602
|
+
["Create as draft PR (safe, allows review before merge)", "draft"],
|
|
603
|
+
["Create as ready PR (immediately reviewable)", "ready"],
|
|
604
|
+
["Create and auto-merge (fully autonomous, requires approval rules)", "auto_merge"]
|
|
605
|
+
]
|
|
606
|
+
pr_strategy_default = existing[:pr_strategy] || "draft"
|
|
607
|
+
pr_strategy_default_label = pr_strategy_choices.find { |label, value| value == pr_strategy_default }&.first
|
|
608
|
+
|
|
609
|
+
pr_strategy = prompt.select("PR creation strategy:", default: pr_strategy_default_label) do |menu|
|
|
610
|
+
pr_strategy_choices.each { |label, value| menu.choice label, value }
|
|
483
611
|
end
|
|
484
612
|
|
|
485
613
|
{
|
|
@@ -614,11 +742,16 @@ module Aidp
|
|
|
614
742
|
prompt.say("-" * 40)
|
|
615
743
|
existing = get([:logging]) || {}
|
|
616
744
|
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
745
|
+
log_level_choices = [
|
|
746
|
+
["Debug", "debug"],
|
|
747
|
+
["Info", "info"],
|
|
748
|
+
["Error", "error"]
|
|
749
|
+
]
|
|
750
|
+
log_level_default = existing[:level] || "info"
|
|
751
|
+
log_level_default_label = log_level_choices.find { |label, value| value == log_level_default }&.first
|
|
752
|
+
|
|
753
|
+
log_level = prompt.select("Log level:", default: log_level_default_label) do |menu|
|
|
754
|
+
log_level_choices.each { |label, value| menu.choice label, value }
|
|
622
755
|
end
|
|
623
756
|
json = prompt.yes?("Use JSON log format?", default: existing.fetch(:json, false))
|
|
624
757
|
max_size = ask_with_default("Max log size (MB)", (existing[:max_size_mb] || 10).to_s) { |value| value.to_i }
|
|
@@ -897,44 +1030,132 @@ module Aidp
|
|
|
897
1030
|
prompt.say("Only the billing model (subscription vs usage_based) is recorded for fallback decisions.")
|
|
898
1031
|
end
|
|
899
1032
|
|
|
1033
|
+
def show_provider_summary(primary, fallbacks)
|
|
1034
|
+
prompt.say("\n📋 Provider Configuration Summary:")
|
|
1035
|
+
providers_config = get([:providers]) || {}
|
|
1036
|
+
|
|
1037
|
+
# Show primary
|
|
1038
|
+
if primary && primary != "custom"
|
|
1039
|
+
primary_cfg = providers_config[primary.to_sym] || {}
|
|
1040
|
+
prompt.say(" ✓ Primary: #{primary} (#{primary_cfg[:type] || "not configured"}, #{primary_cfg[:model_family] || "auto"})")
|
|
1041
|
+
end
|
|
1042
|
+
|
|
1043
|
+
# Show fallbacks
|
|
1044
|
+
if fallbacks && !fallbacks.empty?
|
|
1045
|
+
fallbacks.each do |fallback|
|
|
1046
|
+
fallback_cfg = providers_config[fallback.to_sym] || {}
|
|
1047
|
+
prompt.say(" ✓ Fallback: #{fallback} (#{fallback_cfg[:type] || "not configured"}, #{fallback_cfg[:model_family] || "auto"})")
|
|
1048
|
+
end
|
|
1049
|
+
end
|
|
1050
|
+
end
|
|
1051
|
+
|
|
900
1052
|
# Ensure a minimal billing configuration exists for a selected provider (no secrets)
|
|
901
|
-
def ensure_provider_billing_config(provider_name)
|
|
1053
|
+
def ensure_provider_billing_config(provider_name, force: false)
|
|
902
1054
|
return if provider_name.nil? || provider_name == "custom"
|
|
903
1055
|
providers_section = get([:providers]) || {}
|
|
904
1056
|
existing = providers_section[provider_name.to_sym]
|
|
905
1057
|
|
|
906
|
-
if existing && existing[:type]
|
|
1058
|
+
if existing && existing[:type] && !force
|
|
907
1059
|
prompt.say(" • Provider '#{provider_name}' already configured (type: #{existing[:type]})")
|
|
908
|
-
# Still ask for model family if not set
|
|
909
1060
|
unless existing[:model_family]
|
|
910
|
-
model_family = ask_model_family(provider_name
|
|
1061
|
+
model_family = ask_model_family(provider_name)
|
|
911
1062
|
set([:providers, provider_name.to_sym, :model_family], model_family)
|
|
912
1063
|
end
|
|
913
1064
|
return
|
|
914
1065
|
end
|
|
915
1066
|
|
|
916
|
-
provider_type =
|
|
917
|
-
model_family = ask_model_family(provider_name)
|
|
918
|
-
|
|
919
|
-
|
|
1067
|
+
provider_type = ask_provider_billing_type_with_default(provider_name, existing&.dig(:type))
|
|
1068
|
+
model_family = ask_model_family(provider_name, existing&.dig(:model_family) || "auto")
|
|
1069
|
+
merged = (existing || {}).merge(type: provider_type, model_family: model_family)
|
|
1070
|
+
set([:providers, provider_name.to_sym], merged)
|
|
1071
|
+
normalize_existing_model_families!
|
|
1072
|
+
action_word = if existing
|
|
1073
|
+
force ? "reconfigured" : "updated"
|
|
1074
|
+
else
|
|
1075
|
+
"added"
|
|
1076
|
+
end
|
|
1077
|
+
# Enhance messaging with display name when available
|
|
1078
|
+
display_name = discover_available_providers.invert.fetch(provider_name, provider_name)
|
|
1079
|
+
prompt.say(" • #{action_word.capitalize} provider '#{display_name}' (#{provider_name}) with billing type '#{provider_type}' and model family '#{model_family}'")
|
|
1080
|
+
end
|
|
1081
|
+
|
|
1082
|
+
def edit_provider_configuration(provider_name)
|
|
1083
|
+
existing = get([:providers, provider_name.to_sym]) || {}
|
|
1084
|
+
prompt.say("\n🔧 Editing provider '#{provider_name}' (current: type=#{existing[:type] || "unset"}, model_family=#{existing[:model_family] || "unset"})")
|
|
1085
|
+
new_type = ask_provider_billing_type_with_default(provider_name, existing[:type])
|
|
1086
|
+
new_family = ask_model_family(provider_name, existing[:model_family] || "auto")
|
|
1087
|
+
set([:providers, provider_name.to_sym], {type: new_type, model_family: new_family})
|
|
1088
|
+
# Normalize immediately so tests relying on canonical value see 'claude' rather than label
|
|
1089
|
+
normalize_existing_model_families!
|
|
1090
|
+
prompt.ok("Updated '#{provider_name}' → type=#{new_type}, model_family=#{new_family}")
|
|
920
1091
|
end
|
|
921
1092
|
|
|
922
1093
|
def ask_provider_billing_type(provider_name)
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
1094
|
+
ask_provider_billing_type_with_default(provider_name, nil)
|
|
1095
|
+
end
|
|
1096
|
+
|
|
1097
|
+
BILLING_TYPE_CHOICES = [
|
|
1098
|
+
["Subscription / flat-rate", "subscription"],
|
|
1099
|
+
["Usage-based / metered (API)", "usage_based"],
|
|
1100
|
+
["Passthrough / local (no billing)", "passthrough"]
|
|
1101
|
+
].freeze
|
|
1102
|
+
|
|
1103
|
+
def ask_provider_billing_type_with_default(provider_name, default_value)
|
|
1104
|
+
default_label = BILLING_TYPE_CHOICES.find { |label, value| value == default_value }&.first
|
|
1105
|
+
suffix = default_value ? " (current: #{default_value})" : ""
|
|
1106
|
+
prompt.select("Billing model for #{provider_name}:#{suffix}", default: default_label) do |menu|
|
|
1107
|
+
BILLING_TYPE_CHOICES.each do |label, value|
|
|
1108
|
+
menu.choice(label, value)
|
|
1109
|
+
end
|
|
928
1110
|
end
|
|
929
1111
|
end
|
|
930
1112
|
|
|
1113
|
+
MODEL_FAMILY_CHOICES = [
|
|
1114
|
+
["Auto (let provider decide)", "auto"],
|
|
1115
|
+
["OpenAI o-series (reasoning models)", "openai_o"],
|
|
1116
|
+
["Anthropic Claude (balanced)", "claude"],
|
|
1117
|
+
["Mistral (European/open)", "mistral"],
|
|
1118
|
+
["Local LLM (self-hosted)", "local"]
|
|
1119
|
+
].freeze
|
|
1120
|
+
|
|
931
1121
|
def ask_model_family(provider_name, default = "auto")
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
1122
|
+
# TTY::Prompt validates defaults against the displayed choice labels, not values.
|
|
1123
|
+
# Map the value default (e.g. "auto") to its corresponding label.
|
|
1124
|
+
default_label = MODEL_FAMILY_CHOICES.find { |label, value| value == default }&.first
|
|
1125
|
+
|
|
1126
|
+
prompt.select("Preferred model family for #{provider_name}:", default: default_label) do |menu|
|
|
1127
|
+
MODEL_FAMILY_CHOICES.each do |label, value|
|
|
1128
|
+
menu.choice(label, value)
|
|
1129
|
+
end
|
|
1130
|
+
end
|
|
1131
|
+
end
|
|
1132
|
+
|
|
1133
|
+
# Canonicalization helpers ------------------------------------------------
|
|
1134
|
+
MODEL_FAMILY_LABEL_TO_VALUE = MODEL_FAMILY_CHOICES.each_with_object({}) do |(label, value), h|
|
|
1135
|
+
h[label] = value
|
|
1136
|
+
end.freeze
|
|
1137
|
+
MODEL_FAMILY_VALUES = MODEL_FAMILY_CHOICES.map { |(_, value)| value }.freeze
|
|
1138
|
+
|
|
1139
|
+
def normalize_model_family(value)
|
|
1140
|
+
return "auto" if value.nil? || value.to_s.strip.empty?
|
|
1141
|
+
# Already a canonical value
|
|
1142
|
+
return value if MODEL_FAMILY_VALUES.include?(value)
|
|
1143
|
+
# Try label -> value
|
|
1144
|
+
mapped = MODEL_FAMILY_LABEL_TO_VALUE[value]
|
|
1145
|
+
return mapped if mapped
|
|
1146
|
+
# Unknown legacy entry -> fallback to auto
|
|
1147
|
+
"auto"
|
|
1148
|
+
end
|
|
1149
|
+
|
|
1150
|
+
def normalize_existing_model_families!
|
|
1151
|
+
providers_cfg = @config[:providers]
|
|
1152
|
+
return unless providers_cfg.is_a?(Hash)
|
|
1153
|
+
providers_cfg.each do |prov_name, prov_cfg|
|
|
1154
|
+
next unless prov_cfg.is_a?(Hash)
|
|
1155
|
+
mf = prov_cfg[:model_family]
|
|
1156
|
+
# Normalize and write back only if different to avoid unnecessary YAML churn
|
|
1157
|
+
normalized = normalize_model_family(mf)
|
|
1158
|
+
prov_cfg[:model_family] = normalized
|
|
938
1159
|
end
|
|
939
1160
|
end
|
|
940
1161
|
|
data/lib/aidp/version.rb
CHANGED
|
@@ -209,6 +209,7 @@ module Aidp
|
|
|
209
209
|
number: raw["number"],
|
|
210
210
|
title: raw["title"],
|
|
211
211
|
body: raw["body"] || "",
|
|
212
|
+
author: raw.dig("author", "login") || raw["author"],
|
|
212
213
|
comments: Array(raw["comments"]).map { |comment| normalize_comment(comment) },
|
|
213
214
|
labels: Array(raw["labels"]).map { |label| label.is_a?(Hash) ? label["name"] : label },
|
|
214
215
|
state: raw["state"],
|
|
@@ -223,6 +224,7 @@ module Aidp
|
|
|
223
224
|
number: raw["number"],
|
|
224
225
|
title: raw["title"],
|
|
225
226
|
body: raw["body"] || "",
|
|
227
|
+
author: raw.dig("user", "login"),
|
|
226
228
|
comments: Array(raw["comments"]).map { |comment| normalize_comment(comment) },
|
|
227
229
|
labels: Array(raw["labels"]).map { |label| label["name"] },
|
|
228
230
|
state: raw["state"],
|