Class: Aidp::Watch::PlanProcessor

Inherits:
Object
  • Object
show all
Includes:
MessageDisplay
Defined in:
lib/aidp/watch/plan_processor.rb

Overview

Handles the aidp-plan label trigger by generating an implementation plan and posting it back to the originating GitHub issue.

Constant Summary collapse

DEFAULT_PLAN_LABEL =

Default label names

"aidp-plan"
DEFAULT_NEEDS_INPUT_LABEL =
"aidp-needs-input"
DEFAULT_READY_LABEL =
"aidp-ready"
DEFAULT_BUILD_LABEL =
"aidp-build"
COMMENT_HEADER =
"## 🤖 AIDP Plan Proposal"

Constants included from MessageDisplay

MessageDisplay::COLOR_MAP, MessageDisplay::CRITICAL_TYPES

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from MessageDisplay

#display_message, included, #message_display_prompt, #quiet_mode?

Constructor Details

#initialize(repository_client:, state_store:, plan_generator:, label_config: {}) ⇒ PlanProcessor

Returns a new instance of PlanProcessor.



25
26
27
28
29
30
31
32
33
34
35
# File 'lib/aidp/watch/plan_processor.rb', line 25

def initialize(repository_client:, state_store:, plan_generator:, label_config: {})
  @repository_client = repository_client
  @state_store = state_store
  @plan_generator = plan_generator

  # Load label configuration with defaults
  @plan_label = label_config[:plan_trigger] || label_config["plan_trigger"] || DEFAULT_PLAN_LABEL
  @needs_input_label = label_config[:needs_input] || label_config["needs_input"] || DEFAULT_NEEDS_INPUT_LABEL
  @ready_label = label_config[:ready_to_build] || label_config["ready_to_build"] || DEFAULT_READY_LABEL
  @build_label = label_config[:build_trigger] || label_config["build_trigger"] || DEFAULT_BUILD_LABEL
end

Instance Attribute Details

#build_labelObject (readonly)

Returns the value of attribute build_label.



23
24
25
# File 'lib/aidp/watch/plan_processor.rb', line 23

def build_label
  @build_label
end

#needs_input_labelObject (readonly)

Returns the value of attribute needs_input_label.



23
24
25
# File 'lib/aidp/watch/plan_processor.rb', line 23

def needs_input_label
  @needs_input_label
end

#plan_labelObject (readonly)

Returns the value of attribute plan_label.



23
24
25
# File 'lib/aidp/watch/plan_processor.rb', line 23

def plan_label
  @plan_label
end

#ready_labelObject (readonly)

Returns the value of attribute ready_label.



23
24
25
# File 'lib/aidp/watch/plan_processor.rb', line 23

def ready_label
  @ready_label
end

Instance Method Details

#process(issue) ⇒ Object



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/aidp/watch/plan_processor.rb', line 37

def process(issue)
  number = issue[:number]
  existing_plan = @state_store.plan_data(number)

  if existing_plan
    display_message("🔄 Re-planning for issue ##{number} (iteration #{@state_store.plan_iteration_count(number) + 1})", type: :info)
  else
    display_message("🧠 Generating plan for issue ##{number} (#{issue[:title]})", type: :info)
  end

  plan_data = @plan_generator.generate(issue)

  # If plan generation failed (all providers unavailable), silently skip
  unless plan_data
    Aidp.log_warn("plan_processor", "plan_generation_failed", issue: number, reason: "no plan data returned")
    display_message("⚠️  Unable to generate plan for issue ##{number} - all providers failed", type: :warn)
    return
  end

  # Fetch the user who added the most recent label
  label_actor = @repository_client.most_recent_label_actor(number)

  # If updating existing plan, archive the previous content
  archived_content = existing_plan ? archive_previous_plan(number, existing_plan) : nil

  comment_body = build_comment(issue: issue, plan: plan_data, label_actor: label_actor, archived_content: archived_content)
  comment_body_with_feedback = FeedbackCollector.append_feedback_prompt(comment_body)
  comment_id = nil

  if existing_plan && existing_plan["comment_id"]
    # Update existing comment
    @repository_client.update_comment(existing_plan["comment_id"], comment_body_with_feedback)
    comment_id = existing_plan["comment_id"]
    display_message("📝 Updated plan comment for issue ##{number}", type: :success)
  elsif existing_plan
    # Try to find existing comment by header
    existing_comment = @repository_client.find_comment(number, COMMENT_HEADER)
    if existing_comment
      @repository_client.update_comment(existing_comment[:id], comment_body_with_feedback)
      comment_id = existing_comment[:id]
      display_message("📝 Updated plan comment for issue ##{number}", type: :success)
    else
      # Fallback to posting new comment if we can't find the old one
      result = @repository_client.post_comment(number, comment_body_with_feedback)
      comment_id = result[:id] if result.is_a?(Hash)
      display_message("💬 Posted new plan comment for issue ##{number}", type: :success)
    end
  else
    # First time planning - post new comment
    result = @repository_client.post_comment(number, comment_body_with_feedback)
    comment_id = result[:id] if result.is_a?(Hash)
    display_message("💬 Posted plan comment for issue ##{number}", type: :success)
  end

  plan_data = plan_data.merge(comment_id: comment_id) if comment_id
  @state_store.record_plan(number, plan_data.merge(comment_body: comment_body, comment_hint: COMMENT_HEADER))

  # Update labels: remove plan trigger, add appropriate status label
  update_labels_after_plan(number, plan_data)
end