aidp 0.23.0 ā 0.25.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/README.md +27 -1
- data/lib/aidp/auto_update/bundler_adapter.rb +66 -0
- data/lib/aidp/auto_update/checkpoint.rb +178 -0
- data/lib/aidp/auto_update/checkpoint_store.rb +182 -0
- data/lib/aidp/auto_update/coordinator.rb +204 -0
- data/lib/aidp/auto_update/errors.rb +17 -0
- data/lib/aidp/auto_update/failure_tracker.rb +162 -0
- data/lib/aidp/auto_update/rubygems_api_adapter.rb +95 -0
- data/lib/aidp/auto_update/update_check.rb +106 -0
- data/lib/aidp/auto_update/update_logger.rb +143 -0
- data/lib/aidp/auto_update/update_policy.rb +109 -0
- data/lib/aidp/auto_update/version_detector.rb +144 -0
- data/lib/aidp/auto_update.rb +52 -0
- data/lib/aidp/cli.rb +168 -1
- 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 +75 -0
- data/lib/aidp/harness/configuration.rb +69 -6
- data/lib/aidp/harness/error_handler.rb +117 -44
- data/lib/aidp/harness/provider_factory.rb +2 -0
- 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/message_display.rb +10 -2
- data/lib/aidp/prompt_optimization/style_guide_indexer.rb +3 -1
- data/lib/aidp/provider_manager.rb +2 -0
- 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/providers/kilocode.rb +202 -0
- data/lib/aidp/setup/provider_registry.rb +122 -0
- data/lib/aidp/setup/wizard.rb +125 -33
- data/lib/aidp/skills/composer.rb +4 -0
- data/lib/aidp/skills/loader.rb +3 -1
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +323 -33
- data/lib/aidp/watch/ci_fix_processor.rb +448 -0
- data/lib/aidp/watch/plan_processor.rb +12 -2
- data/lib/aidp/watch/repository_client.rb +384 -4
- data/lib/aidp/watch/review_processor.rb +266 -0
- data/lib/aidp/watch/reviewers/base_reviewer.rb +164 -0
- data/lib/aidp/watch/reviewers/performance_reviewer.rb +65 -0
- data/lib/aidp/watch/reviewers/security_reviewer.rb +65 -0
- data/lib/aidp/watch/reviewers/senior_dev_reviewer.rb +33 -0
- data/lib/aidp/watch/runner.rb +222 -5
- data/lib/aidp/watch/state_store.rb +53 -0
- data/lib/aidp/workflows/guided_agent.rb +53 -0
- data/lib/aidp/worktree.rb +67 -10
- data/lib/aidp.rb +1 -0
- data/templates/work_loop/decide_whats_next.md +21 -0
- data/templates/work_loop/diagnose_failures.md +21 -0
- metadata +29 -3
- /data/{bin ā exe}/aidp +0 -0
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,24 +1120,83 @@ 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
|
+
]
|
|
1150
|
+
end
|
|
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.")
|
|
1132
1174
|
end
|
|
1175
|
+
else
|
|
1176
|
+
prompt.say(" (No providers configured)")
|
|
1133
1177
|
end
|
|
1134
1178
|
end
|
|
1135
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
|
|
1198
|
+
end
|
|
1199
|
+
|
|
1136
1200
|
# Ensure a minimal billing configuration exists for a selected provider (no secrets)
|
|
1137
1201
|
def ensure_provider_billing_config(provider_name, force: false)
|
|
1138
1202
|
return if provider_name.nil? || provider_name == "custom"
|
|
@@ -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,55 +1278,47 @@ 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
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1308
|
+
|
|
1309
|
+
normalized_input = value.to_s.strip.downcase
|
|
1310
|
+
|
|
1311
|
+
# Check for exact canonical value match (case-insensitive)
|
|
1312
|
+
canonical_match = ProviderRegistry.model_family_values.find do |v|
|
|
1313
|
+
v.downcase == normalized_input
|
|
1314
|
+
end
|
|
1315
|
+
return canonical_match if canonical_match
|
|
1316
|
+
|
|
1317
|
+
# Try label -> value mapping (case-insensitive)
|
|
1318
|
+
choices = ProviderRegistry.model_family_choices
|
|
1319
|
+
mapped = choices.find { |label, _| label.downcase == value.to_s.downcase }&.last
|
|
1229
1320
|
return mapped if mapped
|
|
1321
|
+
|
|
1230
1322
|
# Unknown legacy entry -> fallback to auto
|
|
1231
1323
|
"auto"
|
|
1232
1324
|
end
|
data/lib/aidp/skills/composer.rb
CHANGED
|
@@ -82,6 +82,8 @@ module Aidp
|
|
|
82
82
|
def render_template(template, options: {})
|
|
83
83
|
return template if options.empty?
|
|
84
84
|
|
|
85
|
+
# Ensure template is UTF-8 encoded
|
|
86
|
+
template = template.encode("UTF-8", invalid: :replace, undef: :replace) unless template.encoding == Encoding::UTF_8
|
|
85
87
|
rendered = template.dup
|
|
86
88
|
|
|
87
89
|
options.each do |key, value|
|
|
@@ -158,6 +160,8 @@ module Aidp
|
|
|
158
160
|
def extract_placeholders(text)
|
|
159
161
|
return [] if text.nil? || text.empty?
|
|
160
162
|
|
|
163
|
+
# Ensure text is UTF-8 encoded
|
|
164
|
+
text = text.encode("UTF-8", invalid: :replace, undef: :replace) unless text.encoding == Encoding::UTF_8
|
|
161
165
|
scanner = StringScanner.new(text)
|
|
162
166
|
placeholders = []
|
|
163
167
|
|
data/lib/aidp/skills/loader.rb
CHANGED
|
@@ -34,7 +34,7 @@ module Aidp
|
|
|
34
34
|
raise Aidp::Errors::ValidationError, "Skill file not found: #{file_path}"
|
|
35
35
|
end
|
|
36
36
|
|
|
37
|
-
content = File.read(file_path)
|
|
37
|
+
content = File.read(file_path, encoding: "UTF-8")
|
|
38
38
|
load_from_string(content, source_path: file_path, provider: provider)
|
|
39
39
|
end
|
|
40
40
|
|
|
@@ -139,6 +139,8 @@ module Aidp
|
|
|
139
139
|
# @return [Array(Hash, String)] Tuple of [metadata, markdown_content]
|
|
140
140
|
# @raise [Aidp::Errors::ValidationError] if frontmatter is missing or invalid
|
|
141
141
|
def self.parse_frontmatter(content, source_path:)
|
|
142
|
+
# Ensure content is UTF-8 encoded
|
|
143
|
+
content = content.encode("UTF-8", invalid: :replace, undef: :replace) unless content.encoding == Encoding::UTF_8
|
|
142
144
|
lines = content.lines
|
|
143
145
|
|
|
144
146
|
unless lines.first&.strip == "---"
|
data/lib/aidp/version.rb
CHANGED