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.
Files changed (90) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -0
  3. data/lib/aidp/cli/models_command.rb +5 -6
  4. data/lib/aidp/cli.rb +10 -8
  5. data/lib/aidp/config.rb +54 -0
  6. data/lib/aidp/debug_mixin.rb +23 -1
  7. data/lib/aidp/execute/agent_signal_parser.rb +22 -0
  8. data/lib/aidp/execute/repl_macros.rb +2 -2
  9. data/lib/aidp/execute/steps.rb +94 -1
  10. data/lib/aidp/execute/work_loop_runner.rb +209 -17
  11. data/lib/aidp/execute/workflow_selector.rb +2 -25
  12. data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
  13. data/lib/aidp/harness/ai_decision_engine.rb +35 -2
  14. data/lib/aidp/harness/config_manager.rb +0 -5
  15. data/lib/aidp/harness/config_schema.rb +8 -0
  16. data/lib/aidp/harness/configuration.rb +27 -19
  17. data/lib/aidp/harness/enhanced_runner.rb +1 -4
  18. data/lib/aidp/harness/error_handler.rb +1 -72
  19. data/lib/aidp/harness/provider_factory.rb +11 -2
  20. data/lib/aidp/harness/state_manager.rb +0 -7
  21. data/lib/aidp/harness/thinking_depth_manager.rb +47 -68
  22. data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
  23. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
  24. data/lib/aidp/harness/ui/progress_display.rb +6 -2
  25. data/lib/aidp/harness/user_interface.rb +0 -58
  26. data/lib/aidp/init/runner.rb +7 -2
  27. data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
  28. data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
  29. data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
  30. data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
  31. data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
  32. data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
  33. data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
  34. data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
  35. data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
  36. data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
  37. data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
  38. data/lib/aidp/planning/parsers/document_parser.rb +141 -0
  39. data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
  40. data/lib/aidp/provider_manager.rb +8 -32
  41. data/lib/aidp/providers/aider.rb +264 -0
  42. data/lib/aidp/providers/anthropic.rb +74 -2
  43. data/lib/aidp/providers/base.rb +25 -1
  44. data/lib/aidp/providers/codex.rb +26 -3
  45. data/lib/aidp/providers/cursor.rb +16 -0
  46. data/lib/aidp/providers/gemini.rb +13 -0
  47. data/lib/aidp/providers/github_copilot.rb +17 -0
  48. data/lib/aidp/providers/kilocode.rb +11 -0
  49. data/lib/aidp/providers/opencode.rb +11 -0
  50. data/lib/aidp/setup/wizard.rb +249 -39
  51. data/lib/aidp/version.rb +1 -1
  52. data/lib/aidp/watch/build_processor.rb +211 -30
  53. data/lib/aidp/watch/change_request_processor.rb +128 -14
  54. data/lib/aidp/watch/ci_fix_processor.rb +103 -37
  55. data/lib/aidp/watch/ci_log_extractor.rb +258 -0
  56. data/lib/aidp/watch/github_state_extractor.rb +177 -0
  57. data/lib/aidp/watch/implementation_verifier.rb +284 -0
  58. data/lib/aidp/watch/plan_generator.rb +7 -43
  59. data/lib/aidp/watch/plan_processor.rb +7 -6
  60. data/lib/aidp/watch/repository_client.rb +245 -17
  61. data/lib/aidp/watch/review_processor.rb +98 -17
  62. data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
  63. data/lib/aidp/watch/runner.rb +181 -29
  64. data/lib/aidp/watch/state_store.rb +22 -1
  65. data/lib/aidp/workflows/definitions.rb +147 -0
  66. data/lib/aidp/workstream_cleanup.rb +245 -0
  67. data/lib/aidp/worktree.rb +19 -0
  68. data/templates/aidp.yml.example +57 -0
  69. data/templates/implementation/generate_tdd_specs.md +213 -0
  70. data/templates/implementation/iterative_implementation.md +122 -0
  71. data/templates/planning/agile/analyze_feedback.md +183 -0
  72. data/templates/planning/agile/generate_iteration_plan.md +179 -0
  73. data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
  74. data/templates/planning/agile/generate_marketing_report.md +162 -0
  75. data/templates/planning/agile/generate_mvp_scope.md +127 -0
  76. data/templates/planning/agile/generate_user_test_plan.md +143 -0
  77. data/templates/planning/agile/ingest_feedback.md +174 -0
  78. data/templates/planning/assemble_project_plan.md +113 -0
  79. data/templates/planning/assign_personas.md +108 -0
  80. data/templates/planning/create_tasks.md +52 -6
  81. data/templates/planning/generate_gantt.md +86 -0
  82. data/templates/planning/generate_wbs.md +85 -0
  83. data/templates/planning/initialize_planning_mode.md +70 -0
  84. data/templates/skills/README.md +2 -2
  85. data/templates/skills/marketing_strategist/SKILL.md +279 -0
  86. data/templates/skills/product_manager/SKILL.md +177 -0
  87. data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
  88. data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
  89. data/templates/skills/ux_researcher/SKILL.md +222 -0
  90. metadata +39 -1
@@ -308,8 +308,12 @@ module Aidp
308
308
  end
309
309
 
310
310
  # Check if user wants to use automated discovery
311
- existing_tiers = get([:thinking, :tiers])
312
- if existing_tiers && !existing_tiers.empty?
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
- tier_config = generate_tier_configuration(discovered_models, primary_provider)
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
- display_tier_preview(tier_config)
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
- set([:thinking, :tiers], tier_config)
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 generate_tier_configuration(discovered_models, primary_provider)
478
- tier_config = {}
484
+ def generate_provider_tier_configurations(discovered_models)
485
+ provider_configs = {}
479
486
 
480
- # Configure the three most common tiers: mini, standard, and pro
481
- DEFAULT_AUTOCONFIG_TIERS.each do |tier|
482
- tier_models = []
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
- # Add models from other providers
489
- discovered_models.each do |provider, models|
490
- next if provider == primary_provider
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
- # Add to config if we found any models for this tier
496
- if tier_models.any?
497
- tier_config[tier.to_sym] = {
498
- models: tier_models.map { |m|
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
- tier_config
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 display_tier_preview(tier_config)
524
- return if tier_config.empty?
522
+ def display_provider_tier_preview(provider_configs)
523
+ return if provider_configs.empty?
525
524
 
526
- tier_config.each do |tier, config|
527
- models = config[:models] || []
528
- prompt.say(" #{tier}:")
529
- models.each do |model_entry|
530
- prompt.say(" - provider: #{model_entry[:provider]}")
531
- prompt.say(" model: #{model_entry[:model]}")
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
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Aidp
4
- VERSION = "0.27.0"
4
+ VERSION = "0.28.0"
5
5
  end
@@ -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(number)
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
- # Create error result to pass to handle_failure
91
- error_result = {
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
- error: e.message,
94
- error_class: e.class.name,
95
- message: "Exception during harness execution: #{e.message}"
96
- }
97
-
98
- # Handle as failure (posts comment, updates state) but DON'T re-raise
99
- handle_failure(issue: issue, slug: slug, result: error_result)
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, recorded, and reported to GitHub
108
+ # The error has been logged and recorded internally
103
109
  end
104
110
 
105
111
  private
106
112
 
107
- def ensure_plan_data(number)
108
- data = @state_store.plan_data(number)
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)