aidp 0.43.0 โ 0.45.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/lib/aidp/config.rb +15 -1
- data/lib/aidp/setup/wizard.rb +14 -0
- data/lib/aidp/version.rb +1 -1
- data/lib/aidp/watch/gantt_synchronizer.rb +273 -0
- data/lib/aidp/watch/plan_generator.rb +2 -1
- data/lib/aidp/watch/plan_processor.rb +494 -18
- data/lib/aidp/watch/prd_parser.rb +395 -0
- data/lib/aidp/watch/projects_processor.rb +88 -11
- data/lib/aidp/watch/repository_client.rb +212 -1
- data/lib/aidp/watch/runner.rb +117 -9
- data/lib/aidp/watch/state_store.rb +41 -1
- data/lib/aidp/watch/sub_issue_creator.rb +133 -19
- metadata +3 -1
|
@@ -9,12 +9,16 @@ module Aidp
|
|
|
9
9
|
class SubIssueCreator
|
|
10
10
|
include Aidp::MessageDisplay
|
|
11
11
|
|
|
12
|
+
UnresolvedDependenciesError = Class.new(StandardError)
|
|
13
|
+
|
|
12
14
|
attr_reader :repository_client, :state_store, :project_id
|
|
13
15
|
|
|
14
|
-
def initialize(repository_client:, state_store:, project_id: nil)
|
|
16
|
+
def initialize(repository_client:, state_store:, project_id: nil, build_label: "aidp-build", blocked_label: "aidp-blocked")
|
|
15
17
|
@repository_client = repository_client
|
|
16
18
|
@state_store = state_store
|
|
17
19
|
@project_id = project_id
|
|
20
|
+
@build_label = build_label
|
|
21
|
+
@blocked_label = blocked_label
|
|
18
22
|
end
|
|
19
23
|
|
|
20
24
|
# Creates sub-issues from hierarchical plan data
|
|
@@ -28,22 +32,29 @@ module Aidp
|
|
|
28
32
|
display_message("๐จ Creating #{sub_issues_data.size} sub-issues for ##{parent_number}", type: :info)
|
|
29
33
|
|
|
30
34
|
created_issues = []
|
|
35
|
+
creation_failed = false
|
|
31
36
|
|
|
32
37
|
sub_issues_data.each_with_index do |sub_data, index|
|
|
33
38
|
issue = create_single_sub_issue(parent_issue, sub_data, index + 1)
|
|
34
39
|
created_issues << issue
|
|
35
40
|
display_message(" โ Created sub-issue ##{issue[:number]}: #{sub_data[:title]}", type: :success)
|
|
36
41
|
rescue => e
|
|
42
|
+
creation_failed = true
|
|
37
43
|
Aidp.log_error("sub_issue_creator", "Failed to create sub-issue", parent: parent_number, index: index, error: e.message)
|
|
38
44
|
display_message(" โ Failed to create sub-issue #{index + 1}: #{e.message}", type: :error)
|
|
45
|
+
break
|
|
39
46
|
end
|
|
40
47
|
|
|
48
|
+
return handle_partial_creation_failure(parent_number, created_issues) if creation_failed
|
|
49
|
+
|
|
50
|
+
record_dependencies(created_issues)
|
|
51
|
+
|
|
41
52
|
# Link all created issues to project if project_id is configured
|
|
42
53
|
if @project_id && created_issues.any?
|
|
43
54
|
link_issues_to_project(parent_number, created_issues.map { |i| i[:number] })
|
|
44
55
|
end
|
|
45
56
|
|
|
46
|
-
#
|
|
57
|
+
# Persist the hierarchy only after creation and dependency resolution succeed.
|
|
47
58
|
@state_store.record_sub_issues(parent_number, created_issues.map { |i| i[:number] })
|
|
48
59
|
|
|
49
60
|
# Post summary comment on parent issue
|
|
@@ -51,6 +62,23 @@ module Aidp
|
|
|
51
62
|
|
|
52
63
|
Aidp.log_debug("sub_issue_creator", "create_sub_issues_complete", parent: parent_number, created: created_issues.size)
|
|
53
64
|
created_issues
|
|
65
|
+
rescue UnresolvedDependenciesError
|
|
66
|
+
cleanup_created_issues(parent_number, created_issues)
|
|
67
|
+
raise
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def planned_sub_issue_attributes(parent_issue, sub_data, sequence_number)
|
|
71
|
+
dependencies = sub_issue_dependencies(sub_data)
|
|
72
|
+
title = sub_data[:title]
|
|
73
|
+
title = "#{parent_issue[:title]} - Part #{sequence_number}" if title.to_s.strip.empty?
|
|
74
|
+
|
|
75
|
+
{
|
|
76
|
+
title: title,
|
|
77
|
+
body: build_sub_issue_body(parent_issue, sub_data, sequence_number),
|
|
78
|
+
labels: sub_issue_labels(sub_data, dependencies),
|
|
79
|
+
assignees: Array(sub_data[:assignees]),
|
|
80
|
+
dependencies: dependencies
|
|
81
|
+
}
|
|
54
82
|
end
|
|
55
83
|
|
|
56
84
|
private
|
|
@@ -59,23 +87,14 @@ module Aidp
|
|
|
59
87
|
parent_number = parent_issue[:number]
|
|
60
88
|
Aidp.log_debug("sub_issue_creator", "create_single_sub_issue", parent: parent_number, sequence: sequence_number)
|
|
61
89
|
|
|
62
|
-
|
|
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]
|
|
90
|
+
attributes = planned_sub_issue_attributes(parent_issue, sub_data, sequence_number)
|
|
72
91
|
|
|
73
92
|
# Create the issue
|
|
74
93
|
result = @repository_client.create_issue(
|
|
75
|
-
title: title,
|
|
76
|
-
body: body,
|
|
77
|
-
labels: labels,
|
|
78
|
-
assignees:
|
|
94
|
+
title: attributes[:title],
|
|
95
|
+
body: attributes[:body],
|
|
96
|
+
labels: attributes[:labels],
|
|
97
|
+
assignees: attributes[:assignees]
|
|
79
98
|
)
|
|
80
99
|
|
|
81
100
|
Aidp.log_debug("sub_issue_creator", "issue_created", parent: parent_number, number: result[:number], url: result[:url])
|
|
@@ -83,10 +102,10 @@ module Aidp
|
|
|
83
102
|
{
|
|
84
103
|
number: result[:number],
|
|
85
104
|
url: result[:url],
|
|
86
|
-
title: title,
|
|
105
|
+
title: attributes[:title],
|
|
87
106
|
skills: sub_data[:skills],
|
|
88
107
|
personas: sub_data[:personas],
|
|
89
|
-
dependencies:
|
|
108
|
+
dependencies: attributes[:dependencies]
|
|
90
109
|
}
|
|
91
110
|
end
|
|
92
111
|
|
|
@@ -201,7 +220,8 @@ module Aidp
|
|
|
201
220
|
end
|
|
202
221
|
|
|
203
222
|
parts << ""
|
|
204
|
-
parts << "
|
|
223
|
+
parts << "Dependency-ready sub-issues have been labeled with `#{@build_label}`."
|
|
224
|
+
parts << "Sub-issues waiting on prerequisites have been labeled with `#{@blocked_label}` until their dependencies are complete."
|
|
205
225
|
parts << ""
|
|
206
226
|
parts << "---"
|
|
207
227
|
parts << "_This breakdown was automatically generated by AIDP._"
|
|
@@ -216,6 +236,100 @@ module Aidp
|
|
|
216
236
|
display_message("โ ๏ธ Failed to post summary comment: #{e.message}", type: :warn)
|
|
217
237
|
end
|
|
218
238
|
end
|
|
239
|
+
|
|
240
|
+
def record_dependencies(created_issues)
|
|
241
|
+
title_map = build_title_map(created_issues)
|
|
242
|
+
|
|
243
|
+
created_issues.each do |issue|
|
|
244
|
+
dependency_numbers, unresolved_dependencies = resolve_dependency_numbers(issue[:dependencies], title_map)
|
|
245
|
+
raise_unresolved_dependencies!(issue, unresolved_dependencies) if unresolved_dependencies.any?
|
|
246
|
+
|
|
247
|
+
@state_store.record_issue_dependencies(issue[:number], dependency_numbers)
|
|
248
|
+
issue[:dependencies] = dependency_numbers
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
|
|
252
|
+
def resolve_dependency_numbers(dependencies, title_map)
|
|
253
|
+
Array(dependencies).each_with_object([[], []]) do |dependency, (resolved, unresolved)|
|
|
254
|
+
dependency_number = resolve_dependency_number(dependency, title_map)
|
|
255
|
+
dependency_number ? resolved << dependency_number : unresolved << dependency
|
|
256
|
+
end
|
|
257
|
+
end
|
|
258
|
+
|
|
259
|
+
def raise_unresolved_dependencies!(issue, unresolved_dependencies)
|
|
260
|
+
dependency_list = unresolved_dependencies.join(", ")
|
|
261
|
+
raise UnresolvedDependenciesError,
|
|
262
|
+
"Unable to resolve dependencies for sub-issue ##{issue[:number]} (#{issue[:title]}): #{dependency_list}"
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def build_title_map(created_issues)
|
|
266
|
+
duplicate_titles = duplicate_normalized_titles(created_issues)
|
|
267
|
+
if duplicate_titles.empty?
|
|
268
|
+
return created_issues.each_with_object({}) do |issue, memo|
|
|
269
|
+
memo[normalize_title(issue[:title])] = issue[:number]
|
|
270
|
+
end
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
raise UnresolvedDependenciesError,
|
|
274
|
+
"Duplicate sub-issue titles after normalization are not allowed: #{duplicate_titles.join(", ")}"
|
|
275
|
+
end
|
|
276
|
+
|
|
277
|
+
def duplicate_normalized_titles(created_issues)
|
|
278
|
+
grouped_titles = created_issues.each_with_object(Hash.new { |hash, key| hash[key] = [] }) do |issue, memo|
|
|
279
|
+
normalized_title = normalize_title(issue[:title])
|
|
280
|
+
next if normalized_title.empty?
|
|
281
|
+
|
|
282
|
+
memo[normalized_title] << issue[:title].to_s.strip
|
|
283
|
+
end
|
|
284
|
+
|
|
285
|
+
grouped_titles.filter_map do |_normalized_title, titles|
|
|
286
|
+
next unless titles.size > 1
|
|
287
|
+
|
|
288
|
+
titles.uniq.join(" / ")
|
|
289
|
+
end
|
|
290
|
+
end
|
|
291
|
+
|
|
292
|
+
def normalize_title(title)
|
|
293
|
+
title.to_s.strip.downcase
|
|
294
|
+
end
|
|
295
|
+
|
|
296
|
+
def resolve_dependency_number(dependency, title_map)
|
|
297
|
+
text = dependency.to_s.strip
|
|
298
|
+
match = text.match(/\A#(\d+)\z/)
|
|
299
|
+
return match[1].to_i if match
|
|
300
|
+
|
|
301
|
+
title_map[normalize_title(text)]
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
def sub_issue_dependencies(sub_data)
|
|
305
|
+
Array(sub_data[:dependencies]).map(&:to_s).reject(&:empty?)
|
|
306
|
+
end
|
|
307
|
+
|
|
308
|
+
def sub_issue_labels(sub_data, dependencies)
|
|
309
|
+
[dependencies.any? ? @blocked_label : @build_label].concat(Array(sub_data[:labels]))
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def handle_partial_creation_failure(parent_number, created_issues)
|
|
313
|
+
cleanup_created_issues(parent_number, created_issues)
|
|
314
|
+
[]
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def cleanup_created_issues(parent_number, created_issues)
|
|
318
|
+
return if created_issues.empty?
|
|
319
|
+
|
|
320
|
+
Aidp.log_warn("sub_issue_creator", "cleanup_created_sub_issues",
|
|
321
|
+
parent: parent_number,
|
|
322
|
+
sub_issue_numbers: created_issues.map { |issue| issue[:number] })
|
|
323
|
+
display_message("๐งน Cleaning up #{created_issues.size} partially created sub-issues for ##{parent_number}", type: :warn)
|
|
324
|
+
|
|
325
|
+
created_issues.each do |issue|
|
|
326
|
+
@repository_client.close_issue(issue[:number])
|
|
327
|
+
display_message(" โ Closed sub-issue ##{issue[:number]}", type: :success)
|
|
328
|
+
rescue => e
|
|
329
|
+
Aidp.log_error("sub_issue_creator", "Failed to clean up sub-issue", issue: issue[:number], error: e.message)
|
|
330
|
+
display_message(" โ Failed to close sub-issue ##{issue[:number]}: #{e.message}", type: :warn)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
219
333
|
end
|
|
220
334
|
end
|
|
221
335
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: aidp
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.45.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Bart Agapinan
|
|
@@ -609,11 +609,13 @@ files:
|
|
|
609
609
|
- lib/aidp/watch/ci_fix_processor.rb
|
|
610
610
|
- lib/aidp/watch/ci_log_extractor.rb
|
|
611
611
|
- lib/aidp/watch/feedback_collector.rb
|
|
612
|
+
- lib/aidp/watch/gantt_synchronizer.rb
|
|
612
613
|
- lib/aidp/watch/github_state_extractor.rb
|
|
613
614
|
- lib/aidp/watch/hierarchical_pr_strategy.rb
|
|
614
615
|
- lib/aidp/watch/implementation_verifier.rb
|
|
615
616
|
- lib/aidp/watch/plan_generator.rb
|
|
616
617
|
- lib/aidp/watch/plan_processor.rb
|
|
618
|
+
- lib/aidp/watch/prd_parser.rb
|
|
617
619
|
- lib/aidp/watch/projects_processor.rb
|
|
618
620
|
- lib/aidp/watch/rebase_processor.rb
|
|
619
621
|
- lib/aidp/watch/repository_client.rb
|