aidp 0.33.0 → 0.34.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 (83) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
  4. data/lib/aidp/cli/eval_command.rb +399 -0
  5. data/lib/aidp/cli/harness_command.rb +1 -1
  6. data/lib/aidp/cli/security_command.rb +416 -0
  7. data/lib/aidp/cli/tools_command.rb +6 -4
  8. data/lib/aidp/cli.rb +170 -3
  9. data/lib/aidp/concurrency/exec.rb +3 -0
  10. data/lib/aidp/config.rb +113 -0
  11. data/lib/aidp/config_paths.rb +20 -0
  12. data/lib/aidp/daemon/runner.rb +8 -4
  13. data/lib/aidp/errors.rb +134 -0
  14. data/lib/aidp/evaluations/context_capture.rb +205 -0
  15. data/lib/aidp/evaluations/evaluation_record.rb +114 -0
  16. data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
  17. data/lib/aidp/evaluations.rb +23 -0
  18. data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
  19. data/lib/aidp/execute/interactive_repl.rb +6 -2
  20. data/lib/aidp/execute/prompt_evaluator.rb +359 -0
  21. data/lib/aidp/execute/repl_macros.rb +100 -1
  22. data/lib/aidp/execute/work_loop_runner.rb +399 -47
  23. data/lib/aidp/execute/work_loop_state.rb +4 -1
  24. data/lib/aidp/execute/workflow_selector.rb +3 -0
  25. data/lib/aidp/harness/ai_decision_engine.rb +79 -0
  26. data/lib/aidp/harness/capability_registry.rb +2 -0
  27. data/lib/aidp/harness/condition_detector.rb +3 -0
  28. data/lib/aidp/harness/config_loader.rb +3 -0
  29. data/lib/aidp/harness/enhanced_runner.rb +14 -11
  30. data/lib/aidp/harness/error_handler.rb +3 -0
  31. data/lib/aidp/harness/provider_factory.rb +3 -0
  32. data/lib/aidp/harness/provider_manager.rb +6 -0
  33. data/lib/aidp/harness/runner.rb +5 -1
  34. data/lib/aidp/harness/state/persistence.rb +3 -0
  35. data/lib/aidp/harness/state_manager.rb +3 -0
  36. data/lib/aidp/harness/status_display.rb +28 -20
  37. data/lib/aidp/harness/thinking_depth_manager.rb +32 -32
  38. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
  39. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
  40. data/lib/aidp/harness/ui/error_handler.rb +3 -0
  41. data/lib/aidp/harness/ui/job_monitor.rb +4 -0
  42. data/lib/aidp/harness/ui/navigation/submenu.rb +2 -0
  43. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
  44. data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
  45. data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
  46. data/lib/aidp/harness/user_interface.rb +3 -0
  47. data/lib/aidp/loader.rb +2 -2
  48. data/lib/aidp/logger.rb +3 -0
  49. data/lib/aidp/message_display.rb +31 -0
  50. data/lib/aidp/pr_worktree_manager.rb +18 -6
  51. data/lib/aidp/provider_manager.rb +3 -0
  52. data/lib/aidp/providers/base.rb +2 -0
  53. data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
  54. data/lib/aidp/security/secrets_proxy.rb +328 -0
  55. data/lib/aidp/security/secrets_registry.rb +227 -0
  56. data/lib/aidp/security/trifecta_state.rb +220 -0
  57. data/lib/aidp/security/watch_mode_handler.rb +306 -0
  58. data/lib/aidp/security/work_loop_adapter.rb +277 -0
  59. data/lib/aidp/security.rb +56 -0
  60. data/lib/aidp/setup/wizard.rb +4 -2
  61. data/lib/aidp/version.rb +1 -1
  62. data/lib/aidp/watch/auto_merger.rb +274 -0
  63. data/lib/aidp/watch/auto_pr_processor.rb +125 -7
  64. data/lib/aidp/watch/build_processor.rb +16 -1
  65. data/lib/aidp/watch/change_request_processor.rb +680 -286
  66. data/lib/aidp/watch/ci_fix_processor.rb +262 -4
  67. data/lib/aidp/watch/feedback_collector.rb +191 -0
  68. data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
  69. data/lib/aidp/watch/implementation_verifier.rb +142 -1
  70. data/lib/aidp/watch/plan_generator.rb +70 -13
  71. data/lib/aidp/watch/plan_processor.rb +12 -5
  72. data/lib/aidp/watch/projects_processor.rb +286 -0
  73. data/lib/aidp/watch/repository_client.rb +861 -53
  74. data/lib/aidp/watch/review_processor.rb +33 -6
  75. data/lib/aidp/watch/runner.rb +51 -11
  76. data/lib/aidp/watch/state_store.rb +233 -0
  77. data/lib/aidp/watch/sub_issue_creator.rb +221 -0
  78. data/lib/aidp/workflows/guided_agent.rb +4 -0
  79. data/lib/aidp/workstream_executor.rb +3 -0
  80. data/lib/aidp/worktree.rb +61 -11
  81. data/lib/aidp/worktree_branch_manager.rb +347 -101
  82. data/templates/implementation/iterative_implementation.md +46 -3
  83. metadata +20 -1
@@ -0,0 +1,286 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../message_display"
4
+
5
+ module Aidp
6
+ module Watch
7
+ # Synchronizes GitHub issues with GitHub Projects V2.
8
+ # Updates project fields based on issue state and handles blocking relationships.
9
+ class ProjectsProcessor
10
+ include Aidp::MessageDisplay
11
+
12
+ # Default field mapping configuration
13
+ DEFAULT_FIELD_MAPPINGS = {
14
+ status: "Status",
15
+ priority: "Priority",
16
+ skills: "Skills",
17
+ personas: "Personas",
18
+ blocking: "Blocking"
19
+ }.freeze
20
+
21
+ # Status values for different issue states
22
+ STATUS_VALUES = {
23
+ backlog: "Backlog",
24
+ todo: "Todo",
25
+ in_progress: "In Progress",
26
+ in_review: "In Review",
27
+ done: "Done",
28
+ blocked: "Blocked"
29
+ }.freeze
30
+
31
+ attr_reader :repository_client, :state_store, :project_id
32
+
33
+ def initialize(repository_client:, state_store:, project_id:, config: {})
34
+ @repository_client = repository_client
35
+ @state_store = state_store
36
+ @project_id = project_id
37
+ @config = config
38
+ @field_mappings = config[:field_mappings] || DEFAULT_FIELD_MAPPINGS
39
+ @auto_create_fields = config[:auto_create_fields] != false
40
+ @project_fields_cache = nil
41
+ end
42
+
43
+ # Sync a single issue to the project
44
+ # @param issue_number [Integer] The issue number
45
+ # @param status [String, nil] Optional status to set
46
+ # @return [Boolean] True if sync was successful
47
+ def sync_issue_to_project(issue_number, status: nil)
48
+ Aidp.log_debug("projects_processor", "sync_issue_to_project", issue_number: issue_number, status: status)
49
+
50
+ # Check if issue is already linked to project
51
+ item_id = @state_store.project_item_id(issue_number)
52
+
53
+ unless item_id
54
+ # Link issue to project
55
+ begin
56
+ item_id = @repository_client.link_issue_to_project(@project_id, issue_number)
57
+ @state_store.record_project_item_id(issue_number, item_id)
58
+ display_message("📊 Linked issue ##{issue_number} to project", type: :success)
59
+ rescue => e
60
+ Aidp.log_error("projects_processor", "Failed to link issue to project",
61
+ issue_number: issue_number, error: e.message)
62
+ display_message("⚠️ Failed to link issue ##{issue_number} to project: #{e.message}", type: :warn)
63
+ return false
64
+ end
65
+ end
66
+
67
+ # Update status if provided
68
+ if status
69
+ update_issue_status(issue_number, status)
70
+ end
71
+
72
+ # Check and update blocking status
73
+ check_blocking_dependencies(issue_number)
74
+
75
+ @state_store.record_project_sync(issue_number, {
76
+ last_sync: Time.now.utc.iso8601,
77
+ status: status
78
+ })
79
+
80
+ true
81
+ rescue => e
82
+ Aidp.log_error("projects_processor", "Failed to sync issue to project",
83
+ issue_number: issue_number, error: e.message)
84
+ false
85
+ end
86
+
87
+ # Update the status field for an issue in the project
88
+ # @param issue_number [Integer] The issue number
89
+ # @param status [String] The status value (e.g., "In Progress", "Done")
90
+ # @return [Boolean] True if update was successful
91
+ def update_issue_status(issue_number, status)
92
+ Aidp.log_debug("projects_processor", "update_issue_status",
93
+ issue_number: issue_number, status: status)
94
+
95
+ item_id = @state_store.project_item_id(issue_number)
96
+ return false unless item_id
97
+
98
+ status_field = find_or_create_field(@field_mappings[:status], "SINGLE_SELECT", STATUS_VALUES.values)
99
+ return false unless status_field
100
+
101
+ option_id = find_option_id(status_field, status)
102
+ return false unless option_id
103
+
104
+ begin
105
+ @repository_client.update_project_item_field(
106
+ item_id,
107
+ status_field[:id],
108
+ {project_id: @project_id, option_id: option_id}
109
+ )
110
+ display_message("✓ Updated status for ##{issue_number} to '#{status}'", type: :success)
111
+ true
112
+ rescue => e
113
+ Aidp.log_error("projects_processor", "Failed to update status",
114
+ issue_number: issue_number, status: status, error: e.message)
115
+ display_message("⚠️ Failed to update status: #{e.message}", type: :warn)
116
+ false
117
+ end
118
+ end
119
+
120
+ # Check if an issue is blocked by any of its sub-issues
121
+ # @param issue_number [Integer] The parent issue number
122
+ # @return [Hash] Blocking status with :blocked flag and :blockers list
123
+ def check_blocking_dependencies(issue_number)
124
+ Aidp.log_debug("projects_processor", "check_blocking_dependencies", issue_number: issue_number)
125
+
126
+ status = @state_store.blocking_status(issue_number)
127
+
128
+ if status[:blocked]
129
+ # Fetch current status of sub-issues
130
+ open_blockers = []
131
+ status[:blockers].each do |sub_number|
132
+ issue = @repository_client.fetch_issue(sub_number)
133
+ open_blockers << sub_number if issue[:state] == "open"
134
+ rescue => e
135
+ Aidp.log_warn("projects_processor", "Failed to fetch sub-issue",
136
+ sub_issue: sub_number, error: e.message)
137
+ # Assume still blocking if we can't check
138
+ open_blockers << sub_number
139
+ end
140
+
141
+ if open_blockers.any?
142
+ update_blocking_field(issue_number, open_blockers)
143
+ display_message("⚠️ Issue ##{issue_number} is blocked by #{open_blockers.size} open sub-issues",
144
+ type: :warn)
145
+ {blocked: true, blockers: open_blockers}
146
+ else
147
+ # All sub-issues are closed - unblock parent
148
+ clear_blocking_field(issue_number)
149
+ update_issue_status(issue_number, STATUS_VALUES[:todo])
150
+ display_message("✓ Issue ##{issue_number} is no longer blocked", type: :success)
151
+ {blocked: false, blockers: []}
152
+ end
153
+ else
154
+ {blocked: false, blockers: []}
155
+ end
156
+ end
157
+
158
+ # Sync all active issues in a project
159
+ # @param issues [Array<Hash>] Array of issue data with :number keys
160
+ def sync_all_issues(issues)
161
+ Aidp.log_debug("projects_processor", "sync_all_issues", count: issues.size)
162
+
163
+ display_message("📊 Syncing #{issues.size} issues to project...", type: :info)
164
+
165
+ synced = 0
166
+ failed = 0
167
+
168
+ issues.each do |issue|
169
+ success = sync_issue_to_project(issue[:number])
170
+ if success
171
+ synced += 1
172
+ else
173
+ failed += 1
174
+ end
175
+ end
176
+
177
+ display_message("📊 Sync complete: #{synced} synced, #{failed} failed", type: :info)
178
+ {synced: synced, failed: failed}
179
+ end
180
+
181
+ # Initialize required project fields if they don't exist
182
+ # @return [Boolean] True if all fields are ready
183
+ def ensure_project_fields
184
+ return true unless @auto_create_fields
185
+
186
+ Aidp.log_debug("projects_processor", "ensure_project_fields", project_id: @project_id)
187
+
188
+ required_fields = [
189
+ {name: @field_mappings[:status], type: "SINGLE_SELECT", options: STATUS_VALUES.values},
190
+ {name: @field_mappings[:blocking], type: "TEXT"}
191
+ ]
192
+
193
+ all_ready = true
194
+ required_fields.each do |field_spec|
195
+ field = find_or_create_field(field_spec[:name], field_spec[:type], field_spec[:options])
196
+ all_ready = false unless field
197
+ end
198
+
199
+ all_ready
200
+ end
201
+
202
+ private
203
+
204
+ def project_fields
205
+ @project_fields_cache ||= begin
206
+ @repository_client.fetch_project_fields(@project_id)
207
+ rescue => e
208
+ Aidp.log_error("projects_processor", "Failed to fetch project fields", error: e.message)
209
+ []
210
+ end
211
+ end
212
+
213
+ def invalidate_fields_cache
214
+ @project_fields_cache = nil
215
+ end
216
+
217
+ def find_or_create_field(name, field_type, options = nil)
218
+ # Search existing fields
219
+ field = project_fields.find { |f| f[:name].downcase == name.downcase }
220
+ return field if field
221
+
222
+ # Create if auto-create is enabled
223
+ return nil unless @auto_create_fields
224
+
225
+ Aidp.log_debug("projects_processor", "creating_project_field",
226
+ name: name, field_type: field_type, project_id: @project_id)
227
+
228
+ begin
229
+ formatted_options = if options && field_type == "SINGLE_SELECT"
230
+ options.map { |opt| {name: opt} }
231
+ end
232
+
233
+ field = @repository_client.create_project_field(
234
+ @project_id,
235
+ name,
236
+ field_type,
237
+ options: formatted_options
238
+ )
239
+
240
+ invalidate_fields_cache
241
+ display_message("✓ Created project field '#{name}'", type: :success)
242
+ field
243
+ rescue => e
244
+ Aidp.log_error("projects_processor", "Failed to create project field",
245
+ name: name, error: e.message)
246
+ display_message("⚠️ Failed to create project field '#{name}': #{e.message}", type: :warn)
247
+ nil
248
+ end
249
+ end
250
+
251
+ def find_option_id(field, value)
252
+ return nil unless field[:options]
253
+
254
+ option = field[:options].find { |opt| opt[:name].downcase == value.downcase }
255
+ option&.dig(:id)
256
+ end
257
+
258
+ def update_blocking_field(issue_number, blockers)
259
+ item_id = @state_store.project_item_id(issue_number)
260
+ return false unless item_id
261
+
262
+ blocking_field = find_or_create_field(@field_mappings[:blocking], "TEXT")
263
+ return false unless blocking_field
264
+
265
+ blocker_text = blockers.map { |n| "##{n}" }.join(", ")
266
+
267
+ begin
268
+ @repository_client.update_project_item_field(
269
+ item_id,
270
+ blocking_field[:id],
271
+ {project_id: @project_id, text: blocker_text}
272
+ )
273
+ true
274
+ rescue => e
275
+ Aidp.log_warn("projects_processor", "Failed to update blocking field",
276
+ issue_number: issue_number, error: e.message)
277
+ false
278
+ end
279
+ end
280
+
281
+ def clear_blocking_field(issue_number)
282
+ update_blocking_field(issue_number, [])
283
+ end
284
+ end
285
+ end
286
+ end