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.
- goodtogo/__init__.py +66 -0
- goodtogo/adapters/__init__.py +22 -0
- goodtogo/adapters/agent_state.py +490 -0
- goodtogo/adapters/cache_memory.py +208 -0
- goodtogo/adapters/cache_sqlite.py +305 -0
- goodtogo/adapters/github.py +523 -0
- goodtogo/adapters/time_provider.py +123 -0
- goodtogo/cli.py +311 -0
- goodtogo/container.py +313 -0
- goodtogo/core/__init__.py +0 -0
- goodtogo/core/analyzer.py +982 -0
- goodtogo/core/errors.py +100 -0
- goodtogo/core/interfaces.py +388 -0
- goodtogo/core/models.py +312 -0
- goodtogo/core/validation.py +144 -0
- goodtogo/parsers/__init__.py +0 -0
- goodtogo/parsers/claude.py +188 -0
- goodtogo/parsers/coderabbit.py +352 -0
- goodtogo/parsers/cursor.py +135 -0
- goodtogo/parsers/generic.py +192 -0
- goodtogo/parsers/greptile.py +249 -0
- gtg-0.4.0.dist-info/METADATA +278 -0
- gtg-0.4.0.dist-info/RECORD +27 -0
- gtg-0.4.0.dist-info/WHEEL +5 -0
- gtg-0.4.0.dist-info/entry_points.txt +2 -0
- gtg-0.4.0.dist-info/licenses/LICENSE +21 -0
- gtg-0.4.0.dist-info/top_level.txt +1 -0
goodtogo/core/errors.py
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""Error handling utilities for GoodToMerge.
|
|
2
|
+
|
|
3
|
+
This module provides secure error handling that prevents sensitive information
|
|
4
|
+
like GitHub tokens and credentials from leaking in error messages, logs, or
|
|
5
|
+
exception tracebacks.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import re
|
|
11
|
+
from typing import Optional
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class RedactedError(Exception):
|
|
15
|
+
"""Exception with sensitive data redacted from the message.
|
|
16
|
+
|
|
17
|
+
This exception wraps an original exception while redacting any sensitive
|
|
18
|
+
information (tokens, credentials, etc.) from the error message.
|
|
19
|
+
|
|
20
|
+
Attributes:
|
|
21
|
+
original: The original exception that was redacted, if any.
|
|
22
|
+
|
|
23
|
+
Example:
|
|
24
|
+
>>> original = Exception("Failed with token ghp_secret123")
|
|
25
|
+
>>> redacted = RedactedError("Failed with token <REDACTED_TOKEN>", original)
|
|
26
|
+
>>> str(redacted)
|
|
27
|
+
'Failed with token <REDACTED_TOKEN>'
|
|
28
|
+
>>> redacted.original
|
|
29
|
+
Exception('Failed with token ghp_secret123')
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
def __init__(self, message: str, original: Optional[Exception] = None) -> None:
|
|
33
|
+
"""Initialize a RedactedError.
|
|
34
|
+
|
|
35
|
+
Args:
|
|
36
|
+
message: The redacted error message (should already be sanitized).
|
|
37
|
+
original: The original exception before redaction.
|
|
38
|
+
"""
|
|
39
|
+
self.original = original
|
|
40
|
+
super().__init__(message)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def redact_error(error: Exception) -> RedactedError:
|
|
44
|
+
"""Redact sensitive information from an exception's error message.
|
|
45
|
+
|
|
46
|
+
This function creates a new RedactedError with all sensitive patterns
|
|
47
|
+
removed from the message. The original exception is preserved in the
|
|
48
|
+
`original` attribute for debugging purposes.
|
|
49
|
+
|
|
50
|
+
Redacted patterns include:
|
|
51
|
+
- GitHub Personal Access Tokens (ghp_*)
|
|
52
|
+
- GitHub OAuth Tokens (gho_*)
|
|
53
|
+
- GitHub PAT tokens (github_pat_*)
|
|
54
|
+
- URL credentials (://user:pass@host)
|
|
55
|
+
- Authorization headers (Authorization: Bearer/token ...)
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
error: The exception to redact.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
A RedactedError with sensitive data replaced with placeholders.
|
|
62
|
+
|
|
63
|
+
Example:
|
|
64
|
+
>>> error = Exception("Auth failed: ghp_abc123xyz789")
|
|
65
|
+
>>> redacted = redact_error(error)
|
|
66
|
+
>>> "ghp_abc123xyz789" in str(redacted)
|
|
67
|
+
False
|
|
68
|
+
>>> "<REDACTED_TOKEN>" in str(redacted)
|
|
69
|
+
True
|
|
70
|
+
>>> redacted.original is error
|
|
71
|
+
True
|
|
72
|
+
"""
|
|
73
|
+
message = str(error)
|
|
74
|
+
|
|
75
|
+
# Redact GitHub tokens (ghp_, gho_, github_pat_)
|
|
76
|
+
# Pattern: prefix followed by alphanumeric characters and underscores
|
|
77
|
+
message = re.sub(
|
|
78
|
+
r"(ghp_|gho_|github_pat_)[a-zA-Z0-9_]+",
|
|
79
|
+
"<REDACTED_TOKEN>",
|
|
80
|
+
message,
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
# Redact URL credentials (://user:pass@host)
|
|
84
|
+
# Pattern: :// followed by non-colon chars, colon, non-@ chars, @
|
|
85
|
+
message = re.sub(
|
|
86
|
+
r"://[^:]+:[^@]+@",
|
|
87
|
+
"://<REDACTED>@",
|
|
88
|
+
message,
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
# Redact Authorization headers
|
|
92
|
+
# Pattern: Authorization (with optional quotes/colons) followed by Bearer/token and the value
|
|
93
|
+
message = re.sub(
|
|
94
|
+
r'(Authorization["\']?\s*:\s*["\']?)(Bearer\s+)?[a-zA-Z0-9_-]+',
|
|
95
|
+
r"\1<REDACTED>",
|
|
96
|
+
message,
|
|
97
|
+
flags=re.IGNORECASE,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return RedactedError(message, original=error)
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
"""Abstract base classes (ports) for the GoodToMerge hexagonal architecture.
|
|
2
|
+
|
|
3
|
+
This module defines the abstract interfaces (ports) that the core domain
|
|
4
|
+
depends on. Concrete implementations (adapters) must implement these
|
|
5
|
+
interfaces to integrate with external systems like GitHub API and caching.
|
|
6
|
+
|
|
7
|
+
Following the Ports & Adapters (Hexagonal) architecture pattern, these
|
|
8
|
+
interfaces ensure the core domain has no external dependencies.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
from abc import ABC, abstractmethod
|
|
14
|
+
from typing import TYPE_CHECKING, Optional
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
from goodtogo.core.models import (
|
|
18
|
+
CacheStats,
|
|
19
|
+
CommentClassification,
|
|
20
|
+
Priority,
|
|
21
|
+
ReviewerType,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class GitHubPort(ABC):
|
|
26
|
+
"""Abstract interface for GitHub API access.
|
|
27
|
+
|
|
28
|
+
This port defines the contract for fetching PR-related data from GitHub.
|
|
29
|
+
Implementations may use REST API, GraphQL, or any other mechanism to
|
|
30
|
+
fulfill these requirements.
|
|
31
|
+
|
|
32
|
+
All methods accept validated inputs (owner, repo, pr_number) and return
|
|
33
|
+
raw dictionary data that will be processed by the analyzer.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
@abstractmethod
|
|
37
|
+
def get_pr(self, owner: str, repo: str, pr_number: int) -> dict:
|
|
38
|
+
"""Fetch PR metadata.
|
|
39
|
+
|
|
40
|
+
Retrieves basic PR information including title, state, head SHA,
|
|
41
|
+
base branch, and timestamps.
|
|
42
|
+
|
|
43
|
+
Args:
|
|
44
|
+
owner: Repository owner (organization or user name).
|
|
45
|
+
repo: Repository name.
|
|
46
|
+
pr_number: Pull request number.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Dictionary containing PR metadata with keys like:
|
|
50
|
+
- 'number': PR number
|
|
51
|
+
- 'title': PR title
|
|
52
|
+
- 'state': PR state ('open', 'closed', 'merged')
|
|
53
|
+
- 'head': Dictionary with 'sha' key for latest commit
|
|
54
|
+
- 'base': Dictionary with 'ref' key for base branch
|
|
55
|
+
- 'created_at': ISO timestamp
|
|
56
|
+
- 'updated_at': ISO timestamp
|
|
57
|
+
|
|
58
|
+
Raises:
|
|
59
|
+
Exception: If the API request fails or PR is not found.
|
|
60
|
+
"""
|
|
61
|
+
pass
|
|
62
|
+
|
|
63
|
+
@abstractmethod
|
|
64
|
+
def get_pr_comments(self, owner: str, repo: str, pr_number: int) -> list[dict]:
|
|
65
|
+
"""Fetch all PR comments (inline + review + issue).
|
|
66
|
+
|
|
67
|
+
Retrieves all types of comments associated with a PR:
|
|
68
|
+
- Inline review comments on specific code lines
|
|
69
|
+
- Review body comments
|
|
70
|
+
- Issue-style comments on the PR itself
|
|
71
|
+
|
|
72
|
+
Args:
|
|
73
|
+
owner: Repository owner (organization or user name).
|
|
74
|
+
repo: Repository name.
|
|
75
|
+
pr_number: Pull request number.
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of dictionaries, each containing:
|
|
79
|
+
- 'id': Unique comment identifier
|
|
80
|
+
- 'user': Dictionary with 'login' key for author
|
|
81
|
+
- 'body': Comment text content
|
|
82
|
+
- 'created_at': ISO timestamp
|
|
83
|
+
- 'path': File path (for inline comments, None otherwise)
|
|
84
|
+
- 'line': Line number (for inline comments, None otherwise)
|
|
85
|
+
- 'in_reply_to_id': Parent comment ID if reply (None otherwise)
|
|
86
|
+
|
|
87
|
+
Raises:
|
|
88
|
+
Exception: If the API request fails.
|
|
89
|
+
"""
|
|
90
|
+
pass
|
|
91
|
+
|
|
92
|
+
@abstractmethod
|
|
93
|
+
def get_pr_reviews(self, owner: str, repo: str, pr_number: int) -> list[dict]:
|
|
94
|
+
"""Fetch all PR reviews.
|
|
95
|
+
|
|
96
|
+
Retrieves all reviews submitted on a PR, including approvals,
|
|
97
|
+
change requests, and comment-only reviews.
|
|
98
|
+
|
|
99
|
+
Args:
|
|
100
|
+
owner: Repository owner (organization or user name).
|
|
101
|
+
repo: Repository name.
|
|
102
|
+
pr_number: Pull request number.
|
|
103
|
+
|
|
104
|
+
Returns:
|
|
105
|
+
List of dictionaries, each containing:
|
|
106
|
+
- 'id': Unique review identifier
|
|
107
|
+
- 'user': Dictionary with 'login' key for reviewer
|
|
108
|
+
- 'body': Review body text (may be empty)
|
|
109
|
+
- 'state': Review state ('APPROVED', 'CHANGES_REQUESTED',
|
|
110
|
+
'COMMENTED', 'DISMISSED', 'PENDING')
|
|
111
|
+
- 'submitted_at': ISO timestamp
|
|
112
|
+
|
|
113
|
+
Raises:
|
|
114
|
+
Exception: If the API request fails.
|
|
115
|
+
"""
|
|
116
|
+
pass
|
|
117
|
+
|
|
118
|
+
@abstractmethod
|
|
119
|
+
def get_pr_threads(self, owner: str, repo: str, pr_number: int) -> list[dict]:
|
|
120
|
+
"""Fetch all review threads with resolution status.
|
|
121
|
+
|
|
122
|
+
Retrieves all review threads on a PR using GitHub's GraphQL API,
|
|
123
|
+
which provides thread resolution status not available in REST API.
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
owner: Repository owner (organization or user name).
|
|
127
|
+
repo: Repository name.
|
|
128
|
+
pr_number: Pull request number.
|
|
129
|
+
|
|
130
|
+
Returns:
|
|
131
|
+
List of dictionaries, each containing:
|
|
132
|
+
- 'id': Thread node ID
|
|
133
|
+
- 'is_resolved': Boolean indicating if thread is resolved
|
|
134
|
+
- 'is_outdated': Boolean indicating if thread is outdated
|
|
135
|
+
(code has changed since comment)
|
|
136
|
+
- 'path': File path the thread is attached to
|
|
137
|
+
- 'line': Line number in the diff
|
|
138
|
+
- 'comments': List of comments in the thread
|
|
139
|
+
|
|
140
|
+
Raises:
|
|
141
|
+
Exception: If the API request fails.
|
|
142
|
+
"""
|
|
143
|
+
pass
|
|
144
|
+
|
|
145
|
+
@abstractmethod
|
|
146
|
+
def get_ci_status(self, owner: str, repo: str, ref: str) -> dict:
|
|
147
|
+
"""Fetch CI/CD check status for a commit.
|
|
148
|
+
|
|
149
|
+
Retrieves the combined status of all CI checks for a specific
|
|
150
|
+
commit reference (usually the head SHA of the PR).
|
|
151
|
+
|
|
152
|
+
Args:
|
|
153
|
+
owner: Repository owner (organization or user name).
|
|
154
|
+
repo: Repository name.
|
|
155
|
+
ref: Git reference (commit SHA, branch name, or tag).
|
|
156
|
+
|
|
157
|
+
Returns:
|
|
158
|
+
Dictionary containing:
|
|
159
|
+
- 'state': Overall state ('success', 'failure', 'pending')
|
|
160
|
+
- 'total_count': Total number of checks
|
|
161
|
+
- 'statuses': List of individual status checks
|
|
162
|
+
- 'check_runs': List of check runs (GitHub Actions, etc.)
|
|
163
|
+
|
|
164
|
+
Each status/check_run contains:
|
|
165
|
+
- 'name': Check name
|
|
166
|
+
- 'state' or 'status': Individual check state
|
|
167
|
+
- 'conclusion': Final conclusion (for completed checks)
|
|
168
|
+
- 'target_url' or 'html_url': Link to check details
|
|
169
|
+
|
|
170
|
+
Raises:
|
|
171
|
+
Exception: If the API request fails.
|
|
172
|
+
"""
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class CachePort(ABC):
|
|
177
|
+
"""Abstract interface for caching.
|
|
178
|
+
|
|
179
|
+
This port defines the contract for caching PR analysis data to reduce
|
|
180
|
+
API calls and improve response times. Implementations may use SQLite,
|
|
181
|
+
Redis, or in-memory storage.
|
|
182
|
+
|
|
183
|
+
All keys are strings constructed from validated inputs. Values are
|
|
184
|
+
serialized as strings (typically JSON). TTL (Time To Live) controls
|
|
185
|
+
automatic expiration.
|
|
186
|
+
"""
|
|
187
|
+
|
|
188
|
+
@abstractmethod
|
|
189
|
+
def get(self, key: str) -> Optional[str]:
|
|
190
|
+
"""Get cached value.
|
|
191
|
+
|
|
192
|
+
Retrieves a value from the cache if it exists and has not expired.
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
key: Cache key (e.g., 'pr:myorg:myrepo:123:meta').
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Cached value as string if found and not expired, None otherwise.
|
|
199
|
+
"""
|
|
200
|
+
pass
|
|
201
|
+
|
|
202
|
+
@abstractmethod
|
|
203
|
+
def set(self, key: str, value: str, ttl_seconds: int) -> None:
|
|
204
|
+
"""Set cached value with TTL.
|
|
205
|
+
|
|
206
|
+
Stores a value in the cache with an expiration time.
|
|
207
|
+
|
|
208
|
+
Args:
|
|
209
|
+
key: Cache key (e.g., 'pr:myorg:myrepo:123:meta').
|
|
210
|
+
value: Value to cache (typically JSON string).
|
|
211
|
+
ttl_seconds: Time to live in seconds. After this duration,
|
|
212
|
+
the entry is considered expired.
|
|
213
|
+
"""
|
|
214
|
+
pass
|
|
215
|
+
|
|
216
|
+
@abstractmethod
|
|
217
|
+
def delete(self, key: str) -> None:
|
|
218
|
+
"""Delete cached value.
|
|
219
|
+
|
|
220
|
+
Removes a specific entry from the cache.
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
key: Cache key to delete.
|
|
224
|
+
"""
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
@abstractmethod
|
|
228
|
+
def invalidate_pattern(self, pattern: str) -> None:
|
|
229
|
+
"""Invalidate all keys matching pattern.
|
|
230
|
+
|
|
231
|
+
Removes all cache entries whose keys match the given pattern.
|
|
232
|
+
Pattern syntax depends on the implementation but typically
|
|
233
|
+
supports glob-style wildcards (e.g., 'pr:myorg:myrepo:123:*').
|
|
234
|
+
|
|
235
|
+
This is used to invalidate all cached data for a PR when
|
|
236
|
+
a new commit is detected.
|
|
237
|
+
|
|
238
|
+
Args:
|
|
239
|
+
pattern: Pattern to match keys against (e.g., 'pr:*:*:123:*').
|
|
240
|
+
"""
|
|
241
|
+
pass
|
|
242
|
+
|
|
243
|
+
@abstractmethod
|
|
244
|
+
def cleanup_expired(self) -> None:
|
|
245
|
+
"""Remove expired entries.
|
|
246
|
+
|
|
247
|
+
Deletes all entries whose TTL has expired. This should be called
|
|
248
|
+
periodically to prevent unbounded cache growth.
|
|
249
|
+
|
|
250
|
+
For some implementations (like Redis), this may be a no-op as
|
|
251
|
+
expiration is handled automatically.
|
|
252
|
+
"""
|
|
253
|
+
pass
|
|
254
|
+
|
|
255
|
+
@abstractmethod
|
|
256
|
+
def get_stats(self) -> CacheStats:
|
|
257
|
+
"""Get cache hit/miss statistics.
|
|
258
|
+
|
|
259
|
+
Returns metrics about cache performance for monitoring and
|
|
260
|
+
debugging purposes.
|
|
261
|
+
|
|
262
|
+
Returns:
|
|
263
|
+
CacheStats object containing:
|
|
264
|
+
- hits: Number of successful cache lookups
|
|
265
|
+
- misses: Number of cache misses
|
|
266
|
+
- hit_rate: Ratio of hits to total lookups (0.0 to 1.0)
|
|
267
|
+
"""
|
|
268
|
+
pass
|
|
269
|
+
|
|
270
|
+
|
|
271
|
+
class TimeProvider(ABC):
|
|
272
|
+
"""Abstract interface for time operations.
|
|
273
|
+
|
|
274
|
+
This port defines the contract for time-related operations, enabling
|
|
275
|
+
dependency injection of time for deterministic testing. Production
|
|
276
|
+
code uses real system time; tests use a controllable mock.
|
|
277
|
+
|
|
278
|
+
This pattern eliminates flaky tests caused by real time.sleep() calls
|
|
279
|
+
and non-deterministic time.time() values.
|
|
280
|
+
"""
|
|
281
|
+
|
|
282
|
+
@abstractmethod
|
|
283
|
+
def now(self) -> float:
|
|
284
|
+
"""Get current time as Unix timestamp.
|
|
285
|
+
|
|
286
|
+
Returns:
|
|
287
|
+
Current time as seconds since epoch (float).
|
|
288
|
+
"""
|
|
289
|
+
pass
|
|
290
|
+
|
|
291
|
+
@abstractmethod
|
|
292
|
+
def now_int(self) -> int:
|
|
293
|
+
"""Get current time as Unix timestamp (integer).
|
|
294
|
+
|
|
295
|
+
Returns:
|
|
296
|
+
Current time as seconds since epoch (int).
|
|
297
|
+
"""
|
|
298
|
+
pass
|
|
299
|
+
|
|
300
|
+
@abstractmethod
|
|
301
|
+
def sleep(self, seconds: float) -> None:
|
|
302
|
+
"""Sleep for the specified duration.
|
|
303
|
+
|
|
304
|
+
In production, this calls time.sleep().
|
|
305
|
+
In tests, this can advance simulated time instantly.
|
|
306
|
+
|
|
307
|
+
Args:
|
|
308
|
+
seconds: Duration to sleep in seconds.
|
|
309
|
+
"""
|
|
310
|
+
pass
|
|
311
|
+
|
|
312
|
+
|
|
313
|
+
class ReviewerParser(ABC):
|
|
314
|
+
"""Abstract interface for reviewer-specific parsing.
|
|
315
|
+
|
|
316
|
+
This port defines the contract for parsing comments from different
|
|
317
|
+
automated reviewers (CodeRabbit, Greptile, Claude Code, etc.).
|
|
318
|
+
|
|
319
|
+
Each parser knows how to:
|
|
320
|
+
1. Identify comments from its reviewer (via author or body patterns)
|
|
321
|
+
2. Classify comments as ACTIONABLE, NON_ACTIONABLE, or AMBIGUOUS
|
|
322
|
+
3. Determine comment priority (CRITICAL, MAJOR, MINOR, TRIVIAL)
|
|
323
|
+
|
|
324
|
+
Parsers are registered in the container and selected based on
|
|
325
|
+
can_parse() returning True for a given comment.
|
|
326
|
+
"""
|
|
327
|
+
|
|
328
|
+
@property
|
|
329
|
+
@abstractmethod
|
|
330
|
+
def reviewer_type(self) -> ReviewerType:
|
|
331
|
+
"""Return the reviewer type this parser handles.
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
ReviewerType enum value (e.g., ReviewerType.CODERABBIT,
|
|
335
|
+
ReviewerType.GREPTILE, ReviewerType.HUMAN).
|
|
336
|
+
"""
|
|
337
|
+
pass
|
|
338
|
+
|
|
339
|
+
@abstractmethod
|
|
340
|
+
def can_parse(self, author: str, body: str) -> bool:
|
|
341
|
+
"""Check if this parser can handle the comment.
|
|
342
|
+
|
|
343
|
+
Determines whether this parser is appropriate for a given comment
|
|
344
|
+
based on the author name and/or body content patterns.
|
|
345
|
+
|
|
346
|
+
Args:
|
|
347
|
+
author: Comment author's username/login.
|
|
348
|
+
body: Comment body text.
|
|
349
|
+
|
|
350
|
+
Returns:
|
|
351
|
+
True if this parser can handle the comment, False otherwise.
|
|
352
|
+
|
|
353
|
+
Note:
|
|
354
|
+
Multiple parsers may return True for the same comment.
|
|
355
|
+
The analyzer should use the first matching parser based
|
|
356
|
+
on parser priority.
|
|
357
|
+
"""
|
|
358
|
+
pass
|
|
359
|
+
|
|
360
|
+
@abstractmethod
|
|
361
|
+
def parse(self, comment: dict) -> tuple[CommentClassification, Priority, bool]:
|
|
362
|
+
"""Parse comment and return classification.
|
|
363
|
+
|
|
364
|
+
Analyzes the comment content and determines its classification,
|
|
365
|
+
priority, and whether it requires human investigation.
|
|
366
|
+
|
|
367
|
+
Args:
|
|
368
|
+
comment: Dictionary containing comment data with keys:
|
|
369
|
+
- 'body': Comment text content
|
|
370
|
+
- 'user': Dictionary with 'login' key
|
|
371
|
+
- 'id': Comment identifier
|
|
372
|
+
- Additional keys depending on comment type
|
|
373
|
+
|
|
374
|
+
Returns:
|
|
375
|
+
Tuple of (classification, priority, requires_investigation):
|
|
376
|
+
- classification: CommentClassification enum value
|
|
377
|
+
(ACTIONABLE, NON_ACTIONABLE, or AMBIGUOUS)
|
|
378
|
+
- priority: Priority enum value
|
|
379
|
+
(CRITICAL, MAJOR, MINOR, TRIVIAL, or UNKNOWN)
|
|
380
|
+
- requires_investigation: Boolean, True if the comment
|
|
381
|
+
could not be definitively classified and needs human
|
|
382
|
+
review (always True for AMBIGUOUS classification)
|
|
383
|
+
|
|
384
|
+
Note:
|
|
385
|
+
If classification is AMBIGUOUS, requires_investigation MUST
|
|
386
|
+
be True. Never silently skip ambiguous comments.
|
|
387
|
+
"""
|
|
388
|
+
pass
|