gitpr-cli 1.2.0__py3-none-any.whl → 1.3.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.
- {gitpr_cli-1.2.0.dist-info → gitpr_cli-1.3.0.dist-info}/METADATA +66 -12
- gitpr_cli-1.3.0.dist-info/RECORD +108 -0
- src/application/__init__.py +2 -0
- src/application/use_cases/__init__.py +8 -0
- src/application/use_cases/generate_pr_explanation.py +73 -0
- src/application/use_cases/generate_test_file.py +227 -0
- src/branding/__init__.py +1 -0
- src/branding/badge_builder.py +139 -0
- src/branding/badge_data.py +53 -0
- src/changelog_builder.py +88 -15
- src/config.py +165 -1
- src/config_schema.py +107 -0
- src/core.py +75 -0
- src/demo/__init__.py +7 -0
- src/demo/demo_runner.py +336 -0
- src/demo/fake_ai_provider.py +112 -0
- src/demo/scenarios/__init__.py +128 -0
- src/demo/scenarios/laravel_bug_fix.py +443 -0
- src/demo/scenarios/security_issue.py +511 -0
- src/domain/linter/__init__.py +8 -0
- src/domain/linter/sast_finding_mapper.py +79 -0
- src/domain/pr/__init__.py +14 -0
- src/domain/pr/explain_section_builder.py +124 -0
- src/domain/tests_generation/__init__.py +14 -0
- src/domain/tests_generation/framework_detector.py +120 -0
- src/domain/tests_generation/test_content_types.py +43 -0
- src/domain/tests_generation/test_scaffold_builder.py +92 -0
- src/fix/__init__.py +1 -1
- src/fix/patch_applier.py +38 -168
- src/i18n.py +5 -1
- src/infrastructure/git/__init__.py +1 -0
- src/infrastructure/git/patch_applier.py +209 -0
- src/infrastructure/git/selective_stager.py +198 -0
- src/infrastructure/linter/external/__init__.py +9 -0
- src/infrastructure/linter/external/bandit_bridge.py +127 -0
- src/infrastructure/linter/external/base_bridge.py +105 -0
- src/infrastructure/linter/external/gitleaks_bridge.py +168 -0
- src/infrastructure/linter/external/semgrep_bridge.py +139 -0
- src/infrastructure/scm/__init__.py +11 -0
- src/infrastructure/scm/web_links.py +103 -0
- src/linter_engine.py +125 -6
- src/main.py +636 -2
- src/mcp_server.py +17 -1
- src/release_engine.py +358 -22
- src/security_ruleset.py +108 -0
- src/split/__init__.py +17 -0
- src/split/apply_split_plan.py +251 -0
- src/split/generate_split_plan.py +222 -0
- src/split/hunk_grouper.py +347 -0
- src/split/hunk_parser.py +256 -0
- src/split/split_plan.py +153 -0
- src/ui/chat_app.py +35 -9
- src/ui/demo/__init__.py +1 -0
- src/ui/demo/demo_app.py +91 -0
- src/ui/demo/demo_help_screen.py +52 -0
- src/ui/demo/demo_screens.py +60 -0
- src/updater.py +2 -2
- gitpr_cli-1.2.0.dist-info/RECORD +0 -67
- {gitpr_cli-1.2.0.dist-info → gitpr_cli-1.3.0.dist-info}/WHEEL +0 -0
- {gitpr_cli-1.2.0.dist-info → gitpr_cli-1.3.0.dist-info}/entry_points.txt +0 -0
- {gitpr_cli-1.2.0.dist-info → gitpr_cli-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {gitpr_cli-1.2.0.dist-info → gitpr_cli-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitpr-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: AI-powered PR automation, commit messages, and code review (Gemini, DeepSeek, and Ollama)
|
|
5
5
|
Author-email: Natan Fiuza <contato@natanfiuza.dev.br>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -24,6 +24,10 @@ Dynamic: license-file
|
|
|
24
24
|
<img src="https://raw.githubusercontent.com/natanfiuza/gitpr/main/docs/logo.png" alt="GitPR Logo" width="150">
|
|
25
25
|
</p>
|
|
26
26
|
|
|
27
|
+
<p align="center">
|
|
28
|
+
<a href="https://gitpr.natanfiuza.dev.br/"><img src="https://img.shields.io/badge/GitPR-quality--checked-blue" alt="GitPR"></a>
|
|
29
|
+
</p>
|
|
30
|
+
|
|
27
31
|
GitPR CLI is a command-line automation tool that uses **Google Gemini**, **DeepSeek**, and **Ollama** artificial intelligence to analyze your code changes (git diff) or entire files. The tool automatically generates commit messages in the *Conventional Commits* standard, detailed Pull Request descriptions, and deep Code Reviews aimed at reducing technical debt.
|
|
28
32
|
|
|
29
33
|
🌐 **Website:** [gitpr.natanfiuza.dev.br](https://gitpr.natanfiuza.dev.br/) · 📂 **Repository:** [https://github.com/gitpr-cli/gitpr.git](https://github.com/gitpr-cli/gitpr.git)
|
|
@@ -40,7 +44,18 @@ Install GitPR CLI using `pip`:
|
|
|
40
44
|
pip install gitpr-cli
|
|
41
45
|
```
|
|
42
46
|
|
|
43
|
-
### **2.
|
|
47
|
+
### **2. Seeing It Work (No Configuration)**
|
|
48
|
+
|
|
49
|
+
GitPR ships a guided tour over an example diff. It needs no API key, no Git repository and no connection:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
gitpr demo
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
> **Guided Tour:** Six screens walking through a real example — the diff, the commit message, the code review and the pull request description — with the answers recorded once and replayed. Add `--no-tui` for plain text, `--scenario security-issue` for the other example.
|
|
56
|
+
> 📖 **Full Documentation:** [https://gitpr.natanfiuza.dev.br/docs/demo?lang=en_us](https://gitpr.natanfiuza.dev.br/docs/demo?lang=en_us)
|
|
57
|
+
|
|
58
|
+
### **3. Initializing in a Repository**
|
|
44
59
|
|
|
45
60
|
To set up GitPR in a folder of a new repository, run:
|
|
46
61
|
|
|
@@ -113,15 +128,19 @@ Pytest will automatically detect files inside the `tests/` folder and display a
|
|
|
113
128
|
|
|
114
129
|
### From Source Code
|
|
115
130
|
|
|
116
|
-
1. Clone the repository: `git clone https://github.com/gitpr-cli/gitpr.git
|
|
131
|
+
1. Clone the repository: `git clone https://github.com/gitpr-cli/gitpr.git`
|
|
117
132
|
|
|
118
133
|
2. Enter the folder: `cd gitpr`
|
|
119
134
|
|
|
120
|
-
3.
|
|
135
|
+
3. Install in editable mode:
|
|
121
136
|
```bash
|
|
122
|
-
|
|
137
|
+
pip install -e .
|
|
123
138
|
```
|
|
124
|
-
4. Run:
|
|
139
|
+
4. Run: `gitpr`
|
|
140
|
+
|
|
141
|
+
> Prefer an isolated environment? Install the dependencies with `pipenv install google-genai openai python-dotenv click cryptography` and run `pipenv run python src/main.py` instead.
|
|
142
|
+
>
|
|
143
|
+
> **Note on the update gate:** a source install reports the version found in your checkout, so a tree behind the published release is stopped at startup. See [Installing GitPR from Source](docs/tutorial/install-from-source.md).
|
|
125
144
|
|
|
126
145
|
## **💻 How to Use**
|
|
127
146
|
|
|
@@ -137,6 +156,15 @@ The tool will sync with the remote (`git fetch`), compare your changes with the
|
|
|
137
156
|
### **Advanced Options and Commands**
|
|
138
157
|
You can pass the following *flags* for specific actions:
|
|
139
158
|
|
|
159
|
+
* `gitpr demo`: **Guided tour** over an example diff that ships with the tool — the commit message, the code review and the pull request description, produced by the real pipeline on a recorded example. Needs no API key, no Git repository and no connection, which makes it the first thing to run on a new machine. Use `--scenario <name>` for another example, `--lang <code>` for another language, `--no-tui` for plain text. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/demo.md)
|
|
160
|
+
* `gitpr badge`: **Adoption badge for your README.** Prints the Markdown snippet — `--readme` prints only the line, `--style` picks the shields.io style (`flat`, `flat-square`, `for-the-badge`). Nothing is written to your README. Published pull requests carry a badge of their own, with the linter counts of the diff; `GITPR_BADGE=false` turns it off. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/badge.md)
|
|
161
|
+
* `gitpr config`: **Interactive configuration screen.** Opens a master-detail TUI to view, edit, search, and validate all settings stored in `~/.gitpr/.env` (with secret encryption and live validation). 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/config-tui.md)
|
|
162
|
+
* `gitpr release`: **Changelog and Release Notes generator.** Scans commits since the previous version, categorizes them according to Conventional Commits, suggests a semantic version bump, generates an AI executive summary, and optionally publishes the release to your forge. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/release-notes.md)
|
|
163
|
+
* `gitpr fix [<finding_id>]`: **Review findings patch application & rollback.** Turns findings from the last code review (`gitpr -r`) into safe, reviewable unified diffs. Includes safety classification (`safe`, `review_required`, `experimental`), `--all-safe` batching, `--force` validation, and `--rollback <patch-id>`. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/fix-command.md)
|
|
164
|
+
* `gitpr split`: **Atomic commit splitter.** Reads uncommitted working tree changes, groups hunks by intent using AI, and proposes ordered atomic commits without altering your working tree files (`--dry-run`, `--apply`). 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/split-command.md)
|
|
165
|
+
* `gitpr tests generate`: **AI test suite generation & scaffolding.** Analyzes your diff, a target file (`--file`), or a review finding (`--finding`) and generates complete, executable test suites (supporting pytest, jest, vitest, phpunit, pest). Use `--apply` to write to disk.
|
|
166
|
+
* `gitpr explain`: **Reviewer Guide generator.** Generates a concise summary explaining what changed, why it changed, where reviewers should focus, and potential regression risks. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/pull-request-publication.md)
|
|
167
|
+
* `gitpr review-pr <pr_number>`: **Remote Pull Request review.** Fetches and reviews an open pull request directly from the forge (GitHub, GitLab, Bitbucket, Azure DevOps) without checking out the branch locally. Use `--post-comment` to post the review directly to the PR. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/review-pr.md)
|
|
140
168
|
* `-c` or `--commit`: Runs a local `git diff` and displays **only the suggested commit message**.
|
|
141
169
|
* `-r` or `--review`: Performs a detailed **Code Review** of local changes.
|
|
142
170
|
* `-f` or `--fullreview`: Performs a **Full Code Review** analyzing all changes since the remote branch.
|
|
@@ -147,6 +175,9 @@ You can pass the following *flags* for specific actions:
|
|
|
147
175
|
* `-l` or `--linter`: Runs **only the local static linter** (no AI calls). Ideal for use in CI/CD pipelines to block non-compliant code.
|
|
148
176
|
* `--status`: Lists uncommitted file changes categorized as **new**, **modified**, and **deleted** — fast, no AI, no network. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/git-status.md)
|
|
149
177
|
* `--no-unstaged-check`: Skips the unstaged files verification before AI processing for a single invocation. Equivalent to `GITPR_SKIP_UNSTAGED_CHECK=true` for one run. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/git-status.md)
|
|
178
|
+
* `--no-suggest-reviewers`: Disables the suggested reviewers computation in the interactive PR publisher (default ON). 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/suggested-reviewers.md)
|
|
179
|
+
* `--explain`: Includes the Reviewer Guide section ("Explain my PR") in the Pull Request description.
|
|
180
|
+
* `--init`: **Interactive SCM forge wizard.** Detects the repository's forge (GitHub, GitLab, Bitbucket Cloud, Azure DevOps), configures the provider, and stores the access token encrypted. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/scm-multiforge.md)
|
|
150
181
|
* `--linter-setup`: **Interactive external linter wizard.** Guides you through installing and configuring external linters (ESLint, PHPCS, Stylelint) as a Checkstyle XML bridge. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/linter-regras-customizadas.md)
|
|
151
182
|
* `--mcp`: Starts GitPR as an **MCP server** (Model Context Protocol) on stdio transport. Enables integration with VS Code, Cursor, Claude Desktop, and other MCP-compatible editors — exposing all GitPR AI capabilities as 14 annotated tools, 18 resources, and 7 pre-built prompts directly inside your IDE. Also available as the standalone `gitpr-mcp` command.
|
|
152
183
|
* `--plugins`: Lists all **globally installed plugins** — custom linter packs from `~/.gitpr/plugins/linter/` and MCP prompt templates from `~/.gitpr/plugins/prompts/`. These plugins apply across all your projects without duplication. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/plugins-system.md)
|
|
@@ -157,7 +188,7 @@ You can pass the following *flags* for specific actions:
|
|
|
157
188
|
* **New Code Issue (`gitpr -is`):** Reads the current `git diff`. **Why use:** Ideal for quickly documenting the task you just finished programming, before committing.
|
|
158
189
|
* **Epic/Release Issue (`gitpr -is -ht`):** Reads the full history of the current branch (Git Log + PR Cache). **Why use:** Ideal for generating consolidated documentation of an entire release or a large *feature* that took several days/commits to complete.
|
|
159
190
|
* **Archaeological/Technical Debt Issue (`gitpr -is -b file:lines`):** Reads the timeline of a specific business rule. **Why use:** Ideal for documenting technical debt, explaining how a legacy code block evolved and why it needs to be refactored.
|
|
160
|
-
|
|
191
|
+
* **PR Publisher (default):** Running `gitpr` generates the PR description with AI, saves the `.md` file to `.gitpr/reports/pr_desc/`, and opens an interactive terminal interface (TUI) to review, edit, and publish the Pull Request directly to GitHub, GitLab, Bitbucket, or Azure DevOps via REST API. Before generation, it checks for unstaged files, calculates suggested reviewers, and offers a modal to manage them. Use `--no-publish` to save only the PR file locally without opening the publisher, or `--no-edit` to auto-commit pending changes (with lint validation), auto-push, and publish immediately — handling existing PR updates, optional auto-merge, and clear error feedback when merge conflicts occur. Use `--base <branch>` to change the target branch. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/pull-request-publication.md)
|
|
161
192
|
* `-h` or `--help`: Shows the general help with all options. Use together with another flag for **contextual help** (e.g.: `gitpr -h --issue`, `gitpr -h --linter`) with a direct link to the detailed documentation of each feature.
|
|
162
193
|
* `-u` or `--update`: Checks the latest version of GitPR on PyPI and shows how to update it (Auto-Updater).
|
|
163
194
|
|
|
@@ -171,7 +202,7 @@ When your diff is too large for a single AI call (over ~90k estimated tokens), G
|
|
|
171
202
|
|
|
172
203
|
📚 Full documentation: [docs/map-reduce-diff.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/map-reduce-diff.md)
|
|
173
204
|
|
|
174
|
-
## 🛡️ Local Linter (Static Analysis)
|
|
205
|
+
## 🛡️ Local Linter (Static Analysis & Security)
|
|
175
206
|
|
|
176
207
|
GitPR CLI allows you to define strict rules that will be validated instantly during `--review` or `--fullreview`, without depending on AI. This is ideal for preventing common errors (like `console.log` or test IPs) from reaching the repository.
|
|
177
208
|
|
|
@@ -192,9 +223,9 @@ rules:
|
|
|
192
223
|
|
|
193
224
|
The Linter analyzes only the **added lines** in your `git diff`, ensuring a focused and extremely fast execution. If there are violations, they will appear highlighted at the top of your review file.
|
|
194
225
|
|
|
195
|
-
### External Linters (Checkstyle Bridge)
|
|
226
|
+
### External Linters & SAST Bridges (Checkstyle Bridge)
|
|
196
227
|
|
|
197
|
-
If your project already uses tools like ESLint, PHP_CodeSniffer or
|
|
228
|
+
If your project already uses tools like ESLint, PHP_CodeSniffer, Stylelint, Semgrep, Gitleaks, or Bandit, GitPR can act as a bridge — running them in the background and filtering errors **only for the lines you changed** in your current diff. Any linter or SAST scanner that emits reports in the `checkstyle` format is supported. In addition, GitPR includes embedded secret scanning rules to catch leaked API keys, tokens, and credentials before they leave your machine.
|
|
198
229
|
|
|
199
230
|
Instead of configuring the YAML manually, use the interactive wizard:
|
|
200
231
|
|
|
@@ -204,7 +235,7 @@ gitpr --linter-setup
|
|
|
204
235
|
|
|
205
236
|
The wizard shows pre-configured presets (PHPCS, ESLint, Stylelint — controlled remotely via `templates/gitpr.linter-presets.json`), guides you through the native installation command (e.g.: `npm install --save-dev eslint`), and injects the correct `external_linters` block into your `.gitpr.linter.yml`.
|
|
206
237
|
|
|
207
|
-
Every run — manual via `--linter` or automatic before commits — consolidates the Regex Rules and External Linters into a single Markdown report saved at `.gitpr/reports/linter/` (customizable via `OUTPUT_FILE_NAME_LINTER`). The report is generated only when violations are found — clean runs create no files.
|
|
238
|
+
Every run — manual via `--linter` or automatic before commits — consolidates the Regex Rules, Embedded Security Rules, and External Linters into a single Markdown report saved at `.gitpr/reports/linter/` (customizable via `OUTPUT_FILE_NAME_LINTER`). The report is generated only when violations are found — clean runs create no files.
|
|
208
239
|
|
|
209
240
|
## 🤝 Co-Author Signature
|
|
210
241
|
|
|
@@ -218,6 +249,22 @@ The trailer is appended programmatically (never by the AI) in all flows: console
|
|
|
218
249
|
|
|
219
250
|
📖 **Full documentation:** [docs/commit-message-ia.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/commit-message-ia.md)
|
|
220
251
|
|
|
252
|
+
## 🏷️ Pull Request Badge
|
|
253
|
+
|
|
254
|
+
Every pull request GitPR publishes carries a badge at the foot of the body, with what the local linter counted on that very diff:
|
|
255
|
+
|
|
256
|
+
```text
|
|
257
|
+
GitPR | 0 errors · 2 warnings
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
It is built from a real measurement, never from a claim: red when a blocking finding is there, yellow for advice, green when the rules found nothing — and **no badge at all when no linter rules are configured**, because a green badge over a diff nobody checked would say something GitPR cannot back. It is on by default; `GITPR_BADGE=false` turns it off, and `gitpr --skill` is what gives it rules to count.
|
|
261
|
+
|
|
262
|
+
For your own README there is a static badge. `gitpr badge --readme` prints the line and writes nothing — pasting it is your call:
|
|
263
|
+
|
|
264
|
+
[](https://gitpr.natanfiuza.dev.br/)
|
|
265
|
+
|
|
266
|
+
📖 **Full documentation:** [docs/badge.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/badge.md)
|
|
267
|
+
|
|
221
268
|
## 🧠 Multi-Model Architecture (AI-Agnostic)
|
|
222
269
|
|
|
223
270
|
GitPR is not tied to a single Artificial Intelligence. During initial setup, the user can choose their default engine. We currently support:
|
|
@@ -430,6 +477,8 @@ If you want to implement GitPR as an automated quality barrier in your team, che
|
|
|
430
477
|
|
|
431
478
|
### Core Features
|
|
432
479
|
|
|
480
|
+
* [**Guided Tour (gitpr demo)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/demo.md) — How the `gitpr demo` subcommand walks through the commit message, the code review and the pull request description over a recorded example, with no API key, no repository and no network.
|
|
481
|
+
* [**Pull Request Badge**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/badge.md) — What the badge GitPR attaches to published pull requests says, where it is attached, when it is left out, and how to print the static one for your own README.
|
|
433
482
|
* [**Pull Request (Default Mode)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/pr-descricao-padrao.md) — Complete flow for generating PR descriptions without flags.
|
|
434
483
|
* [**Pull Request Publisher TUI**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/pull-request-publication.md) — How to review and publish Pull Requests directly to GitHub from the terminal.
|
|
435
484
|
* [**AI Code Review**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/code-review-ia.md) — Guide to review modes (`--review`, `--fullreview`), file auditing (`--input`) and remote pull request review (`gitpr review-pr`).
|
|
@@ -438,11 +487,16 @@ If you want to implement GitPR as an automated quality barrier in your team, che
|
|
|
438
487
|
* [**Issue Generation and TUI Interface**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/issue-tui-help.md) — How to use the terminal graphical interface (TUI) and the 3 context engines to manage structured Issues.
|
|
439
488
|
* [**Code Archaeologist (Git Blame)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/blame-arqueologo.md) — How to trace the origin of business rules with `git blame` and AI.
|
|
440
489
|
* [**Skills and Templates System**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/skill-template.md) — How to customize AI behavior with `.gitpr.*.md` files.
|
|
441
|
-
* [**Release Notes & Changelog (gitpr release)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/release-notes.md) — How the `gitpr release` subcommand generates the changelog / release notes of a repository, suggests the next semantic version and publishes releases on the forge.
|
|
490
|
+
* [**Release Notes & Changelog (gitpr release)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/release-notes.md) — How the `gitpr release` subcommand generates the changelog / release notes of a repository from the delta of the previous version, links every commit, pull request and contributor, suggests the next semantic version and publishes releases on the forge.
|
|
442
491
|
* [**Fix Command (gitpr fix)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/fix-command.md) — How the `gitpr fix` subcommand turns the findings of the last review into patches you read before they touch your tree, classifies each one by safety, and undoes an applied patch on demand.
|
|
492
|
+
* [**Split Command (gitpr split)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/split-command.md) — How the `gitpr split` subcommand reads a working tree holding several concerns, groups the hunks by intent with AI, and turns them into ordered atomic commits without ever writing to your files.
|
|
443
493
|
|
|
444
494
|
### Configuration & Infrastructure
|
|
445
495
|
|
|
496
|
+
* [**Interactive Configuration TUI (gitpr config)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/config-tui.md) — Master-detail terminal interface to view, edit, and validate settings in `~/.gitpr/.env`.
|
|
497
|
+
* [**Multi-Forge SCM Integration**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/scm-multiforge.md) — Unified SCM provider layer for GitHub, GitLab, Bitbucket Cloud, and Azure DevOps with `--init` wizard.
|
|
498
|
+
* [**Suggested Reviewers**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/suggested-reviewers.md) — Automatic reviewer suggestion via Git blame of added lines mapped to forge logins.
|
|
499
|
+
* [**Usage Telemetry & Audit Log**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/usage-log.md) — Local invocation logging and audit history for tracking CLI executions.
|
|
446
500
|
* [**Install Wizard**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/install-wizard.md) — Step-by-step guided setup for configuring GitPR in a new project.
|
|
447
501
|
* [**AI Providers**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/providers-ia.md) — Configuration and selection between Google Gemini, DeepSeek, and Ollama.
|
|
448
502
|
* [**Auto-Updater**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/auto-update.md) — How GitPR's automatic update and the mandatory update block work.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
gitpr_cli-1.3.0.dist-info/licenses/LICENSE,sha256=UQzLC6mwqb-ZNDkBBAqOxC-SMQcEID0gaM6oFcu6u48,27030
|
|
2
|
+
src/__init__.py,sha256=WZW9x8P5CYnru29UiarFa8q1nQzuwYWyIodSOlruCWI,29
|
|
3
|
+
src/ai_providers.py,sha256=QyFEOsXyA59lZziO5bl-4nlYFJpxBcxU9aLhcTBnkWE,14633
|
|
4
|
+
src/blame_engine.py,sha256=Zov1hxrt7Dcmf56ovJKz3QmHIfzDKtSub2iItXeg9-A,17104
|
|
5
|
+
src/cache.py,sha256=GC2ZT0ThAtH22aRRZapIeauATv_nqBfiRsXjqeMhAiI,6368
|
|
6
|
+
src/changelog_builder.py,sha256=8GicvIhREWo3B8XhQugVi2_OWzhN_ik_2GxpxmiJVTg,9650
|
|
7
|
+
src/chat_memory.py,sha256=PcohnyspAfIwwwbsp9U2Wk_1AylJMFEMoWqRSJe_w30,8949
|
|
8
|
+
src/commit_classifier.py,sha256=8_LHQEzisWflV5ZsK31P3fwyu2QWacv8cdFErXYe030,4668
|
|
9
|
+
src/config.py,sha256=sY9qd-aNB00Z_QNefi_jguAl8GoZgi7XBv0S1n1mufM,40323
|
|
10
|
+
src/config_schema.py,sha256=pegWMWQ0ALZvipf90f_dGSsnjDfu8xxlzw6v_Mt71zM,38783
|
|
11
|
+
src/core.py,sha256=mmmFwcEGCVp3ZyrkqYn3v9HbYTAiD7CVLndpKKVGibY,79844
|
|
12
|
+
src/diff_parser.py,sha256=62X69ves4tlzNgVKXNsfWdOMwqn2h16RkUOPqszMU7k,11252
|
|
13
|
+
src/doc_links.py,sha256=DuovU-RF9kjbisBdEoGAsuavQ2y0MYDhqy6Sz6vbrGI,1157
|
|
14
|
+
src/github_api.py,sha256=ILm7e03UP80swc3XuJvIJJvhLp81-V_3kQQ1EuAcN8Q,3836
|
|
15
|
+
src/i18n.py,sha256=PdUmtZlH1zogbiDc3j6xiwtnvUd7XiNbiS-xKjYK4WM,4844
|
|
16
|
+
src/issue_engine.py,sha256=XwW9C5uX84eOWWZMerm0DvE8LpLRcjhrLsXtt7yftak,10872
|
|
17
|
+
src/linter_engine.py,sha256=QZ-mJz5amh3rafsKSxjTL4NyelJKLQo5IERz1k1SJZE,17280
|
|
18
|
+
src/linter_wizard.py,sha256=7J_nQFghVwCaKG6QI3K45KcOaxHSH1oTCAvP_6tnCQM,7424
|
|
19
|
+
src/main.py,sha256=ivcSlV3sGqUf7-P3pGpPW_bDuSYVZXLko6QlEwNUHME,120679
|
|
20
|
+
src/mcp_server.py,sha256=jHKUjhO225WbZVv8xkQXAXyS9ckP34cjTMwbrxJLNfE,91147
|
|
21
|
+
src/metrics.py,sha256=VC68t8xGUBFry-ANvEb_TjaRkboPi37__GxK0eHZOe4,21250
|
|
22
|
+
src/net.py,sha256=f0MOkXyIxi4HVetsybtWpV_yHjiC17T2JMWySBdoV2I,2774
|
|
23
|
+
src/release_engine.py,sha256=S9qMg3l4Vjx9gBDSX6cwaacYF-tDcQbj7Rj_7AMgkQk,34629
|
|
24
|
+
src/reviewer_resolution.py,sha256=lZ3csSJHN6Y5TJ3U3p9uuXikOgG7fH4qqx7X9-4F7pc,5754
|
|
25
|
+
src/reviewer_suggestion.py,sha256=jk9FAyp7WkkJbi-yepz7LDF1_tSHxJEEjcajMKF5HdY,8501
|
|
26
|
+
src/security.py,sha256=fkWy-r4tdISH0xOyWZyR8ZCFyLuYqu8fn9sKsIeNAso,1351
|
|
27
|
+
src/security_ruleset.py,sha256=L754S4RlVoZGWm0nuCoeli9-49mKydZpwi31V319DQ0,4337
|
|
28
|
+
src/spinner.py,sha256=63pEotiDa3TVwBr-sn0MJoDPQ_uWJTTwINXieJIDDKc,8964
|
|
29
|
+
src/suggest_reviewers.py,sha256=-wRe0HYksx4FWQZ-eDvU9Ghdk5gqtEt457KQP7sE31Y,5181
|
|
30
|
+
src/tui_issue.py,sha256=WEFxT4kzWQ_KbSDsOnS_iuYXasZySB-68jryGNm0peg,10331
|
|
31
|
+
src/updater.py,sha256=MZgHnlmKss0Vft-c0ys-fzt5ninPGnZ8F3Wq8QvvsTY,4771
|
|
32
|
+
src/usage_log.py,sha256=314sTT5Gl_uOXqWSIi-V42bnTHuMSYAZ6G6-_15uEGQ,5968
|
|
33
|
+
src/version_bump.py,sha256=OjqJe9OL1-1dT5od7-SgU5ZN_dTtR_aKX_oSjSY64YQ,2954
|
|
34
|
+
src/application/__init__.py,sha256=0GADzpCB3CdCzw-d3az0DVOtTvXfCppt-0hY-Js5a1U,29
|
|
35
|
+
src/application/use_cases/__init__.py,sha256=7rwBoDbGxnsjVe32j7yj9yCA9g2E-DWGeokOfDbl_O0,284
|
|
36
|
+
src/application/use_cases/generate_pr_explanation.py,sha256=5BYUPgGBdR29P-9zt75IcKO4KOwGhZnapws7Y6A4574,3202
|
|
37
|
+
src/application/use_cases/generate_test_file.py,sha256=WlHnhwCWHe-XBrL9m1xvJOkn1wqr8vnsE3s3x9wgUi0,8615
|
|
38
|
+
src/branding/__init__.py,sha256=6ORCsJHtd422jUiQNMCf6eL8kwW4QMf6dJ9Z4Gf9bEc,80
|
|
39
|
+
src/branding/badge_builder.py,sha256=ZO-rXMPoSPYodvILfY2wHufYs0ZQwgFxSs0SkHd6m4A,4899
|
|
40
|
+
src/branding/badge_data.py,sha256=eamUtEKeg3NwF9MCipb5EXFlwL8vgJnR6yUw2TrFlEw,1925
|
|
41
|
+
src/demo/__init__.py,sha256=lF-kdDOqylPXRiuSmT3XwqdghMlRIiOuzpNYs7ElGiE,370
|
|
42
|
+
src/demo/demo_runner.py,sha256=lqnJZB27GSJan7bwV3Gf1wnCBYn7Wws_DEOFdB5PADc,10721
|
|
43
|
+
src/demo/fake_ai_provider.py,sha256=9slMebDqKvHv6wmhms_YhEqPhQJBlxYwAiCzehpkR3A,4038
|
|
44
|
+
src/demo/scenarios/__init__.py,sha256=t8PRSdgNM9f0tb53lBSkehiQ_liI-azts7BdhMdUZrs,4041
|
|
45
|
+
src/demo/scenarios/laravel_bug_fix.py,sha256=lU3XoKaGvcDIlMmzZFeWvEI6tGjgz5bWGflBbUNvgHU,20461
|
|
46
|
+
src/demo/scenarios/security_issue.py,sha256=sI8YykXcYOkpgHygAOX6pYLHwSkHZoE5OjrYsRqOCXQ,23821
|
|
47
|
+
src/domain/linter/__init__.py,sha256=WoLci3LhRc7S2IlytrveRPbEQVrAwI4bqazVmGP08Ak,236
|
|
48
|
+
src/domain/linter/sast_finding_mapper.py,sha256=GaeRqyD9NOL_crJLV9qAbY6hrUknXZakLXBB3T_5FsU,3105
|
|
49
|
+
src/domain/pr/__init__.py,sha256=O9C2dQpbMnbF3mOXTMUvHPmIAF0G-QxhDlVXjB_wShs,274
|
|
50
|
+
src/domain/pr/explain_section_builder.py,sha256=RMPErD_X8nLhbfCwxHCBmI3AFLilO_eNPpEDZE6pyns,3799
|
|
51
|
+
src/domain/tests_generation/__init__.py,sha256=OcHvtEilwu_8z_PV_prq2x_GGBGxmlsCW2XM1xU8dl0,251
|
|
52
|
+
src/domain/tests_generation/framework_detector.py,sha256=DyJZcttvpRkZMEx8a9X3ksveJ-PNub49XXNzqERQSU4,4439
|
|
53
|
+
src/domain/tests_generation/test_content_types.py,sha256=jnPywEMAGtm340e8V1kuHbx9Nxa9nhC7mKnubduxEZM,1019
|
|
54
|
+
src/domain/tests_generation/test_scaffold_builder.py,sha256=NkTbYswWiuWikLfz6nRi34zU8e7HvzMzkbj3q2W__Ws,3800
|
|
55
|
+
src/fix/__init__.py,sha256=haZFtazOc4v9SmtqBtyPNUvYrEQXNaChTIZiOpZlNSc,937
|
|
56
|
+
src/fix/apply_fix.py,sha256=yMinMW50Fw62V-k7hRwN1lsikCJbz6xV5iX4IH0sdxY,17912
|
|
57
|
+
src/fix/fix_history.py,sha256=pHXHpt-SstitbLDmlPloDVKlH79FUD8hqKjVK5xKLMA,5626
|
|
58
|
+
src/fix/patch_applier.py,sha256=3tIXwp8yPwelngXvhmQhvCYVpEOPfvCUwrnG_B9Yf0c,1235
|
|
59
|
+
src/fix/patch_extractor.py,sha256=gEiwSvxGNHM74TnubTN22NBpbXDwvNpB2vMqgpF7hQQ,2514
|
|
60
|
+
src/fix/patch_provenance.py,sha256=sF934ntcZjJCTzzhtuR2Pi-vMUdXFElKcuw5riwPnUk,3195
|
|
61
|
+
src/fix/patch_safety_classifier.py,sha256=StZvh_0Y-ZCdf_LwFoVAdfhpi0b7v1TOxTRoG86_63M,4121
|
|
62
|
+
src/fix/rollback_fix.py,sha256=rMBYewuvlhJOFdo-Ke_SPfu1O-yd6NJzEYiO5o-LcKk,2501
|
|
63
|
+
src/infrastructure/__init__.py,sha256=zTIcY3cscsdkXogSGXoJbH0mVGFbKYTRK32fg26wr9U,81
|
|
64
|
+
src/infrastructure/git/__init__.py,sha256=j9zQUXKNp5NcOzTK-qHGNh75YkufGlw5rgH1wgRknX4,87
|
|
65
|
+
src/infrastructure/git/patch_applier.py,sha256=RgbrpAp-6x401Eydz6yLMTOXKn5E9LQzV9ZB_n7E90M,7988
|
|
66
|
+
src/infrastructure/git/selective_stager.py,sha256=sMRPZmq2EWipcabCCETeMu_0in66BFSzQAqZ7h1y2hc,8038
|
|
67
|
+
src/infrastructure/linter/external/__init__.py,sha256=2OvWKwC2AAtuMZ7KwZuqywwt181WoRcUuyzSCtTQLtM,277
|
|
68
|
+
src/infrastructure/linter/external/bandit_bridge.py,sha256=8poP9g0Fts0F7EpervYqfGvJpadreBGCJBOvVUghFhI,4290
|
|
69
|
+
src/infrastructure/linter/external/base_bridge.py,sha256=n9StBko-Cu86u0EzCooS_OKNCDT2cGwxSTBubxvgt4U,3214
|
|
70
|
+
src/infrastructure/linter/external/gitleaks_bridge.py,sha256=OMpARs53bSJuhA429mbAbRxUyvbmJ7ovha9wFjd6JY4,5841
|
|
71
|
+
src/infrastructure/linter/external/semgrep_bridge.py,sha256=N-mKeiBF0uQz4XrH6bR-sCwesgoiiFfiwnZtM7ndlSc,4464
|
|
72
|
+
src/infrastructure/scm/__init__.py,sha256=kNiYljRCJRwi5WoPckn8NbZt6qbEQZFMwaBY3-kepRo,1825
|
|
73
|
+
src/infrastructure/scm/azure_devops_provider.py,sha256=ezRS_O2450mfYEfqFOD4UBSVFhT3aJdx3DEVkZcJjac,14717
|
|
74
|
+
src/infrastructure/scm/base.py,sha256=L-7NYG2hqIWKzKCsQ5NurtgJJvvYvjTVOs0NJ2Aqx_M,9536
|
|
75
|
+
src/infrastructure/scm/bitbucket_provider.py,sha256=jv62MlOd0Iiu3VYAHUffJO2D95TE4k2lmukQL93bOd8,12105
|
|
76
|
+
src/infrastructure/scm/factory.py,sha256=Qeearq8eWnF_4boI68rVBkoGEiS-xRGD4V-O_FF0204,3856
|
|
77
|
+
src/infrastructure/scm/github_provider.py,sha256=9RXRAocYEVWS6NR2PsOg7wtqiF8989x1BxAqgBSoLOI,16438
|
|
78
|
+
src/infrastructure/scm/gitlab_provider.py,sha256=mznhtBhXtc4zRut_Jb0pM57RIXViNYYiKiM7VKDFhl8,13881
|
|
79
|
+
src/infrastructure/scm/web_links.py,sha256=9DHFCwYc6TVzO3INe0rvEicgoD2-kxQSrgq3vt_p5Ow,3621
|
|
80
|
+
src/review/__init__.py,sha256=XVyQZFf6UAqIKi8Gb1pQ4dKjcrC50soUvNuKu1DBspc,687
|
|
81
|
+
src/review/diff_normalizer.py,sha256=PRXQ7b81EBH1nx_8_Tm9G7Kjdax_6hIVtFgEixicT0E,3551
|
|
82
|
+
src/review/diff_source.py,sha256=O8bIlff0Zr7wPMzCKAVk_LF2pMDr3sc9KAuyA3OvHG0,2664
|
|
83
|
+
src/review/remote_pr.py,sha256=maPCQO5p7u9-_PA0FEBNgimfA6na2BdQGBPmqNj1_0c,11817
|
|
84
|
+
src/review/render.py,sha256=Hnkz0PvtYIaaikTS1inTzrneAJL4Xqjd-F8Lr6Yh6mQ,2691
|
|
85
|
+
src/split/__init__.py,sha256=JuXxPxoG1NrWtLAQARSBh59tsNXvrUoS-T-vHcHeDPM,912
|
|
86
|
+
src/split/apply_split_plan.py,sha256=-ESEEl973Ai9czEOVfS697Dy196QX8EKbVrNZlgrQhY,10678
|
|
87
|
+
src/split/generate_split_plan.py,sha256=I370yLCxqeZRemIIcLswYVI07fet5JJMRxxht21HlOA,8786
|
|
88
|
+
src/split/hunk_grouper.py,sha256=rNH64CJQ80ue6YjTTaVxwtQug1tY6yCjEEScjj8wL2o,14249
|
|
89
|
+
src/split/hunk_parser.py,sha256=Lvmpd8qPYT2a0pw-XCSvUJqm_b9P5tAw9Kj_DlbBWTk,10010
|
|
90
|
+
src/split/split_plan.py,sha256=rim_iUlyQO8EpqXjN-YzRFv5mbo2pwZlXdrkuZ7PGxM,6419
|
|
91
|
+
src/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
92
|
+
src/ui/chat_app.py,sha256=O21d4-_6u87pHeFKtBJbTpQxkBQWmaNa617CO0f4-0Q,26777
|
|
93
|
+
src/ui/config_app.py,sha256=xgppZbEBFrFijgLGo1b1PN1vTGCg8xpI8gpMYBv2KzU,73417
|
|
94
|
+
src/ui/help_screen.py,sha256=EvbP8kFZ6Sdehi7bJ6Wo_YcTTk5MTkgneeidWwefYrA,1890
|
|
95
|
+
src/ui/issue_app.py,sha256=eINMmamloeopbtwyS1rCPwosaHeZW5oLebnEV4TfamY,9281
|
|
96
|
+
src/ui/linter_app.py,sha256=Y5vwU7D5PQIR_HaY6t40jZy7Guc8z2w-auyosCQqoro,1566
|
|
97
|
+
src/ui/metrics_app.py,sha256=a3jsB_kjMusSL57A6LpJyb9LFN7JJWlOdUNClklwq-c,16016
|
|
98
|
+
src/ui/pr_publish_app.py,sha256=cZ9xr1TSZF6fjvQPlAdSjzSNykufHF1l7hsmo3Ib2j0,67311
|
|
99
|
+
src/ui/pr_publish_help.py,sha256=fBiUtD97ngxbSfAvPVGGXU19r5kbALCKyi4QeKqyQ2c,1891
|
|
100
|
+
src/ui/demo/__init__.py,sha256=PGXenIwpS1lVDE164CZ55q8_65XtJq5H300NIpnqOh0,58
|
|
101
|
+
src/ui/demo/demo_app.py,sha256=EDeuqxh_v9KrPhib49TyirmFlHZN7I5NQkg9AjDGhcs,3074
|
|
102
|
+
src/ui/demo/demo_help_screen.py,sha256=m8SuvhzGmkMsahUBA6Aq7ull4W65nS0KOX4hLx-GGps,2056
|
|
103
|
+
src/ui/demo/demo_screens.py,sha256=3Ntp-A7OB43yEtniRqV1UKWc-p5lHRyxeadpe8bxJpM,1937
|
|
104
|
+
gitpr_cli-1.3.0.dist-info/METADATA,sha256=uqEGURIaBMXUiX9a0FFUWuqBTNT_argqEcxvTTUAj_M,42671
|
|
105
|
+
gitpr_cli-1.3.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
106
|
+
gitpr_cli-1.3.0.dist-info/entry_points.txt,sha256=TstGnMUzdOI5qNmpM97EPDBPuMqOZxoo6EoLxHSOMSQ,71
|
|
107
|
+
gitpr_cli-1.3.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
108
|
+
gitpr_cli-1.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
from src.application.use_cases.generate_test_file import generate_test_file, validate_test_syntax
|
|
2
|
+
from src.application.use_cases.generate_pr_explanation import generate_pr_explanation
|
|
3
|
+
|
|
4
|
+
__all__ = [
|
|
5
|
+
"generate_test_file",
|
|
6
|
+
"validate_test_syntax",
|
|
7
|
+
"generate_pr_explanation",
|
|
8
|
+
]
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from src.ai_providers import call_ai_model
|
|
3
|
+
from src.config import get_ai_provider, get_api_key, get_api_model
|
|
4
|
+
from src.core import get_skill_context
|
|
5
|
+
from src.domain.pr.explain_section_builder import PrExplanation, parse_explain_payload
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def generate_pr_explanation(
|
|
9
|
+
diff_content: str,
|
|
10
|
+
ai_provider: str | None = None,
|
|
11
|
+
skill_context: str | None = None,
|
|
12
|
+
quiet: bool = False,
|
|
13
|
+
) -> PrExplanation:
|
|
14
|
+
"""Generates a structured reviewer explanation for the current PR diff."""
|
|
15
|
+
if not diff_content or not diff_content.strip():
|
|
16
|
+
return PrExplanation(
|
|
17
|
+
what_changes="",
|
|
18
|
+
why_it_changes="",
|
|
19
|
+
reviewer_focus_points=[],
|
|
20
|
+
regression_risk="",
|
|
21
|
+
has_sufficient_evidence=False,
|
|
22
|
+
markdown="",
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
provider = ai_provider or get_ai_provider()
|
|
26
|
+
api_key = get_api_key(provider)
|
|
27
|
+
if not api_key:
|
|
28
|
+
return PrExplanation(
|
|
29
|
+
what_changes="No API key configured.",
|
|
30
|
+
why_it_changes="No API key configured.",
|
|
31
|
+
reviewer_focus_points=[],
|
|
32
|
+
regression_risk="No API key configured.",
|
|
33
|
+
has_sufficient_evidence=False,
|
|
34
|
+
markdown="⚠️ No API key configured for AI provider.",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
api_model = get_api_model(provider, task_complexity="advanced")
|
|
38
|
+
|
|
39
|
+
# Load skill context
|
|
40
|
+
skill_instruction = skill_context or get_skill_context("explain", quiet=True)
|
|
41
|
+
if not skill_instruction:
|
|
42
|
+
skill_instruction = (
|
|
43
|
+
"You are an expert Code Reviewer and Software Architect. "
|
|
44
|
+
"Explain this pull request diff from the reviewer's perspective: What changes, Why it changes, Where to focus review, and Regression risk. "
|
|
45
|
+
"You MUST ONLY return a valid JSON object matching this schema:\n"
|
|
46
|
+
'{"what_changes": "2-4 plain language sentences", "why_it_changes": "motivation or [FILL: question]", '
|
|
47
|
+
'"reviewer_focus_points": [{"description": "...", "file_path": "...", "related_line": 0}], '
|
|
48
|
+
'"regression_risk": "short assessment of potential regressions"}'
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
user_prompt = (
|
|
52
|
+
"Generate a reviewer guide for the following diff changes:\n\n"
|
|
53
|
+
f"Diff:\n```diff\n{diff_content}\n```\n\n"
|
|
54
|
+
"Instructions:\n"
|
|
55
|
+
"1. In 'what_changes', provide 2-4 plain sentences describing the functional changes without excessive jargon.\n"
|
|
56
|
+
"2. In 'why_it_changes', explain the inferred motivation. If motivation cannot be determined with certainty, use '[FILL: What is the primary business motivation for this change?]'.\n"
|
|
57
|
+
"3. In 'reviewer_focus_points', list 2-5 concrete points requiring specific review attention.\n"
|
|
58
|
+
"4. In 'regression_risk', state any objective risks (critical files, missing tests, auth changes).\n"
|
|
59
|
+
"5. Return strictly JSON with 'what_changes', 'why_it_changes', 'reviewer_focus_points', 'regression_risk'."
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
ai_raw = call_ai_model(
|
|
63
|
+
provider=provider,
|
|
64
|
+
api_key=api_key,
|
|
65
|
+
api_model=api_model,
|
|
66
|
+
prompt=user_prompt,
|
|
67
|
+
system_instruction=skill_instruction,
|
|
68
|
+
quiet=quiet,
|
|
69
|
+
action="explain",
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
return parse_explain_payload(ai_raw or "")
|
|
73
|
+
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
import subprocess
|
|
5
|
+
import tempfile
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
from typing import Any
|
|
8
|
+
|
|
9
|
+
from src.ai_providers import call_ai_model
|
|
10
|
+
from src.config import get_ai_provider, get_api_key, get_api_model
|
|
11
|
+
from src.core import get_skill_context, resolve_skill_path
|
|
12
|
+
from src.domain.tests_generation.framework_detector import detect_test_framework
|
|
13
|
+
from src.domain.tests_generation.test_content_types import (
|
|
14
|
+
GeneratedTest,
|
|
15
|
+
TestFramework,
|
|
16
|
+
TestGenerationTarget,
|
|
17
|
+
TestScaffold,
|
|
18
|
+
)
|
|
19
|
+
from src.domain.tests_generation.test_scaffold_builder import build_test_scaffold
|
|
20
|
+
from src.i18n import __
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def validate_test_syntax(content: str, framework: TestFramework) -> tuple[bool, str | None]:
|
|
24
|
+
"""Validates the syntax of the generated test file using local tools if present.
|
|
25
|
+
|
|
26
|
+
Returns:
|
|
27
|
+
tuple[bool, str | None]: (is_valid, error_message)
|
|
28
|
+
"""
|
|
29
|
+
if not content or not content.strip():
|
|
30
|
+
return False, "Generated test content is empty."
|
|
31
|
+
|
|
32
|
+
if framework in (TestFramework.PEST, TestFramework.PHPUNIT):
|
|
33
|
+
php_bin = shutil.which("php")
|
|
34
|
+
if php_bin:
|
|
35
|
+
try:
|
|
36
|
+
with tempfile.NamedTemporaryFile("w", suffix=".php", delete=False, encoding="utf-8") as tf:
|
|
37
|
+
tf.write(content)
|
|
38
|
+
temp_name = tf.name
|
|
39
|
+
|
|
40
|
+
try:
|
|
41
|
+
res = subprocess.run(
|
|
42
|
+
[php_bin, "-l", temp_name],
|
|
43
|
+
capture_output=True,
|
|
44
|
+
text=True,
|
|
45
|
+
encoding="utf-8",
|
|
46
|
+
errors="replace",
|
|
47
|
+
)
|
|
48
|
+
if res.returncode != 0:
|
|
49
|
+
return False, res.stderr.strip() or res.stdout.strip()
|
|
50
|
+
finally:
|
|
51
|
+
if os.path.exists(temp_name):
|
|
52
|
+
os.unlink(temp_name)
|
|
53
|
+
except Exception as e:
|
|
54
|
+
return True, f"Syntax validation skipped: {e}"
|
|
55
|
+
|
|
56
|
+
elif framework in (TestFramework.JEST, TestFramework.VITEST):
|
|
57
|
+
node_bin = shutil.which("node")
|
|
58
|
+
if node_bin:
|
|
59
|
+
try:
|
|
60
|
+
with tempfile.NamedTemporaryFile("w", suffix=".js", delete=False, encoding="utf-8") as tf:
|
|
61
|
+
tf.write(content)
|
|
62
|
+
temp_name = tf.name
|
|
63
|
+
try:
|
|
64
|
+
res = subprocess.run(
|
|
65
|
+
[node_bin, "--check", temp_name],
|
|
66
|
+
capture_output=True,
|
|
67
|
+
text=True,
|
|
68
|
+
encoding="utf-8",
|
|
69
|
+
errors="replace",
|
|
70
|
+
)
|
|
71
|
+
if res.returncode != 0:
|
|
72
|
+
return False, res.stderr.strip() or res.stdout.strip()
|
|
73
|
+
finally:
|
|
74
|
+
if os.path.exists(temp_name):
|
|
75
|
+
os.unlink(temp_name)
|
|
76
|
+
except Exception as e:
|
|
77
|
+
return True, f"Syntax validation skipped: {e}"
|
|
78
|
+
|
|
79
|
+
elif framework == TestFramework.PYTEST:
|
|
80
|
+
python_bin = shutil.which("python") or shutil.which("python3")
|
|
81
|
+
if python_bin:
|
|
82
|
+
try:
|
|
83
|
+
with tempfile.NamedTemporaryFile("w", suffix=".py", delete=False, encoding="utf-8") as tf:
|
|
84
|
+
tf.write(content)
|
|
85
|
+
temp_name = tf.name
|
|
86
|
+
try:
|
|
87
|
+
res = subprocess.run(
|
|
88
|
+
[python_bin, "-m", "py_compile", temp_name],
|
|
89
|
+
capture_output=True,
|
|
90
|
+
text=True,
|
|
91
|
+
encoding="utf-8",
|
|
92
|
+
errors="replace",
|
|
93
|
+
)
|
|
94
|
+
if res.returncode != 0:
|
|
95
|
+
return False, res.stderr.strip() or res.stdout.strip()
|
|
96
|
+
finally:
|
|
97
|
+
if os.path.exists(temp_name):
|
|
98
|
+
os.unlink(temp_name)
|
|
99
|
+
except Exception as e:
|
|
100
|
+
return True, f"Syntax validation skipped: {e}"
|
|
101
|
+
|
|
102
|
+
return True, None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def generate_test_file(
|
|
106
|
+
target: TestGenerationTarget,
|
|
107
|
+
repo_path: str = ".",
|
|
108
|
+
ai_provider: str | None = None,
|
|
109
|
+
framework_override: TestFramework | str | None = None,
|
|
110
|
+
apply: bool = False,
|
|
111
|
+
quiet: bool = False,
|
|
112
|
+
) -> GeneratedTest:
|
|
113
|
+
"""Orchestrates test generation from a target diff/file/finding."""
|
|
114
|
+
warnings: list[str] = []
|
|
115
|
+
|
|
116
|
+
# 1. Detect Framework
|
|
117
|
+
detected_fw, fw_warnings = detect_test_framework(repo_path, framework_override)
|
|
118
|
+
warnings.extend(fw_warnings)
|
|
119
|
+
|
|
120
|
+
# 2. Build Scaffold
|
|
121
|
+
scaffold = build_test_scaffold(target.file_path, detected_fw, repo_path)
|
|
122
|
+
|
|
123
|
+
# 3. Setup AI provider & system instruction
|
|
124
|
+
provider = ai_provider or get_ai_provider()
|
|
125
|
+
api_key = get_api_key(provider)
|
|
126
|
+
if not api_key:
|
|
127
|
+
warnings.append(f"No API key configured for {provider}. Generate one with --install.")
|
|
128
|
+
return GeneratedTest(
|
|
129
|
+
scaffold=scaffold,
|
|
130
|
+
content="",
|
|
131
|
+
covered_scenarios=[],
|
|
132
|
+
warnings=warnings,
|
|
133
|
+
)
|
|
134
|
+
|
|
135
|
+
api_model = get_api_model(provider, task_complexity="advanced")
|
|
136
|
+
|
|
137
|
+
# Load skill context or use built-in instruction
|
|
138
|
+
skill_content = get_skill_context("tests", quiet=True)
|
|
139
|
+
if not skill_content:
|
|
140
|
+
skill_content = (
|
|
141
|
+
"You are an expert QA and Test Automation Engineer. "
|
|
142
|
+
"Generate a complete, executable, clean test file adhering strictly to the requested test framework and conventions. "
|
|
143
|
+
"You MUST ONLY return a valid JSON object matching this schema:\n"
|
|
144
|
+
'{"content": "complete test file source code as a string", "covered_scenarios": ["scenario 1", "scenario 2"], "warnings": []}'
|
|
145
|
+
)
|
|
146
|
+
|
|
147
|
+
# 4. Construct Prompt
|
|
148
|
+
target_info = f"Target Type: {target.source_type}\n"
|
|
149
|
+
if target.file_path:
|
|
150
|
+
target_info += f"Target File: {target.file_path}\n"
|
|
151
|
+
if target.finding_id:
|
|
152
|
+
target_info += f"Target Finding ID: {target.finding_id}\n"
|
|
153
|
+
|
|
154
|
+
user_prompt = (
|
|
155
|
+
f"Generate a comprehensive test file for the following changes:\n\n"
|
|
156
|
+
f"Framework: {detected_fw.value}\n"
|
|
157
|
+
f"Target Test Path: {scaffold.target_test_path}\n"
|
|
158
|
+
f"{target_info}\n"
|
|
159
|
+
f"Diff Context:\n```diff\n{target.diff_content}\n```\n\n"
|
|
160
|
+
"Requirements:\n"
|
|
161
|
+
"1. Write complete, ready-to-run test code (imports, test cases, assertions).\n"
|
|
162
|
+
"2. Include Happy Path scenarios covering main changes.\n"
|
|
163
|
+
"3. Include Edge Cases and Regression scenarios where applicable.\n"
|
|
164
|
+
"4. Follow the idiomatic conventions of the detected framework.\n"
|
|
165
|
+
"5. Return strictly JSON with 'content', 'covered_scenarios', and 'warnings'."
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# 5. Call AI
|
|
169
|
+
ai_raw = call_ai_model(
|
|
170
|
+
provider=provider,
|
|
171
|
+
api_key=api_key,
|
|
172
|
+
api_model=api_model,
|
|
173
|
+
prompt=user_prompt,
|
|
174
|
+
system_instruction=skill_content,
|
|
175
|
+
quiet=quiet,
|
|
176
|
+
action="tests",
|
|
177
|
+
)
|
|
178
|
+
|
|
179
|
+
if not ai_raw:
|
|
180
|
+
warnings.append("AI model did not return a response.")
|
|
181
|
+
return GeneratedTest(
|
|
182
|
+
scaffold=scaffold,
|
|
183
|
+
content="",
|
|
184
|
+
covered_scenarios=[],
|
|
185
|
+
warnings=warnings,
|
|
186
|
+
)
|
|
187
|
+
|
|
188
|
+
# 6. Parse AI JSON
|
|
189
|
+
test_code = ""
|
|
190
|
+
covered_scenarios = []
|
|
191
|
+
try:
|
|
192
|
+
data = json.loads(ai_raw)
|
|
193
|
+
test_code = data.get("content", "")
|
|
194
|
+
covered_scenarios = data.get("covered_scenarios", [])
|
|
195
|
+
if "warnings" in data and isinstance(data["warnings"], list):
|
|
196
|
+
warnings.extend(data["warnings"])
|
|
197
|
+
except Exception:
|
|
198
|
+
# If response wasn't strict JSON, use clean raw text
|
|
199
|
+
test_code = ai_raw.strip()
|
|
200
|
+
if test_code.startswith("```"):
|
|
201
|
+
lines = test_code.split("\n")
|
|
202
|
+
if lines[0].startswith("```"):
|
|
203
|
+
lines = lines[1:]
|
|
204
|
+
if lines and lines[-1].startswith("```"):
|
|
205
|
+
lines = lines[:-1]
|
|
206
|
+
test_code = "\n".join(lines).strip()
|
|
207
|
+
covered_scenarios.append("Generated from raw model output")
|
|
208
|
+
|
|
209
|
+
# 7. Validate Syntax
|
|
210
|
+
is_valid, syn_err = validate_test_syntax(test_code, detected_fw)
|
|
211
|
+
if not is_valid:
|
|
212
|
+
warnings.append(f"Syntax validation failed ({syn_err}). Marked as EXPERIMENTAL / low confidence.")
|
|
213
|
+
|
|
214
|
+
# 8. Apply if requested
|
|
215
|
+
if apply and test_code:
|
|
216
|
+
full_dest_path = Path(repo_path) / scaffold.target_test_path
|
|
217
|
+
full_dest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
218
|
+
with open(full_dest_path, "w", encoding="utf-8", errors="replace") as f:
|
|
219
|
+
f.write(test_code)
|
|
220
|
+
|
|
221
|
+
return GeneratedTest(
|
|
222
|
+
scaffold=scaffold,
|
|
223
|
+
content=test_code,
|
|
224
|
+
covered_scenarios=covered_scenarios,
|
|
225
|
+
warnings=warnings,
|
|
226
|
+
)
|
|
227
|
+
|
src/branding/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Public badges: the automatic one on published PRs and the README snippet."""
|