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
|
@@ -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
|
|
@@ -69,14 +69,14 @@ module Aidp
|
|
|
69
69
|
end
|
|
70
70
|
end
|
|
71
71
|
|
|
72
|
-
# All providers exhausted
|
|
73
|
-
Aidp.log_warn("plan_generator", "all_providers_exhausted", attempted: @providers_attempted,
|
|
74
|
-
display_message("⚠️ All providers unavailable or failed.
|
|
75
|
-
|
|
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
|
|
76
76
|
rescue => e
|
|
77
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}).
|
|
79
|
-
|
|
78
|
+
display_message("⚠️ Plan generation failed unexpectedly (#{e.message}).", type: :warn)
|
|
79
|
+
nil
|
|
80
80
|
end
|
|
81
81
|
|
|
82
82
|
private
|
|
@@ -115,7 +115,7 @@ module Aidp
|
|
|
115
115
|
|
|
116
116
|
Aidp.log_debug("plan_generator", "resolve_provider", provider: provider_name)
|
|
117
117
|
|
|
118
|
-
provider = Aidp::ProviderManager.get_provider(provider_name
|
|
118
|
+
provider = Aidp::ProviderManager.get_provider(provider_name)
|
|
119
119
|
|
|
120
120
|
if provider&.available?
|
|
121
121
|
Aidp.log_debug("plan_generator", "provider_resolved", provider: provider_name, available: true)
|
|
@@ -219,42 +219,6 @@ module Aidp
|
|
|
219
219
|
json_match = text.match(/\{.*\}/m)
|
|
220
220
|
json_match ? json_match[0] : nil
|
|
221
221
|
end
|
|
222
|
-
|
|
223
|
-
def heuristic_plan(issue)
|
|
224
|
-
body = issue[:body].to_s
|
|
225
|
-
bullet_tasks = body.lines
|
|
226
|
-
.map(&:strip)
|
|
227
|
-
.select { |line| line.start_with?("-", "*") }
|
|
228
|
-
.map { |line| line.sub(/\A[-*]\s*/, "") }
|
|
229
|
-
.uniq
|
|
230
|
-
.first(5)
|
|
231
|
-
|
|
232
|
-
paragraphs = body.split(/\n{2,}/).map(&:strip).reject(&:empty?)
|
|
233
|
-
summary = paragraphs.first(2).join(" ")
|
|
234
|
-
summary = summary.empty? ? "Implement the requested changes described in the issue." : summary
|
|
235
|
-
|
|
236
|
-
tasks = if bullet_tasks.empty?
|
|
237
|
-
[
|
|
238
|
-
"Review the repository context and identify impacted components.",
|
|
239
|
-
"Implement the necessary code changes and add tests.",
|
|
240
|
-
"Document the changes and ensure lint/test pipelines succeed."
|
|
241
|
-
]
|
|
242
|
-
else
|
|
243
|
-
bullet_tasks
|
|
244
|
-
end
|
|
245
|
-
|
|
246
|
-
questions = [
|
|
247
|
-
"Are there constraints (framework versions, performance budgets) we must respect?",
|
|
248
|
-
"Are there existing tests or acceptance criteria we should extend?",
|
|
249
|
-
"Is there additional context (design docs, related issues) we should review?"
|
|
250
|
-
]
|
|
251
|
-
|
|
252
|
-
{
|
|
253
|
-
summary: summary,
|
|
254
|
-
tasks: tasks,
|
|
255
|
-
questions: questions
|
|
256
|
-
}
|
|
257
|
-
end
|
|
258
222
|
end
|
|
259
223
|
end
|
|
260
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
|
|