aidp 0.32.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 (112) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +35 -0
  3. data/lib/aidp/analyze/feature_analyzer.rb +322 -320
  4. data/lib/aidp/analyze/tree_sitter_scan.rb +3 -0
  5. data/lib/aidp/auto_update/coordinator.rb +97 -7
  6. data/lib/aidp/auto_update.rb +0 -12
  7. data/lib/aidp/cli/devcontainer_commands.rb +0 -5
  8. data/lib/aidp/cli/eval_command.rb +399 -0
  9. data/lib/aidp/cli/harness_command.rb +1 -1
  10. data/lib/aidp/cli/security_command.rb +416 -0
  11. data/lib/aidp/cli/tools_command.rb +6 -4
  12. data/lib/aidp/cli.rb +172 -4
  13. data/lib/aidp/comment_consolidator.rb +78 -0
  14. data/lib/aidp/concurrency/exec.rb +3 -0
  15. data/lib/aidp/concurrency.rb +0 -3
  16. data/lib/aidp/config.rb +113 -1
  17. data/lib/aidp/config_paths.rb +91 -0
  18. data/lib/aidp/daemon/runner.rb +8 -4
  19. data/lib/aidp/errors.rb +134 -0
  20. data/lib/aidp/evaluations/context_capture.rb +205 -0
  21. data/lib/aidp/evaluations/evaluation_record.rb +114 -0
  22. data/lib/aidp/evaluations/evaluation_storage.rb +250 -0
  23. data/lib/aidp/evaluations.rb +23 -0
  24. data/lib/aidp/execute/async_work_loop_runner.rb +4 -1
  25. data/lib/aidp/execute/interactive_repl.rb +6 -2
  26. data/lib/aidp/execute/prompt_evaluator.rb +359 -0
  27. data/lib/aidp/execute/repl_macros.rb +100 -1
  28. data/lib/aidp/execute/work_loop_runner.rb +719 -58
  29. data/lib/aidp/execute/work_loop_state.rb +4 -1
  30. data/lib/aidp/execute/workflow_selector.rb +3 -0
  31. data/lib/aidp/harness/ai_decision_engine.rb +79 -0
  32. data/lib/aidp/harness/ai_filter_factory.rb +285 -0
  33. data/lib/aidp/harness/capability_registry.rb +2 -0
  34. data/lib/aidp/harness/condition_detector.rb +3 -0
  35. data/lib/aidp/harness/config_loader.rb +3 -0
  36. data/lib/aidp/harness/config_schema.rb +97 -1
  37. data/lib/aidp/harness/config_validator.rb +1 -1
  38. data/lib/aidp/harness/configuration.rb +61 -5
  39. data/lib/aidp/harness/enhanced_runner.rb +14 -11
  40. data/lib/aidp/harness/error_handler.rb +3 -0
  41. data/lib/aidp/harness/filter_definition.rb +212 -0
  42. data/lib/aidp/harness/generated_filter_strategy.rb +197 -0
  43. data/lib/aidp/harness/output_filter.rb +50 -25
  44. data/lib/aidp/harness/output_filter_config.rb +129 -0
  45. data/lib/aidp/harness/provider_factory.rb +3 -0
  46. data/lib/aidp/harness/provider_manager.rb +96 -2
  47. data/lib/aidp/harness/runner.rb +5 -12
  48. data/lib/aidp/harness/state/persistence.rb +3 -0
  49. data/lib/aidp/harness/state_manager.rb +3 -0
  50. data/lib/aidp/harness/status_display.rb +28 -20
  51. data/lib/aidp/harness/test_runner.rb +179 -41
  52. data/lib/aidp/harness/thinking_depth_manager.rb +44 -28
  53. data/lib/aidp/harness/ui/enhanced_tui.rb +4 -0
  54. data/lib/aidp/harness/ui/enhanced_workflow_selector.rb +4 -0
  55. data/lib/aidp/harness/ui/error_handler.rb +3 -0
  56. data/lib/aidp/harness/ui/job_monitor.rb +4 -0
  57. data/lib/aidp/harness/ui/navigation/submenu.rb +2 -2
  58. data/lib/aidp/harness/ui/navigation/workflow_selector.rb +6 -0
  59. data/lib/aidp/harness/ui/spinner_helper.rb +3 -0
  60. data/lib/aidp/harness/ui/workflow_controller.rb +3 -0
  61. data/lib/aidp/harness/user_interface.rb +3 -0
  62. data/lib/aidp/loader.rb +195 -0
  63. data/lib/aidp/logger.rb +3 -0
  64. data/lib/aidp/message_display.rb +31 -0
  65. data/lib/aidp/metadata/compiler.rb +29 -17
  66. data/lib/aidp/metadata/query.rb +1 -1
  67. data/lib/aidp/metadata/scanner.rb +8 -1
  68. data/lib/aidp/metadata/tool_metadata.rb +13 -13
  69. data/lib/aidp/metadata/validator.rb +10 -0
  70. data/lib/aidp/metadata.rb +16 -0
  71. data/lib/aidp/pr_worktree_manager.rb +20 -8
  72. data/lib/aidp/provider_manager.rb +4 -7
  73. data/lib/aidp/providers/base.rb +2 -0
  74. data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
  75. data/lib/aidp/security/secrets_proxy.rb +328 -0
  76. data/lib/aidp/security/secrets_registry.rb +227 -0
  77. data/lib/aidp/security/trifecta_state.rb +220 -0
  78. data/lib/aidp/security/watch_mode_handler.rb +306 -0
  79. data/lib/aidp/security/work_loop_adapter.rb +277 -0
  80. data/lib/aidp/security.rb +56 -0
  81. data/lib/aidp/setup/wizard.rb +283 -11
  82. data/lib/aidp/skills.rb +0 -5
  83. data/lib/aidp/storage/csv_storage.rb +3 -0
  84. data/lib/aidp/style_guide/selector.rb +360 -0
  85. data/lib/aidp/tooling_detector.rb +283 -16
  86. data/lib/aidp/version.rb +1 -1
  87. data/lib/aidp/watch/auto_merger.rb +274 -0
  88. data/lib/aidp/watch/auto_pr_processor.rb +125 -7
  89. data/lib/aidp/watch/build_processor.rb +16 -1
  90. data/lib/aidp/watch/change_request_processor.rb +682 -150
  91. data/lib/aidp/watch/ci_fix_processor.rb +262 -4
  92. data/lib/aidp/watch/feedback_collector.rb +191 -0
  93. data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
  94. data/lib/aidp/watch/implementation_verifier.rb +142 -1
  95. data/lib/aidp/watch/plan_generator.rb +70 -13
  96. data/lib/aidp/watch/plan_processor.rb +12 -5
  97. data/lib/aidp/watch/projects_processor.rb +286 -0
  98. data/lib/aidp/watch/repository_client.rb +871 -22
  99. data/lib/aidp/watch/review_processor.rb +33 -6
  100. data/lib/aidp/watch/runner.rb +80 -29
  101. data/lib/aidp/watch/state_store.rb +233 -0
  102. data/lib/aidp/watch/sub_issue_creator.rb +221 -0
  103. data/lib/aidp/watch.rb +5 -7
  104. data/lib/aidp/workflows/guided_agent.rb +4 -0
  105. data/lib/aidp/workstream_cleanup.rb +0 -2
  106. data/lib/aidp/workstream_executor.rb +3 -4
  107. data/lib/aidp/worktree.rb +61 -12
  108. data/lib/aidp/worktree_branch_manager.rb +347 -101
  109. data/lib/aidp.rb +21 -106
  110. data/templates/implementation/iterative_implementation.md +46 -3
  111. metadata +91 -36
  112. data/lib/aidp/config/paths.rb +0 -131
@@ -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
data/lib/aidp/watch.rb CHANGED
@@ -1,9 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "message_display"
4
- require_relative "watch/repository_client"
5
- require_relative "watch/state_store"
6
- require_relative "watch/plan_generator"
7
- require_relative "watch/plan_processor"
8
- require_relative "watch/build_processor"
9
- require_relative "watch/runner"
3
+ module Aidp
4
+ # Watch mode functionality for monitoring GitHub issues and PRs
5
+ module Watch
6
+ end
7
+ 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
 
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "worktree"
4
- require_relative "workstream_state"
5
3
  require "open3"
6
4
  require "tty-prompt"
7
5
 
@@ -2,10 +2,6 @@
2
2
 
3
3
  require "concurrent-ruby"
4
4
  require "time"
5
- require_relative "worktree"
6
- require_relative "workstream_state"
7
- require_relative "harness/runner"
8
- require_relative "message_display"
9
5
 
10
6
  module Aidp
11
7
  # Executes multiple workstreams in parallel using concurrent-ruby.
@@ -13,6 +9,9 @@ module Aidp
13
9
  class WorkstreamExecutor
14
10
  include Aidp::MessageDisplay
15
11
 
12
+ # Expose for testability
13
+ attr_reader :project_dir, :max_concurrent, :results, :start_times
14
+
16
15
  # Result from executing a workstream
17
16
  WorkstreamResult = Struct.new(
18
17
  :slug,
data/lib/aidp/worktree.rb CHANGED
@@ -3,7 +3,7 @@
3
3
  require "fileutils"
4
4
  require "json"
5
5
  require "open3"
6
- require_relative "workstream_state"
6
+ require "time"
7
7
 
8
8
  module Aidp
9
9
  # Manages git worktree operations for parallel workstreams.
@@ -145,19 +145,43 @@ module Aidp
145
145
  #
146
146
  # @param branch [String] Branch name to search for
147
147
  # @param project_dir [String] Project root directory
148
- # @return [Hash, nil] Worktree info or nil if not found
149
- def find_by_branch(branch:, project_dir: Dir.pwd)
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
150
153
  registry = load_registry(project_dir)
151
154
  slug, data = registry.find { |_slug, info| info["branch"] == branch }
152
- return nil unless data
153
155
 
154
- {
155
- slug: slug,
156
- path: data["path"],
157
- branch: data["branch"],
158
- created_at: data["created_at"],
159
- active: Dir.exist?(data["path"])
160
- }
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
161
185
  end
162
186
 
163
187
  private
@@ -228,11 +252,36 @@ module Aidp
228
252
  end
229
253
  end
230
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
+
231
265
  def run_worktree_add!(project_dir:, branch:, branch_exists:, worktree_path:, base_branch:)
232
266
  prune_attempted = false
233
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
+
234
278
  loop do
235
- cmd = build_worktree_command(branch_exists: branch_exists, branch: branch, worktree_path: worktree_path, base_branch: base_branch)
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
+ )
236
285
  stdout, stderr, status = Dir.chdir(project_dir) { Open3.capture3(*cmd) }
237
286
 
238
287
  return if status.success?