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,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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (80.9.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ gtg = goodtogo.cli:main
@@ -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