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
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""Greptile parser for classifying comments from Greptile code reviewer.
|
|
2
|
+
|
|
3
|
+
This module provides the GreptileParser class that implements the ReviewerParser
|
|
4
|
+
interface for parsing and classifying comments from the Greptile automated
|
|
5
|
+
code review tool.
|
|
6
|
+
|
|
7
|
+
Greptile comments are identified by:
|
|
8
|
+
- Author: "greptile[bot]"
|
|
9
|
+
- Body patterns: Contains "greptile.com" links or "Greptile" branding
|
|
10
|
+
|
|
11
|
+
Classification rules (per design spec):
|
|
12
|
+
- "Actionable comments posted: 0" -> NON_ACTIONABLE
|
|
13
|
+
- "Actionable comments posted: N" (N > 0) -> ACTIONABLE, MINOR
|
|
14
|
+
- Review summary only -> NON_ACTIONABLE
|
|
15
|
+
- Severity markers (**logic:**, **bug:**, **security:**) -> ACTIONABLE with priority
|
|
16
|
+
- Other -> AMBIGUOUS, UNKNOWN, requires_investigation=True
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
import re
|
|
22
|
+
from typing import TYPE_CHECKING
|
|
23
|
+
|
|
24
|
+
from goodtogo.core.interfaces import ReviewerParser
|
|
25
|
+
from goodtogo.core.models import CommentClassification, Priority, ReviewerType
|
|
26
|
+
|
|
27
|
+
if TYPE_CHECKING:
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class GreptileParser(ReviewerParser):
|
|
32
|
+
"""Parser for Greptile automated code reviewer comments.
|
|
33
|
+
|
|
34
|
+
Implements the ReviewerParser interface to classify comments from
|
|
35
|
+
Greptile based on patterns defined in the GoodToMerge design specification.
|
|
36
|
+
"""
|
|
37
|
+
|
|
38
|
+
# Pattern to detect "Actionable comments posted: N" where N is a number
|
|
39
|
+
ACTIONABLE_PATTERN = re.compile(r"Actionable comments posted:\s*(\d+)", re.IGNORECASE)
|
|
40
|
+
|
|
41
|
+
# Pattern to detect Greptile signature/branding in body
|
|
42
|
+
GREPTILE_SIGNATURE_PATTERN = re.compile(r"greptile\.com|greptile|Greptile", re.IGNORECASE)
|
|
43
|
+
|
|
44
|
+
# Patterns indicating a review summary (non-actionable)
|
|
45
|
+
REVIEW_SUMMARY_PATTERNS = [
|
|
46
|
+
re.compile(r"^#+\s*(Summary|Review Summary|PR Summary)", re.MULTILINE),
|
|
47
|
+
re.compile(r"(reviewed|analyzed)\s+(this\s+)?(PR|pull\s+request)", re.IGNORECASE),
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
# Severity marker patterns for Greptile inline comments
|
|
51
|
+
# Format: **category:** description (e.g., **logic:** The function...)
|
|
52
|
+
# These patterns indicate specific issues found by Greptile
|
|
53
|
+
SEVERITY_PATTERNS: list[tuple[re.Pattern[str], Priority]] = [
|
|
54
|
+
# Critical severity markers (security, bug, error)
|
|
55
|
+
(re.compile(r"\*\*security[:\s]", re.IGNORECASE), Priority.CRITICAL),
|
|
56
|
+
(re.compile(r"\*\*bug[:\s]", re.IGNORECASE), Priority.MAJOR),
|
|
57
|
+
(re.compile(r"\*\*error[:\s]", re.IGNORECASE), Priority.MAJOR),
|
|
58
|
+
# Medium severity markers (logic, performance)
|
|
59
|
+
(re.compile(r"\*\*logic[:\s]", re.IGNORECASE), Priority.MINOR),
|
|
60
|
+
(re.compile(r"\*\*performance[:\s]", re.IGNORECASE), Priority.MINOR),
|
|
61
|
+
# Lower severity markers (style, typo, suggestion)
|
|
62
|
+
(re.compile(r"\*\*style[:\s]", re.IGNORECASE), Priority.TRIVIAL),
|
|
63
|
+
(re.compile(r"\*\*typo[:\s]", re.IGNORECASE), Priority.TRIVIAL),
|
|
64
|
+
(re.compile(r"\*\*nitpick[:\s]", re.IGNORECASE), Priority.TRIVIAL),
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
# PR-level summary comment patterns (non-actionable)
|
|
68
|
+
# These are posted at the PR level (path=None) and contain overview information.
|
|
69
|
+
# The actual actionable items are in inline comments.
|
|
70
|
+
PR_SUMMARY_PATTERNS = [
|
|
71
|
+
# Greptile Summary HTML header
|
|
72
|
+
re.compile(r"<h3>Greptile Summary</h3>", re.IGNORECASE),
|
|
73
|
+
# "N files reviewed" pattern
|
|
74
|
+
re.compile(r"\d+\s+files?\s+reviewed", re.IGNORECASE),
|
|
75
|
+
# Edit Code Review Agent Settings link
|
|
76
|
+
re.compile(r"Edit Code Review Agent Settings", re.IGNORECASE),
|
|
77
|
+
]
|
|
78
|
+
|
|
79
|
+
@property
|
|
80
|
+
def reviewer_type(self) -> ReviewerType:
|
|
81
|
+
"""Return the reviewer type this parser handles.
|
|
82
|
+
|
|
83
|
+
Returns:
|
|
84
|
+
ReviewerType.GREPTILE
|
|
85
|
+
"""
|
|
86
|
+
return ReviewerType.GREPTILE
|
|
87
|
+
|
|
88
|
+
def can_parse(self, author: str, body: str) -> bool:
|
|
89
|
+
"""Check if this parser can handle the comment.
|
|
90
|
+
|
|
91
|
+
Greptile comments are identified by:
|
|
92
|
+
1. Author is "greptile[bot]"
|
|
93
|
+
2. Body contains Greptile signature/links (fallback detection)
|
|
94
|
+
|
|
95
|
+
Args:
|
|
96
|
+
author: Comment author's username/login.
|
|
97
|
+
body: Comment body text.
|
|
98
|
+
|
|
99
|
+
Returns:
|
|
100
|
+
True if this appears to be a Greptile comment, False otherwise.
|
|
101
|
+
"""
|
|
102
|
+
# Primary detection: author is greptile bot
|
|
103
|
+
if author.lower() == "greptile[bot]":
|
|
104
|
+
return True
|
|
105
|
+
|
|
106
|
+
# Fallback detection: body contains Greptile signature
|
|
107
|
+
if self.GREPTILE_SIGNATURE_PATTERN.search(body):
|
|
108
|
+
return True
|
|
109
|
+
|
|
110
|
+
return False
|
|
111
|
+
|
|
112
|
+
def parse(self, comment: dict) -> tuple[CommentClassification, Priority, bool]:
|
|
113
|
+
"""Parse comment and return classification.
|
|
114
|
+
|
|
115
|
+
Classification logic (per design spec):
|
|
116
|
+
1. PR-level summary comments -> NON_ACTIONABLE
|
|
117
|
+
2. "Actionable comments posted: 0" -> NON_ACTIONABLE
|
|
118
|
+
3. "Actionable comments posted: N" (N > 0) -> ACTIONABLE, MINOR
|
|
119
|
+
4. Review summary only -> NON_ACTIONABLE
|
|
120
|
+
5. Severity markers (**logic:**, **bug:**) -> ACTIONABLE with priority
|
|
121
|
+
6. Other -> AMBIGUOUS, UNKNOWN, requires_investigation=True
|
|
122
|
+
|
|
123
|
+
Args:
|
|
124
|
+
comment: Dictionary containing comment data with at least:
|
|
125
|
+
- 'body': Comment text content
|
|
126
|
+
- 'user': Dictionary with 'login' key
|
|
127
|
+
- Optionally 'path' and 'line' for inline comments
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
Tuple of (classification, priority, requires_investigation):
|
|
131
|
+
- classification: CommentClassification enum value
|
|
132
|
+
- priority: Priority enum value
|
|
133
|
+
- requires_investigation: Boolean, True for AMBIGUOUS comments
|
|
134
|
+
"""
|
|
135
|
+
# Check for PR-level summary comments first (highest precedence)
|
|
136
|
+
# These are posted at the PR level and contain overview information
|
|
137
|
+
if self._is_pr_summary_comment(comment):
|
|
138
|
+
return CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False
|
|
139
|
+
|
|
140
|
+
body = comment.get("body", "")
|
|
141
|
+
|
|
142
|
+
# Check for "Actionable comments posted: N" pattern
|
|
143
|
+
match = self.ACTIONABLE_PATTERN.search(body)
|
|
144
|
+
if match:
|
|
145
|
+
count = int(match.group(1))
|
|
146
|
+
if count == 0:
|
|
147
|
+
# No actionable comments
|
|
148
|
+
return CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False
|
|
149
|
+
else:
|
|
150
|
+
# Has actionable comments - classify as ACTIONABLE with MINOR priority
|
|
151
|
+
return CommentClassification.ACTIONABLE, Priority.MINOR, False
|
|
152
|
+
|
|
153
|
+
# Check if this is a review summary (non-actionable)
|
|
154
|
+
if self._is_review_summary(body):
|
|
155
|
+
return CommentClassification.NON_ACTIONABLE, Priority.UNKNOWN, False
|
|
156
|
+
|
|
157
|
+
# Check for severity markers (**logic:**, **bug:**, etc.)
|
|
158
|
+
severity_result = self._check_severity_markers(body)
|
|
159
|
+
if severity_result is not None:
|
|
160
|
+
return severity_result
|
|
161
|
+
|
|
162
|
+
# Default: AMBIGUOUS - cannot determine classification
|
|
163
|
+
# Per design spec: AMBIGUOUS comments MUST have requires_investigation=True
|
|
164
|
+
return CommentClassification.AMBIGUOUS, Priority.UNKNOWN, True
|
|
165
|
+
|
|
166
|
+
def _is_review_summary(self, body: str) -> bool:
|
|
167
|
+
"""Check if the body is a review summary.
|
|
168
|
+
|
|
169
|
+
Review summaries typically contain overview information about
|
|
170
|
+
the PR without specific actionable items.
|
|
171
|
+
|
|
172
|
+
Args:
|
|
173
|
+
body: Comment body text.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
True if the body appears to be a review summary.
|
|
177
|
+
"""
|
|
178
|
+
for pattern in self.REVIEW_SUMMARY_PATTERNS:
|
|
179
|
+
if pattern.search(body):
|
|
180
|
+
return True
|
|
181
|
+
return False
|
|
182
|
+
|
|
183
|
+
def _check_severity_markers(
|
|
184
|
+
self, body: str
|
|
185
|
+
) -> tuple[CommentClassification, Priority, bool] | None:
|
|
186
|
+
"""Check for Greptile severity markers in the comment body.
|
|
187
|
+
|
|
188
|
+
Greptile uses **category:** format to indicate the type and
|
|
189
|
+
severity of issues found. For example:
|
|
190
|
+
- **logic:** indicates a logic error
|
|
191
|
+
- **security:** indicates a security issue
|
|
192
|
+
- **bug:** indicates a bug
|
|
193
|
+
|
|
194
|
+
Args:
|
|
195
|
+
body: Comment body text.
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Tuple of (classification, priority, requires_investigation) if a
|
|
199
|
+
severity marker is found, None otherwise.
|
|
200
|
+
"""
|
|
201
|
+
for pattern, priority in self.SEVERITY_PATTERNS:
|
|
202
|
+
if pattern.search(body):
|
|
203
|
+
# Trivial priority markers are non-actionable
|
|
204
|
+
if priority == Priority.TRIVIAL:
|
|
205
|
+
return CommentClassification.NON_ACTIONABLE, priority, False
|
|
206
|
+
return CommentClassification.ACTIONABLE, priority, False
|
|
207
|
+
return None
|
|
208
|
+
|
|
209
|
+
def _is_pr_summary_comment(self, comment: dict) -> bool:
|
|
210
|
+
"""Check if this is a PR-level summary comment.
|
|
211
|
+
|
|
212
|
+
PR-level summary comments are posted at the PR level (not inline)
|
|
213
|
+
and contain overview information like Greptile Summary headers,
|
|
214
|
+
"N files reviewed" counts, or settings links. These should be
|
|
215
|
+
classified as NON_ACTIONABLE because the actual actionable items
|
|
216
|
+
are in inline comments.
|
|
217
|
+
|
|
218
|
+
Key criteria:
|
|
219
|
+
1. Must be a PR-level comment (path=None or missing)
|
|
220
|
+
2. Must match one of the PR summary patterns
|
|
221
|
+
|
|
222
|
+
Args:
|
|
223
|
+
comment: Dictionary containing comment data with 'body' key,
|
|
224
|
+
and optionally 'path' and 'line' keys.
|
|
225
|
+
|
|
226
|
+
Returns:
|
|
227
|
+
True if this is a PR-level summary comment that should be
|
|
228
|
+
classified as NON_ACTIONABLE.
|
|
229
|
+
"""
|
|
230
|
+
# Only filter PR-level comments (path=None or missing)
|
|
231
|
+
# Never filter inline comments (they have path/line set)
|
|
232
|
+
path = comment.get("path")
|
|
233
|
+
if path is not None:
|
|
234
|
+
return False
|
|
235
|
+
|
|
236
|
+
body = comment.get("body", "")
|
|
237
|
+
if not body:
|
|
238
|
+
return False
|
|
239
|
+
|
|
240
|
+
# Check for PR summary patterns
|
|
241
|
+
for pattern in self.PR_SUMMARY_PATTERNS:
|
|
242
|
+
if pattern.search(body):
|
|
243
|
+
return True
|
|
244
|
+
|
|
245
|
+
# Also check for review summary content at PR level
|
|
246
|
+
if self._is_review_summary(body):
|
|
247
|
+
return True
|
|
248
|
+
|
|
249
|
+
return False
|
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gtg
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Deterministic PR readiness detection for AI coding agents
|
|
5
|
+
Author-email: David Sifry <david@sifry.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/dsifry/goodtogo
|
|
8
|
+
Project-URL: Repository, https://github.com/dsifry/goodtogo
|
|
9
|
+
Project-URL: Issues, https://github.com/dsifry/goodtogo/issues
|
|
10
|
+
Keywords: pr-readiness,github,ai-agents,code-review,ci-cd,automation
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
21
|
+
Requires-Python: >=3.9
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: click>=8.0.0
|
|
25
|
+
Requires-Dist: pydantic>=2.0.0
|
|
26
|
+
Requires-Dist: httpx>=0.25.0
|
|
27
|
+
Provides-Extra: redis
|
|
28
|
+
Requires-Dist: redis>=5.0.0; extra == "redis"
|
|
29
|
+
Provides-Extra: dev
|
|
30
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
32
|
+
Requires-Dist: pytest-asyncio>=0.21.0; extra == "dev"
|
|
33
|
+
Requires-Dist: hypothesis>=6.0.0; extra == "dev"
|
|
34
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: ruff>=0.1.0; extra == "dev"
|
|
36
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
37
|
+
Dynamic: license-file
|
|
38
|
+
|
|
39
|
+
# Good To Go
|
|
40
|
+
|
|
41
|
+
**Deterministic PR readiness detection for AI coding agents**
|
|
42
|
+
|
|
43
|
+
Good To Go helps AI agents (like Claude Code) know exactly when a PR is ready to merge. No guessing, no polling indefinitely, no missing comments.
|
|
44
|
+
|
|
45
|
+
## The Problem
|
|
46
|
+
|
|
47
|
+
AI agents creating PRs face a common challenge: **How do I know when I'm actually done?**
|
|
48
|
+
|
|
49
|
+
- CI is still running... is it done yet?
|
|
50
|
+
- CodeRabbit left 12 comments... which ones need action?
|
|
51
|
+
- A reviewer requested changes... did I address them all?
|
|
52
|
+
- There are 3 unresolved threads... are they blocking?
|
|
53
|
+
|
|
54
|
+
Without deterministic answers, agents either wait too long, miss comments, or keep asking "is it ready yet?"
|
|
55
|
+
|
|
56
|
+
## The Solution
|
|
57
|
+
|
|
58
|
+
Good To Go provides **deterministic PR state analysis** via a simple CLI:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
gtg 123 --repo owner/repo
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Returns (default - AI-friendly):
|
|
65
|
+
- **Exit code 0**: Any analyzable state (ready, action required, threads, CI)
|
|
66
|
+
- **Exit code 4**: Error fetching data
|
|
67
|
+
|
|
68
|
+
For shell scripting, use `-q` for semantic exit codes without output, or `--semantic-codes` for output with semantic codes.
|
|
69
|
+
|
|
70
|
+
## Installation
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install gtg
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
That's it. No other dependencies required.
|
|
77
|
+
|
|
78
|
+
## Usage
|
|
79
|
+
|
|
80
|
+
### Basic Check
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
# Check if PR #123 in myorg/myrepo is ready to merge
|
|
84
|
+
gtg 123 --repo myorg/myrepo
|
|
85
|
+
|
|
86
|
+
# With JSON output for programmatic use
|
|
87
|
+
gtg 123 --repo myorg/myrepo --format json
|
|
88
|
+
|
|
89
|
+
# Human-readable text format
|
|
90
|
+
gtg 123 --repo myorg/myrepo --format text
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Authentication
|
|
94
|
+
|
|
95
|
+
Set your GitHub token:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
export GITHUB_TOKEN=ghp_your_token_here
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Note: The CLI reads `GITHUB_TOKEN` from the environment. There is no `--token` flag for security reasons.
|
|
102
|
+
|
|
103
|
+
### Exit Codes
|
|
104
|
+
|
|
105
|
+
**Default (AI-friendly)** - returns 0 for all analyzable states:
|
|
106
|
+
|
|
107
|
+
| Code | Meaning |
|
|
108
|
+
|------|---------|
|
|
109
|
+
| 0 | Any analyzable state (parse the JSON `status` field for details) |
|
|
110
|
+
| 4 | Error fetching PR data |
|
|
111
|
+
|
|
112
|
+
**With `-q` or `--semantic-codes`** - returns different codes per status:
|
|
113
|
+
|
|
114
|
+
| Code | Status | Meaning |
|
|
115
|
+
|------|--------|---------|
|
|
116
|
+
| 0 | READY | All clear - good to go! |
|
|
117
|
+
| 1 | ACTION_REQUIRED | Actionable comments need fixes |
|
|
118
|
+
| 2 | UNRESOLVED | Unresolved review threads |
|
|
119
|
+
| 3 | CI_FAILING | CI/CD checks failing |
|
|
120
|
+
| 4 | ERROR | Error fetching PR data |
|
|
121
|
+
|
|
122
|
+
Use `-q` (quiet mode) for shell scripts that only need the exit code.
|
|
123
|
+
|
|
124
|
+
### Quick Examples
|
|
125
|
+
|
|
126
|
+
Here's what the output looks like for each status (using `--format text`):
|
|
127
|
+
|
|
128
|
+
**READY** - All clear, ready to merge:
|
|
129
|
+
```
|
|
130
|
+
OK PR #123: READY
|
|
131
|
+
CI: success (5/5 passed)
|
|
132
|
+
Threads: 3/3 resolved
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**ACTION_REQUIRED** - Actionable comments need attention:
|
|
136
|
+
```
|
|
137
|
+
!! PR #456: ACTION_REQUIRED
|
|
138
|
+
CI: success (5/5 passed)
|
|
139
|
+
Threads: 8/8 resolved
|
|
140
|
+
|
|
141
|
+
Action required:
|
|
142
|
+
- Fix CRITICAL comment from coderabbit in src/db.py:42
|
|
143
|
+
- 2 comments require investigation (ambiguous)
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
**UNRESOLVED_THREADS** - Review threads need resolution:
|
|
147
|
+
```
|
|
148
|
+
?? PR #789: UNRESOLVED_THREADS
|
|
149
|
+
CI: success (5/5 passed)
|
|
150
|
+
Threads: 2/4 resolved
|
|
151
|
+
|
|
152
|
+
Action required:
|
|
153
|
+
- 2 unresolved review threads need attention
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
**CI_FAILING** - CI checks not passing:
|
|
157
|
+
```
|
|
158
|
+
XX PR #101: CI_FAILING
|
|
159
|
+
CI: failure (3/5 passed)
|
|
160
|
+
Threads: 2/2 resolved
|
|
161
|
+
|
|
162
|
+
Action required:
|
|
163
|
+
- CI checks are failing - fix build/test errors
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Text Format Status Icons
|
|
167
|
+
|
|
168
|
+
| Icon | Status | Meaning |
|
|
169
|
+
|------|--------|---------|
|
|
170
|
+
| `OK` | READY | All clear - good to go! |
|
|
171
|
+
| `!!` | ACTION_REQUIRED | Actionable comments need fixes |
|
|
172
|
+
| `??` | UNRESOLVED_THREADS | Unresolved review threads |
|
|
173
|
+
| `XX` | CI_FAILING | CI/CD checks failing |
|
|
174
|
+
| `##` | ERROR | Error fetching PR data |
|
|
175
|
+
|
|
176
|
+
### JSON Output
|
|
177
|
+
|
|
178
|
+
```bash
|
|
179
|
+
gtg 123 --repo myorg/myrepo --format json
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Returns structured data including:
|
|
183
|
+
- CI status (passed/failed/pending checks)
|
|
184
|
+
- Thread summary (resolved/unresolved counts)
|
|
185
|
+
- Classified comments (actionable vs non-actionable)
|
|
186
|
+
- Action items list
|
|
187
|
+
|
|
188
|
+
See [USAGE.md](USAGE.md) for full JSON schema and examples.
|
|
189
|
+
|
|
190
|
+
### GitHub Actions & Branch Protection
|
|
191
|
+
|
|
192
|
+
Make `gtg` a required check to block merging until PRs are truly ready:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Enable branch protection with gtg-check as required
|
|
196
|
+
# Use actual job names from your CI workflow, not the workflow name
|
|
197
|
+
gh api repos/OWNER/REPO/branches/main/protection -X PUT --input - <<'EOF'
|
|
198
|
+
{
|
|
199
|
+
"required_status_checks": {
|
|
200
|
+
"strict": true,
|
|
201
|
+
"contexts": ["Lint & Format", "Tests (3.9)", "Type Check", "gtg-check"]
|
|
202
|
+
},
|
|
203
|
+
"enforce_admins": true,
|
|
204
|
+
"required_pull_request_reviews": null,
|
|
205
|
+
"restrictions": null,
|
|
206
|
+
"allow_force_pushes": false
|
|
207
|
+
}
|
|
208
|
+
EOF
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
> **Note**: Use actual **job names** (e.g., `"Lint & Format"`) not workflow names (e.g., `"Tests & Quality"`).
|
|
212
|
+
> `enforce_admins: true` means admins must follow all rules. See [USAGE.md](USAGE.md#configuration-options) for details.
|
|
213
|
+
|
|
214
|
+
See [USAGE.md](USAGE.md#github-actions-integration) for the full GitHub Actions workflow setup.
|
|
215
|
+
|
|
216
|
+
## Supported Automated Reviewers
|
|
217
|
+
|
|
218
|
+
Good To Go recognizes and classifies comments from:
|
|
219
|
+
|
|
220
|
+
- **CodeRabbit** - Critical/Major/Minor/Trivial severity
|
|
221
|
+
- **Greptile** - Actionable comment detection
|
|
222
|
+
- **Claude Code** - Must/should/error/bug patterns
|
|
223
|
+
- **Cursor/Bugbot** - Severity-based classification
|
|
224
|
+
- **Generic** - Fallback for unknown reviewers
|
|
225
|
+
|
|
226
|
+
## For AI Agents
|
|
227
|
+
|
|
228
|
+
If you're an AI agent, use Good To Go in your PR workflow:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
import subprocess
|
|
232
|
+
import json
|
|
233
|
+
|
|
234
|
+
result = subprocess.run(
|
|
235
|
+
["gtg", "123", "--repo", "owner/repo", "--format", "json"],
|
|
236
|
+
capture_output=True,
|
|
237
|
+
text=True
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
if result.returncode == 0:
|
|
241
|
+
print("Good to go! Ready to merge.")
|
|
242
|
+
elif result.returncode == 1:
|
|
243
|
+
data = json.loads(result.stdout)
|
|
244
|
+
print(f"Action required: {data['action_items']}")
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Or use the Python API directly:
|
|
248
|
+
|
|
249
|
+
```python
|
|
250
|
+
from goodtogo import PRAnalyzer, Container
|
|
251
|
+
|
|
252
|
+
container = Container.create_default(github_token="ghp_...")
|
|
253
|
+
analyzer = PRAnalyzer(container)
|
|
254
|
+
|
|
255
|
+
result = analyzer.analyze("owner", "repo", 123)
|
|
256
|
+
if result.status == "READY":
|
|
257
|
+
print("Good to go!")
|
|
258
|
+
else:
|
|
259
|
+
for item in result.action_items:
|
|
260
|
+
print(f"- {item}")
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
## Documentation
|
|
264
|
+
|
|
265
|
+
- [USAGE.md](USAGE.md) - Detailed CLI usage and examples
|
|
266
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md) - Development setup and contribution guide
|
|
267
|
+
|
|
268
|
+
## License
|
|
269
|
+
|
|
270
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
271
|
+
|
|
272
|
+
## Credits
|
|
273
|
+
|
|
274
|
+
Created by [David Sifry](https://github.com/dsifry) with Claude Code.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
**Made with Claude Code**
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
goodtogo/__init__.py,sha256=V1s47-NTyFDKRlIvbUzmYlDVMMEkeftWz9WszaDqRpg,1736
|
|
2
|
+
goodtogo/cli.py,sha256=I81m0OxqxvgW8l2TaXflQpmfG42D3lq1t4MxMAntg5c,9698
|
|
3
|
+
goodtogo/container.py,sha256=S2_qtVHfXTTOpYszbW9rpL8Fn67xN_12yuA_pfhzG-8,11677
|
|
4
|
+
goodtogo/adapters/__init__.py,sha256=R9sO07B9G14hyn2fvULFCO7V92Bs_DSWXixe3U_BL5o,696
|
|
5
|
+
goodtogo/adapters/agent_state.py,sha256=s7LBB9y_eQ7mX8YhBdS2BO1XEzmwJQPWaEVo-ec5RHQ,17573
|
|
6
|
+
goodtogo/adapters/cache_memory.py,sha256=jT8BZjqa33QMgA7YklBz-Et8uzi5PJ0ufILhvNMmYWs,6897
|
|
7
|
+
goodtogo/adapters/cache_sqlite.py,sha256=Nngf3puxSevgbvqkOoAUoc6E1vnh7KgbVu_u4Ub2qQk,10759
|
|
8
|
+
goodtogo/adapters/github.py,sha256=GH-U6jorx64QZnvXCf3QMfRNJX0e4bzRkZfi4-uiDFo,18960
|
|
9
|
+
goodtogo/adapters/time_provider.py,sha256=xIDGB6KjQiY4KphghUeleMIji_5_fUMkvxHnyh12VNM,3421
|
|
10
|
+
goodtogo/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
11
|
+
goodtogo/core/analyzer.py,sha256=zH-O_3sJAItOa2_FE7QYu_68PAfUSki4iDJQiOFu8bc,37880
|
|
12
|
+
goodtogo/core/errors.py,sha256=snFY7Okcf1KlARpKovqe6gxtyy3D5Oi2qvYltWbXHnw,3207
|
|
13
|
+
goodtogo/core/interfaces.py,sha256=7KP9Db5jGvJf-U9JNOgRP3PVBy4TG-bdjBNrwV89rXQ,13004
|
|
14
|
+
goodtogo/core/models.py,sha256=Z-X4Djb0PJmUlI1dAfoq0-B6gZkLn-GVTh4BNevmXbk,7852
|
|
15
|
+
goodtogo/core/validation.py,sha256=-2me14ByMmO80KCqhoUK7DxwsKSK0FillJplO0W6ixM,5023
|
|
16
|
+
goodtogo/parsers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
goodtogo/parsers/claude.py,sha256=JwCuoFVkKVOokkVlSopNqsHH3em1QI3v0vF-dH13Nf0,6990
|
|
18
|
+
goodtogo/parsers/coderabbit.py,sha256=Py-YtADRb8gbTAEE5AGbI43zwp580xYWPfLBXmefndU,13582
|
|
19
|
+
goodtogo/parsers/cursor.py,sha256=RmmG877byv59VE6l-X994vqbrdQpVcawfwFbPA7htaM,4465
|
|
20
|
+
goodtogo/parsers/generic.py,sha256=L7S8CpoU73pM49kM3SdWBuJQGaHggeGGw9TMWsKgU8E,7798
|
|
21
|
+
goodtogo/parsers/greptile.py,sha256=AeqG-xi7IhpK1y1YdPTR8wrfkVeimIFVdXx14bYqxIw,9948
|
|
22
|
+
gtg-0.4.0.dist-info/licenses/LICENSE,sha256=pCAhpqrGTNSYSZu5IkJsU9rqu0Zw_7va0IDhcq7E10M,1068
|
|
23
|
+
gtg-0.4.0.dist-info/METADATA,sha256=S9GwNGwXzQIjHdTO-QA7nwtaymP_STzujF7V7dJyVtA,7791
|
|
24
|
+
gtg-0.4.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
25
|
+
gtg-0.4.0.dist-info/entry_points.txt,sha256=J3FgRI2mHiMFvjW8YIObeJ_CnIrXIDBbaPuITNgwOWk,42
|
|
26
|
+
gtg-0.4.0.dist-info/top_level.txt,sha256=V8uJ2KgslnD-R52ZMwLBC43FZPWCZoxLaOIdBxvk2Ic,9
|
|
27
|
+
gtg-0.4.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 David Sifry
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
goodtogo
|