Class: Aidp::Watch::GitHubStateExtractor

Inherits:
Object
  • Object
show all
Defined in:
lib/aidp/watch/github_state_extractor.rb

Overview

Extracts state information from GitHub issues/PRs using labels and comments as the single source of truth, enabling multiple AIDP instances to coordinate without local state files.

Constant Summary collapse

COMPLETION_PATTERN =

Pattern for detecting completion comments

/✅ Implementation complete for #(\d+)/i
DETECTION_PATTERN =

Pattern for detecting detection comments

/aidp detected `([^`]+)` at ([^\n]+) and is working on it/i
PLAN_PROPOSAL_PATTERN =

Pattern for detecting plan proposal comments

/<!-- PLAN_SUMMARY_START -->/i
IN_PROGRESS_LABEL =

Pattern for detecting in-progress label

"aidp-in-progress"

Instance Method Summary collapse

Constructor Details

#initialize(repository_client:) ⇒ GitHubStateExtractor

Returns a new instance of GitHubStateExtractor.



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

def initialize(repository_client:)
  @repository_client = repository_client
end

Instance Method Details

#build_completed?(issue) ⇒ Boolean

Check if build is already completed for an issue Looks for completion comment from AIDP

Returns:

  • (Boolean)


34
35
36
37
38
39
40
# File 'lib/aidp/watch/github_state_extractor.rb', line 34

def build_completed?(issue)
  return false unless issue[:comments]

  issue[:comments].any? do |comment|
    comment["body"]&.match?(COMPLETION_PATTERN)
  end
end

#change_request_processed?(pr) ⇒ Boolean

Check if change request has been processed for a PR

Returns:

  • (Boolean)


112
113
114
115
116
117
118
119
120
121
# File 'lib/aidp/watch/github_state_extractor.rb', line 112

def change_request_processed?(pr)
  return false unless pr[:comments]

  pr[:comments].any? do |comment|
    body = comment["body"]
    next false unless body

    body.include?("") && body.match?(/Change requests? (?:addressed|applied|complete)/i)
  end
end

#ci_fix_completed?(pr) ⇒ Boolean

Check if CI fix has been completed for a PR

Returns:

  • (Boolean)


97
98
99
100
101
102
103
104
105
106
107
108
109
# File 'lib/aidp/watch/github_state_extractor.rb', line 97

def ci_fix_completed?(pr)
  return false unless pr[:comments]

  pr[:comments].any? do |comment|
    body = comment["body"]
    next false unless body

    body.include?("") && (
      body.match?(/CI fixes applied/i) ||
      (body.match?(/CI check/i) && body.match?(/passed/i))
    )
  end
end

#detection_comment_posted?(item, label) ⇒ Boolean

Check if detection comment was already posted for this label

Returns:

  • (Boolean)


76
77
78
79
80
81
82
83
84
85
# File 'lib/aidp/watch/github_state_extractor.rb', line 76

def detection_comment_posted?(item, label)
  return false unless item[:comments]

  item[:comments].any? do |comment|
    next unless comment["body"]

    match = comment["body"].match(DETECTION_PATTERN)
    match && match[1] == label
  end
end

#extract_linked_issue(pr_body) ⇒ Object

Extract linked issue number from PR description Looks for patterns like “Fixes #123”, “Closes #456”, “Resolves #789” Returns the issue number as an integer, or nil if not found



126
127
128
129
130
131
132
133
# File 'lib/aidp/watch/github_state_extractor.rb', line 126

def extract_linked_issue(pr_body)
  return nil unless pr_body

  # Match common GitHub issue linking patterns
  # https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue
  match = pr_body.match(/(?:Fixes|Closes|Resolves|Close|Fix|Resolve)\s+#(\d+)/i)
  match ? match[1].to_i : nil
end

#extract_plan_data(issue) ⇒ Object

Extract the most recent plan data from comments



52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/aidp/watch/github_state_extractor.rb', line 52

def extract_plan_data(issue)
  return nil unless issue[:comments]

  # Find the most recent plan proposal comment
  plan_comment = issue[:comments].reverse.find do |comment|
    comment["body"]&.match?(PLAN_PROPOSAL_PATTERN)
  end

  return nil unless plan_comment

  body = plan_comment["body"]

  {
    summary: extract_section(body, "PLAN_SUMMARY"),
    tasks: extract_tasks(body),
    questions: extract_questions(body),
    comment_body: body,
    comment_hint: "## 🤖 AIDP Plan Proposal",
    comment_id: plan_comment["id"],
    posted_at: plan_comment["createdAt"] || Time.now.utc.iso8601
  }
end

#in_progress?(item) ⇒ Boolean

Check if an issue/PR is currently being worked on by another instance

Returns:

  • (Boolean)


28
29
30
# File 'lib/aidp/watch/github_state_extractor.rb', line 28

def in_progress?(item)
  has_label?(item, IN_PROGRESS_LABEL)
end

#plan_posted?(issue) ⇒ Boolean

Check if a plan has been posted for an issue

Returns:

  • (Boolean)


43
44
45
46
47
48
49
# File 'lib/aidp/watch/github_state_extractor.rb', line 43

def plan_posted?(issue)
  return false unless issue[:comments]

  issue[:comments].any? do |comment|
    comment["body"]&.match?(PLAN_PROPOSAL_PATTERN)
  end
end

#review_completed?(pr) ⇒ Boolean

Check if review has been completed for a PR

Returns:

  • (Boolean)


88
89
90
91
92
93
94
# File 'lib/aidp/watch/github_state_extractor.rb', line 88

def review_completed?(pr)
  return false unless pr[:comments]

  pr[:comments].any? do |comment|
    comment["body"]&.match?(/🔍.*Review complete/i)
  end
end