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.
Files changed (84) 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/ui.rb +11 -0
  47. data/lib/aidp/harness/user_interface.rb +3 -0
  48. data/lib/aidp/loader.rb +2 -2
  49. data/lib/aidp/logger.rb +3 -0
  50. data/lib/aidp/message_display.rb +31 -0
  51. data/lib/aidp/pr_worktree_manager.rb +18 -6
  52. data/lib/aidp/provider_manager.rb +3 -0
  53. data/lib/aidp/providers/base.rb +2 -0
  54. data/lib/aidp/security/rule_of_two_enforcer.rb +210 -0
  55. data/lib/aidp/security/secrets_proxy.rb +328 -0
  56. data/lib/aidp/security/secrets_registry.rb +227 -0
  57. data/lib/aidp/security/trifecta_state.rb +220 -0
  58. data/lib/aidp/security/watch_mode_handler.rb +306 -0
  59. data/lib/aidp/security/work_loop_adapter.rb +277 -0
  60. data/lib/aidp/security.rb +56 -0
  61. data/lib/aidp/setup/wizard.rb +4 -2
  62. data/lib/aidp/version.rb +1 -1
  63. data/lib/aidp/watch/auto_merger.rb +274 -0
  64. data/lib/aidp/watch/auto_pr_processor.rb +125 -7
  65. data/lib/aidp/watch/build_processor.rb +16 -1
  66. data/lib/aidp/watch/change_request_processor.rb +680 -286
  67. data/lib/aidp/watch/ci_fix_processor.rb +262 -4
  68. data/lib/aidp/watch/feedback_collector.rb +191 -0
  69. data/lib/aidp/watch/hierarchical_pr_strategy.rb +256 -0
  70. data/lib/aidp/watch/implementation_verifier.rb +142 -1
  71. data/lib/aidp/watch/plan_generator.rb +70 -13
  72. data/lib/aidp/watch/plan_processor.rb +12 -5
  73. data/lib/aidp/watch/projects_processor.rb +286 -0
  74. data/lib/aidp/watch/repository_client.rb +861 -53
  75. data/lib/aidp/watch/review_processor.rb +33 -6
  76. data/lib/aidp/watch/runner.rb +51 -11
  77. data/lib/aidp/watch/state_store.rb +233 -0
  78. data/lib/aidp/watch/sub_issue_creator.rb +221 -0
  79. data/lib/aidp/workflows/guided_agent.rb +4 -0
  80. data/lib/aidp/workstream_executor.rb +3 -0
  81. data/lib/aidp/worktree.rb +61 -11
  82. data/lib/aidp/worktree_branch_manager.rb +347 -101
  83. data/templates/implementation/iterative_implementation.md +46 -3
  84. metadata +21 -1
@@ -10,6 +10,7 @@ require_relative "implementation_verifier"
10
10
  require_relative "reviewers/senior_dev_reviewer"
11
11
  require_relative "reviewers/security_reviewer"
12
12
  require_relative "reviewers/performance_reviewer"
13
+ require_relative "feedback_collector"
13
14
 
14
15
  module Aidp
15
16
  module Watch
@@ -81,13 +82,16 @@ module Aidp
81
82
  review_results: review_results,
82
83
  verification_result: verification_result
83
84
  )
84
- @repository_client.post_comment(number, comment_body)
85
+ comment_body_with_feedback = FeedbackCollector.append_feedback_prompt(comment_body)
86
+ result = @repository_client.post_comment(number, comment_body_with_feedback)
87
+ comment_id = result[:id] if result.is_a?(Hash)
85
88
 
86
89
  display_message("💬 Posted review comment for PR ##{number}", type: :success)
87
90
  @state_store.record_review(number, {
88
91
  timestamp: Time.now.utc.iso8601,
89
92
  reviewers: review_results.map { |r| r[:persona] },
90
- total_findings: review_results.sum { |r| r[:findings].length }
93
+ total_findings: review_results.sum { |r| r[:findings].length },
94
+ comment_id: comment_id
91
95
  })
92
96
 
93
97
  # Remove review label after processing
@@ -200,12 +204,35 @@ module Aidp
200
204
  else
201
205
  parts << "### ⚠️ Implementation Incomplete"
202
206
  parts << ""
203
- parts << "**This PR appears to be incomplete based on the linked issue requirements:**"
207
+ parts << "**This PR appears to be incomplete based on the linked issue requirements.**"
204
208
  parts << ""
205
- verification_result[:reasons]&.each do |reason|
206
- parts << "- #{reason}"
209
+
210
+ # Show the verification reasoning
211
+ if verification_result[:reason]
212
+ parts << "**Summary:** #{verification_result[:reason]}"
213
+ parts << ""
207
214
  end
208
- parts << ""
215
+
216
+ # Show missing requirements for implementers to address
217
+ if verification_result[:missing_items]&.any?
218
+ parts << "**Missing Requirements:**"
219
+ parts << ""
220
+ verification_result[:missing_items].each do |item|
221
+ parts << "- #{item}"
222
+ end
223
+ parts << ""
224
+ end
225
+
226
+ # Show additional work needed for implementers
227
+ if verification_result[:additional_work]&.any?
228
+ parts << "**Additional Work Needed:**"
229
+ parts << ""
230
+ verification_result[:additional_work].each do |work|
231
+ parts << "- #{work}"
232
+ end
233
+ parts << ""
234
+ end
235
+
209
236
  parts << "**Suggested Action:** Add the `aidp-request-changes` label if you'd like AIDP to help complete the implementation."
210
237
  end
211
238
  parts << ""
@@ -1,6 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "tty-prompt"
4
+ require_relative "feedback_collector"
5
+ require_relative "github_state_extractor"
4
6
 
5
7
  module Aidp
6
8
  module Watch
@@ -11,13 +13,18 @@ module Aidp
11
13
 
12
14
  DEFAULT_INTERVAL = 30
13
15
 
14
- def initialize(issues_url:, interval: DEFAULT_INTERVAL, provider_name: nil, gh_available: nil, project_dir: Dir.pwd, once: false, use_workstreams: true, prompt: TTY::Prompt.new, safety_config: {}, force: false, verbose: false)
16
+ # Expose for testability
17
+ attr_reader :post_detection_comments
18
+ attr_writer :last_update_check
19
+
20
+ def initialize(issues_url:, interval: DEFAULT_INTERVAL, provider_name: nil, gh_available: nil, project_dir: Dir.pwd, once: false, use_workstreams: true, prompt: TTY::Prompt.new, safety_config: {}, force: false, verbose: false, quiet: false)
15
21
  @prompt = prompt
16
22
  @interval = interval
17
23
  @once = once
18
24
  @project_dir = project_dir
19
25
  @force = force
20
26
  @verbose = verbose
27
+ @quiet = quiet
21
28
  @provider_name = provider_name
22
29
  @safety_config = safety_config
23
30
 
@@ -102,6 +109,12 @@ module Aidp
102
109
  safety_config: safety_config[:safety] || safety_config["safety"] || {},
103
110
  verbose: verbose
104
111
  )
112
+
113
+ @feedback_collector = FeedbackCollector.new(
114
+ repository_client: @repository_client,
115
+ state_store: @state_store,
116
+ project_dir: project_dir
117
+ )
105
118
  end
106
119
 
107
120
  def start
@@ -129,6 +142,7 @@ module Aidp
129
142
  process_cycle
130
143
  Aidp.log_debug("watch_runner", "poll_cycle.complete", once: @once, next_poll_in: @once ? nil : @interval)
131
144
  break if @once
145
+
132
146
  Aidp.log_debug("watch_runner", "poll_cycle.sleep", seconds: @interval)
133
147
  sleep @interval
134
148
  end
@@ -150,6 +164,7 @@ module Aidp
150
164
  process_ci_fix_triggers
151
165
  process_auto_pr_triggers
152
166
  process_change_request_triggers
167
+ collect_feedback
153
168
  end
154
169
 
155
170
  def process_plan_triggers
@@ -182,7 +197,8 @@ module Aidp
182
197
 
183
198
  # Check author authorization before processing
184
199
  unless @safety_checker.should_process_issue?(detailed, enforce: false)
185
- Aidp.log_debug("watch_runner", "plan_skip_unauthorized_author", issue: detailed[:number], author: detailed[:author])
200
+ Aidp.log_debug("watch_runner", "plan_skip_unauthorized_author", issue: detailed[:number],
201
+ author: detailed[:author])
186
202
  next
187
203
  end
188
204
 
@@ -238,7 +254,8 @@ module Aidp
238
254
 
239
255
  # Check author authorization before processing
240
256
  unless @safety_checker.should_process_issue?(detailed, enforce: false)
241
- Aidp.log_debug("watch_runner", "build_skip_unauthorized_author", issue: detailed[:number], author: detailed[:author])
257
+ Aidp.log_debug("watch_runner", "build_skip_unauthorized_author", issue: detailed[:number],
258
+ author: detailed[:author])
242
259
  next
243
260
  end
244
261
 
@@ -261,7 +278,8 @@ module Aidp
261
278
  begin
262
279
  @repository_client.remove_labels(detailed[:number], GitHubStateExtractor::IN_PROGRESS_LABEL)
263
280
  rescue => e
264
- Aidp.log_warn("watch_runner", "failed_to_remove_in_progress_label", issue: detailed[:number], error: e.message)
281
+ Aidp.log_warn("watch_runner", "failed_to_remove_in_progress_label", issue: detailed[:number],
282
+ error: e.message)
265
283
  end
266
284
  end
267
285
  end
@@ -281,7 +299,8 @@ module Aidp
281
299
 
282
300
  issues.each do |issue|
283
301
  unless issue_has_label?(issue, auto_label)
284
- Aidp.log_debug("watch_runner", "auto_issue_skip_label_mismatch", issue: issue[:number], labels: issue[:labels])
302
+ Aidp.log_debug("watch_runner", "auto_issue_skip_label_mismatch", issue: issue[:number],
303
+ labels: issue[:labels])
285
304
  next
286
305
  end
287
306
 
@@ -300,7 +319,8 @@ module Aidp
300
319
 
301
320
  # Check author authorization before processing
302
321
  unless @safety_checker.should_process_issue?(detailed, enforce: false)
303
- Aidp.log_debug("watch_runner", "auto_issue_skip_unauthorized_author", issue: detailed[:number], author: detailed[:author])
322
+ Aidp.log_debug("watch_runner", "auto_issue_skip_unauthorized_author", issue: detailed[:number],
323
+ author: detailed[:author])
304
324
  next
305
325
  end
306
326
 
@@ -346,7 +366,8 @@ module Aidp
346
366
 
347
367
  # Check author authorization before processing
348
368
  unless @safety_checker.should_process_issue?(detailed, enforce: false)
349
- Aidp.log_debug("watch_runner", "review_skip_unauthorized_author", pr: detailed[:number], author: detailed[:author])
369
+ Aidp.log_debug("watch_runner", "review_skip_unauthorized_author", pr: detailed[:number],
370
+ author: detailed[:author])
350
371
  next
351
372
  end
352
373
 
@@ -383,7 +404,8 @@ module Aidp
383
404
 
384
405
  # Check author authorization before processing
385
406
  unless @safety_checker.should_process_issue?(detailed, enforce: false)
386
- Aidp.log_debug("watch_runner", "auto_pr_skip_unauthorized_author", pr: detailed[:number], author: detailed[:author])
407
+ Aidp.log_debug("watch_runner", "auto_pr_skip_unauthorized_author", pr: detailed[:number],
408
+ author: detailed[:author])
387
409
  next
388
410
  end
389
411
 
@@ -419,7 +441,8 @@ module Aidp
419
441
 
420
442
  # Check author authorization before processing
421
443
  unless @safety_checker.should_process_issue?(detailed, enforce: false)
422
- Aidp.log_debug("watch_runner", "ci_fix_skip_unauthorized_author", pr: detailed[:number], author: detailed[:author])
444
+ Aidp.log_debug("watch_runner", "ci_fix_skip_unauthorized_author", pr: detailed[:number],
445
+ author: detailed[:author])
423
446
  next
424
447
  end
425
448
 
@@ -455,7 +478,8 @@ module Aidp
455
478
 
456
479
  # Check author authorization before processing
457
480
  unless @safety_checker.should_process_issue?(detailed, enforce: false)
458
- Aidp.log_debug("watch_runner", "change_request_skip_unauthorized_author", pr: detailed[:number], author: detailed[:author])
481
+ Aidp.log_debug("watch_runner", "change_request_skip_unauthorized_author", pr: detailed[:number],
482
+ author: detailed[:author])
459
483
  next
460
484
  end
461
485
 
@@ -511,7 +535,8 @@ module Aidp
511
535
  update_check = @auto_update_coordinator.check_for_update
512
536
 
513
537
  if update_check.should_update?
514
- display_message("🔄 Update available: #{update_check.current_version} → #{update_check.available_version}", type: :highlight)
538
+ display_message("🔄 Update available: #{update_check.current_version} → #{update_check.available_version}",
539
+ type: :highlight)
515
540
 
516
541
  # Prefer hot reloading if available (Zeitwerk enabled with reloading)
517
542
  if @auto_update_coordinator.hot_reload_available?
@@ -644,6 +669,21 @@ module Aidp
644
669
  name.casecmp(label).zero?
645
670
  end
646
671
  end
672
+
673
+ # Collect feedback from reactions on tracked comments
674
+ def collect_feedback
675
+ new_evaluations = @feedback_collector.collect_feedback
676
+ return if new_evaluations.empty?
677
+
678
+ Aidp.log_info("watch_runner", "feedback_collected",
679
+ count: new_evaluations.size,
680
+ evaluations: new_evaluations.map { |e| {id: e[:id], rating: e[:rating]} })
681
+
682
+ display_message("📊 Collected #{new_evaluations.size} new feedback evaluation(s)", type: :info) if @verbose
683
+ rescue => e
684
+ Aidp.log_error("watch_runner", "feedback_collection_failed", error: e.message)
685
+ display_message("⚠️ Feedback collection failed: #{e.message}", type: :warn) if @verbose
686
+ end
647
687
  end
648
688
  end
649
689
  end
@@ -189,6 +189,214 @@ module Aidp
189
189
  save!
190
190
  end
191
191
 
192
+ # Feedback tracking methods - track comments for reaction-based evaluations
193
+
194
+ # Get all tracked comments with their metadata for feedback collection
195
+ # @return [Array<Hash>] List of comment info hashes
196
+ def tracked_comments
197
+ comments = []
198
+
199
+ # Collect from plans
200
+ plans.each do |issue_number, data|
201
+ next unless data["comment_id"]
202
+ comments << {
203
+ comment_id: data["comment_id"],
204
+ processor_type: "plan",
205
+ number: issue_number.to_i,
206
+ posted_at: data["posted_at"]
207
+ }
208
+ end
209
+
210
+ # Collect from reviews (if they store comment_id)
211
+ reviews.each do |pr_number, data|
212
+ next unless data["comment_id"]
213
+ comments << {
214
+ comment_id: data["comment_id"],
215
+ processor_type: "review",
216
+ number: pr_number.to_i,
217
+ posted_at: data["timestamp"]
218
+ }
219
+ end
220
+
221
+ # Collect from builds (if they store comment_id)
222
+ builds.each do |issue_number, data|
223
+ next unless data["comment_id"]
224
+ comments << {
225
+ comment_id: data["comment_id"],
226
+ processor_type: "build",
227
+ number: issue_number.to_i,
228
+ posted_at: data["updated_at"]
229
+ }
230
+ end
231
+
232
+ # Collect from feedback_comments (explicitly tracked)
233
+ feedback_comments.each do |key, data|
234
+ comments << {
235
+ comment_id: data["comment_id"],
236
+ processor_type: data["processor_type"],
237
+ number: data["number"].to_i,
238
+ posted_at: data["posted_at"]
239
+ }
240
+ end
241
+
242
+ comments
243
+ end
244
+
245
+ # Track a comment for feedback collection
246
+ # @param comment_id [Integer, String] GitHub comment ID
247
+ # @param processor_type [String] Type of processor (plan, review, build, etc.)
248
+ # @param number [Integer] Issue or PR number
249
+ def track_comment_for_feedback(comment_id:, processor_type:, number:)
250
+ key = "#{processor_type}_#{number}"
251
+ feedback_comments[key] = {
252
+ "comment_id" => comment_id.to_s,
253
+ "processor_type" => processor_type,
254
+ "number" => number,
255
+ "posted_at" => Time.now.utc.iso8601
256
+ }
257
+ save!
258
+ end
259
+
260
+ # Get IDs of reactions already processed for a comment
261
+ # @param comment_id [Integer, String] GitHub comment ID
262
+ # @return [Array<Integer>] List of processed reaction IDs
263
+ def processed_reaction_ids(comment_id)
264
+ data = processed_reactions[comment_id.to_s]
265
+ return [] unless data
266
+ data["reaction_ids"] || []
267
+ end
268
+
269
+ # Mark a reaction as processed
270
+ # @param comment_id [Integer, String] GitHub comment ID
271
+ # @param reaction_id [Integer] GitHub reaction ID
272
+ def mark_reaction_processed(comment_id, reaction_id)
273
+ key = comment_id.to_s
274
+ processed_reactions[key] ||= {"reaction_ids" => [], "last_checked" => nil}
275
+ processed_reactions[key]["reaction_ids"] << reaction_id unless processed_reactions[key]["reaction_ids"].include?(reaction_id)
276
+ processed_reactions[key]["last_checked"] = Time.now.utc.iso8601
277
+ save!
278
+ end
279
+
280
+ # Auto PR tracking methods - for aidp-auto label on PRs
281
+ # Tracks iteration counts to enforce iteration cap
282
+
283
+ # Get the current iteration count for an auto PR
284
+ # @param pr_number [Integer] PR number
285
+ # @return [Integer] Current iteration count (0 if not tracked)
286
+ def auto_pr_iteration_count(pr_number)
287
+ data = auto_prs[pr_number.to_s]
288
+ return 0 unless data
289
+ data["iteration"] || 0
290
+ end
291
+
292
+ # Get full auto PR data
293
+ # @param pr_number [Integer] PR number
294
+ # @return [Hash, nil] Auto PR tracking data
295
+ def auto_pr_data(pr_number)
296
+ auto_prs[pr_number.to_s]
297
+ end
298
+
299
+ # Record an auto PR iteration
300
+ # @param pr_number [Integer] PR number
301
+ # @param data [Hash] Additional data to store
302
+ # @return [Integer] New iteration count
303
+ def record_auto_pr_iteration(pr_number, data = {})
304
+ key = pr_number.to_s
305
+ existing = auto_prs[key] || {}
306
+ iteration = (existing["iteration"] || 0) + 1
307
+
308
+ auto_prs[key] = {
309
+ "iteration" => iteration,
310
+ "last_processed_at" => Time.now.utc.iso8601,
311
+ "status" => data[:status] || "in_progress",
312
+ "metadata" => stringify_keys(data[:metadata] || {})
313
+ }.merge(stringify_keys(data.except(:status, :metadata)))
314
+
315
+ save!
316
+ iteration
317
+ end
318
+
319
+ # Mark an auto PR as completed (ready for human review)
320
+ # @param pr_number [Integer] PR number
321
+ # @param data [Hash] Additional completion data
322
+ def complete_auto_pr(pr_number, data = {})
323
+ key = pr_number.to_s
324
+ existing = auto_prs[key] || {}
325
+
326
+ auto_prs[key] = existing.merge({
327
+ "status" => "completed",
328
+ "completed_at" => Time.now.utc.iso8601
329
+ }).merge(stringify_keys(data))
330
+
331
+ save!
332
+ end
333
+
334
+ # Check if an auto PR has reached the iteration cap
335
+ # @param pr_number [Integer] PR number
336
+ # @param cap [Integer] Maximum iterations allowed
337
+ # @return [Boolean] True if cap reached
338
+ def auto_pr_cap_reached?(pr_number, cap:)
339
+ auto_pr_iteration_count(pr_number) >= cap
340
+ end
341
+
342
+ # Project tracking methods
343
+ def project_item_id(issue_number)
344
+ projects[issue_number.to_s]&.dig("item_id")
345
+ end
346
+
347
+ def record_project_item_id(issue_number, item_id)
348
+ projects[issue_number.to_s] ||= {}
349
+ projects[issue_number.to_s]["item_id"] = item_id
350
+ projects[issue_number.to_s]["synced_at"] = Time.now.utc.iso8601
351
+ save!
352
+ end
353
+
354
+ def project_sync_data(issue_number)
355
+ projects[issue_number.to_s] || {}
356
+ end
357
+
358
+ def record_project_sync(issue_number, data)
359
+ projects[issue_number.to_s] ||= {}
360
+ projects[issue_number.to_s].merge!(stringify_keys(data))
361
+ projects[issue_number.to_s]["synced_at"] = Time.now.utc.iso8601
362
+ save!
363
+ end
364
+
365
+ # Sub-issue tracking methods
366
+ def sub_issues(parent_number)
367
+ hierarchies[parent_number.to_s]&.dig("sub_issues") || []
368
+ end
369
+
370
+ def parent_issue(sub_issue_number)
371
+ hierarchies[sub_issue_number.to_s]&.dig("parent")
372
+ end
373
+
374
+ def record_sub_issues(parent_number, sub_issue_numbers)
375
+ hierarchies[parent_number.to_s] ||= {}
376
+ hierarchies[parent_number.to_s]["sub_issues"] = Array(sub_issue_numbers)
377
+ hierarchies[parent_number.to_s]["created_at"] = Time.now.utc.iso8601
378
+
379
+ # Also record reverse mapping
380
+ sub_issue_numbers.each do |sub_number|
381
+ hierarchies[sub_number.to_s] ||= {}
382
+ hierarchies[sub_number.to_s]["parent"] = parent_number
383
+ end
384
+
385
+ save!
386
+ end
387
+
388
+ def blocking_status(issue_number)
389
+ # Check if this issue is blocked by any open sub-issues
390
+ sub_issue_numbers = sub_issues(issue_number)
391
+ return {blocked: false, blockers: []} if sub_issue_numbers.empty?
392
+
393
+ {
394
+ blocked: true,
395
+ blockers: sub_issue_numbers,
396
+ blocker_count: sub_issue_numbers.size
397
+ }
398
+ end
399
+
192
400
  private
193
401
 
194
402
  def ensure_directory
@@ -221,6 +429,11 @@ module Aidp
221
429
  base["ci_fixes"] ||= {}
222
430
  base["change_requests"] ||= {}
223
431
  base["detection_comments"] ||= {}
432
+ base["feedback_comments"] ||= {}
433
+ base["processed_reactions"] ||= {}
434
+ base["auto_prs"] ||= {}
435
+ base["projects"] ||= {}
436
+ base["hierarchies"] ||= {}
224
437
  base
225
438
  end
226
439
  end
@@ -249,6 +462,26 @@ module Aidp
249
462
  state["detection_comments"]
250
463
  end
251
464
 
465
+ def feedback_comments
466
+ state["feedback_comments"]
467
+ end
468
+
469
+ def processed_reactions
470
+ state["processed_reactions"]
471
+ end
472
+
473
+ def auto_prs
474
+ state["auto_prs"]
475
+ end
476
+
477
+ def projects
478
+ state["projects"]
479
+ end
480
+
481
+ def hierarchies
482
+ state["hierarchies"]
483
+ end
484
+
252
485
  def stringify_keys(hash)
253
486
  return {} unless hash
254
487