aidp 0.33.0 → 0.34.1
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/ui.rb +11 -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 +21 -1
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "../message_display"
|
|
4
|
+
|
|
5
|
+
module Aidp
|
|
6
|
+
module Watch
|
|
7
|
+
# Creates sub-issues from a hierarchical plan during watch mode.
|
|
8
|
+
# Links sub-issues to parent, adds them to projects, and sets custom fields.
|
|
9
|
+
class SubIssueCreator
|
|
10
|
+
include Aidp::MessageDisplay
|
|
11
|
+
|
|
12
|
+
attr_reader :repository_client, :state_store, :project_id
|
|
13
|
+
|
|
14
|
+
def initialize(repository_client:, state_store:, project_id: nil)
|
|
15
|
+
@repository_client = repository_client
|
|
16
|
+
@state_store = state_store
|
|
17
|
+
@project_id = project_id
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Creates sub-issues from hierarchical plan data
|
|
21
|
+
# @param parent_issue [Hash] The parent issue data
|
|
22
|
+
# @param sub_issues_data [Array<Hash>] Array of sub-issue specifications
|
|
23
|
+
# @return [Array<Hash>] Created sub-issue details
|
|
24
|
+
def create_sub_issues(parent_issue, sub_issues_data)
|
|
25
|
+
parent_number = parent_issue[:number]
|
|
26
|
+
Aidp.log_debug("sub_issue_creator", "create_sub_issues", parent: parent_number, count: sub_issues_data.size)
|
|
27
|
+
|
|
28
|
+
display_message("🔨 Creating #{sub_issues_data.size} sub-issues for ##{parent_number}", type: :info)
|
|
29
|
+
|
|
30
|
+
created_issues = []
|
|
31
|
+
|
|
32
|
+
sub_issues_data.each_with_index do |sub_data, index|
|
|
33
|
+
issue = create_single_sub_issue(parent_issue, sub_data, index + 1)
|
|
34
|
+
created_issues << issue
|
|
35
|
+
display_message(" ✓ Created sub-issue ##{issue[:number]}: #{sub_data[:title]}", type: :success)
|
|
36
|
+
rescue => e
|
|
37
|
+
Aidp.log_error("sub_issue_creator", "Failed to create sub-issue", parent: parent_number, index: index, error: e.message)
|
|
38
|
+
display_message(" ✗ Failed to create sub-issue #{index + 1}: #{e.message}", type: :error)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Link all created issues to project if project_id is configured
|
|
42
|
+
if @project_id && created_issues.any?
|
|
43
|
+
link_issues_to_project(parent_number, created_issues.map { |i| i[:number] })
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Update state store with parent-child relationships
|
|
47
|
+
@state_store.record_sub_issues(parent_number, created_issues.map { |i| i[:number] })
|
|
48
|
+
|
|
49
|
+
# Post summary comment on parent issue
|
|
50
|
+
post_sub_issues_summary(parent_issue, created_issues)
|
|
51
|
+
|
|
52
|
+
Aidp.log_debug("sub_issue_creator", "create_sub_issues_complete", parent: parent_number, created: created_issues.size)
|
|
53
|
+
created_issues
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
private
|
|
57
|
+
|
|
58
|
+
def create_single_sub_issue(parent_issue, sub_data, sequence_number)
|
|
59
|
+
parent_number = parent_issue[:number]
|
|
60
|
+
Aidp.log_debug("sub_issue_creator", "create_single_sub_issue", parent: parent_number, sequence: sequence_number)
|
|
61
|
+
|
|
62
|
+
# Build issue title
|
|
63
|
+
title = sub_data[:title]
|
|
64
|
+
title = "#{parent_issue[:title]} - Part #{sequence_number}" if title.to_s.strip.empty?
|
|
65
|
+
|
|
66
|
+
# Build issue body with parent reference
|
|
67
|
+
body = build_sub_issue_body(parent_issue, sub_data, sequence_number)
|
|
68
|
+
|
|
69
|
+
# Determine labels
|
|
70
|
+
labels = ["aidp-auto"]
|
|
71
|
+
labels.concat(Array(sub_data[:labels])) if sub_data[:labels]
|
|
72
|
+
|
|
73
|
+
# Create the issue
|
|
74
|
+
result = @repository_client.create_issue(
|
|
75
|
+
title: title,
|
|
76
|
+
body: body,
|
|
77
|
+
labels: labels,
|
|
78
|
+
assignees: Array(sub_data[:assignees])
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
Aidp.log_debug("sub_issue_creator", "issue_created", parent: parent_number, number: result[:number], url: result[:url])
|
|
82
|
+
|
|
83
|
+
{
|
|
84
|
+
number: result[:number],
|
|
85
|
+
url: result[:url],
|
|
86
|
+
title: title,
|
|
87
|
+
skills: sub_data[:skills],
|
|
88
|
+
personas: sub_data[:personas],
|
|
89
|
+
dependencies: sub_data[:dependencies]
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def build_sub_issue_body(parent_issue, sub_data, sequence_number)
|
|
94
|
+
parts = []
|
|
95
|
+
|
|
96
|
+
# Parent reference
|
|
97
|
+
parts << "## 🔗 Parent Issue"
|
|
98
|
+
parts << ""
|
|
99
|
+
parts << "This is sub-issue #{sequence_number} of #{parent_issue[:url]}"
|
|
100
|
+
parts << ""
|
|
101
|
+
|
|
102
|
+
# Description
|
|
103
|
+
if sub_data[:description] && !sub_data[:description].to_s.strip.empty?
|
|
104
|
+
parts << "## 📋 Description"
|
|
105
|
+
parts << ""
|
|
106
|
+
parts << sub_data[:description]
|
|
107
|
+
parts << ""
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Tasks
|
|
111
|
+
if sub_data[:tasks]&.any?
|
|
112
|
+
parts << "## ✅ Tasks"
|
|
113
|
+
parts << ""
|
|
114
|
+
sub_data[:tasks].each do |task|
|
|
115
|
+
parts << "- [ ] #{task}"
|
|
116
|
+
end
|
|
117
|
+
parts << ""
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Skills required
|
|
121
|
+
if sub_data[:skills]&.any?
|
|
122
|
+
parts << "## 🛠️ Skills Required"
|
|
123
|
+
parts << ""
|
|
124
|
+
parts << sub_data[:skills].map { |s| "- #{s}" }.join("\n")
|
|
125
|
+
parts << ""
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Personas
|
|
129
|
+
if sub_data[:personas]&.any?
|
|
130
|
+
parts << "## 👤 Suggested Personas"
|
|
131
|
+
parts << ""
|
|
132
|
+
parts << sub_data[:personas].map { |p| "- #{p}" }.join("\n")
|
|
133
|
+
parts << ""
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
# Dependencies
|
|
137
|
+
if sub_data[:dependencies]&.any?
|
|
138
|
+
parts << "## ⚠️ Dependencies"
|
|
139
|
+
parts << ""
|
|
140
|
+
parts << "This issue depends on:"
|
|
141
|
+
parts << sub_data[:dependencies].map { |d| "- #{d}" }.join("\n")
|
|
142
|
+
parts << ""
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# Footer
|
|
146
|
+
parts << "---"
|
|
147
|
+
parts << "_This issue was automatically created by AIDP as part of hierarchical project planning._"
|
|
148
|
+
|
|
149
|
+
parts.join("\n")
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def link_issues_to_project(parent_number, sub_issue_numbers)
|
|
153
|
+
Aidp.log_debug("sub_issue_creator", "link_issues_to_project", parent: parent_number, project_id: @project_id, count: sub_issue_numbers.size + 1)
|
|
154
|
+
|
|
155
|
+
display_message("📊 Linking issues to project #{@project_id}", type: :info)
|
|
156
|
+
|
|
157
|
+
# Link parent issue
|
|
158
|
+
begin
|
|
159
|
+
parent_item_id = @repository_client.link_issue_to_project(@project_id, parent_number)
|
|
160
|
+
@state_store.record_project_item_id(parent_number, parent_item_id)
|
|
161
|
+
display_message(" ✓ Linked parent issue ##{parent_number}", type: :success)
|
|
162
|
+
rescue => e
|
|
163
|
+
Aidp.log_error("sub_issue_creator", "Failed to link parent to project", parent: parent_number, error: e.message)
|
|
164
|
+
display_message(" ✗ Failed to link parent issue: #{e.message}", type: :warn)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
# Link sub-issues
|
|
168
|
+
sub_issue_numbers.each do |number|
|
|
169
|
+
item_id = @repository_client.link_issue_to_project(@project_id, number)
|
|
170
|
+
@state_store.record_project_item_id(number, item_id)
|
|
171
|
+
display_message(" ✓ Linked sub-issue ##{number}", type: :success)
|
|
172
|
+
rescue => e
|
|
173
|
+
Aidp.log_error("sub_issue_creator", "Failed to link sub-issue to project", issue: number, error: e.message)
|
|
174
|
+
display_message(" ✗ Failed to link sub-issue ##{number}: #{e.message}", type: :warn)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def post_sub_issues_summary(parent_issue, created_issues)
|
|
179
|
+
parent_number = parent_issue[:number]
|
|
180
|
+
Aidp.log_debug("sub_issue_creator", "post_sub_issues_summary", parent: parent_number, count: created_issues.size)
|
|
181
|
+
|
|
182
|
+
return if created_issues.empty?
|
|
183
|
+
|
|
184
|
+
parts = []
|
|
185
|
+
parts << "## 🔀 Sub-Issues Created"
|
|
186
|
+
parts << ""
|
|
187
|
+
parts << "AIDP has broken down this issue into #{created_issues.size} sub-issues:"
|
|
188
|
+
parts << ""
|
|
189
|
+
|
|
190
|
+
created_issues.each_with_index do |issue, index|
|
|
191
|
+
parts << "#{index + 1}. ##{issue[:number]} - #{issue[:title]}"
|
|
192
|
+
|
|
193
|
+
metadata = []
|
|
194
|
+
metadata << "**Skills**: #{issue[:skills].join(", ")}" if issue[:skills]&.any?
|
|
195
|
+
metadata << "**Personas**: #{issue[:personas].join(", ")}" if issue[:personas]&.any?
|
|
196
|
+
metadata << "**Depends on**: #{issue[:dependencies].join(", ")}" if issue[:dependencies]&.any?
|
|
197
|
+
|
|
198
|
+
if metadata.any?
|
|
199
|
+
parts << " - #{metadata.join(" | ")}"
|
|
200
|
+
end
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
parts << ""
|
|
204
|
+
parts << "Each sub-issue has been labeled with `aidp-auto` and will be automatically picked up for implementation."
|
|
205
|
+
parts << ""
|
|
206
|
+
parts << "---"
|
|
207
|
+
parts << "_This breakdown was automatically generated by AIDP._"
|
|
208
|
+
|
|
209
|
+
body = parts.join("\n")
|
|
210
|
+
|
|
211
|
+
begin
|
|
212
|
+
@repository_client.post_comment(parent_number, body)
|
|
213
|
+
display_message("💬 Posted sub-issues summary to parent issue ##{parent_number}", type: :success)
|
|
214
|
+
rescue => e
|
|
215
|
+
Aidp.log_error("sub_issue_creator", "Failed to post summary comment", parent: parent_number, error: e.message)
|
|
216
|
+
display_message("⚠️ Failed to post summary comment: #{e.message}", type: :warn)
|
|
217
|
+
end
|
|
218
|
+
end
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
end
|
|
@@ -19,6 +19,10 @@ module Aidp
|
|
|
19
19
|
|
|
20
20
|
class ConversationError < StandardError; end
|
|
21
21
|
|
|
22
|
+
# Expose for testability
|
|
23
|
+
attr_reader :project_dir, :conversation_history, :user_input, :config_manager
|
|
24
|
+
attr_writer :provider_manager
|
|
25
|
+
|
|
22
26
|
def initialize(project_dir, prompt: nil, use_enhanced_input: true, verbose: false, config_manager: nil, provider_manager: nil)
|
|
23
27
|
@project_dir = project_dir
|
|
24
28
|
|
data/lib/aidp/worktree.rb
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
require "fileutils"
|
|
4
4
|
require "json"
|
|
5
5
|
require "open3"
|
|
6
|
+
require "time"
|
|
6
7
|
|
|
7
8
|
module Aidp
|
|
8
9
|
# Manages git worktree operations for parallel workstreams.
|
|
@@ -144,19 +145,43 @@ module Aidp
|
|
|
144
145
|
#
|
|
145
146
|
# @param branch [String] Branch name to search for
|
|
146
147
|
# @param project_dir [String] Project root directory
|
|
147
|
-
# @
|
|
148
|
-
|
|
148
|
+
# @param create_if_not_found [Boolean] Whether to create a worktree if not found
|
|
149
|
+
# @param base_branch [String, nil] Base branch to use if creating a new worktree
|
|
150
|
+
# @return [Hash, nil] Worktree info or nil if not found and create_if_not_found is false
|
|
151
|
+
def find_by_branch(branch:, project_dir: Dir.pwd, create_if_not_found: false, base_branch: nil)
|
|
152
|
+
# First, try exact match in the registry
|
|
149
153
|
registry = load_registry(project_dir)
|
|
150
154
|
slug, data = registry.find { |_slug, info| info["branch"] == branch }
|
|
151
|
-
return nil unless data
|
|
152
155
|
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
# Check if the existing worktree is still active
|
|
157
|
+
if data
|
|
158
|
+
active = Dir.exist?(data["path"])
|
|
159
|
+
Aidp.log_debug("worktree", active ? "found_existing" : "found_inactive", branch: branch, slug: slug)
|
|
160
|
+
return {
|
|
161
|
+
slug: slug,
|
|
162
|
+
path: data["path"],
|
|
163
|
+
branch: data["branch"],
|
|
164
|
+
created_at: data["created_at"],
|
|
165
|
+
active: active
|
|
166
|
+
}
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# If not found and create_if_not_found is true, create a new worktree
|
|
170
|
+
if create_if_not_found
|
|
171
|
+
# Generate a slug from the branch name, replacing problematic characters
|
|
172
|
+
safe_slug = branch.downcase.gsub(/[^a-z0-9\-_]/, "-")
|
|
173
|
+
|
|
174
|
+
Aidp.log_debug("worktree", "creating_for_branch", branch: branch, slug: safe_slug)
|
|
175
|
+
return create(
|
|
176
|
+
slug: safe_slug,
|
|
177
|
+
project_dir: project_dir,
|
|
178
|
+
branch: branch,
|
|
179
|
+
base_branch: base_branch
|
|
180
|
+
)
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
# If no existing worktree and not set to create, return nil
|
|
184
|
+
nil
|
|
160
185
|
end
|
|
161
186
|
|
|
162
187
|
private
|
|
@@ -227,11 +252,36 @@ module Aidp
|
|
|
227
252
|
end
|
|
228
253
|
end
|
|
229
254
|
|
|
255
|
+
# Check if a branch exists on the remote (origin)
|
|
256
|
+
# @param project_dir [String] Project root directory
|
|
257
|
+
# @param branch [String] Branch name to check
|
|
258
|
+
# @return [Boolean] True if branch exists on origin
|
|
259
|
+
def remote_branch_exists?(project_dir, branch)
|
|
260
|
+
Dir.chdir(project_dir) do
|
|
261
|
+
system("git", "show-ref", "--verify", "--quiet", "refs/remotes/origin/#{branch}")
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
230
265
|
def run_worktree_add!(project_dir:, branch:, branch_exists:, worktree_path:, base_branch:)
|
|
231
266
|
prune_attempted = false
|
|
232
267
|
|
|
268
|
+
# If branch doesn't exist locally and no base_branch provided,
|
|
269
|
+
# check if it exists on the remote and use that as the base.
|
|
270
|
+
# This handles PRs created by external tools (Claude Code Web, GitHub UI, etc.)
|
|
271
|
+
effective_base_branch = base_branch
|
|
272
|
+
if !branch_exists && base_branch.nil? && remote_branch_exists?(project_dir, branch)
|
|
273
|
+
effective_base_branch = "origin/#{branch}"
|
|
274
|
+
Aidp.log_debug("worktree", "using_remote_branch_as_base",
|
|
275
|
+
branch: branch, remote_ref: effective_base_branch)
|
|
276
|
+
end
|
|
277
|
+
|
|
233
278
|
loop do
|
|
234
|
-
cmd = build_worktree_command(
|
|
279
|
+
cmd = build_worktree_command(
|
|
280
|
+
branch_exists: branch_exists,
|
|
281
|
+
branch: branch,
|
|
282
|
+
worktree_path: worktree_path,
|
|
283
|
+
base_branch: effective_base_branch
|
|
284
|
+
)
|
|
235
285
|
stdout, stderr, status = Dir.chdir(project_dir) { Open3.capture3(*cmd) }
|
|
236
286
|
|
|
237
287
|
return if status.success?
|