gitpr-cli 1.4.1__py3-none-any.whl → 1.4.3__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gitpr-cli
3
- Version: 1.4.1
3
+ Version: 1.4.3
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
@@ -54,7 +54,7 @@ gitpr demo
54
54
  ```
55
55
 
56
56
  > **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.
57
- > 📖 **Full Documentation:** [https://gitpr.natanfiuza.dev.br/docs/demo?lang=en_us](https://gitpr.natanfiuza.dev.br/docs/demo?lang=en_us)
57
+ > 📖 **Full Documentation:** [https://gitpr.natanfiuza.dev.br/docs/demo](https://gitpr.natanfiuza.dev.br/docs/demo)
58
58
 
59
59
  ### **3. Initializing in a Repository**
60
60
 
@@ -65,7 +65,7 @@ gitpr --install
65
65
  ```
66
66
 
67
67
  > **Guided Setup:** Guided configuration that downloads skill templates, installs Git Hooks, configures MCP for your editors, and verifies the API key for your AI provider.
68
- > 📖 **Full Documentation:** [https://gitpr.natanfiuza.dev.br/docs/install-wizard?lang=en_us](https://gitpr.natanfiuza.dev.br/docs/install-wizard?lang=en_us)
68
+ > 📖 **Full Documentation:** [https://gitpr.natanfiuza.dev.br/docs/install-wizard](https://gitpr.natanfiuza.dev.br/docs/install-wizard)
69
69
 
70
70
  ## **🛠️ Technologies and Libraries Used**
71
71
 
@@ -123,7 +123,7 @@ Pytest will automatically detect files inside the `tests/` folder and display a
123
123
  ```
124
124
  *Note: Your configuration will be securely saved in the `~/.gitpr/.env` file.*
125
125
 
126
- *Note (updates): GitPR is distributed exclusively through PyPI. On every run it checks whether a newer version has been published, and when one exists it **blocks the execution** and asks you to run `pip install --upgrade gitpr-cli`. See [auto-update.md](docs/auto-update.md).*
126
+ *Note (updates): GitPR is distributed exclusively through PyPI. On every run it checks whether a newer version has been published, and when one exists it **blocks the execution** and asks you to run `pip install --upgrade gitpr-cli`. See [gitpr.natanfiuza.dev.br/docs/auto-update](https://gitpr.natanfiuza.dev.br/docs/auto-update).*
127
127
 
128
128
  > **🔒 Security Note:** GitPR CLI uses symmetric encryption (Fernet). Your API key is stored as a hash in the `.env` file, and the master key for decryption is automatically generated in `~/.gitpr/secret.key`. **Never share your secret.key file.**
129
129
 
@@ -157,20 +157,20 @@ The tool will sync with the remote (`git fetch`), compare your changes with the
157
157
  ### **Advanced Options and Commands**
158
158
  You can pass the following *flags* for specific actions:
159
159
 
160
- * `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)
161
- * `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)
162
- * `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)
163
- * `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)
164
- * `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)
165
- * `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)
166
- * `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.
167
- * `gitpr mentor [<finding_id>]`: **Junior Mentor Pedagogical Explanations.** Breaks down code review findings with educational context ("why this matters", real-world analogy, guided questions, and key takeaway) to foster growth and mentor developers. Works standalone over the latest review findings or with `--finding <id>`. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/mentor-mode.md)
168
- * `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)
169
- * `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)
170
- * `gitpr risk`: **Local Risk Scoring for Diffs.** Computes a deterministic, explainable risk score (0.0 to 100.0) and badge (`LOW 🟢`, `MEDIUM 🟡`, `HIGH 🟠`, `CRITICAL 🔴`) using local repository signals (critical paths, missing tests, migrations, bug history, and static findings). Use `--file <path>`, `--format json`, `--base <ref>`, or `--no-history`. Automatically included in code reviews (`GITPR_RISK_INCLUDE_IN_REVIEW=true`). 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/risk-scoring.md)
171
- * `gitpr policy`: **Shared quality policy for the repository.** Adopts a versioned YAML **Policy Pack** — review skills, linter rules, severity overrides, critical paths, risk weights and PR/commit conventions — and records the choice in `.gitpr/policy.lock.yml`, so the review, the linter and the risk score all run under the same policy, with a checksum per pack. `list`, `validate`, `show`, `use`, `init`, `install`, `off`. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/policy-packs.md)
172
- * `gitpr baseline`: **Baseline and auditable suppressions for legacy adoption.** Records the findings a repository already has in `.gitpr/baseline.json`, so a run classifies every finding as `new`, `existing`, `resolved`, `ignored` or `accepted_debt` and **only `new` blocks**. Every suppression carries a reason, and accepted debt carries an owner and an optional deadline. `create`, `show`, `validate`, `update`, `suppress`, `unsuppress`. 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/baseline-suppressions.md)
173
- * `gitpr check`: **Unified CI check command.** Consolidates static linter, secret scanning, external bridges (Semgrep, Gitleaks, Bandit), optional AI code review, risk scoring, and baseline into a single CI pipeline command. Emits machine-readable artifacts in **SARIF 2.1.0**, **JSON**, or **JUnit XML** with deterministic exit codes (`0`, `1`, `2`). 📖 [Full docs](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/check-ci.md)
160
+ * `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://gitpr.natanfiuza.dev.br/docs/demo)
161
+ * `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://gitpr.natanfiuza.dev.br/docs/badge)
162
+ * `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://gitpr.natanfiuza.dev.br/docs/config-tui)
163
+ * `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://gitpr.natanfiuza.dev.br/docs/release-notes)
164
+ * `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://gitpr.natanfiuza.dev.br/docs/fix-command)
165
+ * `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://gitpr.natanfiuza.dev.br/docs/split-command)
166
+ * `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. 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/tests-generate)
167
+ * `gitpr mentor [<finding_id>]`: **Junior Mentor Pedagogical Explanations.** Breaks down code review findings with educational context ("why this matters", real-world analogy, guided questions, and key takeaway) to foster growth and mentor developers. Works standalone over the latest review findings or with `--finding <id>`. 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/mentor-mode)
168
+ * `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://gitpr.natanfiuza.dev.br/docs/explain-pr)
169
+ * `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://gitpr.natanfiuza.dev.br/docs/review-pr)
170
+ * `gitpr risk`: **Local Risk Scoring for Diffs.** Computes a deterministic, explainable risk score (0.0 to 100.0) and badge (`LOW 🟢`, `MEDIUM 🟡`, `HIGH 🟠`, `CRITICAL 🔴`) using local repository signals (critical paths, missing tests, migrations, bug history, and static findings). Use `--file <path>`, `--format json`, `--base <ref>`, or `--no-history`. Automatically included in code reviews (`GITPR_RISK_INCLUDE_IN_REVIEW=true`). 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/risk-scoring)
171
+ * `gitpr policy`: **Shared quality policy for the repository.** Adopts a versioned YAML **Policy Pack** — review skills, linter rules, severity overrides, critical paths, risk weights and PR/commit conventions — and records the choice in `.gitpr/policy.lock.yml`, so the review, the linter and the risk score all run under the same policy, with a checksum per pack. `list`, `validate`, `show`, `use`, `init`, `install`, `off`. 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/policy-packs)
172
+ * `gitpr baseline`: **Baseline and auditable suppressions for legacy adoption.** Records the findings a repository already has in `.gitpr/baseline.json`, so a run classifies every finding as `new`, `existing`, `resolved`, `ignored` or `accepted_debt` and **only `new` blocks**. Every suppression carries a reason, and accepted debt carries an owner and an optional deadline. `create`, `show`, `validate`, `update`, `suppress`, `unsuppress`. 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/baseline-suppressions)
173
+ * `gitpr check`: **Unified CI check command.** Consolidates static linter, secret scanning, external bridges (Semgrep, Gitleaks, Bandit), optional AI code review, risk scoring, and baseline into a single CI pipeline command. Emits machine-readable artifacts in **SARIF 2.1.0**, **JSON**, or **JUnit XML** with deterministic exit codes (`0`, `1`, `2`). 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/check-ci)
174
174
  * `-c` or `--commit`: Runs a local `git diff` and displays **only the suggested commit message**.
175
175
  * `-r` or `--review`: Performs a detailed **Code Review** of local changes. Use with `--mentor` to append pedagogical mentoring explanations for each finding.
176
176
  * `-f` or `--fullreview`: Performs a **Full Code Review** analyzing all changes since the remote branch. Use with `--mentor` to append pedagogical mentoring explanations.
@@ -179,14 +179,14 @@ You can pass the following *flags* for specific actions:
179
179
  * `--lang <code>`: Forces the interface language for this execution (e.g.: `en_us`, `pt_br`). Overrides `GITPR_LANG` in `.env` without persisting the change.
180
180
  * `-ch` or `--chat`: Opens the **Interactive Pair Programming Chat** — a TUI terminal where the AI sees your current diff and maintains a contextual conversation. Features memory per branch, slash commands (`/explain`, `/tests`, `/optimize`, `/clear`), auto-patching (F5), diff refresh (F2), and session export (F6).
181
181
  * `-l` or `--linter`: Runs **only the local static linter** (no AI calls). Ideal for use in CI/CD pipelines to block non-compliant code.
182
- * `--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)
183
- * `--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)
184
- * `--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)
185
- * `--explain`: Includes the Reviewer Guide section ("Explain my PR") in the Pull Request description.
186
- * `--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)
187
- * `--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)
182
+ * `--status`: Lists uncommitted file changes categorized as **new**, **modified**, and **deleted** — fast, no AI, no network. 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/git-status)
183
+ * `--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://gitpr.natanfiuza.dev.br/docs/git-status)
184
+ * `--no-suggest-reviewers`: Disables the suggested reviewers computation in the interactive PR publisher (default ON). 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/suggested-reviewers)
185
+ * `--explain`: Includes the Reviewer Guide section ("Explain my PR") in the Pull Request description. 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/explain-pr)
186
+ * `--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://gitpr.natanfiuza.dev.br/docs/scm-multiforge)
187
+ * `--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://gitpr.natanfiuza.dev.br/docs/linter-regras-customizadas)
188
188
  * `--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 16 annotated tools, 19 resources, and 7 pre-built prompts directly inside your IDE. Also available as the standalone `gitpr-mcp` command.
189
- * `--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)
189
+ * `--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://gitpr.natanfiuza.dev.br/docs/plugins-system)
190
190
  * `--install`: **Interactive Setup Wizard.** Runs a guided 4-step setup: downloads skill templates, installs Git hooks, configures MCP for detected editors, and checks/requests your AI provider API key. Each step asks for confirmation before proceeding.
191
191
  * `-ih` or `--installhooks`: Automatically installs **local Git Hooks** (`pre-commit` and `prepare-commit-msg`) in your repository.
192
192
  * `-s` or `--skill`: Creates the AI context template files (`.gitpr.commit.md`, `.gitpr.pr.md`, `.gitpr.review.md`, `.gitpr.filereview.md`, `.gitpr.issue.md`, `.gitpr.blame.md`) and the Linter (`.gitpr.linter.yml`) inside `.gitpr/skill/`.
@@ -194,7 +194,7 @@ You can pass the following *flags* for specific actions:
194
194
  * **New Code Issue (`gitpr -is`):** Reads the current `git diff`. **Why use:** Ideal for quickly documenting the task you just finished programming, before committing.
195
195
  * **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.
196
196
  * **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.
197
- * **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)
197
+ * **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://gitpr.natanfiuza.dev.br/docs/pull-request-publication)
198
198
  * `-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.
199
199
  * `-u` or `--update`: Checks the latest version of GitPR on PyPI and shows how to update it (Auto-Updater).
200
200
 
@@ -206,7 +206,7 @@ You can pass the following *flags* for specific actions:
206
206
 
207
207
  When your diff is too large for a single AI call (over ~90k estimated tokens), GitPR automatically splits it into batches by file, asks the AI for a technical summary of each part (Map), and unifies everything into the final commit message, review, or PR description (Reduce). No flags needed — it activates on demand and shows the progress in the console.
208
208
 
209
- 📚 Full documentation: [docs/map-reduce-diff.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/map-reduce-diff.md)
209
+ 📚 Full documentation: [gitpr.natanfiuza.dev.br/docs/map-reduce-diff](https://gitpr.natanfiuza.dev.br/docs/map-reduce-diff)
210
210
 
211
211
  ## 🛡️ Local Linter (Static Analysis & Security)
212
212
 
@@ -281,7 +281,7 @@ gitpr risk --format json # Output JSON for CI/CD pipelines
281
281
  gitpr risk --base main # Evaluate against specific git base ref
282
282
  ```
283
283
 
284
- 📖 **Full documentation:** [docs/risk-scoring.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/risk-scoring.md)
284
+ 📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/risk-scoring](https://gitpr.natanfiuza.dev.br/docs/risk-scoring)
285
285
 
286
286
  ## 🧾 Adopting GitPR in a Legacy Repository (gitpr baseline)
287
287
 
@@ -309,7 +309,7 @@ Four variables in `~/.gitpr/.env`, also editable through `gitpr config` in the s
309
309
 
310
310
  Only a `new` error-level finding exits non-zero, and only `new` findings carry risk points — everything else reaches the risk report as zero-point evidence with its status. The baseline is never sent to the AI, and no entry stores code or a secret: the file is committed, so only a digest of the offending line is kept.
311
311
 
312
- 📖 **Full documentation:** [docs/baseline-suppressions.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/baseline-suppressions.md)
312
+ 📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/baseline-suppressions](https://gitpr.natanfiuza.dev.br/docs/baseline-suppressions)
313
313
 
314
314
  ## 🤝 Co-Author Signature
315
315
 
@@ -321,7 +321,7 @@ Co-Authored-By: Gitpr-cli <gitpr@natanfiuza.dev.br>
321
321
 
322
322
  The trailer is appended programmatically (never by the AI) in all flows: console suggestion (`gitpr -c`), the `prepare-commit-msg` hook, auto-commit (`--no-edit`), the PR publication TUI, and the MCP `generate_commit_message` tool. It is idempotent — never duplicated when the message already contains it — and is hidden from the TUI edit screen, injected only when the commit is executed.
323
323
 
324
- 📖 **Full documentation:** [docs/commit-message-ia.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/commit-message-ia.md)
324
+ 📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/commit-message-ia](https://gitpr.natanfiuza.dev.br/docs/commit-message-ia)
325
325
 
326
326
  ## 🏷️ Pull Request Badge
327
327
 
@@ -337,7 +337,7 @@ For your own README there is a static badge. `gitpr badge --readme` prints the l
337
337
 
338
338
  [![GitPR](https://img.shields.io/badge/GitPR-quality--checked-blue)](https://gitpr.natanfiuza.dev.br/)
339
339
 
340
- 📖 **Full documentation:** [docs/badge.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/badge.md)
340
+ 📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/badge](https://gitpr.natanfiuza.dev.br/docs/badge)
341
341
 
342
342
  ## 🧠 Multi-Model Architecture (AI-Agnostic)
343
343
 
@@ -373,7 +373,7 @@ GitPR automatically detects your system language and displays messages in your n
373
373
 
374
374
  To force a specific language, set `GITPR_LANG=pt_br` or `GITPR_LANG=en` in `~/.gitpr/.env`.
375
375
 
376
- > 📖 **Full developer guide:** [docs/i18n_explanation.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/i18n_explanation.md) — architecture, usage patterns, circular import precautions, and how to add new languages.
376
+ > 📖 **Full developer guide:** [gitpr.natanfiuza.dev.br/docs/i18n_explanation](https://gitpr.natanfiuza.dev.br/docs/i18n_explanation) — architecture, usage patterns, circular import precautions, and how to add new languages.
377
377
 
378
378
  ## 🔄 Hook Scripts Versioning & Auto-Sync
379
379
 
@@ -431,7 +431,7 @@ gitpr-mcp --install auto # Auto-detect and install for all found
431
431
  The installer creates the config directory if needed, merges with any existing
432
432
  config (never overwrites other servers), and is safe to run multiple times.
433
433
 
434
- > Manual setup is also supported — see [docs/mcp-integration.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/mcp-integration.md)
434
+ > Manual setup is also supported — see [gitpr.natanfiuza.dev.br/docs/mcp-integration](https://gitpr.natanfiuza.dev.br/docs/mcp-integration)
435
435
  > for the JSON config format for each editor.
436
436
 
437
437
  Once configured, use natural language in your editor's AI chat:
@@ -479,11 +479,11 @@ gitpr-mcp --tool analyze_blame --tool-args '{"file_path":"src/main.py","start_li
479
479
 
480
480
  This is useful for scripting, CI/CD pipelines, and one-off queries where you don't need a persistent MCP server. JSON output goes to stdout; all diagnostic messages go to stderr — safe for piping.
481
481
 
482
- 📖 **Full documentation:** [docs/mcp-integration.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/mcp-integration.md) — available in 5 languages (EN, PT-BR, PT-PT, ES, FR).
482
+ 📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/mcp-integration](https://gitpr.natanfiuza.dev.br/docs/mcp-integration) — available in 5 languages (EN, PT-BR, PT-PT, ES, FR).
483
483
 
484
- > 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics? Install the thin-client extension in [`extensions/vscode/`](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/editor-extension.md), powered by `gitpr-mcp`.
484
+ > 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics? Install the thin-client extension in [`extensions/vscode/`](https://gitpr.natanfiuza.dev.br/docs/editor-extension), powered by `gitpr-mcp`.
485
485
 
486
- > 💬 **MCP Prompts** — GitPR also exposes 7 pre-defined message templates (prompts) for common flows like "Review PR", "Generate Commit Message", and "Create Issue from Diff". See the [MCP Prompts guide](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/mcp-prompts.md) for the full list.
486
+ > 💬 **MCP Prompts** — GitPR also exposes 7 pre-defined message templates (prompts) for common flows like "Review PR", "Generate Commit Message", and "Create Issue from Diff". See the [MCP Prompts guide](https://gitpr.natanfiuza.dev.br/docs/mcp-prompts) for the full list.
487
487
 
488
488
  ## 🎯 Smart Excludes (Token Optimization)
489
489
 
@@ -517,7 +517,7 @@ GitPR automatically removes non-code files from your `git diff` before sending t
517
517
 
518
518
  Add framework-specific build artifacts, generated folders, or any pattern that only applies to this project. The file is safe to commit — your team gets the same exclusions.
519
519
 
520
- > 📖 **Full documentation:** [docs/smart-excludes.md](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/smart-excludes.md) — available in 5 languages (EN, PT-BR, PT-PT, FR, ES).
520
+ > 📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/smart-excludes](https://gitpr.natanfiuza.dev.br/docs/smart-excludes) — available in 5 languages (EN, PT-BR, PT-PT, FR, ES).
521
521
 
522
522
  ## 📁 Output Directory Structure
523
523
 
@@ -545,51 +545,51 @@ If you want to implement GitPR as an automated quality barrier in your team, che
545
545
 
546
546
  ### Chat & Interactive Features
547
547
 
548
- * [**🧠 Interactive Chat (Pair Programming)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/understanding_chat_functionality.md) — How to use the AI chat with memory, slash commands, auto-patch, and session export.
548
+ * [**🧠 Interactive Chat (Pair Programming)**](https://gitpr.natanfiuza.dev.br/docs/understanding_chat_functionality) — How to use the AI chat with memory, slash commands, auto-patch, and session export.
549
549
 
550
550
  ### DevOps & CI/CD
551
551
 
552
- * [**Local Git Hooks (Shift-Left)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/git-hooks-locais.md) — How to use `gitpr --installhooks` to create guardrails on the developer's machine and use AI to automatically write commit messages.
553
- * [**Hook Scripts Versioning & Auto-Sync**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/hooks-versioning.md) — How the automatic versioning and i18n-aware synchronization system keeps your Git hooks up to date.
554
- * [**Customizable Static Linter**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/linter-regras-customizadas.md) — How to create validation rules in `.gitpr.linter.yml`, bridge external linters (ESLint, PHPCS, Stylelint), and generate Markdown reports for CI/CD and pre-commit hooks.
555
- * [**CI/CD Integration (GitHub Actions)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/github-ci-linter.md) — How to run GitPR in the pipeline to block "Merge" of PRs with violations.
552
+ * [**Local Git Hooks (Shift-Left)**](https://gitpr.natanfiuza.dev.br/docs/git-hooks-locais) — How to use `gitpr --installhooks` to create guardrails on the developer's machine and use AI to automatically write commit messages.
553
+ * [**Hook Scripts Versioning & Auto-Sync**](https://gitpr.natanfiuza.dev.br/docs/hooks-versioning) — How the automatic versioning and i18n-aware synchronization system keeps your Git hooks up to date.
554
+ * [**Customizable Static Linter**](https://gitpr.natanfiuza.dev.br/docs/linter-regras-customizadas) — How to create validation rules in `.gitpr.linter.yml`, bridge external linters (ESLint, PHPCS, Stylelint), and generate Markdown reports for CI/CD and pre-commit hooks.
555
+ * [**CI/CD Integration (GitHub Actions)**](https://gitpr.natanfiuza.dev.br/docs/github-ci-linter) — How to run GitPR in the pipeline to block "Merge" of PRs with violations.
556
556
 
557
557
  ### Core Features
558
558
 
559
- * [**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.
560
- * [**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.
561
- * [**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.
562
- * [**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.
563
- * [**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`).
564
- * [**Remote Pull Request Review (gitpr review-pr)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/review-pr.md) — How the `gitpr review-pr` subcommand reviews a pull request that is already open on the forge, fetching its diff from the API without checking out its branch.
565
- * [**AI Commit Messages**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/commit-message-ia.md) — How to generate messages in the Conventional Commits standard and integrate with Git Hooks.
566
- * [**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.
567
- * [**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.
568
- * [**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.
569
- * [**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.
570
- * [**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.
571
- * [**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.
572
- * [**Local Risk Scoring (gitpr risk)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/risk-scoring.md) — How the `gitpr risk` command evaluates regression risk, decomposes signals (50/30/20 formula), and highlights high-risk areas in code reviews offline.
573
- * [**Policy Packs (gitpr policy)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/policy-packs.md) — How a repository adopts a versioned Policy Pack that carries skills, linter rules, severity overrides, critical paths and risk weights together, the precedence ladder that combines packs, the lockfile the team commits, and the three failures that abort instead of silently degrading.
574
- * [**Baseline & Auditable Suppressions (gitpr baseline)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/baseline-suppressions.md) — How a legacy repository records the findings it already has, how a run classifies them as `new`, `existing`, `resolved`, `ignored` or `accepted_debt`, why only `new` blocks, what the fingerprint hashes and what it deliberately leaves out, the four scopes of a suppression, the checksum that catches a hand edit, and the `baseline:` block a Policy Pack may carry.
559
+ * [**Guided Tour (gitpr demo)**](https://gitpr.natanfiuza.dev.br/docs/demo) — 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.
560
+ * [**Pull Request Badge**](https://gitpr.natanfiuza.dev.br/docs/badge) — 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.
561
+ * [**Pull Request (Default Mode)**](https://gitpr.natanfiuza.dev.br/docs/pr-descricao-padrao) — Complete flow for generating PR descriptions without flags.
562
+ * [**Pull Request Publisher TUI**](https://gitpr.natanfiuza.dev.br/docs/pull-request-publication) — How to review and publish Pull Requests directly to GitHub from the terminal.
563
+ * [**AI Code Review**](https://gitpr.natanfiuza.dev.br/docs/code-review-ia) — Guide to review modes (`--review`, `--fullreview`), file auditing (`--input`) and remote pull request review (`gitpr review-pr`).
564
+ * [**Remote Pull Request Review (gitpr review-pr)**](https://gitpr.natanfiuza.dev.br/docs/review-pr) — How the `gitpr review-pr` subcommand reviews a pull request that is already open on the forge, fetching its diff from the API without checking out its branch.
565
+ * [**AI Commit Messages**](https://gitpr.natanfiuza.dev.br/docs/commit-message-ia) — How to generate messages in the Conventional Commits standard and integrate with Git Hooks.
566
+ * [**Issue Generation and TUI Interface**](https://gitpr.natanfiuza.dev.br/docs/issue-tui-help) — How to use the terminal graphical interface (TUI) and the 3 context engines to manage structured Issues.
567
+ * [**Code Archaeologist (Git Blame)**](https://gitpr.natanfiuza.dev.br/docs/blame-arqueologo) — How to trace the origin of business rules with `git blame` and AI.
568
+ * [**Skills and Templates System**](https://gitpr.natanfiuza.dev.br/docs/skill-template) — How to customize AI behavior with `.gitpr.*.md` files.
569
+ * [**Release Notes & Changelog (gitpr release)**](https://gitpr.natanfiuza.dev.br/docs/release-notes) — 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.
570
+ * [**Fix Command (gitpr fix)**](https://gitpr.natanfiuza.dev.br/docs/fix-command) — 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.
571
+ * [**Split Command (gitpr split)**](https://gitpr.natanfiuza.dev.br/docs/split-command) — 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.
572
+ * [**Local Risk Scoring (gitpr risk)**](https://gitpr.natanfiuza.dev.br/docs/risk-scoring) — How the `gitpr risk` command evaluates regression risk, decomposes signals (50/30/20 formula), and highlights high-risk areas in code reviews offline.
573
+ * [**Policy Packs (gitpr policy)**](https://gitpr.natanfiuza.dev.br/docs/policy-packs) — How a repository adopts a versioned Policy Pack that carries skills, linter rules, severity overrides, critical paths and risk weights together, the precedence ladder that combines packs, the lockfile the team commits, and the three failures that abort instead of silently degrading.
574
+ * [**Baseline & Auditable Suppressions (gitpr baseline)**](https://gitpr.natanfiuza.dev.br/docs/baseline-suppressions) — How a legacy repository records the findings it already has, how a run classifies them as `new`, `existing`, `resolved`, `ignored` or `accepted_debt`, why only `new` blocks, what the fingerprint hashes and what it deliberately leaves out, the four scopes of a suppression, the checksum that catches a hand edit, and the `baseline:` block a Policy Pack may carry.
575
575
 
576
576
  ### Configuration & Infrastructure
577
577
 
578
- * [**Editor Extension (VS Code & Cursor)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/editor-extension.md) — Thin-client VSIX extension driving `gitpr-mcp` over stdio for SCM commit generation, Markdown code reviews, two-step PR publishing, and inline `Problems` diagnostics.
579
- * [**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`.
580
- * [**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.
581
- * [**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.
582
- * [**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.
583
- * [**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.
584
- * [**AI Providers**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/providers-ia.md) — Configuration and selection between Google Gemini, DeepSeek, and Ollama.
585
- * [**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.
586
- * [**Architecture**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/ARCHITECTURE.md) — Project architecture, design patterns, and technical stack overview.
587
- * [**GitHub Token (PAT) Integration and Security**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/github-pat-integration.md) — Understand how GitPR creates issues directly in the repository with authentication.
588
- * [**Internationalization (i18n)**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/i18n_explanation.md) — Architecture, usage patterns, and how to add new languages.
589
- * [**MCP Integration**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/mcp-integration.md) — Connect GitPR to VS Code, Cursor, and Claude Desktop via Model Context Protocol.
590
- * [**MCP Prompts**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/mcp-prompts.md) — Pre-built message templates (7 prompts, 35 language variants) for common GitPR workflows in your editor's AI chat.
591
- * [**MCP Tool Annotations**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/mcp-annotations.md) — IDE integration hints (`readOnlyHint`, `destructiveHint`) for smarter UI behavior and safer tool execution.
592
- * [**Metrics & Telemetry**](https://github.com/gitpr-cli/gitpr.git/blob/main/docs/metricas-telemetria.md) — Local offline analytics for team usage metrics, exportable CSV reports, and interactive TUI dashboard.
578
+ * [**Editor Extension (VS Code & Cursor)**](https://gitpr.natanfiuza.dev.br/docs/editor-extension) — Thin-client VSIX extension driving `gitpr-mcp` over stdio for SCM commit generation, Markdown code reviews, two-step PR publishing, and inline `Problems` diagnostics.
579
+ * [**Interactive Configuration TUI (gitpr config)**](https://gitpr.natanfiuza.dev.br/docs/config-tui) — Master-detail terminal interface to view, edit, and validate settings in `~/.gitpr/.env`.
580
+ * [**Multi-Forge SCM Integration**](https://gitpr.natanfiuza.dev.br/docs/scm-multiforge) — Unified SCM provider layer for GitHub, GitLab, Bitbucket Cloud, and Azure DevOps with `--init` wizard.
581
+ * [**Suggested Reviewers**](https://gitpr.natanfiuza.dev.br/docs/suggested-reviewers) — Automatic reviewer suggestion via Git blame of added lines mapped to forge logins.
582
+ * [**Usage Telemetry & Audit Log**](https://gitpr.natanfiuza.dev.br/docs/usage-log) — Local invocation logging and audit history for tracking CLI executions.
583
+ * [**Install Wizard**](https://gitpr.natanfiuza.dev.br/docs/install-wizard) — Step-by-step guided setup for configuring GitPR in a new project.
584
+ * [**AI Providers**](https://gitpr.natanfiuza.dev.br/docs/providers-ia) — Configuration and selection between Google Gemini, DeepSeek, and Ollama.
585
+ * [**Auto-Updater**](https://gitpr.natanfiuza.dev.br/docs/auto-update) — How GitPR's automatic update and the mandatory update block work.
586
+ * [**Architecture**](https://gitpr.natanfiuza.dev.br/docs/ARCHITECTURE) — Project architecture, design patterns, and technical stack overview.
587
+ * [**GitHub Token (PAT) Integration and Security**](https://gitpr.natanfiuza.dev.br/docs/github-pat-integration) — Understand how GitPR creates issues directly in the repository with authentication.
588
+ * [**Internationalization (i18n)**](https://gitpr.natanfiuza.dev.br/docs/i18n_explanation) — Architecture, usage patterns, and how to add new languages.
589
+ * [**MCP Integration**](https://gitpr.natanfiuza.dev.br/docs/mcp-integration) — Connect GitPR to VS Code, Cursor, and Claude Desktop via Model Context Protocol.
590
+ * [**MCP Prompts**](https://gitpr.natanfiuza.dev.br/docs/mcp-prompts) — Pre-built message templates (7 prompts, 35 language variants) for common GitPR workflows in your editor's AI chat.
591
+ * [**MCP Tool Annotations**](https://gitpr.natanfiuza.dev.br/docs/mcp-annotations) — IDE integration hints (`readOnlyHint`, `destructiveHint`) for smarter UI behavior and safer tool execution.
592
+ * [**Metrics & Telemetry**](https://gitpr.natanfiuza.dev.br/docs/metricas-telemetria) — Local offline analytics for team usage metrics, exportable CSV reports, and interactive TUI dashboard.
593
593
 
594
594
  ## ⚡ Local Cache System (Quota Savings)
595
595
 
@@ -1,23 +1,23 @@
1
- gitpr_cli-1.4.1.dist-info/licenses/LICENSE,sha256=UQzLC6mwqb-ZNDkBBAqOxC-SMQcEID0gaM6oFcu6u48,27030
1
+ gitpr_cli-1.4.3.dist-info/licenses/LICENSE,sha256=UQzLC6mwqb-ZNDkBBAqOxC-SMQcEID0gaM6oFcu6u48,27030
2
2
  src/__init__.py,sha256=WZW9x8P5CYnru29UiarFa8q1nQzuwYWyIodSOlruCWI,29
3
3
  src/ai_providers.py,sha256=QyFEOsXyA59lZziO5bl-4nlYFJpxBcxU9aLhcTBnkWE,14633
4
4
  src/blame_engine.py,sha256=Zov1hxrt7Dcmf56ovJKz3QmHIfzDKtSub2iItXeg9-A,17104
5
- src/cache.py,sha256=GC2ZT0ThAtH22aRRZapIeauATv_nqBfiRsXjqeMhAiI,6368
5
+ src/cache.py,sha256=frROc69NyTn3TdJIOPctvuH9z_sh2pdKxL4ekMtyh6k,6572
6
6
  src/changelog_builder.py,sha256=8GicvIhREWo3B8XhQugVi2_OWzhN_ik_2GxpxmiJVTg,9650
7
7
  src/chat_memory.py,sha256=PcohnyspAfIwwwbsp9U2Wk_1AylJMFEMoWqRSJe_w30,8949
8
8
  src/commit_classifier.py,sha256=pgZRj8JvkCBgwgzDFNjjNKJKzddVB4HoPBcFM1F0zjE,6742
9
9
  src/config.py,sha256=5bs6jxxg-CwmtGfJB2eoMUbwRddOnc_MCcadOOcMbHg,53637
10
10
  src/config_schema.py,sha256=z-JC5rULAVkaQG4ppmVcdeNKLGpuXFqglzvCBgZjHVo,43308
11
- src/core.py,sha256=IO7HM3LaKNfs0rKWnDafAQLxrM3O7nwEWTJ6JJttyPM,85522
11
+ src/core.py,sha256=6t2lPcoB1HUdXUaoqSpRLIxMIJ_0E_m6zNKn0BzSZYg,85856
12
12
  src/diff_parser.py,sha256=62X69ves4tlzNgVKXNsfWdOMwqn2h16RkUOPqszMU7k,11252
13
13
  src/doc_links.py,sha256=DuovU-RF9kjbisBdEoGAsuavQ2y0MYDhqy6Sz6vbrGI,1157
14
14
  src/github_api.py,sha256=ILm7e03UP80swc3XuJvIJJvhLp81-V_3kQQ1EuAcN8Q,3836
15
15
  src/i18n.py,sha256=PdUmtZlH1zogbiDc3j6xiwtnvUd7XiNbiS-xKjYK4WM,4844
16
- src/issue_engine.py,sha256=XwW9C5uX84eOWWZMerm0DvE8LpLRcjhrLsXtt7yftak,10872
16
+ src/issue_engine.py,sha256=RmZsQVg6N1vCcpMdv_5FX-cMGgQ3I0vwMN4parQ4zFg,10974
17
17
  src/ledger.py,sha256=s10Sm0qcA1b916X7m8d2osDThCmSK0UQnmUrkyj8Eao,43663
18
18
  src/linter_engine.py,sha256=hIxht8M09Fnm6NiJZOqjI6dyqnFoHUDVA8Ve4dWZV4s,25214
19
19
  src/linter_wizard.py,sha256=7J_nQFghVwCaKG6QI3K45KcOaxHSH1oTCAvP_6tnCQM,7424
20
- src/main.py,sha256=wekm-Q0IBFU6c-rQXoz_56Yxygs-jmNrxHXusbMkBgw,214065
20
+ src/main.py,sha256=71K36Q_35gEDU9vfNDCQJSljFSXc9NpOjYMUuWV4BSo,215874
21
21
  src/mcp_server.py,sha256=gi4LmMYVN4CZWHQgb-5aV5UD2O-tRYzc_BqNg7t-EFA,111654
22
22
  src/metrics.py,sha256=M4Yqa__giuhmkElT-XOWrTfHGsjK_URVLhIZvErD2f0,39560
23
23
  src/net.py,sha256=f0MOkXyIxi4HVetsybtWpV_yHjiC17T2JMWySBdoV2I,2774
@@ -29,7 +29,7 @@ src/security_ruleset.py,sha256=L754S4RlVoZGWm0nuCoeli9-49mKydZpwi31V319DQ0,4337
29
29
  src/spinner.py,sha256=63pEotiDa3TVwBr-sn0MJoDPQ_uWJTTwINXieJIDDKc,8964
30
30
  src/suggest_reviewers.py,sha256=-wRe0HYksx4FWQZ-eDvU9Ghdk5gqtEt457KQP7sE31Y,5181
31
31
  src/tui_issue.py,sha256=WEFxT4kzWQ_KbSDsOnS_iuYXasZySB-68jryGNm0peg,10331
32
- src/updater.py,sha256=nRI22O8pIku2xu2eWxZOR-eyswPw5DtV4youbyTwtpA,4771
32
+ src/updater.py,sha256=WdlaW2QTjSjSj-pXwT7paa7zeL0YZqDHTMTS2fpU-iE,9210
33
33
  src/usage_log.py,sha256=FdfSmZKL_I5BcG750rD4gl3pNHx0jqV8Shij5XkAnzc,4381
34
34
  src/version_bump.py,sha256=OjqJe9OL1-1dT5od7-SgU5ZN_dTtR_aKX_oSjSY64YQ,2954
35
35
  src/application/__init__.py,sha256=0GADzpCB3CdCzw-d3az0DVOtTvXfCppt-0hY-Js5a1U,29
@@ -41,10 +41,10 @@ src/application/use_cases/calculate_risk.py,sha256=oh327qPomI_sO8JXErAHATafu_21K
41
41
  src/application/use_cases/compare_against_baseline.py,sha256=qgidzsloF5b84LXGb42ae4bDdRGBbGTRepLm1Yb9FmA,1875
42
42
  src/application/use_cases/create_baseline.py,sha256=LpXHlF9IkD4P4FL4VKJOh5YyCgfAD6hjt_0bQLy1pqI,25928
43
43
  src/application/use_cases/generate_mentor_explanation.py,sha256=KNj5cDm-i7aPJxxbbLm4Hvtv7g5CI59-pbX7tHPcEtM,9428
44
- src/application/use_cases/generate_pr_explanation.py,sha256=5BYUPgGBdR29P-9zt75IcKO4KOwGhZnapws7Y6A4574,3202
45
- src/application/use_cases/generate_test_file.py,sha256=WlHnhwCWHe-XBrL9m1xvJOkn1wqr8vnsE3s3x9wgUi0,8615
44
+ src/application/use_cases/generate_pr_explanation.py,sha256=Xbn1b9G57O18pT-_FLA_wAEJmyeLCv1n80p49WVxxjo,3336
45
+ src/application/use_cases/generate_test_file.py,sha256=r8kpNFPD5zU0vsshAzqqspU9stlCnopQzS9cwP5XT-I,9410
46
46
  src/application/use_cases/install_policy_pack.py,sha256=U1XvkHt0elXvyM2VRc6TPNukhUomSI2BSWj09hmLC_8,2745
47
- src/application/use_cases/publish_pull_request.py,sha256=DPK8ERbO2fsjoWUhcLQs10z1WhHVhsfTvtGumn50uIA,9369
47
+ src/application/use_cases/publish_pull_request.py,sha256=QtPx6a4oFYaUsYecVokcMNWWL9VomzmqbnHJNBGWSHA,9490
48
48
  src/application/use_cases/resolve_effective_policy.py,sha256=nTbjRR4di1GnCRV7l3yDNw27CQV3heAd45sftHxNyNc,7563
49
49
  src/application/use_cases/run_check.py,sha256=NEhuOZmdWgPFrOPWNku6MwS1Lp0OArk3OMd78M8_EcE,15246
50
50
  src/application/use_cases/suppress_finding.py,sha256=juj2jjk2BLjrAcx6WTKJNr5VMTJQ4Gb3GaEjDNGjM64,17809
@@ -81,7 +81,7 @@ src/domain/policy/policy_manifest.py,sha256=hXIM4u-tKYXTcEu0rBEGJYBuv4dysn9NEjhx
81
81
  src/domain/policy/policy_resolver.py,sha256=5oTVHysPIwRJibRtRp3U3zOJpS07EBRfkhsmpfI6wcE,28883
82
82
  src/domain/policy/policy_types.py,sha256=lhGxapRFAPiVCjmZtwZ9EVViSkDYCJ6UiKNVsaPKvDw,9875
83
83
  src/domain/pr/__init__.py,sha256=O9C2dQpbMnbF3mOXTMUvHPmIAF0G-QxhDlVXjB_wShs,274
84
- src/domain/pr/explain_section_builder.py,sha256=RMPErD_X8nLhbfCwxHCBmI3AFLilO_eNPpEDZE6pyns,3799
84
+ src/domain/pr/explain_section_builder.py,sha256=CttuvidDN3OaZrK8wDSCo_wAGa4bwqDyF7923TBqTuE,5023
85
85
  src/domain/risk/__init__.py,sha256=9vbLfhIomCarCbEw_DCZzri9lzV-lFIXBN-FAq6oSPY,283
86
86
  src/domain/risk/risk_calculator.py,sha256=zjIoLIRdXi3pHNbq0QcRwOznArtNtQQWV8ReLaW6Zn4,5037
87
87
  src/domain/risk/risk_explanation.py,sha256=YuPMBT27x7rnZnmvttIWBsfbTjsIXjMUEP9WLU4VlvE,3005
@@ -166,8 +166,8 @@ src/ui/demo/__init__.py,sha256=PGXenIwpS1lVDE164CZ55q8_65XtJq5H300NIpnqOh0,58
166
166
  src/ui/demo/demo_app.py,sha256=EDeuqxh_v9KrPhib49TyirmFlHZN7I5NQkg9AjDGhcs,3074
167
167
  src/ui/demo/demo_help_screen.py,sha256=m8SuvhzGmkMsahUBA6Aq7ull4W65nS0KOX4hLx-GGps,2056
168
168
  src/ui/demo/demo_screens.py,sha256=3Ntp-A7OB43yEtniRqV1UKWc-p5lHRyxeadpe8bxJpM,1937
169
- gitpr_cli-1.4.1.dist-info/METADATA,sha256=sNb--Fg6TdLtXclPH_pTKhQvVNAmG4irqz8wDlhATUs,52571
170
- gitpr_cli-1.4.1.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
171
- gitpr_cli-1.4.1.dist-info/entry_points.txt,sha256=TstGnMUzdOI5qNmpM97EPDBPuMqOZxoo6EoLxHSOMSQ,71
172
- gitpr_cli-1.4.1.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
173
- gitpr_cli-1.4.1.dist-info/RECORD,,
169
+ gitpr_cli-1.4.3.dist-info/METADATA,sha256=65g6lyhixY8BoxcWT43zWKu811J92hec2eKCvmholUM,51553
170
+ gitpr_cli-1.4.3.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
171
+ gitpr_cli-1.4.3.dist-info/entry_points.txt,sha256=TstGnMUzdOI5qNmpM97EPDBPuMqOZxoo6EoLxHSOMSQ,71
172
+ gitpr_cli-1.4.3.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
173
+ gitpr_cli-1.4.3.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (84.0.0)
2
+ Generator: setuptools (84.2.1)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -1,8 +1,10 @@
1
1
  import json
2
+ import sys
2
3
  from src.ai_providers import call_ai_model
3
4
  from src.config import get_ai_provider, get_api_key, get_api_model
4
5
  from src.core import get_skill_context
5
6
  from src.domain.pr.explain_section_builder import PrExplanation, parse_explain_payload
7
+ from src.i18n import __
6
8
 
7
9
 
8
10
  def generate_pr_explanation(
@@ -25,13 +27,20 @@ def generate_pr_explanation(
25
27
  provider = ai_provider or get_ai_provider()
26
28
  api_key = get_api_key(provider)
27
29
  if not api_key:
30
+ print(
31
+ __(
32
+ "❌ No API key configured for {provider}. Generate one with --install.",
33
+ provider=provider,
34
+ ),
35
+ file=sys.stderr,
36
+ )
28
37
  return PrExplanation(
29
- what_changes="No API key configured.",
30
- why_it_changes="No API key configured.",
38
+ what_changes="",
39
+ why_it_changes="",
31
40
  reviewer_focus_points=[],
32
- regression_risk="No API key configured.",
41
+ regression_risk="",
33
42
  has_sufficient_evidence=False,
34
- markdown="⚠️ No API key configured for AI provider.",
43
+ markdown="",
35
44
  )
36
45
 
37
46
  api_model = get_api_model(provider, task_complexity="advanced")
@@ -185,16 +185,28 @@ def generate_test_file(
185
185
  warnings=warnings,
186
186
  )
187
187
 
188
- # 6. Parse AI JSON
188
+ # 6. Parse AI JSON (call_ai_model returns a parsed dict; raw strings are
189
+ # still accepted for compatibility with cached and legacy responses)
189
190
  test_code = ""
190
191
  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:
192
+ data = ai_raw if isinstance(ai_raw, dict) else None
193
+ if data is None and isinstance(ai_raw, str):
194
+ try:
195
+ data = json.loads(ai_raw)
196
+ except Exception:
197
+ data = None
198
+
199
+ if isinstance(data, dict):
200
+ test_code = data.get("content") or ""
201
+ if not isinstance(test_code, str):
202
+ test_code = str(test_code)
203
+ raw_scenarios = data.get("covered_scenarios") or []
204
+ if isinstance(raw_scenarios, list):
205
+ covered_scenarios = [str(sc) for sc in raw_scenarios]
206
+ raw_warnings = data.get("warnings")
207
+ if isinstance(raw_warnings, list):
208
+ warnings.extend(str(w) for w in raw_warnings)
209
+ elif isinstance(ai_raw, str):
198
210
  # If response wasn't strict JSON, use clean raw text
199
211
  test_code = ai_raw.strip()
200
212
  if test_code.startswith("```"):
@@ -205,6 +217,14 @@ def generate_test_file(
205
217
  lines = lines[:-1]
206
218
  test_code = "\n".join(lines).strip()
207
219
  covered_scenarios.append("Generated from raw model output")
220
+ else:
221
+ warnings.append("AI model returned an unsupported response.")
222
+ return GeneratedTest(
223
+ scaffold=scaffold,
224
+ content="",
225
+ covered_scenarios=[],
226
+ warnings=warnings,
227
+ )
208
228
 
209
229
  # 7. Validate Syntax
210
230
  is_valid, syn_err = validate_test_syntax(test_code, detected_fw)
@@ -129,20 +129,20 @@ def publish_pull_request_use_case(
129
129
  message=__("PR description must not be empty."),
130
130
  )
131
131
 
132
- if not getattr(provider, "token", ""):
133
- return PublishPrOutcome(
134
- status="auth_required",
135
- provider=provider.name,
136
- source_branch=source_branch,
137
- target_branch=target_branch,
138
- http_status=401,
139
- message=__(
140
- "{provider} token is not configured. Run 'gitpr' in a terminal to authenticate.",
141
- provider=label,
142
- ),
143
- )
144
-
145
132
  if check_remote_state:
133
+ if not getattr(provider, "token", ""):
134
+ return PublishPrOutcome(
135
+ status="auth_required",
136
+ provider=provider.name,
137
+ source_branch=source_branch,
138
+ target_branch=target_branch,
139
+ http_status=401,
140
+ message=__(
141
+ "{provider} token is not configured. Run 'gitpr' in a terminal to authenticate.",
142
+ provider=label,
143
+ ),
144
+ )
145
+
146
146
  try:
147
147
  existing: Optional[PullRequestResult] = provider.check_existing_pull_request(
148
148
  repo_ref, source_branch
@@ -254,10 +254,12 @@ def publish_pull_request_use_case(
254
254
  message=mask_sensitive_text(str(exc)),
255
255
  )
256
256
 
257
+ pr_url = getattr(result, "url", "")
258
+ pr_number = getattr(result, "number", 0)
257
259
  return PublishPrOutcome(
258
260
  status="success",
259
- url=result.url,
260
- number=result.number,
261
+ url=pr_url,
262
+ number=pr_number,
261
263
  provider=provider.name,
262
264
  source_branch=source_branch,
263
265
  target_branch=target_branch,
@@ -265,6 +267,6 @@ def publish_pull_request_use_case(
265
267
  message=__(
266
268
  "PR successfully created on {provider}: {pr_url}",
267
269
  provider=label,
268
- pr_url=result.url,
270
+ pr_url=pr_url,
269
271
  ),
270
272
  )
src/cache.py CHANGED
@@ -21,10 +21,20 @@ def get_git_user_info():
21
21
  """Recupera o nome e email configurados no git local."""
22
22
  try:
23
23
  name = subprocess.run(
24
- ["git", "config", "user.name"], capture_output=True, text=True, check=True
24
+ ["git", "config", "user.name"],
25
+ capture_output=True,
26
+ text=True,
27
+ encoding="utf-8",
28
+ errors="replace",
29
+ check=True,
25
30
  ).stdout.strip()
26
31
  email = subprocess.run(
27
- ["git", "config", "user.email"], capture_output=True, text=True, check=True
32
+ ["git", "config", "user.email"],
33
+ capture_output=True,
34
+ text=True,
35
+ encoding="utf-8",
36
+ errors="replace",
37
+ check=True,
28
38
  ).stdout.strip()
29
39
  return name, email
30
40
  except subprocess.CalledProcessError:
src/core.py CHANGED
@@ -1443,6 +1443,8 @@ def get_base_branch():
1443
1443
  capture_output=True,
1444
1444
  stdin=subprocess.DEVNULL,
1445
1445
  text=True,
1446
+ encoding="utf-8",
1447
+ errors="replace",
1446
1448
  check=True,
1447
1449
  )
1448
1450
  # The return is something like 'refs/remotes/origin/main', so we get the last part
@@ -1467,6 +1469,9 @@ def get_git_full_diff():
1467
1469
  check=True,
1468
1470
  capture_output=True,
1469
1471
  stdin=subprocess.DEVNULL,
1472
+ text=True,
1473
+ encoding="utf-8",
1474
+ errors="replace",
1470
1475
  )
1471
1476
 
1472
1477
  base_branch = get_base_branch()
@@ -1477,6 +1482,8 @@ def get_git_full_diff():
1477
1482
  capture_output=True,
1478
1483
  stdin=subprocess.DEVNULL,
1479
1484
  text=True,
1485
+ encoding="utf-8",
1486
+ errors="replace",
1480
1487
  check=True,
1481
1488
  )
1482
1489
  ancestor_hash = merge_base_res.stdout.strip()
@@ -1499,6 +1506,7 @@ def get_git_full_diff():
1499
1506
  stdin=subprocess.DEVNULL,
1500
1507
  text=True,
1501
1508
  encoding="utf-8",
1509
+ errors="replace",
1502
1510
  check=True,
1503
1511
  )
1504
1512
  return result.stdout
@@ -2043,6 +2051,8 @@ def get_branch_history_text():
2043
2051
  capture_output=True,
2044
2052
  stdin=subprocess.DEVNULL,
2045
2053
  text=True,
2054
+ encoding="utf-8",
2055
+ errors="replace",
2046
2056
  check=True,
2047
2057
  )
2048
2058
  ancestor_hash = merge_base_res.stdout.strip()
@@ -2060,6 +2070,7 @@ def get_branch_history_text():
2060
2070
  stdin=subprocess.DEVNULL,
2061
2071
  text=True,
2062
2072
  encoding="utf-8",
2073
+ errors="replace",
2063
2074
  check=True,
2064
2075
  )
2065
2076
  git_log = git_log_res.stdout.strip()
@@ -67,17 +67,36 @@ def build_explain_markdown(
67
67
  return "\n".join(lines).strip()
68
68
 
69
69
 
70
- def parse_explain_payload(raw_json_or_text: str) -> PrExplanation:
71
- """Parses raw AI output into a PrExplanation object."""
72
- if not raw_json_or_text or not raw_json_or_text.strip():
73
- return PrExplanation(
74
- what_changes="",
75
- why_it_changes="",
76
- reviewer_focus_points=[],
77
- regression_risk="",
78
- has_sufficient_evidence=False,
79
- markdown="",
80
- )
70
+ def _as_text(value: object) -> str:
71
+ """Normalizes an AI JSON field to a string (None becomes empty)."""
72
+ if value is None:
73
+ return ""
74
+ return value if isinstance(value, str) else str(value)
75
+
76
+
77
+ def _empty_explanation() -> PrExplanation:
78
+ """Explanation for an empty diff or an empty AI payload."""
79
+ return PrExplanation(
80
+ what_changes="",
81
+ why_it_changes="",
82
+ reviewer_focus_points=[],
83
+ regression_risk="",
84
+ has_sufficient_evidence=False,
85
+ markdown="",
86
+ )
87
+
88
+
89
+ def parse_explain_payload(raw_json_or_text: dict | str | None) -> PrExplanation:
90
+ """Parses AI output into a PrExplanation object.
91
+
92
+ The provider layer returns a parsed dict (``call_ai_model``), while raw
93
+ strings still reach this function from callers and tests; both are
94
+ accepted. A string that is not valid JSON is treated as raw prose.
95
+ """
96
+ if not raw_json_or_text:
97
+ return _empty_explanation()
98
+ if isinstance(raw_json_or_text, str) and not raw_json_or_text.strip():
99
+ return _empty_explanation()
81
100
 
82
101
  what = ""
83
102
  why = ""
@@ -86,24 +105,34 @@ def parse_explain_payload(raw_json_or_text: str) -> PrExplanation:
86
105
  has_evidence = True
87
106
 
88
107
  try:
89
- data = json.loads(raw_json_or_text)
90
- what = data.get("what_changes", "")
91
- why = data.get("why_it_changes", "")
92
- risk = data.get("regression_risk", "")
93
- for raw_pt in data.get("reviewer_focus_points", []):
94
- if isinstance(raw_pt, dict):
95
- focus_points.append(
96
- ReviewerFocusPoint(
97
- description=raw_pt.get("description", ""),
98
- file_path=raw_pt.get("file_path"),
99
- related_line=raw_pt.get("related_line"),
108
+ if isinstance(raw_json_or_text, dict):
109
+ data = raw_json_or_text
110
+ else:
111
+ data = json.loads(raw_json_or_text)
112
+ if not isinstance(data, dict):
113
+ raise ValueError("Explanation payload is not a JSON object")
114
+
115
+ what = _as_text(data.get("what_changes"))
116
+ why = _as_text(data.get("why_it_changes"))
117
+ risk = _as_text(data.get("regression_risk"))
118
+ raw_points = data.get("reviewer_focus_points") or []
119
+ if isinstance(raw_points, list):
120
+ for raw_pt in raw_points:
121
+ if isinstance(raw_pt, dict):
122
+ file_path = raw_pt.get("file_path")
123
+ related_line = raw_pt.get("related_line")
124
+ focus_points.append(
125
+ ReviewerFocusPoint(
126
+ description=_as_text(raw_pt.get("description")),
127
+ file_path=file_path if isinstance(file_path, str) and file_path else None,
128
+ related_line=related_line if isinstance(related_line, int) else None,
129
+ )
100
130
  )
101
- )
102
- elif isinstance(raw_pt, str):
103
- focus_points.append(ReviewerFocusPoint(description=raw_pt))
131
+ elif isinstance(raw_pt, str):
132
+ focus_points.append(ReviewerFocusPoint(description=raw_pt))
104
133
  except Exception:
105
134
  # Fallback if raw text returned
106
- what = raw_json_or_text.strip()
135
+ what = raw_json_or_text.strip() if isinstance(raw_json_or_text, str) else ""
107
136
  why = __("[FILL: Please explain the motivation for these changes]")
108
137
  has_evidence = False
109
138
 
src/issue_engine.py CHANGED
@@ -20,7 +20,12 @@ def get_github_repo_info():
20
20
  """Extracts the owner/repo format from git remote -v."""
21
21
  try:
22
22
  result = subprocess.run(
23
- ["git", "remote", "-v"], capture_output=True, text=True, check=True
23
+ ["git", "remote", "-v"],
24
+ capture_output=True,
25
+ text=True,
26
+ encoding="utf-8",
27
+ errors="replace",
28
+ check=True,
24
29
  )
25
30
 
26
31
  # Search for patterns like git@github.com:owner/repo.git or https://github.com/owner/repo.git
src/main.py CHANGED
@@ -75,6 +75,18 @@ def _may_ask_the_user(quiet=False, hook=False, mcp=False):
75
75
  return False
76
76
 
77
77
 
78
+ def _subcommand_help_in_argv(argv=None):
79
+ """Whether the raw command line asks for help.
80
+
81
+ Click clears the group context arguments before this callback runs, so by
82
+ the time the update gate below executes, ``gitpr release -h`` is
83
+ indistinguishable from ``gitpr release``. The raw tokens are the only
84
+ surviving record. The scan fails open, which costs at most one skipped gate.
85
+ """
86
+ tokens = sys.argv[1:] if argv is None else argv
87
+ return any(token in ("-h", "--help") for token in tokens)
88
+
89
+
78
90
  def print_banner():
79
91
  """Displays the project ASCII Art signature"""
80
92
  banner = r"""
@@ -259,7 +271,7 @@ HELP_MAP: dict[str, dict[str, str]] = {
259
271
  ),
260
272
  },
261
273
  "explain": {
262
- "url": get_doc_url("pull-request-publication.md"),
274
+ "url": get_doc_url("explain-pr.md"),
263
275
  "title": __("Reviewer Guide (--explain / gitpr explain)"),
264
276
  "description": __(
265
277
  "Generates a concise reviewer-centric summary (What changes, Why it changes, Where to focus, and Regression risk)."
@@ -295,6 +307,7 @@ HELP_PRIORITY: dict[str, int] = {
295
307
  "fullreview": 8,
296
308
  "review": 9,
297
309
  "mentor": 9,
310
+ "explain": 9,
298
311
  "risk": 10,
299
312
  "input": 11,
300
313
  "history": 12,
@@ -557,7 +570,7 @@ def _print_baseline_line(compared, quiet=False):
557
570
  )
558
571
  @click.option(
559
572
  "--explain",
560
- "explain_flag",
573
+ "explain",
561
574
  is_flag=True,
562
575
  help=__(
563
576
  "Includes the Reviewer Guide section ('Explain my PR') in the Pull Request description."
@@ -636,7 +649,7 @@ def cli(
636
649
  base,
637
650
  no_publish,
638
651
  no_edit,
639
- explain_flag,
652
+ explain,
640
653
  mentor_flag,
641
654
  plugins,
642
655
  status,
@@ -676,6 +689,13 @@ def cli(
676
689
  # first, so everything below runs only when NO subcommand was requested —
677
690
  # the legacy flag surface stays byte-for-byte intact (ADR-002).
678
691
  if ctx.invoked_subcommand is not None:
692
+ # The mandatory update gate covers subcommands too, but only when the
693
+ # run may stop and ask the user: a release in CI, a pipe or a hook must
694
+ # never be blocked. Help tokens are read from the raw argv because
695
+ # Click clears the context arguments before this callback runs.
696
+ if may_ask and not update and not help_flag and not _subcommand_help_in_argv():
697
+ if enforce_update_required():
698
+ sys.exit(1)
679
699
  return
680
700
 
681
701
  # ============================================================
@@ -1317,10 +1337,18 @@ def cli(
1317
1337
 
1318
1338
  try:
1319
1339
  git_user = subprocess.run(
1320
- ["git", "config", "user.name"], capture_output=True, text=True
1340
+ ["git", "config", "user.name"],
1341
+ capture_output=True,
1342
+ text=True,
1343
+ encoding="utf-8",
1344
+ errors="replace",
1321
1345
  ).stdout.strip()
1322
1346
  git_email = subprocess.run(
1323
- ["git", "config", "user.email"], capture_output=True, text=True
1347
+ ["git", "config", "user.email"],
1348
+ capture_output=True,
1349
+ text=True,
1350
+ encoding="utf-8",
1351
+ errors="replace",
1324
1352
  ).stdout.strip()
1325
1353
  except Exception:
1326
1354
  git_user, git_email = "Dev", "dev@local"
@@ -1440,8 +1468,17 @@ def cli(
1440
1468
  click.secho(__("🚀 Starting pull request generation..."), fg="cyan")
1441
1469
  diff_text = get_git_full_diff()
1442
1470
 
1471
+ # A git failure is not an empty diff. get_git_full_diff()/get_git_diff()
1472
+ # return None when git fails; folding that into "no new code found" hides it.
1473
+ # Exit non-zero so scripts can tell the two apart. Hook mode fails open: a
1474
+ # non-zero prepare-commit-msg hook makes git abort the user's commit.
1475
+ if diff_text is None:
1476
+ if hook:
1477
+ return
1478
+ raise click.exceptions.Exit(1)
1479
+
1443
1480
  # CRITICAL: Warn the user before exiting if there are no changes
1444
- if not diff_text or not diff_text.strip():
1481
+ if not diff_text.strip():
1445
1482
  click.secho(
1446
1483
  __(
1447
1484
  "\n⚠️ No new code found. Make some changes or check your branch before running the command.\n"
@@ -1604,7 +1641,7 @@ def cli(
1604
1641
  pr_desc_body = data.get("pr_description", __("No detailed description."))
1605
1642
 
1606
1643
  from src.config import explain_enabled_by_default
1607
- if explain_flag or explain_enabled_by_default():
1644
+ if explain or explain_enabled_by_default():
1608
1645
  from src.application.use_cases.generate_pr_explanation import generate_pr_explanation
1609
1646
  click.secho("🧐 " + __("Generating Reviewer Guide (Explain my PR)..."), fg="cyan", dim=True)
1610
1647
  explanation = generate_pr_explanation(diff_text, ai_provider=active_provider, quiet=True)
@@ -4759,6 +4796,10 @@ def _kept(entry, status_filter, rule_filter, file_filter, overrides):
4759
4796
  "tests",
4760
4797
  context_settings={"help_option_names": ["-h", "--help"]},
4761
4798
  invoke_without_command=False,
4799
+ epilog="\b\n"
4800
+ + __(">> Full documentation:")
4801
+ + "\n"
4802
+ + get_doc_url("tests-generate.md"),
4762
4803
  )
4763
4804
  def tests_group():
4764
4805
  """AI-powered test suite generation and scaffolding."""
@@ -4769,6 +4810,10 @@ def tests_group():
4769
4810
  @tests_group.command(
4770
4811
  "generate",
4771
4812
  context_settings={"help_option_names": ["-h", "--help"]},
4813
+ epilog="\b\n"
4814
+ + __(">> Full documentation:")
4815
+ + "\n"
4816
+ + get_doc_url("tests-generate.md"),
4772
4817
  )
4773
4818
  @click.option(
4774
4819
  "--file",
@@ -4824,8 +4869,8 @@ def tests_generate(target_file, finding_id, framework_override, apply_flag, ai_p
4824
4869
  f"Finding: {cand.finding.id} - {cand.finding.message}\n"
4825
4870
  f"File: {cand.finding.file_path}:{cand.finding.line_start}-{cand.finding.line_end}\n"
4826
4871
  f"Severity: {cand.finding.severity} | Category: {cand.finding.category}\n"
4827
- f"Suggested Test: {cand.finding.suggested_test}\n"
4828
- f"Diff:\n{cand.diff or '(No diff available)'}"
4872
+ f"Suggested Test: {cand.suggested_test}\n"
4873
+ f"Diff:\n{cand.diff_unified or '(No diff available)'}"
4829
4874
  )
4830
4875
  except Exception as e:
4831
4876
  click.secho(f"⚠️ {e}", fg="yellow")
@@ -4936,7 +4981,7 @@ def tests_generate(target_file, finding_id, framework_override, apply_flag, ai_p
4936
4981
  epilog="\b\n"
4937
4982
  + __(">> Full documentation:")
4938
4983
  + "\n"
4939
- + get_doc_url("pull-request-publication.md"),
4984
+ + get_doc_url("explain-pr.md"),
4940
4985
  )
4941
4986
  @click.option(
4942
4987
  "--provider",
src/updater.py CHANGED
@@ -1,25 +1,38 @@
1
- import urllib.request
2
1
  import json
3
2
  import os
4
- import click
3
+ import tempfile
5
4
  from datetime import datetime
6
5
 
6
+ import click
7
+ from packaging.version import InvalidVersion, Version
8
+
7
9
  # Current version of your local executable (Update this on every new build!)
8
10
  # ── Must stay ABOVE the i18n import: i18n.py lazily imports __lang_version__
9
11
  # from here, and pyproject.toml reads __version__ via setuptools attr:.
10
- __version__ = "1.4.1" # GitPR current version
12
+ __version__ = "1.4.3" # GitPR current version
11
13
  __lang_version__ = "v0.0.35" # Language dictionary version control
12
14
  __scripts_version__ = (
13
15
  "v0.0.4" # Git Hook scripts version control (independent from __lang_version__)
14
16
  )
15
17
 
16
18
  from src.i18n import __
19
+ from src.net import bounded_urlopen
17
20
 
18
21
  PYPI_API_URL = "https://pypi.org/pypi/gitpr-cli/json"
19
22
 
23
+ # How long a PyPI answer is trusted before the next check goes back online.
24
+ # A rolling window, not the local calendar day: a release published after the
25
+ # first run of the day used to stay invisible until midnight.
26
+ UPDATE_CACHE_TTL_SECONDS = 3600
27
+
28
+ # Wall-clock ceiling for the PyPI fetch, DNS resolution included.
29
+ UPDATE_FETCH_HARD_TIMEOUT = 3.0
30
+
20
31
  # Environment switch used to mute the mandatory update check (test harness and
21
- # offline automation). Read as a plain flag: any non-empty value disables it.
32
+ # offline automation). Parsed as a strict boolean: only _SKIP_TRUTHY_VALUES
33
+ # disable it, so `GITPR_SKIP_UPDATE_CHECK=false` still checks.
22
34
  SKIP_UPDATE_CHECK_ENV = "GITPR_SKIP_UPDATE_CHECK"
35
+ _SKIP_TRUTHY_VALUES = ("1", "true", "yes", "on", "y")
23
36
 
24
37
 
25
38
  def get_gitpr_dir():
@@ -32,72 +45,188 @@ def get_update_cache_file():
32
45
  return os.path.join(get_gitpr_dir(), "update_cache.json")
33
46
 
34
47
 
35
- def parse_version(version_str):
36
- """Converts 'v0.1.0' or '0.1.0' into a tuple (0, 1, 0) for version math."""
37
- clean_version = version_str.lower().replace("v", "")
48
+ def _coerce_version(value):
49
+ """Parses *value* as a PEP 440 version, or returns None when it is not one."""
50
+ if not isinstance(value, str):
51
+ return None
52
+ text = value.strip()
53
+ # packaging rejects a leading "v"; the old helper stripped every "v" in the
54
+ # string, which corrupted suffixes such as 1.2.3-dev.
55
+ if text[:1] in ("v", "V"):
56
+ text = text[1:]
57
+ if not text:
58
+ return None
38
59
  try:
39
- return tuple(map(int, clean_version.split(".")))
40
- except ValueError:
41
- return (0, 0, 0)
60
+ return Version(text)
61
+ except InvalidVersion:
62
+ return None
63
+
64
+
65
+ def parse_version(version_str):
66
+ """Converts 'v0.1.0' or '0.1.0' into a tuple (0, 1, 0) for version math.
67
+
68
+ Kept for compatibility with outside callers. The tuple is lossy for
69
+ pre/post/dev markers, so the update gate itself compares Version objects
70
+ through _is_newer() instead.
71
+ """
72
+ parsed = _coerce_version(version_str)
73
+ return parsed.release if parsed is not None else (0, 0, 0)
42
74
 
43
75
 
44
76
  def is_update_check_disabled():
45
77
  """Returns True when the update check must be muted for this execution."""
46
- return bool(os.environ.get(SKIP_UPDATE_CHECK_ENV, "").strip())
78
+ value = os.environ.get(SKIP_UPDATE_CHECK_ENV, "")
79
+ return value.strip().lower() in _SKIP_TRUTHY_VALUES
47
80
 
48
81
 
49
- def get_latest_remote_version():
50
- """Fetches the latest version published on PyPI, with a daily cache."""
51
- cache_file = get_update_cache_file()
52
- today = datetime.now().strftime("%Y-%m-%d")
82
+ def _cache_is_fresh(cache_data, now=None):
83
+ """Whether *cache_data* carries a timestamp inside the freshness window.
84
+
85
+ A timestamp in the future (the clock moved backwards) counts as stale, so
86
+ a clock jump can never freeze the cache on an old answer indefinitely.
87
+ """
88
+ checked_at = cache_data.get("checked_at")
89
+ if not isinstance(checked_at, str):
90
+ return False
91
+ try:
92
+ written_at = datetime.fromisoformat(checked_at)
93
+ current = datetime.now() if now is None else now
94
+ age = (current - written_at).total_seconds()
95
+ except (TypeError, ValueError):
96
+ # Not a valid timestamp, or a timezone-aware stamp this cache never writes.
97
+ return False
98
+ return 0 <= age < UPDATE_CACHE_TTL_SECONDS
99
+
100
+
101
+ def _read_cached_version(cache_file):
102
+ """Returns the cached PyPI version while it is still fresh, else "".
103
+
104
+ Only the current schema is read: a document from the former calendar-day
105
+ cache has no ``checked_at`` and therefore counts as expired.
106
+ """
107
+ try:
108
+ with open(cache_file, "r", encoding="utf-8", errors="replace") as f:
109
+ cache_data = json.load(f)
110
+ except Exception:
111
+ return ""
112
+
113
+ if not isinstance(cache_data, dict) or not _cache_is_fresh(cache_data):
114
+ return ""
115
+
116
+ version = cache_data.get("version")
117
+ if isinstance(version, str) and version.strip():
118
+ return version.strip()
119
+ return ""
120
+
53
121
 
54
- # Try to read from cache to avoid slowing down the user's terminal
55
- if os.path.exists(cache_file):
122
+ def _write_cached_version(cache_file, version):
123
+ """Writes the cache atomically; the caller treats a failure as non-fatal."""
124
+ folder = os.path.dirname(cache_file)
125
+ if folder:
126
+ os.makedirs(folder, exist_ok=True)
127
+
128
+ payload = {"checked_at": datetime.now().isoformat(), "version": version}
129
+ handle = tempfile.NamedTemporaryFile(
130
+ "w",
131
+ encoding="utf-8",
132
+ dir=folder or ".",
133
+ prefix=".update-cache-",
134
+ suffix=".tmp",
135
+ delete=False,
136
+ )
137
+ try:
138
+ with handle:
139
+ json.dump(payload, handle)
140
+ os.replace(handle.name, cache_file)
141
+ except Exception:
56
142
  try:
57
- with open(cache_file, "r", encoding="utf-8", errors="replace") as f:
58
- cache_data = json.load(f)
59
- if cache_data.get("date") == today:
60
- return cache_data.get("version", "")
61
- except Exception:
143
+ os.unlink(handle.name)
144
+ except OSError:
62
145
  pass
146
+ raise
147
+
63
148
 
64
- # Fetch from Web if cache expired
65
- latest_version = ""
149
+ def _fetch_pypi_version():
150
+ """Returns the version published on PyPI, or "" when it cannot be read."""
151
+ raw = bounded_urlopen(
152
+ PYPI_API_URL,
153
+ timeout=3,
154
+ hard_timeout=UPDATE_FETCH_HARD_TIMEOUT,
155
+ headers={"User-Agent": "GitPR-Updater"},
156
+ )
157
+ if not raw:
158
+ return ""
66
159
 
67
160
  try:
68
- req = urllib.request.Request(PYPI_API_URL, headers={"User-Agent": "GitPR-Updater"})
69
- with urllib.request.urlopen(req, timeout=3) as response:
70
- data = json.loads(response.read().decode())
71
- latest_version = data.get("info", {}).get("version", "")
161
+ data = json.loads(raw.decode("utf-8", errors="replace"))
162
+ version = data["info"]["version"]
163
+ except Exception:
164
+ return ""
72
165
 
73
- if latest_version:
74
- os.makedirs(get_gitpr_dir(), exist_ok=True)
75
- with open(cache_file, "w", encoding="utf-8") as f:
76
- json.dump({"date": today, "version": latest_version}, f)
166
+ # Only a parseable version is trusted: a captive portal or a malformed
167
+ # mirror must not poison the cache for a whole hour.
168
+ if _coerce_version(version) is None:
169
+ return ""
170
+ return version.strip()
77
171
 
78
- except Exception:
79
- pass # Silent failure in case of no internet
80
172
 
173
+ def get_latest_remote_version(force=False):
174
+ """Fetches the latest version published on PyPI, with a one-hour cache.
175
+
176
+ ``force=True`` (the ``--update`` flag) always asks PyPI: that command
177
+ promises an immediate verification, so it must not answer from the cache.
178
+ Returns "" when the remote version is unknown — offline, stalled DNS, or a
179
+ body that is not a version — and the caller then lets the run proceed.
180
+ """
181
+ cache_file = get_update_cache_file()
182
+
183
+ if not force:
184
+ cached = _read_cached_version(cache_file)
185
+ if cached:
186
+ return cached
187
+
188
+ latest_version = _fetch_pypi_version()
189
+ if latest_version:
190
+ try:
191
+ _write_cached_version(cache_file, latest_version)
192
+ except Exception:
193
+ pass # A read-only home or a locked file must not hide the answer
81
194
  return latest_version
82
195
 
83
196
 
197
+ def _is_newer(remote_version, local_version=None):
198
+ """Whether *remote_version* is a version this install should upgrade to.
199
+
200
+ PEP 440 ordering instead of a tuple comparison, so 1.4.10 > 1.4.9 and
201
+ pre/post/dev markers follow the standard. A remote pre-release is ignored
202
+ while the local install is stable: pip will not install it without --pre,
203
+ so blocking would tell the user to run a command that does nothing. An
204
+ unparseable version on either side is "unknown", never "newer".
205
+ """
206
+ remote = _coerce_version(remote_version)
207
+ local = _coerce_version(__version__ if local_version is None else local_version)
208
+ if remote is None or local is None:
209
+ return False
210
+ if remote.is_prerelease and not local.is_prerelease:
211
+ return False
212
+ return remote > local
213
+
214
+
84
215
  def enforce_update_required():
85
216
  """Blocks execution while a newer GitPR version is published on PyPI.
86
217
 
87
218
  Prints the upgrade instructions and returns True when the caller must stop.
88
219
  Returns False when the local version is current, when the remote version is
89
- unknown (offline, so the user could not upgrade anyway) or when the check is
90
- disabled via GITPR_SKIP_UPDATE_CHECK.
220
+ unknown (offline, so the user could not upgrade anyway), when it is a
221
+ pre-release a stable install cannot move to, or when the check is disabled
222
+ via GITPR_SKIP_UPDATE_CHECK.
91
223
  """
92
224
  if is_update_check_disabled():
93
225
  return False
94
226
 
95
227
  latest_version = get_latest_remote_version()
96
228
 
97
- if not latest_version:
98
- return False
99
-
100
- if parse_version(latest_version) <= parse_version(__version__):
229
+ if not _is_newer(latest_version):
101
230
  return False
102
231
 
103
232
  click.echo("")
@@ -121,13 +250,13 @@ def enforce_update_required():
121
250
 
122
251
  def check_and_update():
123
252
  """Function triggered only when the user forces the --update flag."""
124
- latest_version = get_latest_remote_version()
253
+ latest_version = get_latest_remote_version(force=True)
125
254
 
126
255
  if not latest_version:
127
256
  click.secho(__("❌ Could not check for updates at this moment."), fg="red")
128
257
  return
129
258
 
130
- if parse_version(latest_version) > parse_version(__version__):
259
+ if _is_newer(latest_version):
131
260
  click.secho(
132
261
  __(
133
262
  "\n🚀 New GitPR version found (v{latest_version})!",