aidp 0.26.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 (113) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +89 -0
  3. data/lib/aidp/cli/checkpoint_command.rb +198 -0
  4. data/lib/aidp/cli/config_command.rb +71 -0
  5. data/lib/aidp/cli/enhanced_input.rb +2 -0
  6. data/lib/aidp/cli/first_run_wizard.rb +8 -7
  7. data/lib/aidp/cli/harness_command.rb +102 -0
  8. data/lib/aidp/cli/jobs_command.rb +3 -3
  9. data/lib/aidp/cli/mcp_dashboard.rb +4 -3
  10. data/lib/aidp/cli/models_command.rb +661 -0
  11. data/lib/aidp/cli/providers_command.rb +223 -0
  12. data/lib/aidp/cli.rb +45 -464
  13. data/lib/aidp/config.rb +54 -0
  14. data/lib/aidp/daemon/runner.rb +2 -2
  15. data/lib/aidp/debug_mixin.rb +25 -10
  16. data/lib/aidp/execute/agent_signal_parser.rb +22 -0
  17. data/lib/aidp/execute/async_work_loop_runner.rb +2 -1
  18. data/lib/aidp/execute/checkpoint_display.rb +38 -37
  19. data/lib/aidp/execute/interactive_repl.rb +2 -1
  20. data/lib/aidp/execute/prompt_manager.rb +4 -4
  21. data/lib/aidp/execute/repl_macros.rb +2 -2
  22. data/lib/aidp/execute/steps.rb +94 -1
  23. data/lib/aidp/execute/work_loop_runner.rb +238 -19
  24. data/lib/aidp/execute/workflow_selector.rb +4 -27
  25. data/lib/aidp/firewall/provider_requirements_collector.rb +262 -0
  26. data/lib/aidp/harness/ai_decision_engine.rb +35 -2
  27. data/lib/aidp/harness/config_manager.rb +5 -10
  28. data/lib/aidp/harness/config_schema.rb +8 -0
  29. data/lib/aidp/harness/configuration.rb +40 -2
  30. data/lib/aidp/harness/enhanced_runner.rb +25 -19
  31. data/lib/aidp/harness/error_handler.rb +23 -73
  32. data/lib/aidp/harness/model_cache.rb +269 -0
  33. data/lib/aidp/harness/model_discovery_service.rb +259 -0
  34. data/lib/aidp/harness/model_registry.rb +201 -0
  35. data/lib/aidp/harness/provider_factory.rb +11 -2
  36. data/lib/aidp/harness/runner.rb +5 -0
  37. data/lib/aidp/harness/state_manager.rb +0 -7
  38. data/lib/aidp/harness/thinking_depth_manager.rb +202 -7
  39. data/lib/aidp/harness/ui/enhanced_tui.rb +8 -18
  40. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +0 -18
  41. data/lib/aidp/harness/ui/progress_display.rb +6 -2
  42. data/lib/aidp/harness/user_interface.rb +0 -58
  43. data/lib/aidp/init/runner.rb +7 -2
  44. data/lib/aidp/message_display.rb +0 -46
  45. data/lib/aidp/planning/analyzers/feedback_analyzer.rb +365 -0
  46. data/lib/aidp/planning/builders/agile_plan_builder.rb +387 -0
  47. data/lib/aidp/planning/builders/project_plan_builder.rb +193 -0
  48. data/lib/aidp/planning/generators/gantt_generator.rb +190 -0
  49. data/lib/aidp/planning/generators/iteration_plan_generator.rb +392 -0
  50. data/lib/aidp/planning/generators/legacy_research_planner.rb +473 -0
  51. data/lib/aidp/planning/generators/marketing_report_generator.rb +348 -0
  52. data/lib/aidp/planning/generators/mvp_scope_generator.rb +310 -0
  53. data/lib/aidp/planning/generators/user_test_plan_generator.rb +373 -0
  54. data/lib/aidp/planning/generators/wbs_generator.rb +259 -0
  55. data/lib/aidp/planning/mappers/persona_mapper.rb +163 -0
  56. data/lib/aidp/planning/parsers/document_parser.rb +141 -0
  57. data/lib/aidp/planning/parsers/feedback_data_parser.rb +252 -0
  58. data/lib/aidp/provider_manager.rb +8 -32
  59. data/lib/aidp/providers/adapter.rb +2 -4
  60. data/lib/aidp/providers/aider.rb +264 -0
  61. data/lib/aidp/providers/anthropic.rb +206 -121
  62. data/lib/aidp/providers/base.rb +123 -3
  63. data/lib/aidp/providers/capability_registry.rb +0 -1
  64. data/lib/aidp/providers/codex.rb +75 -70
  65. data/lib/aidp/providers/cursor.rb +87 -59
  66. data/lib/aidp/providers/gemini.rb +57 -60
  67. data/lib/aidp/providers/github_copilot.rb +19 -66
  68. data/lib/aidp/providers/kilocode.rb +35 -80
  69. data/lib/aidp/providers/opencode.rb +35 -80
  70. data/lib/aidp/setup/wizard.rb +555 -8
  71. data/lib/aidp/version.rb +1 -1
  72. data/lib/aidp/watch/build_processor.rb +211 -30
  73. data/lib/aidp/watch/change_request_processor.rb +128 -14
  74. data/lib/aidp/watch/ci_fix_processor.rb +103 -37
  75. data/lib/aidp/watch/ci_log_extractor.rb +258 -0
  76. data/lib/aidp/watch/github_state_extractor.rb +177 -0
  77. data/lib/aidp/watch/implementation_verifier.rb +284 -0
  78. data/lib/aidp/watch/plan_generator.rb +95 -52
  79. data/lib/aidp/watch/plan_processor.rb +7 -6
  80. data/lib/aidp/watch/repository_client.rb +245 -17
  81. data/lib/aidp/watch/review_processor.rb +100 -19
  82. data/lib/aidp/watch/reviewers/base_reviewer.rb +1 -1
  83. data/lib/aidp/watch/runner.rb +181 -29
  84. data/lib/aidp/watch/state_store.rb +22 -1
  85. data/lib/aidp/workflows/definitions.rb +147 -0
  86. data/lib/aidp/workflows/guided_agent.rb +3 -3
  87. data/lib/aidp/workstream_cleanup.rb +245 -0
  88. data/lib/aidp/worktree.rb +19 -0
  89. data/templates/aidp-development.yml.example +2 -2
  90. data/templates/aidp-production.yml.example +3 -3
  91. data/templates/aidp.yml.example +57 -0
  92. data/templates/implementation/generate_tdd_specs.md +213 -0
  93. data/templates/implementation/iterative_implementation.md +122 -0
  94. data/templates/planning/agile/analyze_feedback.md +183 -0
  95. data/templates/planning/agile/generate_iteration_plan.md +179 -0
  96. data/templates/planning/agile/generate_legacy_research_plan.md +171 -0
  97. data/templates/planning/agile/generate_marketing_report.md +162 -0
  98. data/templates/planning/agile/generate_mvp_scope.md +127 -0
  99. data/templates/planning/agile/generate_user_test_plan.md +143 -0
  100. data/templates/planning/agile/ingest_feedback.md +174 -0
  101. data/templates/planning/assemble_project_plan.md +113 -0
  102. data/templates/planning/assign_personas.md +108 -0
  103. data/templates/planning/create_tasks.md +52 -6
  104. data/templates/planning/generate_gantt.md +86 -0
  105. data/templates/planning/generate_wbs.md +85 -0
  106. data/templates/planning/initialize_planning_mode.md +70 -0
  107. data/templates/skills/README.md +2 -2
  108. data/templates/skills/marketing_strategist/SKILL.md +279 -0
  109. data/templates/skills/product_manager/SKILL.md +177 -0
  110. data/templates/skills/ruby_aidp_planning/SKILL.md +497 -0
  111. data/templates/skills/ruby_rspec_tdd/SKILL.md +514 -0
  112. data/templates/skills/ux_researcher/SKILL.md +222 -0
  113. metadata +47 -1
@@ -0,0 +1,284 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+ require_relative "../harness/ai_decision_engine"
5
+ require_relative "../message_display"
6
+
7
+ module Aidp
8
+ module Watch
9
+ # Verifies that implementation fully addresses issue requirements using ZFC
10
+ # before allowing PR creation in watch mode build workflow
11
+ class ImplementationVerifier
12
+ include Aidp::MessageDisplay
13
+
14
+ def initialize(repository_client:, project_dir:, ai_decision_engine: nil)
15
+ @repository_client = repository_client
16
+ @project_dir = project_dir
17
+ @ai_decision_engine = ai_decision_engine || build_default_ai_decision_engine
18
+ end
19
+
20
+ # Verify implementation against issue requirements
21
+ # Returns: { verified: true/false, reason: String, missing_items: Array }
22
+ def verify(issue:, working_dir:)
23
+ Aidp.log_debug("implementation_verifier", "starting_verification", issue: issue[:number], working_dir: working_dir)
24
+
25
+ display_message("🔍 Verifying implementation completeness...", type: :info)
26
+
27
+ # Gather verification inputs
28
+ issue_requirements = extract_issue_requirements(issue)
29
+ implementation_changes = extract_implementation_changes(working_dir)
30
+
31
+ # Use ZFC to verify completeness
32
+ result = perform_zfc_verification(
33
+ issue_number: issue[:number],
34
+ issue_requirements: issue_requirements,
35
+ implementation_changes: implementation_changes
36
+ )
37
+
38
+ Aidp.log_info(
39
+ "implementation_verifier",
40
+ "verification_complete",
41
+ issue: issue[:number],
42
+ verified: result[:verified],
43
+ reason: result[:reason]
44
+ )
45
+
46
+ result
47
+ end
48
+
49
+ private
50
+
51
+ def extract_issue_requirements(issue)
52
+ # Collect full issue context including comments for plan
53
+ requirements = {
54
+ title: issue[:title],
55
+ body: issue[:body] || "",
56
+ comments: []
57
+ }
58
+
59
+ # Extract relevant comments (include plan comments and user responses)
60
+ issue[:comments]&.each do |comment|
61
+ requirements[:comments] << {
62
+ author: comment["author"] || comment[:author],
63
+ body: comment["body"] || comment[:body],
64
+ created_at: comment["createdAt"] || comment[:createdAt]
65
+ }
66
+ end
67
+
68
+ requirements
69
+ end
70
+
71
+ def extract_implementation_changes(working_dir)
72
+ Dir.chdir(working_dir) do
73
+ # Get the base branch to compare against
74
+ base_branch = detect_base_branch
75
+
76
+ # Get diff from base branch
77
+ diff_output, _stderr, status = Open3.capture3("git", "diff", "#{base_branch}...HEAD")
78
+
79
+ unless status.success?
80
+ Aidp.log_warn("implementation_verifier", "git_diff_failed", working_dir: working_dir)
81
+ return "Unable to extract changes: git diff failed"
82
+ end
83
+
84
+ # Get list of changed files with stats
85
+ files_output, _stderr, files_status = Open3.capture3("git", "diff", "--stat", "#{base_branch}...HEAD")
86
+
87
+ changes = {
88
+ diff: diff_output,
89
+ files_changed: files_status.success? ? files_output : "Unable to get file list"
90
+ }
91
+
92
+ Aidp.log_debug(
93
+ "implementation_verifier",
94
+ "extracted_changes",
95
+ working_dir: working_dir,
96
+ base_branch: base_branch,
97
+ diff_size: diff_output.bytesize,
98
+ files_changed_count: files_output.lines.count
99
+ )
100
+
101
+ changes
102
+ end
103
+ rescue => e
104
+ Aidp.log_error("implementation_verifier", "extract_changes_failed", error: e.message, working_dir: working_dir)
105
+ {error: "Failed to extract changes: #{e.message}"}
106
+ end
107
+
108
+ def detect_base_branch
109
+ stdout, _stderr, status = Open3.capture3("git", "symbolic-ref", "refs/remotes/origin/HEAD")
110
+ if status.success?
111
+ ref = stdout.strip
112
+ return ref.split("/").last if ref.include?("/")
113
+ end
114
+
115
+ # Fallback to common branch names
116
+ %w[main master trunk].find do |candidate|
117
+ _out, _err, branch_status = Open3.capture3("git", "rev-parse", "--verify", candidate)
118
+ branch_status.success?
119
+ end || "main"
120
+ end
121
+
122
+ def perform_zfc_verification(issue_number:, issue_requirements:, implementation_changes:)
123
+ # Check if AI decision engine is available
124
+ unless @ai_decision_engine
125
+ Aidp.log_error(
126
+ "implementation_verifier",
127
+ "ai_decision_engine_not_available",
128
+ issue: issue_number
129
+ )
130
+ return {
131
+ verified: false,
132
+ reason: "AI decision engine not available for verification",
133
+ missing_items: ["Unable to verify - AI decision engine initialization failed"],
134
+ additional_work: []
135
+ }
136
+ end
137
+
138
+ prompt = build_verification_prompt(issue_number, issue_requirements, implementation_changes)
139
+
140
+ schema = {
141
+ type: "object",
142
+ properties: {
143
+ fully_implemented: {
144
+ type: "boolean",
145
+ description: "True if the implementation fully addresses all issue requirements"
146
+ },
147
+ reasoning: {
148
+ type: "string",
149
+ description: "Detailed explanation of the verification decision"
150
+ },
151
+ missing_requirements: {
152
+ type: "array",
153
+ items: {type: "string"},
154
+ description: "List of specific requirements from the issue that are not yet implemented (empty if fully_implemented is true)"
155
+ },
156
+ additional_work_needed: {
157
+ type: "array",
158
+ items: {type: "string"},
159
+ description: "List of specific tasks needed to complete the implementation (empty if fully_implemented is true)"
160
+ }
161
+ },
162
+ required: ["fully_implemented", "reasoning", "missing_requirements", "additional_work_needed"]
163
+ }
164
+
165
+ # Use AIDecisionEngine with custom prompt
166
+ # We use a custom decision type since this is a one-off verification
167
+ decision = @ai_decision_engine.decide(
168
+ :implementation_verification,
169
+ context: {prompt: prompt},
170
+ schema: schema,
171
+ tier: :mini,
172
+ cache_ttl: nil # Don't cache verification results as they're context-specific
173
+ )
174
+
175
+ # Convert AI decision to verification result
176
+ {
177
+ verified: decision[:fully_implemented],
178
+ reason: decision[:reasoning],
179
+ missing_items: decision[:missing_requirements] || [],
180
+ additional_work: decision[:additional_work_needed] || []
181
+ }
182
+ rescue => e
183
+ Aidp.log_error(
184
+ "implementation_verifier",
185
+ "zfc_verification_failed",
186
+ issue: issue_number,
187
+ error: e.message,
188
+ error_class: e.class.name
189
+ )
190
+
191
+ # On error, fail safe by marking as not verified
192
+ {
193
+ verified: false,
194
+ reason: "Verification failed due to error: #{e.message}",
195
+ missing_items: ["Unable to verify due to technical error"],
196
+ additional_work: []
197
+ }
198
+ end
199
+
200
+ def build_verification_prompt(issue_number, issue_requirements, implementation_changes)
201
+ <<~PROMPT
202
+ You are verifying that an implementation fully addresses the requirements specified in a GitHub issue.
203
+
204
+ ## Task
205
+ Compare the issue requirements with the actual implementation changes and determine if the implementation is complete.
206
+
207
+ ## Issue ##{issue_number} Requirements
208
+
209
+ ### Title
210
+ #{issue_requirements[:title]}
211
+
212
+ ### Description
213
+ #{issue_requirements[:body]}
214
+
215
+ ### Discussion Thread / Plan
216
+ #{format_comments(issue_requirements[:comments])}
217
+
218
+ ## Implementation Changes
219
+
220
+ ### Files Changed
221
+ #{implementation_changes[:files_changed]}
222
+
223
+ ### Code Changes (Diff)
224
+ #{truncate_diff(implementation_changes[:diff])}
225
+
226
+ ## Verification Criteria
227
+
228
+ 1. **All explicit requirements** from the issue description must be addressed
229
+ 2. **All tasks from the plan** (if present in comments) must be completed
230
+ 3. **Code changes must be substantive** - not just documentation or planning files
231
+ 4. **Test requirements** are NOT part of this verification (handled separately)
232
+ 5. **Quality/style requirements** are NOT part of this verification (handled by linters)
233
+
234
+ ## Your Decision
235
+
236
+ Determine if the implementation FULLY addresses the issue requirements. Be thorough but fair:
237
+ - If all requirements are met, mark as fully_implemented = true
238
+ - If any requirements are missing or incomplete, mark as fully_implemented = false and list them
239
+ - Focus on FUNCTIONAL requirements, not code quality or style
240
+ PROMPT
241
+ end
242
+
243
+ def format_comments(comments)
244
+ return "_No discussion thread_" if comments.nil? || comments.empty?
245
+
246
+ comments.map do |comment|
247
+ author = comment[:author] || "unknown"
248
+ timestamp = comment[:created_at] || "unknown"
249
+ body = comment[:body] || ""
250
+
251
+ "### #{author} (#{timestamp})\n#{body}"
252
+ end.join("\n\n")
253
+ end
254
+
255
+ def truncate_diff(diff)
256
+ return "_No changes detected_" if diff.nil? || diff.empty?
257
+
258
+ max_size = 15_000 # ~15KB to stay within token limits
259
+ if diff.bytesize > max_size
260
+ truncated = diff.byteslice(0, max_size)
261
+ "#{truncated}\n\n[... diff truncated, showing first #{max_size} bytes of #{diff.bytesize} total ...]"
262
+ else
263
+ diff
264
+ end
265
+ end
266
+
267
+ def build_default_ai_decision_engine
268
+ # Load config and create AI decision engine
269
+ config = Aidp::Harness::Configuration.new(@project_dir)
270
+
271
+ Aidp::Harness::AIDecisionEngine.new(config)
272
+ rescue => e
273
+ Aidp.log_warn(
274
+ "implementation_verifier",
275
+ "failed_to_create_ai_decision_engine",
276
+ error: e.message,
277
+ project_dir: @project_dir
278
+ )
279
+ # Return nil and fail verification gracefully
280
+ nil
281
+ end
282
+ end
283
+ end
284
+ end
@@ -29,32 +29,103 @@ module Aidp
29
29
  def initialize(provider_name: nil, verbose: false)
30
30
  @provider_name = provider_name
31
31
  @verbose = verbose
32
+ @providers_attempted = []
32
33
  end
33
34
 
34
35
  def generate(issue)
35
- provider = resolve_provider
36
- if provider
37
- generate_with_provider(provider, issue)
38
- else
39
- display_message("⚠️ No active provider available. Falling back to heuristic plan.", type: :warn)
40
- heuristic_plan(issue)
36
+ Aidp.log_debug("plan_generator", "generate.start", provider: @provider_name, issue: issue[:number])
37
+
38
+ # Try providers in fallback chain order
39
+ providers_to_try = build_provider_fallback_chain
40
+ Aidp.log_debug("plan_generator", "fallback_chain", providers: providers_to_try, count: providers_to_try.size)
41
+
42
+ providers_to_try.each do |provider_name|
43
+ next if @providers_attempted.include?(provider_name)
44
+
45
+ Aidp.log_debug("plan_generator", "trying_provider", provider: provider_name, attempted: @providers_attempted)
46
+
47
+ provider = resolve_provider(provider_name)
48
+ unless provider
49
+ Aidp.log_debug("plan_generator", "provider_unavailable", provider: provider_name, reason: "not resolved")
50
+ @providers_attempted << provider_name
51
+ next
52
+ end
53
+
54
+ begin
55
+ Aidp.log_info("plan_generator", "generate_with_provider", provider: provider_name, issue: issue[:number])
56
+ result = generate_with_provider(provider, issue, provider_name)
57
+ if result
58
+ Aidp.log_info("plan_generator", "generation_success", provider: provider_name, issue: issue[:number])
59
+ return result
60
+ end
61
+
62
+ # Provider returned nil - try next provider
63
+ Aidp.log_warn("plan_generator", "provider_returned_nil", provider: provider_name)
64
+ @providers_attempted << provider_name
65
+ rescue => e
66
+ # Log error and try next provider in chain
67
+ Aidp.log_warn("plan_generator", "provider_failed", provider: provider_name, error: e.message, error_class: e.class.name)
68
+ @providers_attempted << provider_name
69
+ end
41
70
  end
71
+
72
+ # All providers exhausted - silently fail without heuristic fallback
73
+ Aidp.log_warn("plan_generator", "all_providers_exhausted", attempted: @providers_attempted, result: "failed")
74
+ display_message("⚠️ All providers unavailable or failed. Unable to generate plan.", type: :warn)
75
+ nil
42
76
  rescue => e
43
- display_message("⚠️ Plan generation failed (#{e.message}). Using heuristic.", type: :warn)
44
- heuristic_plan(issue)
77
+ Aidp.log_error("plan_generator", "generation_failed_unexpectedly", error: e.message, backtrace: e.backtrace&.first(3))
78
+ display_message("⚠️ Plan generation failed unexpectedly (#{e.message}).", type: :warn)
79
+ nil
45
80
  end
46
81
 
47
82
  private
48
83
 
49
- def resolve_provider
50
- provider_name = @provider_name || detect_default_provider
84
+ def build_provider_fallback_chain
85
+ # Start with specified provider or default
86
+ primary_provider = @provider_name || detect_default_provider
87
+ providers = []
88
+
89
+ # Add primary provider first
90
+ providers << primary_provider if primary_provider
91
+
92
+ # Try to get fallback chain from config
93
+ begin
94
+ config_manager = Aidp::Harness::ConfigManager.new(Dir.pwd)
95
+ fallback_providers = config_manager.fallback_providers || []
96
+
97
+ # Add fallback providers that aren't already in the list
98
+ fallback_providers.each do |fallback|
99
+ providers << fallback unless providers.include?(fallback)
100
+ end
101
+ rescue => e
102
+ Aidp.log_debug("plan_generator", "config_fallback_unavailable", error: e.message)
103
+ end
104
+
105
+ # If we still have no providers, add cursor as last resort
106
+ providers << "cursor" if providers.empty?
107
+
108
+ # Remove duplicates while preserving order
109
+ providers.uniq
110
+ end
111
+
112
+ def resolve_provider(provider_name = nil)
113
+ provider_name ||= @provider_name || detect_default_provider
51
114
  return nil unless provider_name
52
115
 
53
- provider = Aidp::ProviderManager.get_provider(provider_name, use_harness: false)
54
- return provider if provider&.available?
116
+ Aidp.log_debug("plan_generator", "resolve_provider", provider: provider_name)
117
+
118
+ provider = Aidp::ProviderManager.get_provider(provider_name)
119
+
120
+ if provider&.available?
121
+ Aidp.log_debug("plan_generator", "provider_resolved", provider: provider_name, available: true)
122
+ return provider
123
+ end
55
124
 
125
+ Aidp.log_debug("plan_generator", "provider_not_available", provider: provider_name, available: provider&.available?)
56
126
  nil
57
127
  rescue => e
128
+ Aidp.log_warn("plan_generator", "resolve_provider_failed", provider: provider_name, error: e.message)
58
129
  display_message("⚠️ Failed to resolve provider #{provider_name}: #{e.message}", type: :warn)
59
130
  nil
60
131
  end
@@ -66,9 +137,11 @@ module Aidp
66
137
  "cursor"
67
138
  end
68
139
 
69
- def generate_with_provider(provider, issue)
140
+ def generate_with_provider(provider, issue, provider_name = "unknown")
70
141
  payload = build_prompt(issue)
71
142
 
143
+ Aidp.log_debug("plan_generator", "sending_to_provider", provider: provider_name, prompt_length: payload.length)
144
+
72
145
  if @verbose
73
146
  display_message("\n--- Plan Generation Prompt ---", type: :muted)
74
147
  display_message(payload.strip, type: :muted)
@@ -77,6 +150,8 @@ module Aidp
77
150
 
78
151
  response = provider.send_message(prompt: payload)
79
152
 
153
+ Aidp.log_debug("plan_generator", "provider_response_received", provider: provider_name, response_length: response&.length || 0)
154
+
80
155
  if @verbose
81
156
  display_message("\n--- Provider Response ---", type: :muted)
82
157
  display_message(response.strip, type: :muted)
@@ -85,10 +160,14 @@ module Aidp
85
160
 
86
161
  parsed = parse_structured_response(response)
87
162
 
88
- return parsed if parsed
163
+ if parsed
164
+ Aidp.log_debug("plan_generator", "response_parsed", provider: provider_name, has_summary: !parsed[:summary].to_s.empty?, tasks_count: parsed[:tasks]&.size || 0)
165
+ return parsed
166
+ end
89
167
 
90
- display_message("⚠️ Unable to parse provider response. Using heuristic plan.", type: :warn)
91
- heuristic_plan(issue)
168
+ Aidp.log_warn("plan_generator", "parse_failed", provider: provider_name)
169
+ display_message("⚠️ Unable to parse #{provider_name} response. Trying next provider.", type: :warn)
170
+ nil
92
171
  end
93
172
 
94
173
  def build_prompt(issue)
@@ -140,42 +219,6 @@ module Aidp
140
219
  json_match = text.match(/\{.*\}/m)
141
220
  json_match ? json_match[0] : nil
142
221
  end
143
-
144
- def heuristic_plan(issue)
145
- body = issue[:body].to_s
146
- bullet_tasks = body.lines
147
- .map(&:strip)
148
- .select { |line| line.start_with?("-", "*") }
149
- .map { |line| line.sub(/\A[-*]\s*/, "") }
150
- .uniq
151
- .first(5)
152
-
153
- paragraphs = body.split(/\n{2,}/).map(&:strip).reject(&:empty?)
154
- summary = paragraphs.first(2).join(" ")
155
- summary = summary.empty? ? "Implement the requested changes described in the issue." : summary
156
-
157
- tasks = if bullet_tasks.empty?
158
- [
159
- "Review the repository context and identify impacted components.",
160
- "Implement the necessary code changes and add tests.",
161
- "Document the changes and ensure lint/test pipelines succeed."
162
- ]
163
- else
164
- bullet_tasks
165
- end
166
-
167
- questions = [
168
- "Are there constraints (framework versions, performance budgets) we must respect?",
169
- "Are there existing tests or acceptance criteria we should extend?",
170
- "Is there additional context (design docs, related issues) we should review?"
171
- ]
172
-
173
- {
174
- summary: summary,
175
- tasks: tasks,
176
- questions: questions
177
- }
178
- end
179
222
  end
180
223
  end
181
224
  end
@@ -33,12 +33,6 @@ module Aidp
33
33
  @build_label = label_config[:build_trigger] || label_config["build_trigger"] || DEFAULT_BUILD_LABEL
34
34
  end
35
35
 
36
- # For backward compatibility
37
- def self.plan_label_from_config(config)
38
- labels = config[:labels] || config["labels"] || {}
39
- labels[:plan_trigger] || labels["plan_trigger"] || DEFAULT_PLAN_LABEL
40
- end
41
-
42
36
  def process(issue)
43
37
  number = issue[:number]
44
38
  existing_plan = @state_store.plan_data(number)
@@ -51,6 +45,13 @@ module Aidp
51
45
 
52
46
  plan_data = @plan_generator.generate(issue)
53
47
 
48
+ # If plan generation failed (all providers unavailable), silently skip
49
+ unless plan_data
50
+ Aidp.log_warn("plan_processor", "plan_generation_failed", issue: number, reason: "no plan data returned")
51
+ display_message("⚠️ Unable to generate plan for issue ##{number} - all providers failed", type: :warn)
52
+ return
53
+ end
54
+
54
55
  # Fetch the user who added the most recent label
55
56
  label_actor = @repository_client.most_recent_label_actor(number)
56
57