aidp 0.27.0 → 0.28.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 +89 -0
- data/lib/aidp/cli/models_command.rb +5 -6
- data/lib/aidp/cli.rb +10 -8
- data/lib/aidp/config.rb +54 -0
- data/lib/aidp/debug_mixin.rb +23 -1
- data/lib/aidp/execute/agent_signal_parser.rb +22 -0
- data/lib/aidp/execute/repl_macros.rb +2 -2
- data/lib/aidp/execute/steps.rb +94 -1
- data/lib/aidp/execute/work_loop_runner.rb +209 -17
- data/lib/aidp/execute/workflow_selector.rb +2 -25
- data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
- data/lib/aidp/harness/ai_decision_engine.rb +35 -2
- data/lib/aidp/harness/config_manager.rb +0 -5
- data/lib/aidp/harness/config_schema.rb +8 -0
- data/lib/aidp/harness/configuration.rb +27 -19
- data/lib/aidp/harness/enhanced_runner.rb +1 -4
- data/lib/aidp/harness/error_handler.rb +1 -72
- data/lib/aidp/harness/provider_factory.rb +11 -2
- data/lib/aidp/harness/state_manager.rb +0 -7
- data/lib/aidp/harness/thinking_depth_manager.rb +47 -68
- data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
- data/lib/aidp/harness/ui/progress_display.rb +6 -2
- data/lib/aidp/harness/user_interface.rb +0 -58
- data/lib/aidp/init/runner.rb +7 -2
- data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
- data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
- data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
- data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
- data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
- data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
- data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
- data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
- data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
- data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
- data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
- data/lib/aidp/planning/parsers/document_parser.rb +141 -0
- data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
- data/lib/aidp/provider_manager.rb +8 -32
- data/lib/aidp/providers/aider.rb +264 -0
- data/lib/aidp/providers/anthropic.rb +74 -2
- data/lib/aidp/providers/base.rb +25 -1
- data/lib/aidp/providers/codex.rb +26 -3
- data/lib/aidp/providers/cursor.rb +16 -0
- data/lib/aidp/providers/gemini.rb +13 -0
- data/lib/aidp/providers/github_copilot.rb +17 -0
- data/lib/aidp/providers/kilocode.rb +11 -0
- data/lib/aidp/providers/opencode.rb +11 -0
- data/lib/aidp/setup/wizard.rb +249 -39
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/build_processor.rb +211 -30
- data/lib/aidp/watch/change_request_processor.rb +128 -14
- data/lib/aidp/watch/ci_fix_processor.rb +103 -37
- data/lib/aidp/watch/ci_log_extractor.rb +258 -0
- data/lib/aidp/watch/github_state_extractor.rb +177 -0
- data/lib/aidp/watch/implementation_verifier.rb +284 -0
- data/lib/aidp/watch/plan_generator.rb +7 -43
- data/lib/aidp/watch/plan_processor.rb +7 -6
- data/lib/aidp/watch/repository_client.rb +245 -17
- data/lib/aidp/watch/review_processor.rb +98 -17
- data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
- data/lib/aidp/watch/runner.rb +181 -29
- data/lib/aidp/watch/state_store.rb +22 -1
- data/lib/aidp/workflows/definitions.rb +147 -0
- data/lib/aidp/workstream_cleanup.rb +245 -0
- data/lib/aidp/worktree.rb +19 -0
- data/templates/aidp.yml.example +57 -0
- data/templates/implementation/generate_tdd_specs.md +213 -0
- data/templates/implementation/iterative_implementation.md +122 -0
- data/templates/planning/agile/analyze_feedback.md +183 -0
- data/templates/planning/agile/generate_iteration_plan.md +179 -0
- data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
- data/templates/planning/agile/generate_marketing_report.md +162 -0
- data/templates/planning/agile/generate_mvp_scope.md +127 -0
- data/templates/planning/agile/generate_user_test_plan.md +143 -0
- data/templates/planning/agile/ingest_feedback.md +174 -0
- data/templates/planning/assemble_project_plan.md +113 -0
- data/templates/planning/assign_personas.md +108 -0
- data/templates/planning/create_tasks.md +52 -6
- data/templates/planning/generate_gantt.md +86 -0
- data/templates/planning/generate_wbs.md +85 -0
- data/templates/planning/initialize_planning_mode.md +70 -0
- data/templates/skills/README.md +2 -2
- data/templates/skills/marketing_strategist/SKILL.md +279 -0
- data/templates/skills/product_manager/SKILL.md +177 -0
- data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
- data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
- data/templates/skills/ux_researcher/SKILL.md +222 -0
- metadata +39 -1
data/lib/aidp/setup/wizard.rb
CHANGED
|
@@ -308,8 +308,12 @@ module Aidp
|
|
|
308
308
|
end
|
|
309
309
|
|
|
310
310
|
# Check if user wants to use automated discovery
|
|
311
|
-
|
|
312
|
-
|
|
311
|
+
has_existing_tiers = all_providers.any? do |provider|
|
|
312
|
+
existing_tiers = get([:providers, provider.to_sym, :thinking_tiers])
|
|
313
|
+
existing_tiers && !existing_tiers.empty?
|
|
314
|
+
end
|
|
315
|
+
|
|
316
|
+
if has_existing_tiers
|
|
313
317
|
prompt.say("📝 Found existing tier configuration")
|
|
314
318
|
unless prompt.yes?("Would you like to update it with discovered models?", default: false)
|
|
315
319
|
return
|
|
@@ -332,16 +336,19 @@ module Aidp
|
|
|
332
336
|
# Display discovered models
|
|
333
337
|
display_discovered_models(discovered_models)
|
|
334
338
|
|
|
335
|
-
# Generate tier configuration
|
|
336
|
-
|
|
339
|
+
# Generate tier configuration (now provider-specific)
|
|
340
|
+
tier_configs = generate_provider_tier_configurations(discovered_models)
|
|
337
341
|
|
|
338
342
|
# Show preview
|
|
339
343
|
prompt.say("\n📋 Proposed tier configuration:")
|
|
340
|
-
|
|
344
|
+
display_provider_tier_preview(tier_configs)
|
|
341
345
|
|
|
342
346
|
# Confirm and save
|
|
343
347
|
if prompt.yes?("\nSave this tier configuration?", default: true)
|
|
344
|
-
|
|
348
|
+
# Write to provider-specific paths
|
|
349
|
+
tier_configs.each do |provider, provider_tiers|
|
|
350
|
+
set([:providers, provider.to_sym, :thinking_tiers], provider_tiers)
|
|
351
|
+
end
|
|
345
352
|
prompt.ok("✅ Thinking tiers configured successfully")
|
|
346
353
|
else
|
|
347
354
|
prompt.say("💡 Skipped tier configuration. You can run 'aidp models discover' later")
|
|
@@ -474,38 +481,30 @@ module Aidp
|
|
|
474
481
|
end
|
|
475
482
|
end
|
|
476
483
|
|
|
477
|
-
def
|
|
478
|
-
|
|
484
|
+
def generate_provider_tier_configurations(discovered_models)
|
|
485
|
+
provider_configs = {}
|
|
479
486
|
|
|
480
|
-
#
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
# Collect primary provider models first (if available)
|
|
485
|
-
primary_models = find_models_for_tier(discovered_models[primary_provider], tier)
|
|
486
|
-
tier_models.concat(primary_models) if primary_models&.any?
|
|
487
|
+
# Organize by provider first, then by tier
|
|
488
|
+
discovered_models.each do |provider, models|
|
|
489
|
+
provider_tiers = {}
|
|
487
490
|
|
|
488
|
-
#
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
provider_models = find_models_for_tier(models, tier)
|
|
492
|
-
tier_models.concat(provider_models) if provider_models&.any?
|
|
493
|
-
end
|
|
491
|
+
# Configure the three most common tiers: mini, standard, and pro
|
|
492
|
+
DEFAULT_AUTOCONFIG_TIERS.each do |tier|
|
|
493
|
+
tier_models = find_models_for_tier(models, tier)
|
|
494
494
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
{
|
|
500
|
-
provider: m[:provider],
|
|
501
|
-
model: m[:name]
|
|
502
|
-
}
|
|
495
|
+
# Add to config if we found any models for this tier
|
|
496
|
+
if tier_models.any?
|
|
497
|
+
provider_tiers[tier.to_sym] = {
|
|
498
|
+
models: tier_models.map { |m| m[:name] }
|
|
503
499
|
}
|
|
504
|
-
|
|
500
|
+
end
|
|
505
501
|
end
|
|
502
|
+
|
|
503
|
+
# Only add provider if it has at least one tier configured
|
|
504
|
+
provider_configs[provider] = provider_tiers if provider_tiers.any?
|
|
506
505
|
end
|
|
507
506
|
|
|
508
|
-
|
|
507
|
+
provider_configs
|
|
509
508
|
end
|
|
510
509
|
|
|
511
510
|
def find_model_for_tier(models, target_tier)
|
|
@@ -520,16 +519,19 @@ module Aidp
|
|
|
520
519
|
models.select { |m| m[:tier] == target_tier }
|
|
521
520
|
end
|
|
522
521
|
|
|
523
|
-
def
|
|
524
|
-
return if
|
|
522
|
+
def display_provider_tier_preview(provider_configs)
|
|
523
|
+
return if provider_configs.empty?
|
|
525
524
|
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
prompt.say("
|
|
531
|
-
|
|
525
|
+
provider_configs.each do |provider, provider_tiers|
|
|
526
|
+
prompt.say(" #{provider}:")
|
|
527
|
+
provider_tiers.each do |tier, tier_config|
|
|
528
|
+
models = tier_config[:models] || []
|
|
529
|
+
prompt.say(" #{tier}:")
|
|
530
|
+
models.each do |model_name|
|
|
531
|
+
prompt.say(" - #{model_name}")
|
|
532
|
+
end
|
|
532
533
|
end
|
|
534
|
+
prompt.say("") # Blank line between providers
|
|
533
535
|
end
|
|
534
536
|
end
|
|
535
537
|
|
|
@@ -1066,6 +1068,7 @@ module Aidp
|
|
|
1066
1068
|
|
|
1067
1069
|
configure_watch_safety
|
|
1068
1070
|
configure_watch_labels
|
|
1071
|
+
configure_watch_label_creation
|
|
1069
1072
|
end
|
|
1070
1073
|
|
|
1071
1074
|
def configure_watch_safety
|
|
@@ -1148,6 +1151,213 @@ module Aidp
|
|
|
1148
1151
|
})
|
|
1149
1152
|
end
|
|
1150
1153
|
|
|
1154
|
+
def configure_watch_label_creation
|
|
1155
|
+
prompt.say("\n🏷️ GitHub Label Auto-Creation")
|
|
1156
|
+
prompt.say(" Automatically create GitHub labels for watch mode if they don't exist")
|
|
1157
|
+
|
|
1158
|
+
Aidp.log_debug("setup_wizard.label_creation", "start")
|
|
1159
|
+
|
|
1160
|
+
# Ask if user wants to auto-create labels
|
|
1161
|
+
unless prompt.yes?("Auto-create GitHub labels if missing?", default: true)
|
|
1162
|
+
Aidp.log_debug("setup_wizard.label_creation", "user_declined")
|
|
1163
|
+
return
|
|
1164
|
+
end
|
|
1165
|
+
|
|
1166
|
+
# Check if gh CLI is available
|
|
1167
|
+
unless gh_cli_available?
|
|
1168
|
+
prompt.warn("⚠️ GitHub CLI (gh) not found. Install it to enable label auto-creation.")
|
|
1169
|
+
prompt.say(" Visit: https://cli.github.com/")
|
|
1170
|
+
Aidp.log_debug("setup_wizard.label_creation", "gh_not_available")
|
|
1171
|
+
return
|
|
1172
|
+
end
|
|
1173
|
+
|
|
1174
|
+
# Extract repository info
|
|
1175
|
+
repo_info = extract_repo_info
|
|
1176
|
+
unless repo_info
|
|
1177
|
+
prompt.warn("⚠️ Could not determine GitHub repository from git remote.")
|
|
1178
|
+
prompt.say(" Ensure you're in a git repository with a GitHub remote configured.")
|
|
1179
|
+
Aidp.log_debug("setup_wizard.label_creation", "repo_info_failed")
|
|
1180
|
+
return
|
|
1181
|
+
end
|
|
1182
|
+
|
|
1183
|
+
owner, repo = repo_info
|
|
1184
|
+
Aidp.log_debug("setup_wizard.label_creation", "repo_detected", owner: owner, repo: repo)
|
|
1185
|
+
prompt.say("📦 Repository: #{owner}/#{repo}")
|
|
1186
|
+
|
|
1187
|
+
# Fetch existing labels
|
|
1188
|
+
existing_labels = fetch_existing_labels(owner, repo)
|
|
1189
|
+
unless existing_labels
|
|
1190
|
+
prompt.warn("⚠️ Failed to fetch existing labels. Check your GitHub authentication.")
|
|
1191
|
+
Aidp.log_debug("setup_wizard.label_creation", "fetch_labels_failed")
|
|
1192
|
+
return
|
|
1193
|
+
end
|
|
1194
|
+
|
|
1195
|
+
Aidp.log_debug("setup_wizard.label_creation", "existing_labels_fetched", count: existing_labels.size)
|
|
1196
|
+
|
|
1197
|
+
# Get configured label names
|
|
1198
|
+
labels_config = get([:watch, :labels]) || {}
|
|
1199
|
+
required_labels = collect_required_labels(labels_config)
|
|
1200
|
+
|
|
1201
|
+
# Determine which labels need to be created
|
|
1202
|
+
labels_to_create = required_labels.reject { |label| existing_labels.include?(label[:name]) }
|
|
1203
|
+
|
|
1204
|
+
if labels_to_create.empty?
|
|
1205
|
+
prompt.ok("✅ All required labels already exist!")
|
|
1206
|
+
Aidp.log_debug("setup_wizard.label_creation", "all_labels_exist")
|
|
1207
|
+
return
|
|
1208
|
+
end
|
|
1209
|
+
|
|
1210
|
+
# Show labels to be created
|
|
1211
|
+
prompt.say("\n📝 Labels to create:")
|
|
1212
|
+
labels_to_create.each do |label|
|
|
1213
|
+
prompt.say(" • #{label[:name]} (#{label[:color]})")
|
|
1214
|
+
end
|
|
1215
|
+
|
|
1216
|
+
# Confirm creation
|
|
1217
|
+
unless prompt.yes?("Create these labels?", default: true)
|
|
1218
|
+
Aidp.log_debug("setup_wizard.label_creation", "creation_declined")
|
|
1219
|
+
return
|
|
1220
|
+
end
|
|
1221
|
+
|
|
1222
|
+
# Create labels
|
|
1223
|
+
create_labels(owner, repo, labels_to_create)
|
|
1224
|
+
end
|
|
1225
|
+
|
|
1226
|
+
# Check if gh CLI is available
|
|
1227
|
+
def gh_cli_available?
|
|
1228
|
+
require "open3"
|
|
1229
|
+
_stdout, _stderr, status = Open3.capture3("gh", "--version")
|
|
1230
|
+
Aidp.log_debug("setup_wizard.gh_check", "version_check", success: status.success?)
|
|
1231
|
+
status.success?
|
|
1232
|
+
rescue Errno::ENOENT
|
|
1233
|
+
Aidp.log_debug("setup_wizard.gh_check", "not_found")
|
|
1234
|
+
false
|
|
1235
|
+
end
|
|
1236
|
+
|
|
1237
|
+
# Extract repository owner and name from git remote
|
|
1238
|
+
def extract_repo_info
|
|
1239
|
+
require "open3"
|
|
1240
|
+
stdout, stderr, status = Open3.capture3("git", "remote", "get-url", "origin")
|
|
1241
|
+
|
|
1242
|
+
unless status.success?
|
|
1243
|
+
Aidp.log_debug("setup_wizard.repo_extraction", "git_remote_failed", error: stderr)
|
|
1244
|
+
return nil
|
|
1245
|
+
end
|
|
1246
|
+
|
|
1247
|
+
remote_url = stdout.strip
|
|
1248
|
+
Aidp.log_debug("setup_wizard.repo_extraction", "remote_url_found", url: remote_url)
|
|
1249
|
+
|
|
1250
|
+
# Parse GitHub URL (supports both HTTPS and SSH formats)
|
|
1251
|
+
# HTTPS: https://github.com/owner/repo.git
|
|
1252
|
+
# SSH: git@github.com:owner/repo.git
|
|
1253
|
+
if remote_url =~ %r{github\.com[:/]([^/]+)/(.+?)(?:\.git)?$}
|
|
1254
|
+
owner = Regexp.last_match(1)
|
|
1255
|
+
repo = Regexp.last_match(2)
|
|
1256
|
+
Aidp.log_debug("setup_wizard.repo_extraction", "parsed", owner: owner, repo: repo)
|
|
1257
|
+
[owner, repo]
|
|
1258
|
+
else
|
|
1259
|
+
Aidp.log_debug("setup_wizard.repo_extraction", "parse_failed", url: remote_url)
|
|
1260
|
+
nil
|
|
1261
|
+
end
|
|
1262
|
+
rescue => e
|
|
1263
|
+
Aidp.log_error("setup_wizard.repo_extraction", "exception", error: e.message)
|
|
1264
|
+
nil
|
|
1265
|
+
end
|
|
1266
|
+
|
|
1267
|
+
# Fetch existing labels from GitHub
|
|
1268
|
+
def fetch_existing_labels(owner, repo)
|
|
1269
|
+
require "open3"
|
|
1270
|
+
stdout, stderr, status = Open3.capture3("gh", "label", "list", "-R", "#{owner}/#{repo}", "--json", "name", "--jq", ".[].name")
|
|
1271
|
+
|
|
1272
|
+
unless status.success?
|
|
1273
|
+
Aidp.log_error("setup_wizard.fetch_labels", "gh_failed", error: stderr)
|
|
1274
|
+
return nil
|
|
1275
|
+
end
|
|
1276
|
+
|
|
1277
|
+
labels = stdout.strip.split("\n").map(&:strip).reject(&:empty?)
|
|
1278
|
+
Aidp.log_debug("setup_wizard.fetch_labels", "fetched", count: labels.size)
|
|
1279
|
+
labels
|
|
1280
|
+
rescue => e
|
|
1281
|
+
Aidp.log_error("setup_wizard.fetch_labels", "exception", error: e.message)
|
|
1282
|
+
nil
|
|
1283
|
+
end
|
|
1284
|
+
|
|
1285
|
+
# Collect required labels with their default colors
|
|
1286
|
+
def collect_required_labels(labels_config)
|
|
1287
|
+
default_colors = {
|
|
1288
|
+
plan_trigger: "0E8A16", # Green
|
|
1289
|
+
needs_input: "D93F0B", # Red
|
|
1290
|
+
ready_to_build: "0075CA", # Blue
|
|
1291
|
+
build_trigger: "5319E7", # Purple
|
|
1292
|
+
review_trigger: "FBCA04", # Yellow
|
|
1293
|
+
ci_fix_trigger: "D93F0B", # Red
|
|
1294
|
+
change_request_trigger: "F9D0C4", # Light pink
|
|
1295
|
+
in_progress: "1D76DB" # Dark blue (internal coordination)
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
required = []
|
|
1299
|
+
labels_config.each do |key, name|
|
|
1300
|
+
next if name.nil? || name.to_s.strip.empty?
|
|
1301
|
+
|
|
1302
|
+
color = default_colors[key] || "EDEDED" # Gray fallback
|
|
1303
|
+
required << {name: name, color: color, key: key}
|
|
1304
|
+
end
|
|
1305
|
+
|
|
1306
|
+
# Always include the internal in-progress label for coordination
|
|
1307
|
+
required << {
|
|
1308
|
+
name: "aidp-in-progress",
|
|
1309
|
+
color: default_colors[:in_progress],
|
|
1310
|
+
key: :in_progress,
|
|
1311
|
+
internal: true
|
|
1312
|
+
}
|
|
1313
|
+
|
|
1314
|
+
Aidp.log_debug("setup_wizard.collect_labels", "collected", count: required.size)
|
|
1315
|
+
required
|
|
1316
|
+
end
|
|
1317
|
+
|
|
1318
|
+
# Create labels on GitHub
|
|
1319
|
+
def create_labels(owner, repo, labels)
|
|
1320
|
+
require "open3"
|
|
1321
|
+
|
|
1322
|
+
created = 0
|
|
1323
|
+
failed = 0
|
|
1324
|
+
|
|
1325
|
+
labels.each do |label|
|
|
1326
|
+
Aidp.log_debug("setup_wizard.create_label", "creating", name: label[:name], color: label[:color])
|
|
1327
|
+
|
|
1328
|
+
_stdout, stderr, status = Open3.capture3(
|
|
1329
|
+
"gh", "label", "create", label[:name],
|
|
1330
|
+
"--color", label[:color],
|
|
1331
|
+
"-R", "#{owner}/#{repo}"
|
|
1332
|
+
)
|
|
1333
|
+
|
|
1334
|
+
if status.success?
|
|
1335
|
+
prompt.ok(" ✅ Created: #{label[:name]}")
|
|
1336
|
+
Aidp.log_info("setup_wizard.create_label", "success", name: label[:name])
|
|
1337
|
+
created += 1
|
|
1338
|
+
else
|
|
1339
|
+
prompt.warn(" ⚠️ Failed to create: #{label[:name]} - #{stderr.strip}")
|
|
1340
|
+
Aidp.log_error("setup_wizard.create_label", "failed", name: label[:name], error: stderr.strip)
|
|
1341
|
+
failed += 1
|
|
1342
|
+
end
|
|
1343
|
+
rescue => e
|
|
1344
|
+
prompt.warn(" ⚠️ Error creating #{label[:name]}: #{e.message}")
|
|
1345
|
+
Aidp.log_error("setup_wizard.create_label", "exception", name: label[:name], error: e.message)
|
|
1346
|
+
failed += 1
|
|
1347
|
+
end
|
|
1348
|
+
|
|
1349
|
+
# Summary
|
|
1350
|
+
prompt.say("")
|
|
1351
|
+
if created > 0
|
|
1352
|
+
prompt.ok("✅ Successfully created #{created} label#{"s" unless created == 1}")
|
|
1353
|
+
end
|
|
1354
|
+
if failed > 0
|
|
1355
|
+
prompt.warn("⚠️ Failed to create #{failed} label#{"s" unless failed == 1}")
|
|
1356
|
+
end
|
|
1357
|
+
|
|
1358
|
+
Aidp.log_info("setup_wizard.create_labels", "complete", created: created, failed: failed)
|
|
1359
|
+
end
|
|
1360
|
+
|
|
1151
1361
|
# -------------------------------------------
|
|
1152
1362
|
# Preview & persistence
|
|
1153
1363
|
# -------------------------------------------
|
data/lib/aidp/version.rb
CHANGED
|
@@ -10,6 +10,8 @@ require_relative "../harness/runner"
|
|
|
10
10
|
require_relative "../harness/state_manager"
|
|
11
11
|
require_relative "../worktree"
|
|
12
12
|
require_relative "../execute/progress"
|
|
13
|
+
require_relative "github_state_extractor"
|
|
14
|
+
require_relative "implementation_verifier"
|
|
13
15
|
|
|
14
16
|
module Aidp
|
|
15
17
|
module Watch
|
|
@@ -27,6 +29,8 @@ module Aidp
|
|
|
27
29
|
def initialize(repository_client:, state_store:, project_dir: Dir.pwd, use_workstreams: true, verbose: false, label_config: {})
|
|
28
30
|
@repository_client = repository_client
|
|
29
31
|
@state_store = state_store
|
|
32
|
+
@state_extractor = GitHubStateExtractor.new(repository_client: repository_client)
|
|
33
|
+
@verifier = ImplementationVerifier.new(repository_client: repository_client, project_dir: project_dir)
|
|
30
34
|
@project_dir = project_dir
|
|
31
35
|
@use_workstreams = use_workstreams
|
|
32
36
|
@verbose = verbose
|
|
@@ -40,7 +44,7 @@ module Aidp
|
|
|
40
44
|
number = issue[:number]
|
|
41
45
|
display_message("🛠️ Starting implementation for issue ##{number}", type: :info)
|
|
42
46
|
|
|
43
|
-
plan_data = ensure_plan_data(
|
|
47
|
+
plan_data = ensure_plan_data(issue)
|
|
44
48
|
return unless plan_data
|
|
45
49
|
|
|
46
50
|
slug = workstream_slug_for(issue)
|
|
@@ -87,27 +91,34 @@ module Aidp
|
|
|
87
91
|
backtrace: e.backtrace&.first(10)
|
|
88
92
|
)
|
|
89
93
|
|
|
90
|
-
#
|
|
91
|
-
|
|
94
|
+
# Record failure state internally but DON'T post error to GitHub
|
|
95
|
+
# (per issue #280 - error messages should never appear on issues)
|
|
96
|
+
@state_store.record_build_status(
|
|
97
|
+
issue[:number],
|
|
92
98
|
status: "error",
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
99
|
+
details: {
|
|
100
|
+
error: e.message,
|
|
101
|
+
error_class: e.class.name,
|
|
102
|
+
workstream: slug,
|
|
103
|
+
timestamp: Time.now.utc.iso8601
|
|
104
|
+
}
|
|
105
|
+
)
|
|
100
106
|
|
|
101
107
|
# Note: We intentionally DON'T re-raise here to allow watch mode to continue
|
|
102
|
-
# The error has been logged
|
|
108
|
+
# The error has been logged and recorded internally
|
|
103
109
|
end
|
|
104
110
|
|
|
105
111
|
private
|
|
106
112
|
|
|
107
|
-
def ensure_plan_data(
|
|
108
|
-
|
|
113
|
+
def ensure_plan_data(issue)
|
|
114
|
+
# First try to extract from the issue that was passed in (already has comments)
|
|
115
|
+
data = @state_extractor.extract_plan_data(issue)
|
|
116
|
+
|
|
117
|
+
# Fallback to local state store if needed (for backward compatibility)
|
|
118
|
+
data ||= @state_store.plan_data(issue[:number])
|
|
119
|
+
|
|
109
120
|
unless data
|
|
110
|
-
display_message("⚠️ No recorded plan for issue ##{number}. Skipping build trigger.", type: :warn)
|
|
121
|
+
display_message("⚠️ No recorded plan for issue ##{issue[:number]}. Skipping build trigger.", type: :warn)
|
|
111
122
|
end
|
|
112
123
|
data
|
|
113
124
|
end
|
|
@@ -419,11 +430,36 @@ module Aidp
|
|
|
419
430
|
return
|
|
420
431
|
end
|
|
421
432
|
|
|
433
|
+
# Verify implementation completeness before creating PR
|
|
434
|
+
verification = @verifier.verify(issue: issue, working_dir: working_dir)
|
|
435
|
+
|
|
436
|
+
unless verification[:verified]
|
|
437
|
+
handle_incomplete_implementation(
|
|
438
|
+
issue: issue,
|
|
439
|
+
slug: slug,
|
|
440
|
+
branch_name: branch_name,
|
|
441
|
+
working_dir: working_dir,
|
|
442
|
+
verification: verification
|
|
443
|
+
)
|
|
444
|
+
return
|
|
445
|
+
end
|
|
446
|
+
|
|
447
|
+
display_message("✅ Implementation verified complete", type: :success)
|
|
448
|
+
|
|
422
449
|
# Check if PR should be created based on VCS preferences
|
|
423
450
|
# For watch mode, default to creating PRs (set to false to disable)
|
|
424
451
|
vcs_config = config_dig(:work_loop, :version_control) || {}
|
|
425
452
|
auto_create_pr = config_value(vcs_config, :auto_create_pr, true)
|
|
426
453
|
|
|
454
|
+
Aidp.log_debug(
|
|
455
|
+
"build_processor",
|
|
456
|
+
"evaluating_pr_creation",
|
|
457
|
+
issue: issue[:number],
|
|
458
|
+
changes_committed: changes_committed,
|
|
459
|
+
auto_create_pr: auto_create_pr,
|
|
460
|
+
gh_available: @repository_client.gh_available?
|
|
461
|
+
)
|
|
462
|
+
|
|
427
463
|
pr_url = if !changes_committed
|
|
428
464
|
Aidp.log_info(
|
|
429
465
|
"build_processor",
|
|
@@ -441,14 +477,37 @@ module Aidp
|
|
|
441
477
|
issue: issue[:number],
|
|
442
478
|
branch: branch_name,
|
|
443
479
|
base_branch: base_branch,
|
|
444
|
-
working_dir: working_dir
|
|
480
|
+
working_dir: working_dir,
|
|
481
|
+
gh_available: @repository_client.gh_available?
|
|
445
482
|
)
|
|
446
483
|
create_pull_request(issue: issue, branch_name: branch_name, base_branch: base_branch, working_dir: working_dir)
|
|
447
484
|
else
|
|
485
|
+
Aidp.log_info(
|
|
486
|
+
"build_processor",
|
|
487
|
+
"skipping_pr_vcs_preference",
|
|
488
|
+
issue: issue[:number],
|
|
489
|
+
auto_create_pr: auto_create_pr
|
|
490
|
+
)
|
|
448
491
|
display_message("ℹ️ Skipping PR creation (disabled in VCS preferences)", type: :muted)
|
|
449
492
|
nil
|
|
450
493
|
end
|
|
451
494
|
|
|
495
|
+
# Record build status BEFORE posting completion comment
|
|
496
|
+
@state_store.record_build_status(
|
|
497
|
+
issue[:number],
|
|
498
|
+
status: "completed",
|
|
499
|
+
details: {branch: branch_name, workstream: slug, pr_url: pr_url}
|
|
500
|
+
)
|
|
501
|
+
|
|
502
|
+
# Remove build label BEFORE posting completion comment
|
|
503
|
+
begin
|
|
504
|
+
@repository_client.remove_labels(issue[:number], @build_label)
|
|
505
|
+
display_message("🏷️ Removed '#{@build_label}' label after completion", type: :info)
|
|
506
|
+
rescue => e
|
|
507
|
+
display_message("⚠️ Failed to remove build label: #{e.message}", type: :warn)
|
|
508
|
+
# Don't fail the process if label removal fails
|
|
509
|
+
end
|
|
510
|
+
|
|
452
511
|
# Fetch the user who added the most recent label
|
|
453
512
|
label_actor = @repository_client.most_recent_label_actor(issue[:number])
|
|
454
513
|
|
|
@@ -456,6 +515,7 @@ module Aidp
|
|
|
456
515
|
pr_line = pr_url ? "\n- Pull Request: #{pr_url}" : ""
|
|
457
516
|
actor_tag = label_actor ? "cc @#{label_actor}\n\n" : ""
|
|
458
517
|
|
|
518
|
+
# Post completion comment LAST - after all other operations complete successfully
|
|
459
519
|
comment = <<~COMMENT
|
|
460
520
|
✅ Implementation complete for ##{issue[:number]}.
|
|
461
521
|
|
|
@@ -466,22 +526,8 @@ module Aidp
|
|
|
466
526
|
COMMENT
|
|
467
527
|
|
|
468
528
|
@repository_client.post_comment(issue[:number], comment)
|
|
469
|
-
@state_store.record_build_status(
|
|
470
|
-
issue[:number],
|
|
471
|
-
status: "completed",
|
|
472
|
-
details: {branch: branch_name, workstream: slug, pr_url: pr_url}
|
|
473
|
-
)
|
|
474
529
|
display_message("🎉 Posted completion comment for issue ##{issue[:number]}", type: :success)
|
|
475
530
|
|
|
476
|
-
# Remove build label after successful completion
|
|
477
|
-
begin
|
|
478
|
-
@repository_client.remove_labels(issue[:number], @build_label)
|
|
479
|
-
display_message("🏷️ Removed '#{@build_label}' label after completion", type: :info)
|
|
480
|
-
rescue => e
|
|
481
|
-
display_message("⚠️ Failed to remove build label: #{e.message}", type: :warn)
|
|
482
|
-
# Don't fail the process if label removal fails
|
|
483
|
-
end
|
|
484
|
-
|
|
485
531
|
# Keep workstream for review - don't auto-cleanup on success
|
|
486
532
|
if @use_workstreams
|
|
487
533
|
display_message("ℹ️ Workstream #{slug} preserved for review. Remove with: aidp ws rm #{slug}", type: :muted)
|
|
@@ -624,6 +670,103 @@ module Aidp
|
|
|
624
670
|
)
|
|
625
671
|
end
|
|
626
672
|
|
|
673
|
+
def handle_incomplete_implementation(issue:, slug:, branch_name:, working_dir:, verification:)
|
|
674
|
+
display_message("⚠️ Implementation incomplete; scheduling additional work.", type: :warn)
|
|
675
|
+
|
|
676
|
+
# Create tasks for missing requirements
|
|
677
|
+
if verification[:additional_work] && !verification[:additional_work].empty?
|
|
678
|
+
create_follow_up_tasks(working_dir, verification[:additional_work])
|
|
679
|
+
end
|
|
680
|
+
|
|
681
|
+
# Schedule another agentic work unit
|
|
682
|
+
enqueue_decider_followup(working_dir)
|
|
683
|
+
|
|
684
|
+
workstream_note = @use_workstreams ? " The workstream `#{slug}` has been preserved for continued work." : " The branch has been preserved for continued work."
|
|
685
|
+
|
|
686
|
+
# Fetch the user who added the most recent label
|
|
687
|
+
label_actor = @repository_client.most_recent_label_actor(issue[:number])
|
|
688
|
+
actor_tag = label_actor ? "cc @#{label_actor}\n\n" : ""
|
|
689
|
+
|
|
690
|
+
# Post status comment
|
|
691
|
+
missing_section = if verification[:missing_items] && !verification[:missing_items].empty?
|
|
692
|
+
"\n### Missing Requirements\n" + verification[:missing_items].map { |item| "- #{item}" }.join("\n")
|
|
693
|
+
else
|
|
694
|
+
""
|
|
695
|
+
end
|
|
696
|
+
|
|
697
|
+
additional_work_section = if verification[:additional_work] && !verification[:additional_work].empty?
|
|
698
|
+
"\n### Additional Work Needed\n" + verification[:additional_work].map { |item| "- #{item}" }.join("\n")
|
|
699
|
+
else
|
|
700
|
+
""
|
|
701
|
+
end
|
|
702
|
+
|
|
703
|
+
comment = <<~COMMENT
|
|
704
|
+
🔄 Implementation in progress for ##{issue[:number]}.
|
|
705
|
+
|
|
706
|
+
#{actor_tag}The automated implementation has made progress but is not yet complete. Additional work is needed to fully address the issue requirements.
|
|
707
|
+
|
|
708
|
+
### Verification Result
|
|
709
|
+
#{verification[:reason]}#{missing_section}#{additional_work_section}
|
|
710
|
+
|
|
711
|
+
The work loop will continue automatically to complete the remaining tasks.#{workstream_note}
|
|
712
|
+
COMMENT
|
|
713
|
+
|
|
714
|
+
@repository_client.post_comment(issue[:number], comment)
|
|
715
|
+
display_message("💬 Posted incomplete implementation status for issue ##{issue[:number]}", type: :info)
|
|
716
|
+
|
|
717
|
+
@state_store.record_build_status(
|
|
718
|
+
issue[:number],
|
|
719
|
+
status: "incomplete",
|
|
720
|
+
details: {
|
|
721
|
+
branch: branch_name,
|
|
722
|
+
workstream: slug,
|
|
723
|
+
verification: verification
|
|
724
|
+
}
|
|
725
|
+
)
|
|
726
|
+
|
|
727
|
+
Aidp.log_info(
|
|
728
|
+
"build_processor",
|
|
729
|
+
"incomplete_implementation",
|
|
730
|
+
issue: issue[:number],
|
|
731
|
+
branch: branch_name,
|
|
732
|
+
workstream: slug,
|
|
733
|
+
missing_items: verification[:missing_items],
|
|
734
|
+
additional_work: verification[:additional_work]
|
|
735
|
+
)
|
|
736
|
+
end
|
|
737
|
+
|
|
738
|
+
def create_follow_up_tasks(working_dir, additional_work)
|
|
739
|
+
return if additional_work.nil? || additional_work.empty?
|
|
740
|
+
|
|
741
|
+
tasklist_file = File.join(working_dir, ".aidp", "tasklist.jsonl")
|
|
742
|
+
FileUtils.mkdir_p(File.dirname(tasklist_file))
|
|
743
|
+
|
|
744
|
+
require_relative "../execute/persistent_tasklist"
|
|
745
|
+
tasklist = Aidp::Execute::PersistentTasklist.new(working_dir)
|
|
746
|
+
|
|
747
|
+
additional_work.each do |task_description|
|
|
748
|
+
tasklist.create(
|
|
749
|
+
description: task_description,
|
|
750
|
+
priority: :high,
|
|
751
|
+
tags: ["auto-generated", "verification"]
|
|
752
|
+
)
|
|
753
|
+
end
|
|
754
|
+
|
|
755
|
+
Aidp.log_info(
|
|
756
|
+
"build_processor",
|
|
757
|
+
"created_follow_up_tasks",
|
|
758
|
+
working_dir: working_dir,
|
|
759
|
+
task_count: additional_work.length
|
|
760
|
+
)
|
|
761
|
+
rescue => e
|
|
762
|
+
Aidp.log_warn(
|
|
763
|
+
"build_processor",
|
|
764
|
+
"failed_to_create_follow_up_tasks",
|
|
765
|
+
error: e.message,
|
|
766
|
+
working_dir: working_dir
|
|
767
|
+
)
|
|
768
|
+
end
|
|
769
|
+
|
|
627
770
|
def stage_and_commit(issue, working_dir: @project_dir)
|
|
628
771
|
commit_created = false
|
|
629
772
|
|
|
@@ -771,6 +914,19 @@ module Aidp
|
|
|
771
914
|
fallback_to_author: label_actor.nil?
|
|
772
915
|
)
|
|
773
916
|
|
|
917
|
+
Aidp.log_debug(
|
|
918
|
+
"build_processor",
|
|
919
|
+
"attempting_pr_creation",
|
|
920
|
+
issue: issue[:number],
|
|
921
|
+
branch_name: branch_name,
|
|
922
|
+
base_branch: base_branch,
|
|
923
|
+
draft: draft,
|
|
924
|
+
assignee: assignee,
|
|
925
|
+
gh_available: @repository_client.gh_available?,
|
|
926
|
+
title: title,
|
|
927
|
+
body_length: body.length
|
|
928
|
+
)
|
|
929
|
+
|
|
774
930
|
output = @repository_client.create_pull_request(
|
|
775
931
|
title: title,
|
|
776
932
|
body: body,
|
|
@@ -789,9 +945,34 @@ module Aidp
|
|
|
789
945
|
branch: branch_name,
|
|
790
946
|
base_branch: base_branch,
|
|
791
947
|
pr_url: pr_url,
|
|
792
|
-
assignee: assignee
|
|
948
|
+
assignee: assignee,
|
|
949
|
+
raw_output: output
|
|
793
950
|
)
|
|
794
951
|
pr_url
|
|
952
|
+
rescue => e
|
|
953
|
+
Aidp.log_error(
|
|
954
|
+
"build_processor",
|
|
955
|
+
"pr_creation_failed",
|
|
956
|
+
issue: issue[:number],
|
|
957
|
+
branch_name: branch_name,
|
|
958
|
+
base_branch: base_branch,
|
|
959
|
+
error: e.message,
|
|
960
|
+
error_class: e.class.name,
|
|
961
|
+
gh_available: @repository_client.gh_available?,
|
|
962
|
+
backtrace: e.backtrace&.first(10),
|
|
963
|
+
assignee: assignee,
|
|
964
|
+
title: title
|
|
965
|
+
)
|
|
966
|
+
display_message("⚠️ Failed to create pull request: #{e.message}", type: :warn)
|
|
967
|
+
|
|
968
|
+
# Continue gracefully - PR creation failure shouldn't crash the processor
|
|
969
|
+
Aidp.log_info(
|
|
970
|
+
"build_processor",
|
|
971
|
+
"continuing_after_pr_failure",
|
|
972
|
+
issue: issue[:number],
|
|
973
|
+
message: "Implementation complete but PR creation failed"
|
|
974
|
+
)
|
|
975
|
+
nil
|
|
795
976
|
end
|
|
796
977
|
|
|
797
978
|
def gather_test_summary(working_dir: @project_dir)
|