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
@@ -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, fall back to heuristic
73
- Aidp.log_warn("plan_generator", "all_providers_exhausted", attempted: @providers_attempted, falling_back: "heuristic")
74
- display_message("⚠️ All providers unavailable or failed. Falling back to heuristic plan.", type: :warn)
75
- heuristic_plan(issue)
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}). Using heuristic.", type: :warn)
79
- heuristic_plan(issue)
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, use_harness: false)
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