gtg 0.4.0__py3-none-any.whl

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.
@@ -0,0 +1,352 @@
1
+ """CodeRabbit comment parser for GoodToMerge.
2
+
3
+ This module implements the ReviewerParser interface for parsing comments
4
+ from CodeRabbit, an AI-powered automated code review tool.
5
+
6
+ CodeRabbit uses specific patterns to indicate comment severity and type:
7
+ - Severity indicators with emojis and labels
8
+ - Fingerprinting comments (internal metadata)
9
+ - Resolution status markers
10
+ - Outside diff range notifications
11
+ - Summary/walkthrough sections (non-actionable)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import re
17
+
18
+ from goodtogo.core.interfaces import ReviewerParser
19
+ from goodtogo.core.models import CommentClassification, Priority, ReviewerType
20
+
21
+
22
+ class CodeRabbitParser(ReviewerParser):
23
+ """Parser for CodeRabbit automated code review comments.
24
+
25
+ CodeRabbit posts comments with structured severity indicators that
26
+ can be deterministically parsed to classify comment actionability.
27
+
28
+ Patterns recognized:
29
+ - _Potential issue_ | _Critical/Major/Minor_: ACTIONABLE
30
+ - _Trivial_: NON_ACTIONABLE
31
+ - _Nitpick_: NON_ACTIONABLE
32
+ - Fingerprinting HTML comments: NON_ACTIONABLE
33
+ - Addressed checkmarks: NON_ACTIONABLE
34
+ - Outside diff range mentions: ACTIONABLE (MINOR)
35
+ - Summary/walkthrough sections: NON_ACTIONABLE
36
+ - Tip/info boxes: NON_ACTIONABLE
37
+ - All other: AMBIGUOUS
38
+
39
+ Author detection:
40
+ - Primary: author == "coderabbitai[bot]"
41
+ - Fallback: body contains CodeRabbit signature comment
42
+ """
43
+
44
+ # Author pattern for CodeRabbit bot
45
+ CODERABBIT_AUTHOR = "coderabbitai[bot]"
46
+
47
+ # Body pattern for CodeRabbit signature (fallback detection)
48
+ CODERABBIT_SIGNATURE_PATTERN = re.compile(
49
+ r"<!-- This is an auto-generated comment.*by coderabbit\.ai -->",
50
+ re.IGNORECASE | re.DOTALL,
51
+ )
52
+
53
+ # Severity patterns - using re.escape for literal characters
54
+ # Pattern: _Potential issue_ | _Critical/Major/Minor_
55
+ CRITICAL_PATTERN = re.compile(
56
+ r"_\u26a0\ufe0f\s*Potential issue_\s*\|\s*_\U0001f534\s*Critical_",
57
+ re.IGNORECASE,
58
+ )
59
+ MAJOR_PATTERN = re.compile(
60
+ r"_\u26a0\ufe0f\s*Potential issue_\s*\|\s*_\U0001f7e0\s*Major_",
61
+ re.IGNORECASE,
62
+ )
63
+ MINOR_PATTERN = re.compile(
64
+ r"_\u26a0\ufe0f\s*Potential issue_\s*\|\s*_\U0001f7e1\s*Minor_",
65
+ re.IGNORECASE,
66
+ )
67
+
68
+ # Non-actionable patterns
69
+ TRIVIAL_PATTERN = re.compile(r"_\U0001f535\s*Trivial_", re.IGNORECASE)
70
+ NITPICK_PATTERN = re.compile(r"_\U0001f9f9\s*Nitpick_", re.IGNORECASE)
71
+
72
+ # Fingerprinting comments (internal CodeRabbit metadata)
73
+ FINGERPRINT_PATTERN = re.compile(r"<!--\s*fingerprinting:", re.IGNORECASE)
74
+
75
+ # Addressed status marker
76
+ ADDRESSED_PATTERN = re.compile(r"\u2705\s*Addressed", re.IGNORECASE)
77
+
78
+ # Acknowledgment patterns (thank-you replies indicating issue was addressed)
79
+ # These are reply comments from CodeRabbit confirming a fix was applied
80
+ # Note: GitHub usernames can contain hyphens, so we use [\w-] instead of \w
81
+ ACKNOWLEDGMENT_PATTERNS = [
82
+ # "@username Thank you for the fix/catch/suggestion/addressing"
83
+ re.compile(
84
+ r"`?@[\w-]+`?\s+Thank\s+you\s+for\s+(the\s+)?(fix|catch|suggestion|addressing)",
85
+ re.IGNORECASE,
86
+ ),
87
+ # "Thank you for addressing this"
88
+ re.compile(r"Thank\s+you\s+for\s+addressing\s+this", re.IGNORECASE),
89
+ # Starts with "Thank you" and contains keywords like fix, addressed, suggestion
90
+ re.compile(
91
+ r"^`?@?[\w-]*`?\s*,?\s*[Tt]hank\s+you.*?(fix|addressed|updated|resolved|correct|suggestion)",
92
+ re.IGNORECASE,
93
+ ),
94
+ ]
95
+
96
+ # Outside diff range (in review body)
97
+ OUTSIDE_DIFF_PATTERN = re.compile(r"Outside diff range", re.IGNORECASE)
98
+
99
+ # Summary/walkthrough patterns (non-actionable informational content)
100
+ # These are overview sections that don't require action
101
+ SUMMARY_PATTERNS = [
102
+ # Walkthrough header
103
+ re.compile(r"^##\s*Walkthrough", re.MULTILINE),
104
+ # Changes summary header
105
+ re.compile(r"^##\s*Changes", re.MULTILINE),
106
+ # Summary header
107
+ re.compile(r"^##\s*Summary", re.MULTILINE),
108
+ # PR summary pattern
109
+ re.compile(r"^##\s*PR\s+Summary", re.MULTILINE | re.IGNORECASE),
110
+ # Review summary
111
+ re.compile(r"^##\s*Review\s+Summary", re.MULTILINE | re.IGNORECASE),
112
+ # File summary table pattern (CodeRabbit specific)
113
+ re.compile(r"\|\s*File\s*\|\s*Changes\s*\|", re.IGNORECASE),
114
+ # Sequence diagram indicators
115
+ re.compile(r"```mermaid", re.IGNORECASE),
116
+ # PR Objectives section
117
+ re.compile(r"^##\s*Objectives", re.MULTILINE | re.IGNORECASE),
118
+ ]
119
+
120
+ # Tip/info box patterns (non-actionable)
121
+ TIP_PATTERNS = [
122
+ re.compile(r"^>\s*\[!TIP\]", re.MULTILINE),
123
+ re.compile(r"^>\s*\[!NOTE\]", re.MULTILINE),
124
+ re.compile(r"^>\s*\[!INFO\]", re.MULTILINE),
125
+ ]
126
+
127
+ # PR-level summary comment patterns (non-actionable)
128
+ # These are posted at the PR level (path=None, line=None) and contain
129
+ # overview information. The actual actionable items are in inline comments.
130
+ PR_SUMMARY_PATTERNS = [
131
+ # "Actionable comments posted: N" pattern
132
+ re.compile(r"Actionable comments posted:\s*\d+", re.IGNORECASE),
133
+ # <details> sections with summaries
134
+ re.compile(r"<details>.*?<summary>.*?</summary>", re.IGNORECASE | re.DOTALL),
135
+ # CodeRabbit auto-generated comment signature
136
+ re.compile(
137
+ r"<!-- This is an auto-generated comment.*?by coderabbit",
138
+ re.IGNORECASE | re.DOTALL,
139
+ ),
140
+ ]
141
+
142
+ @property
143
+ def reviewer_type(self) -> ReviewerType:
144
+ """Return the reviewer type this parser handles.
145
+
146
+ Returns:
147
+ ReviewerType.CODERABBIT
148
+ """
149
+ return ReviewerType.CODERABBIT
150
+
151
+ def can_parse(self, author: str, body: str) -> bool:
152
+ """Check if this parser can handle the comment.
153
+
154
+ Identifies CodeRabbit comments by:
155
+ 1. Author being "coderabbitai[bot]" (primary method)
156
+ 2. Body containing CodeRabbit signature HTML comment (fallback)
157
+
158
+ Args:
159
+ author: Comment author's username/login.
160
+ body: Comment body text.
161
+
162
+ Returns:
163
+ True if this is a CodeRabbit comment, False otherwise.
164
+ """
165
+ # Primary detection: check author
166
+ if author == self.CODERABBIT_AUTHOR:
167
+ return True
168
+
169
+ # Fallback detection: check body for signature
170
+ if body and self.CODERABBIT_SIGNATURE_PATTERN.search(body):
171
+ return True
172
+
173
+ return False
174
+
175
+ def parse(self, comment: dict) -> tuple[CommentClassification, Priority, bool]:
176
+ """Parse CodeRabbit comment and return classification.
177
+
178
+ Analyzes the comment body to determine classification and priority
179
+ based on CodeRabbit's severity indicators.
180
+
181
+ Classification rules (in order of precedence):
182
+ 1. PR-level summary comments -> NON_ACTIONABLE
183
+ 2. Fingerprinting comments -> NON_ACTIONABLE
184
+ 3. Addressed marker -> NON_ACTIONABLE
185
+ 4. Critical severity -> ACTIONABLE, CRITICAL
186
+ 5. Major severity -> ACTIONABLE, MAJOR
187
+ 6. Minor severity -> ACTIONABLE, MINOR
188
+ 7. Trivial severity -> NON_ACTIONABLE, TRIVIAL
189
+ 8. Nitpick marker -> NON_ACTIONABLE, TRIVIAL
190
+ 9. Outside diff range -> ACTIONABLE, MINOR
191
+ 10. Summary/walkthrough sections -> NON_ACTIONABLE
192
+ 11. Tip/info boxes -> NON_ACTIONABLE
193
+ 12. All other -> AMBIGUOUS, UNKNOWN, requires_investigation=True
194
+
195
+ Args:
196
+ comment: Dictionary containing comment data with 'body' key,
197
+ and optionally 'path' and 'line' keys for inline comments.
198
+
199
+ Returns:
200
+ Tuple of (classification, priority, requires_investigation):
201
+ - classification: CommentClassification enum value
202
+ - priority: Priority enum value
203
+ - requires_investigation: Boolean, True for AMBIGUOUS comments
204
+ """
205
+ # Check for PR-level summary comments first (highest precedence)
206
+ # These are posted at the PR level and contain overview information
207
+ if self._is_pr_summary_comment(comment):
208
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
209
+
210
+ body = comment.get("body", "")
211
+
212
+ # Early exit for empty body
213
+ if not body:
214
+ return (CommentClassification.AMBIGUOUS, Priority.UNKNOWN, True)
215
+
216
+ # Check fingerprinting comments first (internal metadata, ignore)
217
+ if self.FINGERPRINT_PATTERN.search(body):
218
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
219
+
220
+ # Check addressed marker
221
+ if self.ADDRESSED_PATTERN.search(body):
222
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
223
+
224
+ # Check acknowledgment patterns (thank-you replies)
225
+ if self._is_acknowledgment(body):
226
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
227
+
228
+ # Check severity patterns (most specific first)
229
+ if self.CRITICAL_PATTERN.search(body):
230
+ return (CommentClassification.ACTIONABLE, Priority.CRITICAL, False)
231
+
232
+ if self.MAJOR_PATTERN.search(body):
233
+ return (CommentClassification.ACTIONABLE, Priority.MAJOR, False)
234
+
235
+ if self.MINOR_PATTERN.search(body):
236
+ return (CommentClassification.ACTIONABLE, Priority.MINOR, False)
237
+
238
+ # Check non-actionable patterns
239
+ if self.TRIVIAL_PATTERN.search(body):
240
+ return (CommentClassification.NON_ACTIONABLE, Priority.TRIVIAL, False)
241
+
242
+ if self.NITPICK_PATTERN.search(body):
243
+ return (CommentClassification.NON_ACTIONABLE, Priority.TRIVIAL, False)
244
+
245
+ # Check outside diff range (actionable but lower priority)
246
+ if self.OUTSIDE_DIFF_PATTERN.search(body):
247
+ return (CommentClassification.ACTIONABLE, Priority.MINOR, False)
248
+
249
+ # Check for summary/walkthrough sections (non-actionable informational)
250
+ if self._is_summary_content(body):
251
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
252
+
253
+ # Check for tip/info boxes (non-actionable)
254
+ if self._is_tip_content(body):
255
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
256
+
257
+ # Default: AMBIGUOUS - requires investigation
258
+ return (CommentClassification.AMBIGUOUS, Priority.UNKNOWN, True)
259
+
260
+ def _is_summary_content(self, body: str) -> bool:
261
+ """Check if the body is a summary/walkthrough section.
262
+
263
+ Summary sections are informational overviews that don't require
264
+ specific action. They include walkthroughs, change summaries,
265
+ and file tables.
266
+
267
+ Args:
268
+ body: Comment body text.
269
+
270
+ Returns:
271
+ True if the body appears to be a summary section.
272
+ """
273
+ for pattern in self.SUMMARY_PATTERNS:
274
+ if pattern.search(body):
275
+ return True
276
+ return False
277
+
278
+ def _is_tip_content(self, body: str) -> bool:
279
+ """Check if the body is a tip/info box.
280
+
281
+ Tip boxes are informational callouts that provide helpful
282
+ context but don't require action.
283
+
284
+ Args:
285
+ body: Comment body text.
286
+
287
+ Returns:
288
+ True if the body appears to be a tip/info box.
289
+ """
290
+ for pattern in self.TIP_PATTERNS:
291
+ if pattern.search(body):
292
+ return True
293
+ return False
294
+
295
+ def _is_acknowledgment(self, body: str) -> bool:
296
+ """Check if the body is an acknowledgment/thank-you reply.
297
+
298
+ Acknowledgment comments are replies from CodeRabbit confirming
299
+ that a fix or suggestion was addressed. These don't require action.
300
+
301
+ Args:
302
+ body: Comment body text.
303
+
304
+ Returns:
305
+ True if the body appears to be an acknowledgment.
306
+ """
307
+ for pattern in self.ACKNOWLEDGMENT_PATTERNS:
308
+ if pattern.search(body):
309
+ return True
310
+ return False
311
+
312
+ def _is_pr_summary_comment(self, comment: dict) -> bool:
313
+ """Check if this is a PR-level summary comment.
314
+
315
+ PR-level summary comments are posted at the PR level (not inline)
316
+ and contain overview information like "Actionable comments posted: N",
317
+ walkthrough sections, or CodeRabbit signatures. These should be
318
+ classified as NON_ACTIONABLE because the actual actionable items
319
+ are in inline comments.
320
+
321
+ Key criteria:
322
+ 1. Must be a PR-level comment (path=None or missing)
323
+ 2. Must match one of the PR summary patterns
324
+
325
+ Args:
326
+ comment: Dictionary containing comment data with 'body' key,
327
+ and optionally 'path' and 'line' keys.
328
+
329
+ Returns:
330
+ True if this is a PR-level summary comment that should be
331
+ classified as NON_ACTIONABLE.
332
+ """
333
+ # Only filter PR-level comments (path=None or missing)
334
+ # Never filter inline comments (they have path/line set)
335
+ path = comment.get("path")
336
+ if path is not None:
337
+ return False
338
+
339
+ body = comment.get("body", "")
340
+ if not body:
341
+ return False
342
+
343
+ # Check for PR summary patterns
344
+ for pattern in self.PR_SUMMARY_PATTERNS:
345
+ if pattern.search(body):
346
+ return True
347
+
348
+ # Also check for summary/walkthrough content at PR level
349
+ if self._is_summary_content(body):
350
+ return True
351
+
352
+ return False
@@ -0,0 +1,135 @@
1
+ """Cursor/Bugbot comment parser.
2
+
3
+ This module implements the ReviewerParser interface for parsing comments
4
+ from Cursor's Bugbot automated code reviewer.
5
+
6
+ Cursor/Bugbot uses severity-based classification:
7
+ - Critical Severity: Must fix immediately
8
+ - High Severity: Must fix before merge
9
+ - Medium Severity: Should fix
10
+ - Low Severity: Nice to fix (non-actionable)
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ import re
16
+
17
+ from goodtogo.core.interfaces import ReviewerParser
18
+ from goodtogo.core.models import CommentClassification, Priority, ReviewerType
19
+
20
+
21
+ class CursorBugbotParser(ReviewerParser):
22
+ """Parser for Cursor/Bugbot automated code review comments.
23
+
24
+ Detects comments from Cursor's Bugbot based on author patterns and
25
+ body content signatures. Classifies based on severity indicators
26
+ in the comment body.
27
+
28
+ Author patterns:
29
+ - "cursor[bot]"
30
+ - "cursor-bot"
31
+
32
+ Body signatures:
33
+ - cursor.com links
34
+ - Cursor-specific formatting
35
+
36
+ Severity mapping:
37
+ - Critical Severity -> ACTIONABLE, CRITICAL
38
+ - High Severity -> ACTIONABLE, MAJOR
39
+ - Medium Severity -> ACTIONABLE, MINOR
40
+ - Low Severity -> NON_ACTIONABLE, TRIVIAL
41
+ - No severity indicator -> AMBIGUOUS, UNKNOWN
42
+ """
43
+
44
+ # Author patterns that identify Cursor/Bugbot comments
45
+ _AUTHOR_PATTERNS = frozenset({"cursor[bot]", "cursor-bot"})
46
+
47
+ # Patterns in body that indicate Cursor/Bugbot origin
48
+ _CURSOR_BODY_SIGNATURES = (re.compile(r"cursor\.com", re.IGNORECASE),)
49
+
50
+ # Severity patterns and their classifications
51
+ _SEVERITY_PATTERNS = (
52
+ (
53
+ re.compile(r"Critical\s+Severity", re.IGNORECASE),
54
+ CommentClassification.ACTIONABLE,
55
+ Priority.CRITICAL,
56
+ ),
57
+ (
58
+ re.compile(r"High\s+Severity", re.IGNORECASE),
59
+ CommentClassification.ACTIONABLE,
60
+ Priority.MAJOR,
61
+ ),
62
+ (
63
+ re.compile(r"Medium\s+Severity", re.IGNORECASE),
64
+ CommentClassification.ACTIONABLE,
65
+ Priority.MINOR,
66
+ ),
67
+ (
68
+ re.compile(r"Low\s+Severity", re.IGNORECASE),
69
+ CommentClassification.NON_ACTIONABLE,
70
+ Priority.TRIVIAL,
71
+ ),
72
+ )
73
+
74
+ @property
75
+ def reviewer_type(self) -> ReviewerType:
76
+ """Return the reviewer type this parser handles.
77
+
78
+ Returns:
79
+ ReviewerType.CURSOR
80
+ """
81
+ return ReviewerType.CURSOR
82
+
83
+ def can_parse(self, author: str, body: str) -> bool:
84
+ """Check if this parser can handle the comment.
85
+
86
+ A comment can be parsed by this parser if:
87
+ 1. The author matches known Cursor/Bugbot patterns, OR
88
+ 2. The body contains Cursor-specific signatures
89
+
90
+ Args:
91
+ author: Comment author's username/login.
92
+ body: Comment body text.
93
+
94
+ Returns:
95
+ True if this parser can handle the comment, False otherwise.
96
+ """
97
+ # Check author patterns (case-insensitive)
98
+ author_lower = author.lower()
99
+ if author_lower in self._AUTHOR_PATTERNS:
100
+ return True
101
+
102
+ # Check body signatures
103
+ for pattern in self._CURSOR_BODY_SIGNATURES:
104
+ if pattern.search(body):
105
+ return True
106
+
107
+ return False
108
+
109
+ def parse(self, comment: dict) -> tuple[CommentClassification, Priority, bool]:
110
+ """Parse comment and return classification.
111
+
112
+ Analyzes the comment body for severity indicators to determine
113
+ classification and priority. Comments without a recognized
114
+ severity pattern are classified as AMBIGUOUS.
115
+
116
+ Args:
117
+ comment: Dictionary containing comment data with at least:
118
+ - 'body': Comment text content
119
+
120
+ Returns:
121
+ Tuple of (classification, priority, requires_investigation):
122
+ - classification: CommentClassification enum value
123
+ - priority: Priority enum value
124
+ - requires_investigation: True if AMBIGUOUS, False otherwise
125
+ """
126
+ body = comment.get("body", "")
127
+
128
+ # Check each severity pattern in order of priority
129
+ for pattern, classification, priority in self._SEVERITY_PATTERNS:
130
+ if pattern.search(body):
131
+ return classification, priority, False
132
+
133
+ # No recognized severity pattern - classify as ambiguous
134
+ # This requires investigation by the agent
135
+ return CommentClassification.AMBIGUOUS, Priority.UNKNOWN, True
@@ -0,0 +1,192 @@
1
+ """Generic fallback parser for unknown reviewers.
2
+
3
+ This module provides the GenericParser class, which serves as a fallback
4
+ parser for comments that don't match any specific automated reviewer pattern.
5
+ It handles human comments and unknown reviewer types.
6
+
7
+ Per the design specification, the Generic Parser classification rules are:
8
+ - Thread is resolved -> NON_ACTIONABLE
9
+ - Thread is outdated -> NON_ACTIONABLE
10
+ - Reply confirmation patterns -> NON_ACTIONABLE (acknowledging fixes)
11
+ - Approval patterns (LGTM, etc.) -> NON_ACTIONABLE
12
+ - All other -> AMBIGUOUS with requires_investigation=True
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import re
18
+
19
+ from goodtogo.core.interfaces import ReviewerParser
20
+ from goodtogo.core.models import (
21
+ CommentClassification,
22
+ Priority,
23
+ ReviewerType,
24
+ )
25
+
26
+
27
+ class GenericParser(ReviewerParser):
28
+ """Fallback parser for unknown reviewers and human comments.
29
+
30
+ This parser is used when no specific reviewer parser matches the comment.
31
+ It applies conservative classification rules, marking most comments as
32
+ AMBIGUOUS to ensure nothing is silently skipped.
33
+
34
+ The GenericParser serves two purposes:
35
+ 1. Handle comments from human reviewers (ReviewerType.HUMAN)
36
+ 2. Act as a fallback for any unrecognized automated reviewers
37
+
38
+ Classification logic:
39
+ - Resolved threads -> NON_ACTIONABLE (already addressed)
40
+ - Outdated threads -> NON_ACTIONABLE (code has changed)
41
+ - Reply confirmation patterns -> NON_ACTIONABLE (acknowledging fixes)
42
+ - Approval patterns -> NON_ACTIONABLE (LGTM, looks good, etc.)
43
+ - All other comments -> AMBIGUOUS with requires_investigation=True
44
+
45
+ This conservative approach ensures that AI agents never miss potentially
46
+ important feedback by automatically dismissing it.
47
+ """
48
+
49
+ # Patterns indicating a reply that confirms something was addressed
50
+ # These are typically non-actionable acknowledgments
51
+ REPLY_CONFIRMATION_PATTERNS = [
52
+ # Explicit fix confirmations
53
+ re.compile(r"^(good\s+catch|great\s+catch|nice\s+catch)", re.IGNORECASE),
54
+ re.compile(r"^fixed\s+(in\s+)?(commit|[a-f0-9]{7})", re.IGNORECASE),
55
+ re.compile(r"^done[.!]?\s*$", re.IGNORECASE),
56
+ re.compile(r"^addressed[.!]?\s*$", re.IGNORECASE),
57
+ re.compile(r"^resolved[.!]?\s*$", re.IGNORECASE),
58
+ # Acknowledgments and thanks
59
+ re.compile(r"^thanks[!.,]?\s*$", re.IGNORECASE),
60
+ re.compile(r"^thank\s+you[!.,]?\s*$", re.IGNORECASE),
61
+ re.compile(r"^will\s+(fix|do|address|update)", re.IGNORECASE),
62
+ re.compile(r"^updated[.!]?\s*$", re.IGNORECASE),
63
+ re.compile(r"^applied[.!]?\s*$", re.IGNORECASE),
64
+ # Agreement patterns - must be complete confirmations, not prefixes
65
+ re.compile(r"^(yep|yeah|yes)[,.]?\s+(fixed|done|updated|addressed)", re.IGNORECASE),
66
+ re.compile(r"^agreed[,.]?\s*(fixed|done|updated)?[.!]?\s*$", re.IGNORECASE),
67
+ re.compile(r"^makes\s+sense[.!]?\s*$", re.IGNORECASE),
68
+ ]
69
+
70
+ # Patterns indicating approval or positive feedback (non-actionable)
71
+ APPROVAL_PATTERNS = [
72
+ re.compile(r"^lgtm[!.]?\s*$", re.IGNORECASE),
73
+ re.compile(r"^looks\s+good(\s+to\s+me)?[!.]?\s*$", re.IGNORECASE),
74
+ re.compile(r"^ship\s+it[!.]?\s*$", re.IGNORECASE),
75
+ re.compile(r"^\+1\s*$"),
76
+ re.compile(r"^:?\+1:?\s*$"), # emoji format
77
+ re.compile(r"^approved[!.]?\s*$", re.IGNORECASE),
78
+ ]
79
+
80
+ @property
81
+ def reviewer_type(self) -> ReviewerType:
82
+ """Return ReviewerType.HUMAN.
83
+
84
+ The generic parser is used for human reviewers and as a fallback
85
+ for unknown reviewer types. HUMAN is returned as it's the most
86
+ appropriate classification for non-automated reviews.
87
+
88
+ Returns:
89
+ ReviewerType.HUMAN
90
+ """
91
+ return ReviewerType.HUMAN
92
+
93
+ def can_parse(self, author: str, body: str) -> bool:
94
+ """Return True for all comments.
95
+
96
+ As the fallback parser, GenericParser accepts all comments that
97
+ weren't matched by more specific parsers. This ensures no comments
98
+ are dropped or unhandled.
99
+
100
+ Args:
101
+ author: Comment author's username/login (ignored).
102
+ body: Comment body text (ignored).
103
+
104
+ Returns:
105
+ Always True - this is the catch-all parser.
106
+ """
107
+ return True
108
+
109
+ def parse(self, comment: dict) -> tuple[CommentClassification, Priority, bool]:
110
+ """Parse comment and return classification.
111
+
112
+ Classification logic:
113
+ 1. Resolved threads -> NON_ACTIONABLE
114
+ 2. Outdated threads -> NON_ACTIONABLE
115
+ 3. Reply confirmation patterns -> NON_ACTIONABLE
116
+ 4. Approval patterns -> NON_ACTIONABLE
117
+ 5. All other -> AMBIGUOUS with requires_investigation=True
118
+
119
+ Note: This parser intentionally does NOT try to interpret comment
120
+ content for actionability. That would be unreliable for human comments.
121
+ Instead, it relies on metadata (resolved/outdated status) and simple
122
+ patterns that indicate the comment has been addressed.
123
+
124
+ Args:
125
+ comment: Dictionary containing comment data with:
126
+ - 'body': Comment text content (optional)
127
+ - 'is_resolved': Boolean indicating if thread is resolved
128
+ - 'is_outdated': Boolean indicating if comment is outdated
129
+
130
+ Returns:
131
+ Tuple of (classification, priority, requires_investigation):
132
+ - classification: CommentClassification enum value
133
+ - priority: Priority.UNKNOWN (generic parser doesn't assess priority)
134
+ - requires_investigation: True for AMBIGUOUS, False otherwise
135
+ """
136
+ # Check if thread is resolved
137
+ if comment.get("is_resolved", False):
138
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
139
+
140
+ # Check if thread is outdated
141
+ if comment.get("is_outdated", False):
142
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
143
+
144
+ body = comment.get("body", "")
145
+
146
+ # Check for reply confirmation patterns (acknowledging fixes)
147
+ if self._is_reply_confirmation(body):
148
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
149
+
150
+ # Check for approval patterns (LGTM, looks good, etc.)
151
+ if self._is_approval(body):
152
+ return (CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False)
153
+
154
+ # All other cases: AMBIGUOUS with requires_investigation=True
155
+ # Critical: AMBIGUOUS comments MUST always have requires_investigation=True
156
+ return (CommentClassification.AMBIGUOUS, Priority.UNKNOWN, True)
157
+
158
+ def _is_reply_confirmation(self, body: str) -> bool:
159
+ """Check if the body is a reply confirmation.
160
+
161
+ Reply confirmations are comments that acknowledge something was
162
+ addressed or fixed, such as "Good catch!", "Fixed in commit abc123", etc.
163
+
164
+ Args:
165
+ body: Comment body text.
166
+
167
+ Returns:
168
+ True if the body appears to be a reply confirmation.
169
+ """
170
+ body_stripped = body.strip()
171
+ for pattern in self.REPLY_CONFIRMATION_PATTERNS:
172
+ if pattern.search(body_stripped):
173
+ return True
174
+ return False
175
+
176
+ def _is_approval(self, body: str) -> bool:
177
+ """Check if the body is an approval comment.
178
+
179
+ Approval comments are positive feedback that indicate the code
180
+ is acceptable, such as "LGTM", "Looks good", "+1", etc.
181
+
182
+ Args:
183
+ body: Comment body text.
184
+
185
+ Returns:
186
+ True if the body appears to be an approval.
187
+ """
188
+ body_stripped = body.strip()
189
+ for pattern in self.APPROVAL_PATTERNS:
190
+ if pattern.search(body_stripped):
191
+ return True
192
+ return False