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.
- checksums.yaml +4 -4
- data/README.md +35 -0
- data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
- data/lib/aidp/cli/eval_command.rb +399 -0
- data/lib/aidp/cli/harness_command.rb +1 -1
- data/lib/aidp/cli/security_command.rb +416 -0
- data/lib/aidp/cli/tools_command.rb +6 -4
- data/lib/aidp/cli.rb +170 -3
- data/lib/aidp/concurrency/exec.rb +3 -0
- data/lib/aidp/config.rb +113 -0
- data/lib/aidp/config_paths.rb +20 -0
- data/lib/aidp/daemon/runner.rb +8 -4
- data/lib/aidp/errors.rb +134 -0
- data/lib/aidp/evaluations/context_capture.rb +205 -0
- data/lib/aidp/evaluations/evaluation_record.rb +114 -0
- data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
- data/lib/aidp/evaluations.rb +23 -0
- data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
- data/lib/aidp/execute/interactive_repl.rb +6 -2
- data/lib/aidp/execute/prompt_evaluator.rb +359 -0
- data/lib/aidp/execute/repl_macros.rb +100 -1
- data/lib/aidp/execute/work_loop_runner.rb +399 -47
- data/lib/aidp/execute/work_loop_state.rb +4 -1
- data/lib/aidp/execute/workflow_selector.rb +3 -0
- data/lib/aidp/harness/ai_decision_engine.rb +79 -0
- data/lib/aidp/harness/capability_registry.rb +2 -0
- data/lib/aidp/harness/condition_detector.rb +3 -0
- data/lib/aidp/harness/config_loader.rb +3 -0
- data/lib/aidp/harness/enhanced_runner.rb +14 -11
- data/lib/aidp/harness/error_handler.rb +3 -0
- data/lib/aidp/harness/provider_factory.rb +3 -0
- data/lib/aidp/harness/provider_manager.rb +6 -0
- data/lib/aidp/harness/runner.rb +5 -1
- data/lib/aidp/harness/state/persistence.rb +3 -0
- data/lib/aidp/harness/state_manager.rb +3 -0
- data/lib/aidp/harness/status_display.rb +28 -20
- data/lib/aidp/harness/thinking_depth_manager.rb +32 -32
- data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
- data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
- data/lib/aidp/harness/ui/error_handler.rb +3 -0
- data/lib/aidp/harness/ui/job_monitor.rb +4 -0
- data/lib/aidp/harness/ui/navigation/submenu.rb +2 -0
- data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
- data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
- data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
- data/lib/aidp/harness/user_interface.rb +3 -0
- data/lib/aidp/loader.rb +2 -2
- data/lib/aidp/logger.rb +3 -0
- data/lib/aidp/message_display.rb +31 -0
- data/lib/aidp/pr_worktree_manager.rb +18 -6
- data/lib/aidp/provider_manager.rb +3 -0
- data/lib/aidp/providers/base.rb +2 -0
- data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
- data/lib/aidp/security/secrets_proxy.rb +328 -0
- data/lib/aidp/security/secrets_registry.rb +227 -0
- data/lib/aidp/security/trifecta_state.rb +220 -0
- data/lib/aidp/security/watch_mode_handler.rb +306 -0
- data/lib/aidp/security/work_loop_adapter.rb +277 -0
- data/lib/aidp/security.rb +56 -0
- data/lib/aidp/setup/wizard.rb +4 -2
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/auto_merger.rb +274 -0
- data/lib/aidp/watch/auto_pr_processor.rb +125 -7
- data/lib/aidp/watch/build_processor.rb +16 -1
- data/lib/aidp/watch/change_request_processor.rb +680 -286
- data/lib/aidp/watch/ci_fix_processor.rb +262 -4
- data/lib/aidp/watch/feedback_collector.rb +191 -0
- data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
- data/lib/aidp/watch/implementation_verifier.rb +142 -1
- data/lib/aidp/watch/plan_generator.rb +70 -13
- data/lib/aidp/watch/plan_processor.rb +12 -5
- data/lib/aidp/watch/projects_processor.rb +286 -0
- data/lib/aidp/watch/repository_client.rb +861 -53
- data/lib/aidp/watch/review_processor.rb +33 -6
- data/lib/aidp/watch/runner.rb +51 -11
- data/lib/aidp/watch/state_store.rb +233 -0
- data/lib/aidp/watch/sub_issue_creator.rb +221 -0
- data/lib/aidp/workflows/guided_agent.rb +4 -0
- data/lib/aidp/workstream_executor.rb +3 -0
- data/lib/aidp/worktree.rb +61 -11
- data/lib/aidp/worktree_branch_manager.rb +347 -101
- data/templates/implementation/iterative_implementation.md +46 -3
- 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
|