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.
- checksums.yaml +4 -4
- data/lib/aidp/cli.rb +3 -0
- data/lib/aidp/execute/work_loop_runner.rb +252 -45
- data/lib/aidp/execute/work_loop_unit_scheduler.rb +27 -2
- data/lib/aidp/harness/condition_detector.rb +42 -8
- data/lib/aidp/harness/config_manager.rb +7 -0
- data/lib/aidp/harness/config_schema.rb +25 -0
- data/lib/aidp/harness/configuration.rb +69 -6
- data/lib/aidp/harness/error_handler.rb +117 -44
- data/lib/aidp/harness/provider_manager.rb +64 -0
- data/lib/aidp/harness/provider_metrics.rb +138 -0
- data/lib/aidp/harness/runner.rb +90 -29
- data/lib/aidp/harness/simple_user_interface.rb +4 -0
- data/lib/aidp/harness/state/ui_state.rb +0 -10
- data/lib/aidp/harness/state_manager.rb +1 -15
- data/lib/aidp/harness/test_runner.rb +39 -2
- data/lib/aidp/logger.rb +34 -4
- data/lib/aidp/providers/adapter.rb +241 -0
- data/lib/aidp/providers/anthropic.rb +75 -7
- data/lib/aidp/providers/base.rb +29 -1
- data/lib/aidp/providers/capability_registry.rb +205 -0
- data/lib/aidp/providers/codex.rb +14 -0
- data/lib/aidp/providers/error_taxonomy.rb +195 -0
- data/lib/aidp/providers/gemini.rb +3 -2
- data/lib/aidp/setup/provider_registry.rb +107 -0
- data/lib/aidp/setup/wizard.rb +115 -31
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +263 -23
- data/lib/aidp/watch/repository_client.rb +4 -4
- data/lib/aidp/watch/runner.rb +37 -5
- data/lib/aidp/workflows/guided_agent.rb +53 -0
- data/lib/aidp/worktree.rb +67 -10
- data/templates/work_loop/decide_whats_next.md +21 -0
- data/templates/work_loop/diagnose_failures.md +21 -0
- metadata +10 -3
- /data/{bin → exe}/aidp +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Aidp
|
|
4
|
+
module Setup
|
|
5
|
+
# Centralized registry for provider metadata including billing types and model families.
|
|
6
|
+
# This module provides a single source of truth for provider configuration options.
|
|
7
|
+
module ProviderRegistry
|
|
8
|
+
# Billing type options for providers
|
|
9
|
+
BILLING_TYPES = [
|
|
10
|
+
{
|
|
11
|
+
label: "Subscription / flat-rate",
|
|
12
|
+
value: "subscription",
|
|
13
|
+
description: "Monthly or annual subscription with unlimited usage"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
label: "Usage-based / metered (API)",
|
|
17
|
+
value: "usage_based",
|
|
18
|
+
description: "Pay per API call or token usage"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
label: "Passthrough / local (no billing)",
|
|
22
|
+
value: "passthrough",
|
|
23
|
+
description: "Local execution or proxy without direct billing"
|
|
24
|
+
}
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
# Model family options for providers
|
|
28
|
+
MODEL_FAMILIES = [
|
|
29
|
+
{
|
|
30
|
+
label: "Auto (let provider decide)",
|
|
31
|
+
value: "auto",
|
|
32
|
+
description: "Use provider's default model selection"
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
label: "OpenAI o-series (reasoning models)",
|
|
36
|
+
value: "openai_o",
|
|
37
|
+
description: "Advanced reasoning capabilities, slower but more thorough"
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
label: "Anthropic Claude (balanced)",
|
|
41
|
+
value: "claude",
|
|
42
|
+
description: "Balanced performance for general-purpose tasks"
|
|
43
|
+
},
|
|
44
|
+
{
|
|
45
|
+
label: "Mistral (European/open)",
|
|
46
|
+
value: "mistral",
|
|
47
|
+
description: "European provider with open-source focus"
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
label: "Local LLM (self-hosted)",
|
|
51
|
+
value: "local",
|
|
52
|
+
description: "Self-hosted or local model execution"
|
|
53
|
+
}
|
|
54
|
+
].freeze
|
|
55
|
+
|
|
56
|
+
# Returns array of [label, value] pairs for billing types
|
|
57
|
+
def self.billing_type_choices
|
|
58
|
+
BILLING_TYPES.map { |bt| [bt[:label], bt[:value]] }
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns array of [label, value] pairs for model families
|
|
62
|
+
def self.model_family_choices
|
|
63
|
+
MODEL_FAMILIES.map { |mf| [mf[:label], mf[:value]] }
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Finds label for a given billing type value
|
|
67
|
+
def self.billing_type_label(value)
|
|
68
|
+
BILLING_TYPES.find { |bt| bt[:value] == value }&.dig(:label) || value
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# Finds label for a given model family value
|
|
72
|
+
def self.model_family_label(value)
|
|
73
|
+
MODEL_FAMILIES.find { |mf| mf[:value] == value }&.dig(:label) || value
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Finds description for a given billing type value
|
|
77
|
+
def self.billing_type_description(value)
|
|
78
|
+
BILLING_TYPES.find { |bt| bt[:value] == value }&.dig(:description)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Finds description for a given model family value
|
|
82
|
+
def self.model_family_description(value)
|
|
83
|
+
MODEL_FAMILIES.find { |mf| mf[:value] == value }&.dig(:description)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Validates if a billing type value is valid
|
|
87
|
+
def self.valid_billing_type?(value)
|
|
88
|
+
BILLING_TYPES.any? { |bt| bt[:value] == value }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Validates if a model family value is valid
|
|
92
|
+
def self.valid_model_family?(value)
|
|
93
|
+
MODEL_FAMILIES.any? { |mf| mf[:value] == value }
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
# Returns all valid billing type values
|
|
97
|
+
def self.billing_type_values
|
|
98
|
+
BILLING_TYPES.map { |bt| bt[:value] }
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Returns all valid model family values
|
|
102
|
+
def self.model_family_values
|
|
103
|
+
MODEL_FAMILIES.map { |mf| mf[:value] }
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
data/lib/aidp/setup/wizard.rb
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "tty-prompt"
|
|
4
|
+
require "tty-table"
|
|
4
5
|
require "yaml"
|
|
5
6
|
require "time"
|
|
6
7
|
require "fileutils"
|
|
@@ -8,6 +9,7 @@ require "json"
|
|
|
8
9
|
|
|
9
10
|
require_relative "../util"
|
|
10
11
|
require_relative "../config/paths"
|
|
12
|
+
require_relative "provider_registry"
|
|
11
13
|
require_relative "devcontainer/parser"
|
|
12
14
|
require_relative "devcontainer/generator"
|
|
13
15
|
require_relative "devcontainer/port_manager"
|
|
@@ -260,7 +262,10 @@ module Aidp
|
|
|
260
262
|
editable = ([provider_choice] + cleaned_fallbacks).uniq.reject { |p| p == "custom" }
|
|
261
263
|
end
|
|
262
264
|
else
|
|
263
|
-
|
|
265
|
+
# Edit the selected provider or offer to remove it
|
|
266
|
+
edit_or_remove_provider(to_edit, provider_choice, cleaned_fallbacks)
|
|
267
|
+
# Refresh editable list after potential removal
|
|
268
|
+
editable = ([provider_choice] + cleaned_fallbacks).uniq.reject { |p| p == "custom" }
|
|
264
269
|
end
|
|
265
270
|
end
|
|
266
271
|
end
|
|
@@ -1115,22 +1120,81 @@ module Aidp
|
|
|
1115
1120
|
end
|
|
1116
1121
|
|
|
1117
1122
|
def show_provider_summary(primary, fallbacks)
|
|
1123
|
+
Aidp.log_debug("wizard.provider_summary", "displaying provider configuration table", primary: primary, fallback_count: fallbacks&.size || 0)
|
|
1118
1124
|
prompt.say("\n📋 Provider Configuration Summary:")
|
|
1119
1125
|
providers_config = get([:providers]) || {}
|
|
1120
1126
|
|
|
1121
|
-
|
|
1127
|
+
rows = []
|
|
1128
|
+
|
|
1129
|
+
# Add primary provider to table
|
|
1122
1130
|
if primary && primary != "custom"
|
|
1123
1131
|
primary_cfg = providers_config[primary.to_sym] || {}
|
|
1124
|
-
|
|
1132
|
+
rows << [
|
|
1133
|
+
"Primary",
|
|
1134
|
+
primary,
|
|
1135
|
+
primary_cfg[:type] || "not configured",
|
|
1136
|
+
primary_cfg[:model_family] || "auto"
|
|
1137
|
+
]
|
|
1125
1138
|
end
|
|
1126
1139
|
|
|
1127
|
-
#
|
|
1140
|
+
# Add fallback providers to table
|
|
1128
1141
|
if fallbacks && !fallbacks.empty?
|
|
1129
|
-
fallbacks.
|
|
1142
|
+
fallbacks.each_with_index do |fallback, index|
|
|
1130
1143
|
fallback_cfg = providers_config[fallback.to_sym] || {}
|
|
1131
|
-
|
|
1144
|
+
rows << [
|
|
1145
|
+
"Fallback #{index + 1}",
|
|
1146
|
+
fallback,
|
|
1147
|
+
fallback_cfg[:type] || "not configured",
|
|
1148
|
+
fallback_cfg[:model_family] || "auto"
|
|
1149
|
+
]
|
|
1132
1150
|
end
|
|
1133
1151
|
end
|
|
1152
|
+
|
|
1153
|
+
# Detect duplicate providers with identical characteristics
|
|
1154
|
+
duplicates = detect_duplicate_providers(rows)
|
|
1155
|
+
if duplicates.any?
|
|
1156
|
+
Aidp.log_warn("wizard.provider_summary", "duplicate provider configurations detected", duplicates: duplicates)
|
|
1157
|
+
end
|
|
1158
|
+
|
|
1159
|
+
if rows.any?
|
|
1160
|
+
table = TTY::Table.new(
|
|
1161
|
+
header: ["Role", "Provider", "Billing Type", "Model Family"],
|
|
1162
|
+
rows: rows
|
|
1163
|
+
)
|
|
1164
|
+
prompt.say(table.render(:unicode, padding: [0, 1]))
|
|
1165
|
+
|
|
1166
|
+
# Show warning for duplicates
|
|
1167
|
+
if duplicates.any?
|
|
1168
|
+
prompt.say("")
|
|
1169
|
+
prompt.warn("⚠️ Duplicate configurations detected:")
|
|
1170
|
+
duplicates.each do |dup|
|
|
1171
|
+
prompt.say(" • #{dup[:providers].join(" and ")} have identical billing type (#{dup[:type]}) and model family (#{dup[:family]})")
|
|
1172
|
+
end
|
|
1173
|
+
prompt.say(" Consider using different providers or model families for better redundancy.")
|
|
1174
|
+
end
|
|
1175
|
+
else
|
|
1176
|
+
prompt.say(" (No providers configured)")
|
|
1177
|
+
end
|
|
1178
|
+
end
|
|
1179
|
+
|
|
1180
|
+
def detect_duplicate_providers(rows)
|
|
1181
|
+
# Group providers by their billing type and model family
|
|
1182
|
+
# Returns array of duplicate groups with identical characteristics
|
|
1183
|
+
duplicates = []
|
|
1184
|
+
config_groups = rows.group_by { |row| [row[2], row[3]] }
|
|
1185
|
+
|
|
1186
|
+
config_groups.each do |(type, family), group|
|
|
1187
|
+
next if group.size < 2
|
|
1188
|
+
next if type == "not configured" # Skip unconfigured providers
|
|
1189
|
+
|
|
1190
|
+
duplicates << {
|
|
1191
|
+
providers: group.map { |row| row[1] },
|
|
1192
|
+
type: type,
|
|
1193
|
+
family: family
|
|
1194
|
+
}
|
|
1195
|
+
end
|
|
1196
|
+
|
|
1197
|
+
duplicates
|
|
1134
1198
|
end
|
|
1135
1199
|
|
|
1136
1200
|
# Ensure a minimal billing configuration exists for a selected provider (no secrets)
|
|
@@ -1163,6 +1227,42 @@ module Aidp
|
|
|
1163
1227
|
prompt.say(" • #{action_word.capitalize} provider '#{display_name}' (#{provider_name}) with billing type '#{provider_type}' and model family '#{model_family}'")
|
|
1164
1228
|
end
|
|
1165
1229
|
|
|
1230
|
+
def edit_or_remove_provider(provider_name, primary_provider, fallbacks)
|
|
1231
|
+
is_primary = (provider_name == primary_provider)
|
|
1232
|
+
display_name = discover_available_providers.invert.fetch(provider_name, provider_name)
|
|
1233
|
+
|
|
1234
|
+
action = prompt.select("What would you like to do with '#{display_name}'?") do |menu|
|
|
1235
|
+
menu.choice "Edit configuration", :edit
|
|
1236
|
+
unless is_primary
|
|
1237
|
+
menu.choice "Remove from configuration", :remove
|
|
1238
|
+
end
|
|
1239
|
+
menu.choice "Cancel", :cancel
|
|
1240
|
+
end
|
|
1241
|
+
|
|
1242
|
+
case action
|
|
1243
|
+
when :edit
|
|
1244
|
+
edit_provider_configuration(provider_name)
|
|
1245
|
+
when :remove
|
|
1246
|
+
if is_primary
|
|
1247
|
+
prompt.warn("Cannot remove primary provider. Change primary provider first.")
|
|
1248
|
+
else
|
|
1249
|
+
remove_fallback_provider(provider_name, fallbacks)
|
|
1250
|
+
end
|
|
1251
|
+
when :cancel
|
|
1252
|
+
Aidp.log_debug("wizard.edit_provider", "user cancelled edit operation", provider: provider_name)
|
|
1253
|
+
end
|
|
1254
|
+
end
|
|
1255
|
+
|
|
1256
|
+
def remove_fallback_provider(provider_name, fallbacks)
|
|
1257
|
+
display_name = discover_available_providers.invert.fetch(provider_name, provider_name)
|
|
1258
|
+
if prompt.yes?("Remove '#{display_name}' from fallback providers?", default: false)
|
|
1259
|
+
fallbacks.delete(provider_name)
|
|
1260
|
+
set([:harness, :fallback_providers], fallbacks)
|
|
1261
|
+
Aidp.log_info("wizard.remove_provider", "removed fallback provider", provider: provider_name)
|
|
1262
|
+
prompt.ok("Removed '#{display_name}' from fallback providers")
|
|
1263
|
+
end
|
|
1264
|
+
end
|
|
1265
|
+
|
|
1166
1266
|
def edit_provider_configuration(provider_name)
|
|
1167
1267
|
existing = get([:providers, provider_name.to_sym]) || {}
|
|
1168
1268
|
prompt.say("\n🔧 Editing provider '#{provider_name}' (current: type=#{existing[:type] || "unset"}, model_family=#{existing[:model_family] || "unset"})")
|
|
@@ -1178,54 +1278,38 @@ module Aidp
|
|
|
1178
1278
|
ask_provider_billing_type_with_default(provider_name, nil)
|
|
1179
1279
|
end
|
|
1180
1280
|
|
|
1181
|
-
BILLING_TYPE_CHOICES = [
|
|
1182
|
-
["Subscription / flat-rate", "subscription"],
|
|
1183
|
-
["Usage-based / metered (API)", "usage_based"],
|
|
1184
|
-
["Passthrough / local (no billing)", "passthrough"]
|
|
1185
|
-
].freeze
|
|
1186
|
-
|
|
1187
1281
|
def ask_provider_billing_type_with_default(provider_name, default_value)
|
|
1188
|
-
|
|
1282
|
+
choices = ProviderRegistry.billing_type_choices
|
|
1283
|
+
default_label = choices.find { |label, value| value == default_value }&.first
|
|
1189
1284
|
suffix = default_value ? " (current: #{default_value})" : ""
|
|
1190
1285
|
prompt.select("Billing model for #{provider_name}:#{suffix}", default: default_label) do |menu|
|
|
1191
|
-
|
|
1286
|
+
choices.each do |label, value|
|
|
1192
1287
|
menu.choice(label, value)
|
|
1193
1288
|
end
|
|
1194
1289
|
end
|
|
1195
1290
|
end
|
|
1196
1291
|
|
|
1197
|
-
MODEL_FAMILY_CHOICES = [
|
|
1198
|
-
["Auto (let provider decide)", "auto"],
|
|
1199
|
-
["OpenAI o-series (reasoning models)", "openai_o"],
|
|
1200
|
-
["Anthropic Claude (balanced)", "claude"],
|
|
1201
|
-
["Mistral (European/open)", "mistral"],
|
|
1202
|
-
["Local LLM (self-hosted)", "local"]
|
|
1203
|
-
].freeze
|
|
1204
|
-
|
|
1205
1292
|
def ask_model_family(provider_name, default = "auto")
|
|
1206
1293
|
# TTY::Prompt validates defaults against the displayed choice labels, not values.
|
|
1207
1294
|
# Map the value default (e.g. "auto") to its corresponding label.
|
|
1208
|
-
|
|
1295
|
+
choices = ProviderRegistry.model_family_choices
|
|
1296
|
+
default_label = choices.find { |label, value| value == default }&.first
|
|
1209
1297
|
|
|
1210
1298
|
prompt.select("Preferred model family for #{provider_name}:", default: default_label) do |menu|
|
|
1211
|
-
|
|
1299
|
+
choices.each do |label, value|
|
|
1212
1300
|
menu.choice(label, value)
|
|
1213
1301
|
end
|
|
1214
1302
|
end
|
|
1215
1303
|
end
|
|
1216
1304
|
|
|
1217
1305
|
# Canonicalization helpers ------------------------------------------------
|
|
1218
|
-
MODEL_FAMILY_LABEL_TO_VALUE = MODEL_FAMILY_CHOICES.each_with_object({}) do |(label, value), h|
|
|
1219
|
-
h[label] = value
|
|
1220
|
-
end.freeze
|
|
1221
|
-
MODEL_FAMILY_VALUES = MODEL_FAMILY_CHOICES.map { |(_, value)| value }.freeze
|
|
1222
|
-
|
|
1223
1306
|
def normalize_model_family(value)
|
|
1224
1307
|
return "auto" if value.nil? || value.to_s.strip.empty?
|
|
1225
1308
|
# Already a canonical value
|
|
1226
|
-
return value if
|
|
1309
|
+
return value if ProviderRegistry.valid_model_family?(value)
|
|
1227
1310
|
# Try label -> value
|
|
1228
|
-
|
|
1311
|
+
choices = ProviderRegistry.model_family_choices
|
|
1312
|
+
mapped = choices.find { |label, _| label == value }&.last
|
|
1229
1313
|
return mapped if mapped
|
|
1230
1314
|
# Unknown legacy entry -> fallback to auto
|
|
1231
1315
|
"auto"
|
data/lib/aidp/version.rb
CHANGED