gitpr-cli 1.4.4__py3-none-any.whl → 1.6.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.
Files changed (176) hide show
  1. {gitpr_cli-1.4.4.dist-info → gitpr_cli-1.6.0.dist-info}/METADATA +20 -1
  2. gitpr_cli-1.6.0.dist-info/RECORD +183 -0
  3. src/__init__.py +1 -1
  4. src/application/__init__.py +2 -2
  5. src/application/use_cases/__init__.py +8 -8
  6. src/application/use_cases/activate_policy_pack.py +122 -122
  7. src/application/use_cases/baseline_gate.py +415 -415
  8. src/application/use_cases/baseline_summary.py +259 -259
  9. src/application/use_cases/calculate_risk.py +309 -309
  10. src/application/use_cases/compare_against_baseline.py +41 -41
  11. src/application/use_cases/create_baseline.py +710 -709
  12. src/application/use_cases/generate_mentor_explanation.py +279 -279
  13. src/application/use_cases/generate_pr_explanation.py +82 -82
  14. src/application/use_cases/generate_test_file.py +247 -247
  15. src/application/use_cases/install_policy_pack.py +71 -71
  16. src/application/use_cases/publish_pull_request.py +272 -272
  17. src/application/use_cases/resolve_effective_policy.py +208 -208
  18. src/application/use_cases/run_check.py +449 -446
  19. src/application/use_cases/suppress_finding.py +483 -483
  20. src/application/use_cases/validate_policy_pack.py +263 -263
  21. src/branding/__init__.py +1 -1
  22. src/branding/badge_builder.py +139 -139
  23. src/branding/badge_data.py +53 -53
  24. src/changelog_builder.py +272 -272
  25. src/commit_classifier.py +178 -178
  26. src/config.py +1264 -1369
  27. src/config_schema.py +398 -48
  28. src/core.py +580 -100
  29. src/demo/__init__.py +7 -7
  30. src/demo/demo_runner.py +336 -336
  31. src/demo/fake_ai_provider.py +112 -112
  32. src/demo/scenarios/__init__.py +128 -128
  33. src/demo/scenarios/laravel_bug_fix.py +443 -443
  34. src/demo/scenarios/security_issue.py +511 -511
  35. src/diff_parser.py +423 -321
  36. src/doc_links.py +29 -4
  37. src/domain/baseline/__init__.py +143 -143
  38. src/domain/baseline/baseline_comparator.py +268 -268
  39. src/domain/baseline/baseline_fingerprint.py +103 -103
  40. src/domain/baseline/baseline_manifest.py +291 -291
  41. src/domain/baseline/baseline_types.py +289 -289
  42. src/domain/baseline/suppression_policy.py +661 -661
  43. src/domain/check/__init__.py +31 -31
  44. src/domain/check/check_config.py +122 -122
  45. src/domain/check/check_output.py +112 -112
  46. src/domain/check/check_types.py +165 -165
  47. src/domain/finding/__init__.py +5 -5
  48. src/domain/finding/finding_types.py +25 -25
  49. src/domain/linter/__init__.py +8 -8
  50. src/domain/linter/sast_finding_mapper.py +83 -83
  51. src/domain/mentor/__init__.py +15 -15
  52. src/domain/mentor/mentor_explanation_builder.py +228 -228
  53. src/domain/policy/__init__.py +116 -116
  54. src/domain/policy/policy_checksum.py +60 -60
  55. src/domain/policy/policy_compatibility.py +210 -210
  56. src/domain/policy/policy_manifest.py +694 -694
  57. src/domain/policy/policy_resolver.py +706 -706
  58. src/domain/policy/policy_types.py +237 -237
  59. src/domain/pr/__init__.py +14 -14
  60. src/domain/pr/explain_section_builder.py +153 -153
  61. src/domain/risk/__init__.py +18 -18
  62. src/domain/risk/risk_calculator.py +144 -144
  63. src/domain/risk/risk_explanation.py +78 -78
  64. src/domain/risk/risk_rules.py +261 -261
  65. src/domain/risk/risk_types.py +113 -113
  66. src/domain/tests_generation/__init__.py +14 -14
  67. src/domain/tests_generation/framework_detector.py +120 -120
  68. src/domain/tests_generation/test_content_types.py +43 -43
  69. src/domain/tests_generation/test_scaffold_builder.py +92 -92
  70. src/fix/__init__.py +16 -16
  71. src/fix/apply_fix.py +479 -479
  72. src/fix/fix_history.py +153 -153
  73. src/fix/patch_applier.py +42 -42
  74. src/fix/patch_extractor.py +61 -61
  75. src/fix/patch_provenance.py +96 -96
  76. src/fix/patch_safety_classifier.py +101 -101
  77. src/fix/rollback_fix.py +69 -69
  78. src/github_api.py +112 -112
  79. src/i18n.py +142 -136
  80. src/infrastructure/__init__.py +1 -1
  81. src/infrastructure/baseline/__init__.py +28 -28
  82. src/infrastructure/baseline/local_baseline_repository.py +355 -355
  83. src/infrastructure/ci/__init__.py +11 -11
  84. src/infrastructure/ci/junit_builder.py +102 -102
  85. src/infrastructure/ci/masking.py +65 -65
  86. src/infrastructure/ci/sarif_builder.py +152 -152
  87. src/infrastructure/git/__init__.py +1 -1
  88. src/infrastructure/git/identity.py +155 -155
  89. src/infrastructure/git/patch_applier.py +209 -209
  90. src/infrastructure/git/risk_history_reader.py +93 -93
  91. src/infrastructure/git/selective_stager.py +198 -198
  92. src/infrastructure/git/test_matcher.py +78 -78
  93. src/infrastructure/linter/external/__init__.py +9 -9
  94. src/infrastructure/linter/external/bandit_bridge.py +127 -127
  95. src/infrastructure/linter/external/base_bridge.py +95 -95
  96. src/infrastructure/linter/external/gitleaks_bridge.py +168 -168
  97. src/infrastructure/linter/external/semgrep_bridge.py +139 -139
  98. src/infrastructure/policy/__init__.py +45 -45
  99. src/infrastructure/policy/local_policy_repository.py +142 -142
  100. src/infrastructure/policy/policy_pack_loader.py +240 -240
  101. src/infrastructure/scm/__init__.py +62 -62
  102. src/infrastructure/scm/azure_devops_provider.py +27 -0
  103. src/infrastructure/scm/base.py +421 -352
  104. src/infrastructure/scm/bitbucket_provider.py +41 -0
  105. src/infrastructure/scm/factory.py +109 -97
  106. src/infrastructure/scm/github_provider.py +564 -531
  107. src/infrastructure/scm/gitlab_provider.py +34 -0
  108. src/infrastructure/scm/web_links.py +103 -103
  109. src/issue_autoclose/__init__.py +12 -0
  110. src/issue_autoclose/insert_close_line.py +46 -0
  111. src/issue_autoclose/issue_registry.py +285 -0
  112. src/issue_autoclose/register_created_issue.py +81 -0
  113. src/issue_autoclose/resolve_close_issue.py +236 -0
  114. src/ledger.py +7 -2
  115. src/linter_wizard.py +210 -210
  116. src/main.py +12492 -5931
  117. src/mcp_server.py +73 -36
  118. src/metrics.py +1352 -1105
  119. src/net.py +74 -74
  120. src/policy_packs/laravel-quality/CHANGELOG.md +25 -25
  121. src/policy_packs/laravel-quality/README.md +41 -41
  122. src/policy_packs/laravel-quality/linter.yml +61 -61
  123. src/policy_packs/laravel-quality/policy.yml +110 -110
  124. src/policy_packs/node-quality/README.md +27 -27
  125. src/policy_packs/node-quality/linter.yml +57 -57
  126. src/policy_packs/node-quality/policy.yml +82 -82
  127. src/policy_packs/php-security/README.md +33 -33
  128. src/policy_packs/php-security/linter.yml +69 -69
  129. src/policy_packs/php-security/policy.yml +75 -75
  130. src/policy_packs/vue-quality/README.md +29 -29
  131. src/policy_packs/vue-quality/linter.yml +50 -50
  132. src/policy_packs/vue-quality/policy.yml +78 -78
  133. src/project_config.py +484 -0
  134. src/release_engine.py +1060 -990
  135. src/review/__init__.py +13 -13
  136. src/review/diff_normalizer.py +8 -8
  137. src/review/diff_source.py +69 -69
  138. src/review/remote_pr.py +350 -350
  139. src/review/render.py +83 -83
  140. src/reviewer_resolution.py +159 -159
  141. src/reviewer_suggestion.py +260 -258
  142. src/scm_settings.py +441 -0
  143. src/security_ruleset.py +115 -108
  144. src/skill_frontmatter.py +70 -0
  145. src/spinner.py +4 -1
  146. src/split/__init__.py +17 -17
  147. src/split/apply_split_plan.py +251 -251
  148. src/split/generate_split_plan.py +222 -222
  149. src/split/hunk_grouper.py +347 -347
  150. src/split/hunk_parser.py +256 -256
  151. src/split/split_plan.py +153 -153
  152. src/suggest_reviewers.py +155 -146
  153. src/tui_issue.py +301 -281
  154. src/ui/chat_app.py +15 -14
  155. src/ui/commit_app.py +220 -220
  156. src/ui/config_app.py +518 -22
  157. src/ui/demo/__init__.py +1 -1
  158. src/ui/demo/demo_app.py +91 -91
  159. src/ui/demo/demo_help_screen.py +52 -52
  160. src/ui/demo/demo_screens.py +60 -60
  161. src/ui/help_screen.py +46 -46
  162. src/ui/issue_app.py +260 -236
  163. src/ui/linter_app.py +2 -0
  164. src/ui/metrics_app.py +466 -456
  165. src/ui/metrics_migration_app.py +173 -173
  166. src/ui/pr_base_app.py +98 -0
  167. src/ui/pr_publish_app.py +1783 -1775
  168. src/ui/pr_publish_help.py +50 -50
  169. src/updater.py +274 -274
  170. src/usage_log.py +127 -127
  171. src/version_bump.py +87 -87
  172. gitpr_cli-1.4.4.dist-info/RECORD +0 -174
  173. {gitpr_cli-1.4.4.dist-info → gitpr_cli-1.6.0.dist-info}/WHEEL +0 -0
  174. {gitpr_cli-1.4.4.dist-info → gitpr_cli-1.6.0.dist-info}/entry_points.txt +0 -0
  175. {gitpr_cli-1.4.4.dist-info → gitpr_cli-1.6.0.dist-info}/licenses/LICENSE +0 -0
  176. {gitpr_cli-1.4.4.dist-info → gitpr_cli-1.6.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.4.4
3
+ Version: 1.6.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
@@ -194,6 +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
+ * `--close-issue` / `--no-close-issue`: Control the **automatic issue closure**. After `gitpr -is` creates an issue on GitHub, the next `gitpr` run offers to add `close #N` to the Pull Request description — the record lives in `.gitpr/conf/github.yml`, which GitPR adds to `.gitignore` automatically. `--close-issue` accepts without asking, `--no-close-issue` skips the check for the run, and `GITPR_ISSUE_AUTOCLOSE=false` turns the feature off. 📖 [Full docs](https://gitpr.natanfiuza.dev.br/docs/issue-autoclose)
197
198
  * **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
199
  * `-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
200
  * `-u` or `--update`: Checks the latest version of GitPR on PyPI and shows how to update it (Auto-Updater).
@@ -339,6 +340,23 @@ For your own README there is a static badge. `gitpr badge --readme` prints the l
339
340
 
340
341
  📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/badge](https://gitpr.natanfiuza.dev.br/docs/badge)
341
342
 
343
+ ## 🔗 Automatic Issue Closure (--close-issue)
344
+
345
+ `gitpr -is` creates an issue, and the pull request is where it gets closed. This feature carries the number between the two: when `F3` creates the issue on GitHub, GitPR records it in `.gitpr/conf/github.yml` (added to `.gitignore` automatically) — one issue at a time, the last one wins.
346
+
347
+ On the next `gitpr` run, before the description is generated, GitPR checks the recorded issue on GitHub. While it is still open, it offers to add `close #N` to the pull request body; the line becomes its own paragraph at the end and is never duplicated. Merging the pull request then closes the issue automatically.
348
+
349
+ | Flag / setting | Effect |
350
+ |---|---|
351
+ | `--close-issue` | Adds the line without asking (the issue is still checked) |
352
+ | `--no-close-issue` | Skips the check for this run — no record is read and no forge call is made |
353
+ | `GITPR_ISSUE_AUTOCLOSE=false` | Turns the feature off for every run; `--close-issue` does not override it |
354
+ | `GITPR_ISSUE_AUTOCLOSE_TIMEOUT` | Seconds to wait for the forge while checking the issue (default `5`) |
355
+
356
+ GitHub only; the other forges skip the feature in silence. A run with no terminal does not ask — use `--close-issue` to opt in. The record survives a failed or cancelled publish and is cleared only after the body reaches the forge; a closed, deleted or pull-request number forgets itself, and a record from another repository is ignored and kept.
357
+
358
+ 📖 **Full documentation:** [gitpr.natanfiuza.dev.br/docs/issue-autoclose](https://gitpr.natanfiuza.dev.br/docs/issue-autoclose)
359
+
342
360
  ## 🧠 Multi-Model Architecture (AI-Agnostic)
343
361
 
344
362
  GitPR is not tied to a single Artificial Intelligence. During initial setup, the user can choose their default engine. We currently support:
@@ -564,6 +582,7 @@ If you want to implement GitPR as an automated quality barrier in your team, che
564
582
  * [**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
583
  * [**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
584
  * [**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.
585
+ * [**Automatic Issue Closure (--close-issue)**](https://gitpr.natanfiuza.dev.br/docs/issue-autoclose) — How the issue created by `gitpr -is` is recorded in `.gitpr/conf/github.yml`, the gate order that decides when the next pull request offers `close #N`, why the record survives a failed publish, and how the line is kept from being duplicated.
567
586
  * [**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
587
  * [**Skills and Templates System**](https://gitpr.natanfiuza.dev.br/docs/skill-template) — How to customize AI behavior with `.gitpr.*.md` files.
569
588
  * [**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.
@@ -0,0 +1,183 @@
1
+ gitpr_cli-1.6.0.dist-info/licenses/LICENSE,sha256=UQzLC6mwqb-ZNDkBBAqOxC-SMQcEID0gaM6oFcu6u48,27030
2
+ src/__init__.py,sha256=ZHymXsocMDazI_fjOIgAQsRQvFPN2tV8FOuv0qptI_o,30
3
+ src/ai_providers.py,sha256=QyFEOsXyA59lZziO5bl-4nlYFJpxBcxU9aLhcTBnkWE,14633
4
+ src/blame_engine.py,sha256=Zov1hxrt7Dcmf56ovJKz3QmHIfzDKtSub2iItXeg9-A,17104
5
+ src/cache.py,sha256=frROc69NyTn3TdJIOPctvuH9z_sh2pdKxL4ekMtyh6k,6572
6
+ src/changelog_builder.py,sha256=Ea0coS6LCqsdxTWrKW8SZyVsD02HuNNYzCoggI0loi8,9922
7
+ src/chat_memory.py,sha256=PcohnyspAfIwwwbsp9U2Wk_1AylJMFEMoWqRSJe_w30,8949
8
+ src/commit_classifier.py,sha256=HQHxPbCfgW_GD1_4mbO2No16jh_Ci5RTSoLbTU4IWYI,6920
9
+ src/config.py,sha256=R1TETnHD_W22s3DBuzh5_XSISkiY6tM3ty9itsDXckU,47036
10
+ src/config_schema.py,sha256=SkcjI1vF1FpeFq6WK7c5HJPwHURUeiyhY7MMOgfSoc0,54494
11
+ src/core.py,sha256=IGtKsqAPJ3K4DbeMBtsSI7IX2CEmQ1ZGR4cs1Uwo-mQ,98763
12
+ src/diff_parser.py,sha256=JHeaTJflNOEbR9SYK_Ljr2hctrrABUIb31IFHAbyIDo,15405
13
+ src/doc_links.py,sha256=GdANhE6XbEnVDsbpCf22eUZnqFXor_2SU1O4S-pteIE,2028
14
+ src/github_api.py,sha256=i1o58oKWWmMCerP-g-hOQHGCwPWd01ooKntTKjpFhIA,3948
15
+ src/i18n.py,sha256=Xp9c1dNGmTHbo8SyXa-LQuO71nWx3RP-Us7jJ7ARtUY,4804
16
+ src/issue_engine.py,sha256=RmZsQVg6N1vCcpMdv_5FX-cMGgQ3I0vwMN4parQ4zFg,10974
17
+ src/ledger.py,sha256=3p5nX9gPGHgANQxBxEywrKo3TNb5KdWDeTI2mslYopo,43787
18
+ src/linter_engine.py,sha256=hIxht8M09Fnm6NiJZOqjI6dyqnFoHUDVA8Ve4dWZV4s,25214
19
+ src/linter_wizard.py,sha256=mo8aSTZmVLluCOskWv3nji74WPJh_6c122zwADN9aCc,7634
20
+ src/main.py,sha256=zo0PEpxWzrsP8o61AAFDWwoThfWj5YLl_75WmKDT12I,246130
21
+ src/mcp_server.py,sha256=z3yf2eyKPxq4trAaeKrLQNQiN_rjeWYzDgbvAY5ziXc,112996
22
+ src/metrics.py,sha256=Pk8-HkK9dvr8MgBx6QJ_5iTLVgpohPww9C4pQ6wWLzM,48650
23
+ src/net.py,sha256=CjhgmQnp4OHVmO-3Lm-DLhNfo2RW0M9FnD_HYPKcnNI,2848
24
+ src/project_config.py,sha256=r-gzjKeFuTYvUzOflQlpbijn6uLXtPEUBzA-1PzUR_k,16749
25
+ src/release_engine.py,sha256=S0kgO-a7RSC07AzvRWRlGo32jx38PAQGIzgvYXOfthk,41274
26
+ src/reviewer_resolution.py,sha256=bFRdf4GVWIpz4kk7cyF7EYWV43aP7V8sAlNGqKRPd7A,5913
27
+ src/reviewer_suggestion.py,sha256=YeXJLjJjn1YS69FChWZBF4qHUuIwvv_n-GPiymLN8LQ,8904
28
+ src/scm_settings.py,sha256=3pXgBbxqekvD4LM-I3GgfV4IOa6QJn4x3-DPAThCris,14156
29
+ src/security.py,sha256=fkWy-r4tdISH0xOyWZyR8ZCFyLuYqu8fn9sKsIeNAso,1351
30
+ src/security_ruleset.py,sha256=4mqmqtWLHuhuCcjjaoKUM_YFzil4cWZ_ubjlOLgbM_4,4874
31
+ src/skill_frontmatter.py,sha256=K-HsjwSYPHUsDRjIiugBE3koxVdGf38L5d4l8eYVqEo,2167
32
+ src/spinner.py,sha256=JGOO_taTXbrQK0KnNxUQ40PcpS802bYYBosWWm_NG9o,9012
33
+ src/suggest_reviewers.py,sha256=LTOtwDIjaBS_FGDU6bjN6oGy-YD112hIPugYytNKD_A,5821
34
+ src/tui_issue.py,sha256=yaNpj9SSXiWWjMJ7a9FABcNWSffCkA1tjUSnJ1hN-MQ,11477
35
+ src/updater.py,sha256=VoHhIBzb02wRNNGlxK4AMQlvGnuf6azaPB--m-tTpTk,9560
36
+ src/usage_log.py,sha256=CBCNlUm_xftXsBaGRHvsEjTENvZfE-lW70HGLMkisSg,4508
37
+ src/version_bump.py,sha256=Dw1LZ_c6VzaO_s9cDeYSevoWtXVr_oP70eS_mkrOm8s,3041
38
+ src/application/__init__.py,sha256=3YXFcmxJh0f7BwryKXxUrOhGrLnZLsagV-VbBIk0eGk,31
39
+ src/application/use_cases/__init__.py,sha256=jgOjxxTdvgGTmaHA7VIcAnNoDKIdvqOs8IYhCV3N3aE,292
40
+ src/application/use_cases/activate_policy_pack.py,sha256=U6nNsESRDJv4bW_v__qc7zSo2LxiLcBkkppievfiSls,4627
41
+ src/application/use_cases/baseline_gate.py,sha256=-3c5iy7i-HJ7_lsgHGftc8JDYOct0OnrkppQEJn47HE,16734
42
+ src/application/use_cases/baseline_summary.py,sha256=BK5LibkNbQsIRmYvqv8AcS_kkC1b_Lt9Q6ZiOMzYCO0,10550
43
+ src/application/use_cases/calculate_risk.py,sha256=Kgd6YVTrt6DtizXAZQ97XCYd0TBXXytiloauke-L-ko,13513
44
+ src/application/use_cases/compare_against_baseline.py,sha256=hePa-yidv9OfBe8AZoNhu6FA5MBg5izZyL5K9ZAOWAI,1916
45
+ src/application/use_cases/create_baseline.py,sha256=ZJKO_hTmC01CLJ-cnbDVddY3PPOgWobNMYq5U94YwG4,26715
46
+ src/application/use_cases/generate_mentor_explanation.py,sha256=pPXSLYBJwum6a2P1TYv_B1zwAuM3xYtT8T2LhKO60rY,9707
47
+ src/application/use_cases/generate_pr_explanation.py,sha256=vlR6Lw7XBnHD7Cloz2gZMrW-kx31cBeC2kSM0qSnUjk,3418
48
+ src/application/use_cases/generate_test_file.py,sha256=U6EyPbZSXEv9A34PYHkQDToenjqu5Fu1h-nIBqcT9Ds,9657
49
+ src/application/use_cases/install_policy_pack.py,sha256=7s8wUL_SticgRnUMpzLJCmHso6y6tDHLibSdBLyXb0M,2816
50
+ src/application/use_cases/publish_pull_request.py,sha256=sBQTBhFGJ98YKTqatZdEccTHfNrp3rxZ_qbKUR9nZrw,9762
51
+ src/application/use_cases/resolve_effective_policy.py,sha256=HQruehhgqzT5cX2YcVftm1cZqiwDS5cAkRO0txj3hns,7771
52
+ src/application/use_cases/run_check.py,sha256=40txVJrLPmRjAJLMVnlYGVm8qo68Cnx5awmVFnnW8fM,15921
53
+ src/application/use_cases/suppress_finding.py,sha256=a9oXVFZmvNFrKbEMNXepAu9dEIUqqBb-H1ZSjM0hcAs,18292
54
+ src/application/use_cases/validate_policy_pack.py,sha256=OaHfs_coNFR9s6c2I7MKFVZRy2Hw2q_y9_B48poOZrE,9906
55
+ src/branding/__init__.py,sha256=BJ7o5Z13LvWnUVb1aN1tz2XlFk7h89MQGkB2nJuJO0s,81
56
+ src/branding/badge_builder.py,sha256=y-YYE3UCWuZ6aBte857TIgaFXJ4rpgqDLr5qBnP45jI,5038
57
+ src/branding/badge_data.py,sha256=ZJGQsbguX3p9n6_XQbyO7UBEc-Guub731Q1Vbe8YCSM,1978
58
+ src/demo/__init__.py,sha256=0sJ_1xV65ND-HNZjUBMFtIFdiqfskl5BR_BDkR9Yoq4,377
59
+ src/demo/demo_runner.py,sha256=s3ghZ6VJLlol6qPjNiuHV0fvSJd64gqACTgfC08nOzU,11057
60
+ src/demo/fake_ai_provider.py,sha256=PbdZoY0rlWoA-LUx0oD9ERjZL69N1BoohQbgRYLcLLk,4150
61
+ src/demo/scenarios/__init__.py,sha256=OSXSvpqm4xi6HElA0-xZjUcvTFgMZQ91aHMHwwvZyiM,4169
62
+ src/demo/scenarios/laravel_bug_fix.py,sha256=OOh4AVWYDah0sCQ2kU4UaqRM5I56nSqhG5EYOhVQQSY,20904
63
+ src/demo/scenarios/security_issue.py,sha256=dnrnWyUDOfUTk2ANbwXQTnuXk50tohG7AtdPdthVjyE,24332
64
+ src/domain/baseline/__init__.py,sha256=mHQRdNMh_VMUzm2mVFqwHdcqI_d9ipxEA0CJwH010Cs,3945
65
+ src/domain/baseline/baseline_comparator.py,sha256=G0QUBpDpxUHjIepevOp9S8RQ3wpG1OmEEMYEX2ST6Yc,8970
66
+ src/domain/baseline/baseline_fingerprint.py,sha256=8RqBJMnz9yqRvqzpldV7_ynv_hHOiu-Hi1n8ubomV0A,4473
67
+ src/domain/baseline/baseline_manifest.py,sha256=q3svlaqso0mYGIP0xQQSmA_oWLDbQmfl3AmAbawYDvE,10720
68
+ src/domain/baseline/baseline_types.py,sha256=oRMdXxAdpx94Ur-n9XwSSO--d-CC4w2-k6M549h6ADI,11509
69
+ src/domain/baseline/suppression_policy.py,sha256=x6W-4_Hl16q5lA3wgXWa0BkIkE4J5W3k0MCzhQ49dhM,23150
70
+ src/domain/check/__init__.py,sha256=OA8heAdclSkZRPY6pumHGPPq9yC6XL5FLD__JHUo85A,712
71
+ src/domain/check/check_config.py,sha256=b8--2g4PVu_CxnTvPhRagNpxFr7I3YtQFlRp4ox8zZI,3801
72
+ src/domain/check/check_output.py,sha256=6kAf0zkuQZi6J79fL7JrwEvbF8sQbly557rxt_Caz4Q,3946
73
+ src/domain/check/check_types.py,sha256=TS7oZEg82geIn-q601CV6NP4co1laN_HnqAhnE7aeYw,5221
74
+ src/domain/finding/__init__.py,sha256=j-veoGxtyF0TVJTQxV2jy-Jgy5k3u6dAzg6d-i8g2nI,171
75
+ src/domain/finding/finding_types.py,sha256=_ZVC-KB48qXlD1Q5y8aSwnfQD5GSeeKZUIkfJoSmHJU,858
76
+ src/domain/linter/__init__.py,sha256=Ic3z4fXCxESzLPZF7LLEzgyWjrEF_A6vh3QEL0ScYOo,244
77
+ src/domain/linter/sast_finding_mapper.py,sha256=oD7wLH0LlMcC-_6Luv6rKPNfynBRcDCoa-zRFx0M1dU,3419
78
+ src/domain/mentor/__init__.py,sha256=w1_wx0el3z03QwSiKB6mr8dyprML-AFUsE99Ez8_wUg,372
79
+ src/domain/mentor/mentor_explanation_builder.py,sha256=6eON3x20BEpi7bVdu3TCJh_qstkMYUoLpz-VfORVTqQ,7004
80
+ src/domain/policy/__init__.py,sha256=n15hFYtfI-mg0_Z47A6Bm4rqbjUklQXAxaHTTdr27F0,3617
81
+ src/domain/policy/policy_checksum.py,sha256=5rzJo31iAkbbuJREyNKm5FVOyL6SH6roYlxDXc9YYRg,2342
82
+ src/domain/policy/policy_compatibility.py,sha256=_optz7r56tF_rmq42DeA-34VbUolUcP6kvo5TcPJdDQ,8477
83
+ src/domain/policy/policy_manifest.py,sha256=d9PEvZKaGMg3koGEJ6XIwNevJGssrw3Q6VqUkvvcwq4,26986
84
+ src/domain/policy/policy_resolver.py,sha256=xduWDDaqU1T-BDSckhmyOViXwXZQiXLrPyOsh9qvB3U,29589
85
+ src/domain/policy/policy_types.py,sha256=IOgGVM6oIDrFmSdOkkNpwxkQpTgXIBqY1hxVY-RxMwo,10112
86
+ src/domain/pr/__init__.py,sha256=zCKL3_JVnOAfAoKQGT4dbpJmpF2mU9KKeumxIN50MzE,288
87
+ src/domain/pr/explain_section_builder.py,sha256=Nhnmm7166V1rEft4uRaNxx5_gV0MoCJOQkMOogbWzHg,5176
88
+ src/domain/risk/__init__.py,sha256=wvsE1QhTUVSwcnwDDMEnQTgdbzurCFJDxsbrjKjbve4,301
89
+ src/domain/risk/risk_calculator.py,sha256=ZSJeU6YaQAxQfo5mLyAqtvPVLuWT4kYKqFId8bXtq4M,5181
90
+ src/domain/risk/risk_explanation.py,sha256=JIoeP_GYvVe-1ul-HU43qedoCpgAjWuVtH0WvFdfTrM,3083
91
+ src/domain/risk/risk_rules.py,sha256=6dP4QVaaDwJbrdX401ahG_p_wNCG5JKIrvWAsq1X93g,8966
92
+ src/domain/risk/risk_types.py,sha256=-kgr_a1ky4RKhP17tm2Q33VD4C_pdz9GD5FK3Be2zow,4154
93
+ src/domain/tests_generation/__init__.py,sha256=TqKcuJhvH3v_S_1R8ju1dRFx-cmp9v0ewPYjyQsqxOU,265
94
+ src/domain/tests_generation/framework_detector.py,sha256=s4yN5ZjtCGX8hDiMHDPO9dZs8jWU2m8bBMP_Iihx0MM,4559
95
+ src/domain/tests_generation/test_content_types.py,sha256=tfBGn26jx5VMK2PxRkJf3Md25Z4ofCkLaNktvyzKDns,1062
96
+ src/domain/tests_generation/test_scaffold_builder.py,sha256=t3CUBuUchTPgKVgZicjCz-VS5Ea7WUhvd4dbbi2UV6g,3892
97
+ src/fix/__init__.py,sha256=tkdj-xF8PCJ6PaIRmH61W3vUn8vnRJXz00Atnf61Yo8,953
98
+ src/fix/apply_fix.py,sha256=XURlvTnkBjwLzdwixEy5v8PBAKCTam6B3CCSNMFEhTE,18391
99
+ src/fix/fix_history.py,sha256=jxUbwsSi4lqw-64zlVVj2suVnOrvRPrrgWVlzjRO4Gw,5779
100
+ src/fix/patch_applier.py,sha256=DJ5rS5tOM3v5g8l9dWVpNoalWhukj6wVRUwdTkXJACg,1277
101
+ src/fix/patch_extractor.py,sha256=8SdltGtbccBowHbnYd4BU-oUcEQegel_tB7o_2uK6wo,2575
102
+ src/fix/patch_provenance.py,sha256=7PlygeSHp4qSa-XmUUkEkV-N5aSeaHhRvNQA35aA-MQ,3291
103
+ src/fix/patch_safety_classifier.py,sha256=cEkzMyQkXQeCE-btY1Tl9xdvah6RMioQ_L5HAoqzqDs,4222
104
+ src/fix/rollback_fix.py,sha256=jwmISZP-EiaNFEKlSNdhiKWuPytV635BdDmzAurmegY,2570
105
+ src/infrastructure/__init__.py,sha256=FDQv7sZaZ3bZFyIm7MJap-99SdeDRqiLcXhZudjDYnk,82
106
+ src/infrastructure/baseline/__init__.py,sha256=FIG9NT1dTUA4i59xXIhwzLKmNCa7sOIf1KRWHO8-lE0,642
107
+ src/infrastructure/baseline/local_baseline_repository.py,sha256=6r4_qpzEGUFClD1Yt-Vm-ws5AQrDpzqsq70J3V0uBHA,13815
108
+ src/infrastructure/ci/__init__.py,sha256=epqkB76zTFODGQ2ZknO-x3-F83WOQ-ZtJXFaFYoPX70,345
109
+ src/infrastructure/ci/junit_builder.py,sha256=uxII_90biDKOm-l0xn2Q4QAAxDt0kl0oYAsQk4etBUQ,3921
110
+ src/infrastructure/ci/masking.py,sha256=neKqDWAlnlg2n78vCdb8v78mNylXxpSWxpsqNCJaKb0,2218
111
+ src/infrastructure/ci/sarif_builder.py,sha256=fbAshYS0BTBH3Vg_FEkzK8o3rSD6xOvHrq0g2K08wsc,5385
112
+ src/infrastructure/git/__init__.py,sha256=YERpBrzf1S76qi9rHqDuaq9_X5pDeKKXaAmw9C6SOuI,88
113
+ src/infrastructure/git/identity.py,sha256=SdcNyPF_eISGGye0PtxiSWAU4YyDcTIFYtQyKa6590Q,5877
114
+ src/infrastructure/git/patch_applier.py,sha256=x7xU2mdq6eEDJtpO7G4ZZKq1hsIJ81HiFCwdF2360Oc,8197
115
+ src/infrastructure/git/risk_history_reader.py,sha256=YtKSguDZgMqVXx3IpEXQCGB1yzuCro1S8KjaU8VkgpE,2826
116
+ src/infrastructure/git/selective_stager.py,sha256=yjztG4fV1mp-KHCvF7a-caGtcg_WUSrPAtYH96tGq0k,8236
117
+ src/infrastructure/git/test_matcher.py,sha256=Uk4f7f2XeVuUbLyQ7miGwAhOAN0m_YS3stqrumNPhRc,2664
118
+ src/infrastructure/linter/external/__init__.py,sha256=OOIw9u587FnscgyPBnR2X3cvNjT0iiNH9XY45DI5FOU,326
119
+ src/infrastructure/linter/external/bandit_bridge.py,sha256=U_e-h74S9LcnRMJxoJxPPzN9YKb69NZOrJjpFugBKCs,4457
120
+ src/infrastructure/linter/external/base_bridge.py,sha256=iMQwRWy5BibgWEDs-Pi3-YvKEmy452u5qG8yHYtmEhI,3158
121
+ src/infrastructure/linter/external/gitleaks_bridge.py,sha256=9v3jnDcgwVfJQS4M6jTvt69X061S7YshmiecVjaWLiY,6049
122
+ src/infrastructure/linter/external/semgrep_bridge.py,sha256=7iZMm2QY44_dUnpqeBIXLMkn0SAuuiswaSvGVeg-VtU,4643
123
+ src/infrastructure/policy/__init__.py,sha256=mgznDOqCM9iazgY8DTi3uJqrc9wqIQp9D5eCDa5tyAk,1084
124
+ src/infrastructure/policy/local_policy_repository.py,sha256=-dW4QR-rzUM6qwOSC5UhA51tg1iWAXkTxKcZJPK0eBo,5786
125
+ src/infrastructure/policy/policy_pack_loader.py,sha256=uRTp64ksVkdC3GBYgaHxkXUjeTk7UWPy0ePWVRo92k4,9559
126
+ src/infrastructure/scm/__init__.py,sha256=CbW2p9DsYpwwUqy4xdtgoPugsLijopfNwLT-IAfEcqA,1887
127
+ src/infrastructure/scm/azure_devops_provider.py,sha256=9IpgB-Vqzgj0VDFXaYs4PFRiecsQGU9svW5m3OpefsI,18790
128
+ src/infrastructure/scm/base.py,sha256=9tN2IivEngHoo1pDK3PivWHvHSkXokuyQQUJ680gsXU,17198
129
+ src/infrastructure/scm/bitbucket_provider.py,sha256=A58dMDa60lLUynENrinWENFz5dSA_lb9VJSg22ptbkI,16895
130
+ src/infrastructure/scm/factory.py,sha256=O3bRT6f15AozcldZfiH2LgOQ4g4GqMb0sbEWCIeF0HE,4487
131
+ src/infrastructure/scm/github_provider.py,sha256=vdJDTTYd-CYkAQos-6IZ2hFOJkXG09SrkIcZzHbuuIc,21316
132
+ src/infrastructure/scm/gitlab_provider.py,sha256=VAYjdg5gU43FDamXUNMdFzs9MwIGyNRqMzEnLTacRE8,17951
133
+ src/infrastructure/scm/web_links.py,sha256=-7oF7oUwK_UvKVJZWDPbTnKBi9JEQu2dMDYtgqH10iM,3724
134
+ src/issue_autoclose/__init__.py,sha256=9IuLWTrq7Vvk2pf3BWMtxVFJ0ALmmHOpYjG8Rwd_x5U,651
135
+ src/issue_autoclose/insert_close_line.py,sha256=MY8Hr1HWbdIflI6L0Drg9x3ouEJy9BbYxnm1PNguzxY,1779
136
+ src/issue_autoclose/issue_registry.py,sha256=ttZ8hjuBRWn3iAK9s-KYBybpsW7NRlVxpLyHzD8eKoY,10297
137
+ src/issue_autoclose/register_created_issue.py,sha256=6RwTYepPu1mvuF2T-RcKL3r9THk9zNwCthjg__uQ9JI,2871
138
+ src/issue_autoclose/resolve_close_issue.py,sha256=IS-F39Kp8yvQJhKozIwy2gFEtCy5m1geCwf4cdcdsWo,8094
139
+ src/policy_packs/laravel-quality/CHANGELOG.md,sha256=g7f9coAAKmPMa3IxpI1bduvr1WfapgfM6KE_kBs5vLM,1154
140
+ src/policy_packs/laravel-quality/README.md,sha256=MhSQV-p8F6jmBEenvDvklseQ3FDvnvvmN6A16L6-x4s,1852
141
+ src/policy_packs/laravel-quality/linter.yml,sha256=bzA9yWf1obx3qDs4TCPzfkinOUJaScDI7mK8BlAm8xo,3191
142
+ src/policy_packs/laravel-quality/policy.yml,sha256=mQFsaJn1_QZ7lfHB24Sz3qvAN6CsEoI9oYwNDD5ARdg,3813
143
+ src/policy_packs/node-quality/README.md,sha256=1C_o-8y6jsI2x963KjQ0VeSB4MlxDHK3ACEYO81rmPI,1313
144
+ src/policy_packs/node-quality/linter.yml,sha256=0211mNdOU70N5C-U4tT1qivkis5_bptU_y6fLGnRc1Q,2874
145
+ src/policy_packs/node-quality/policy.yml,sha256=7BByVT_qE9q5YIT0Tt2_7eM9eTMdcFF_HsCugc-q0uQ,3219
146
+ src/policy_packs/php-security/README.md,sha256=wVOu2uPyDR0sAjo605jTijJ6AprYKNRma5MRFqAIEqw,1489
147
+ src/policy_packs/php-security/linter.yml,sha256=2WTX_2YajaH7wwB7t-A-W4u317jG7wkf5jW5qrhsvdQ,3260
148
+ src/policy_packs/php-security/policy.yml,sha256=cQkffWrRgLu-KUyDaLuTuJ_9ahwLySNbAV0OO9GP8kY,3258
149
+ src/policy_packs/vue-quality/README.md,sha256=2CCPyZbePU4Aiq06lygcVlOcFtNALJaxh0-xt7t4ZPM,1293
150
+ src/policy_packs/vue-quality/linter.yml,sha256=_G_CMBB4hvo-mkRI0rv77530Es5GnXoNipD5wpfPwvg,2508
151
+ src/policy_packs/vue-quality/policy.yml,sha256=FyVL17Fl_bgVjxZU5lO7MME5w04vjKV3UfISOBkGBXc,3099
152
+ src/review/__init__.py,sha256=ux2rkwebfnzuQpFc57eEceOC4CZkojPOtfp951Ux5Gc,700
153
+ src/review/diff_normalizer.py,sha256=8KnFbauGK-fuLGSSL_ox4kAwCE048MuTmorPwBrhlEU,3675
154
+ src/review/diff_source.py,sha256=V6fMtgxNJbMBL_qRifkLGfzFwtaTCbFFKFvcutHZjLk,2733
155
+ src/review/remote_pr.py,sha256=9oEEo-v-D08egxwN_EINh5Ju74vow91-CAH1kAb2ql0,13669
156
+ src/review/render.py,sha256=l63vQ3WiGxPSzCk4JxyQoFNfIyG0AdHgMobAjMeaOcY,3194
157
+ src/split/__init__.py,sha256=8XZn4jwPhQ0pJyNEMTBfgbGTMJkQUvePNp5ZncWCJ-Q,929
158
+ src/split/apply_split_plan.py,sha256=ZKoACWLCaBndLzvRl8wHyAyPlalTrjrPJ2ep_AjpWCE,10929
159
+ src/split/generate_split_plan.py,sha256=60nMWOsokcweWE6tX2RJqeJ_8o-XByAMtg2Zcmwumt0,9008
160
+ src/split/hunk_grouper.py,sha256=35PU7I_9iUzaN9xaJvoK2y9KGkeKjKofO_7l6xOWWks,14596
161
+ src/split/hunk_parser.py,sha256=DYc82pCKnuquJJwB7nm44WtZKK22lv-d75g9EeBhCm8,10266
162
+ src/split/split_plan.py,sha256=Y0Ah3LbzixKgAy74dh7XiQImsw6-f2dxwfJSkQHreCw,6572
163
+ src/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
164
+ src/ui/chat_app.py,sha256=eOhGsaC2EweYNJe9kdCbN9AMYcN_LI2BCj_tmOYZ7Bs,26789
165
+ src/ui/commit_app.py,sha256=mggIsdHkg3EIvQoSc11XL7tpkSGJ25WWszHdgmJnMjc,8304
166
+ src/ui/config_app.py,sha256=7-Cwcygft2VQm_zoUr9VDHp2DrXPiQTpCZ4qQwGdRng,94655
167
+ src/ui/help_screen.py,sha256=PxdLLey13DycZpIY0nj2Zd0DSF1uV02Gcrq3Ny88CuU,1723
168
+ src/ui/issue_app.py,sha256=ea8RYK0QcS9izoJ9e60TTTznOX8QVhMREW_GQEyi_xM,10192
169
+ src/ui/linter_app.py,sha256=xVMMZJhVQAo6XKI_MrRQvnGXNoDjELRs7k8rNaF4yws,1602
170
+ src/ui/metrics_app.py,sha256=dkXXb6-JkvBGrsjkvbk0A_sfsCCrcBC6pXiJBsvxCbA,17023
171
+ src/ui/metrics_migration_app.py,sha256=U6l-ORUk1NlC2aIQSLvFjahMVM1dat-wFjWlct0fRYQ,6096
172
+ src/ui/pr_base_app.py,sha256=OvPcQVeyBrGBH8lhlVy0Ks1kVdvqcYHl7Zc3P2tXPGI,3857
173
+ src/ui/pr_publish_app.py,sha256=TU5IrSB3bkQEhl2N0W8KCwHfRGGacijqziMD9I7By04,71802
174
+ src/ui/pr_publish_help.py,sha256=3fcTbrQIoPM72kTbNjFu4PAin592DuNQlNIn_AeRmVk,1941
175
+ src/ui/demo/__init__.py,sha256=aOdUN0izuwW2FfLAHG9Qga-DJG5NBVqP0gYNO8OiKls,59
176
+ src/ui/demo/demo_app.py,sha256=4t_Mct-BJXpe5tlt1XmtRyN1IT0OL6U3WSmGqOJqMG4,3165
177
+ src/ui/demo/demo_help_screen.py,sha256=I_LELswhPtbHjfMWGVUqLtOwxGbzSYJsVWXjqZrLoLA,2108
178
+ src/ui/demo/demo_screens.py,sha256=GXaQFTgTn9awSP-EYiBXsCZqB583WrSQcuNYoBK12g8,1997
179
+ gitpr_cli-1.6.0.dist-info/METADATA,sha256=2X1Zhfrrpr5qke816yN3qEFunDO6FK1BQy60YYlXTtE,54016
180
+ gitpr_cli-1.6.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
181
+ gitpr_cli-1.6.0.dist-info/entry_points.txt,sha256=TstGnMUzdOI5qNmpM97EPDBPuMqOZxoo6EoLxHSOMSQ,71
182
+ gitpr_cli-1.6.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
183
+ gitpr_cli-1.6.0.dist-info/RECORD,,
src/__init__.py CHANGED
@@ -1 +1 @@
1
- # Módulo principal do GitPR
1
+ # Módulo principal do GitPR
@@ -1,2 +1,2 @@
1
- # Application layer markers
2
-
1
+ # Application layer markers
2
+
@@ -1,8 +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
- ]
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
+ ]
@@ -1,122 +1,122 @@
1
- """Use case: record which pack a repository follows.
2
-
3
- The record is ``.gitpr/policy.lock.yml``, a file meant to be committed: it is
4
- what makes the policy the same for everyone on the team and what lets a later
5
- run notice that a pack changed under it.
6
-
7
- Activation is **one pack per repository**. Choosing a second one replaces the
8
- first rather than adding to it, so there is never a question about which pack
9
- owns a value — the graph below the root is reached through ``extends``, and only
10
- through it.
11
-
12
- Building the lockfile and writing it are separate functions because the CLI has
13
- to show the user what will be written *before* asking whether to write it. A
14
- command that writes first and asks afterwards is not asking.
15
- """
16
-
17
- import os
18
- from datetime import datetime, timezone
19
- from typing import Any
20
-
21
- from src.domain.policy import set_active_policy
22
- from src.domain.policy.policy_resolver import resolve_graph
23
- from src.i18n import __
24
- from src.infrastructure.policy.local_policy_repository import (
25
- LOCKFILE_SCHEMA_VERSION,
26
- lockfile_path,
27
- write_lockfile,
28
- )
29
- from src.infrastructure.policy.policy_pack_loader import (
30
- load_pack_by_reference,
31
- make_loader,
32
- )
33
-
34
- from src.application.use_cases.validate_policy_pack import split_reference
35
-
36
-
37
- def _relative_to_repo(absolute: str, repo_path: str) -> str | None:
38
- """*absolute* as a repository-relative POSIX path, or None when outside.
39
-
40
- Only packs that live inside the repository are recorded by path. A pack in
41
- ``~/.gitpr/policies/`` is recorded by name and version instead, so the
42
- lockfile stays valid for a teammate who has it installed somewhere else.
43
- """
44
- try:
45
- relative = os.path.relpath(absolute, repo_path)
46
- except ValueError:
47
- # Different drives on Windows.
48
- return None
49
- parts = relative.split(os.sep)
50
- if not relative or parts[0] == "..":
51
- return None
52
- return relative.replace(os.sep, "/")
53
-
54
-
55
- def _lockfile_entry(pack: Any, repo_path: str) -> dict[str, Any]:
56
- entry: dict[str, Any] = {
57
- "name": pack.reference.name,
58
- "version": pack.reference.version,
59
- "source": pack.reference.source.value,
60
- "checksum": pack.reference.checksum,
61
- }
62
- if pack.reference.path:
63
- relative = _relative_to_repo(pack.reference.path, repo_path)
64
- if relative:
65
- entry["path"] = relative
66
- return entry
67
-
68
-
69
- def build_lockfile(root_reference: str, repo_path: str | None = None) -> dict[str, Any]:
70
- """Resolves *root_reference* and the lockfile that would pin it.
71
-
72
- Raises ``PolicyError`` when the pack cannot be found or its graph does not
73
- resolve, and writes nothing. The CLI calls this first, shows the result, and
74
- only then decides whether to call :func:`activate_policy_pack`.
75
- """
76
- from src.updater import __version__
77
-
78
- repo = os.path.abspath(repo_path or os.getcwd())
79
- name, version_range = split_reference(root_reference)
80
- root = load_pack_by_reference(name, version_range, repo)
81
- packs = resolve_graph(root, make_loader(repo))
82
-
83
- return {
84
- "schema_version": LOCKFILE_SCHEMA_VERSION,
85
- "gitpr_version": __version__,
86
- "activated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
87
- "root": _lockfile_entry(root, repo),
88
- "packs": [_lockfile_entry(pack, repo) for pack in packs],
89
- }
90
-
91
-
92
- def activate_policy_pack(root_reference: str, repo_path: str | None = None) -> str:
93
- """Pins *root_reference* for the repository and returns the lockfile path."""
94
- data = build_lockfile(root_reference, repo_path)
95
- return write_lockfile(data, repo_path)
96
-
97
-
98
- def deactivate_policy_pack(repo_path: str | None = None) -> bool:
99
- """Removes the lockfile. Returns whether there was one to remove.
100
-
101
- The overrides file and the packs themselves are left alone: they are the
102
- user's material, and a decision to stop following a pack is not a decision to
103
- delete it.
104
- """
105
- target = lockfile_path(repo_path)
106
- if not os.path.isfile(target):
107
- return False
108
- os.remove(target)
109
- set_active_policy(None)
110
- return True
111
-
112
-
113
- def describe_lockfile(data: dict[str, Any]) -> str:
114
- """A one-line summary for the confirmation prompt."""
115
- root = data.get("root") or {}
116
- dependencies = [p for p in data.get("packs", []) if p.get("name") != root.get("name")]
117
- return __(
118
- "{pack}@{version} with {count} dependency pack(s)",
119
- pack=root.get("name", "?"),
120
- version=root.get("version", "?"),
121
- count=len(dependencies),
122
- )
1
+ """Use case: record which pack a repository follows.
2
+
3
+ The record is ``.gitpr/policy.lock.yml``, a file meant to be committed: it is
4
+ what makes the policy the same for everyone on the team and what lets a later
5
+ run notice that a pack changed under it.
6
+
7
+ Activation is **one pack per repository**. Choosing a second one replaces the
8
+ first rather than adding to it, so there is never a question about which pack
9
+ owns a value — the graph below the root is reached through ``extends``, and only
10
+ through it.
11
+
12
+ Building the lockfile and writing it are separate functions because the CLI has
13
+ to show the user what will be written *before* asking whether to write it. A
14
+ command that writes first and asks afterwards is not asking.
15
+ """
16
+
17
+ import os
18
+ from datetime import datetime, timezone
19
+ from typing import Any
20
+
21
+ from src.domain.policy import set_active_policy
22
+ from src.domain.policy.policy_resolver import resolve_graph
23
+ from src.i18n import __
24
+ from src.infrastructure.policy.local_policy_repository import (
25
+ LOCKFILE_SCHEMA_VERSION,
26
+ lockfile_path,
27
+ write_lockfile,
28
+ )
29
+ from src.infrastructure.policy.policy_pack_loader import (
30
+ load_pack_by_reference,
31
+ make_loader,
32
+ )
33
+
34
+ from src.application.use_cases.validate_policy_pack import split_reference
35
+
36
+
37
+ def _relative_to_repo(absolute: str, repo_path: str) -> str | None:
38
+ """*absolute* as a repository-relative POSIX path, or None when outside.
39
+
40
+ Only packs that live inside the repository are recorded by path. A pack in
41
+ ``~/.gitpr/policies/`` is recorded by name and version instead, so the
42
+ lockfile stays valid for a teammate who has it installed somewhere else.
43
+ """
44
+ try:
45
+ relative = os.path.relpath(absolute, repo_path)
46
+ except ValueError:
47
+ # Different drives on Windows.
48
+ return None
49
+ parts = relative.split(os.sep)
50
+ if not relative or parts[0] == "..":
51
+ return None
52
+ return relative.replace(os.sep, "/")
53
+
54
+
55
+ def _lockfile_entry(pack: Any, repo_path: str) -> dict[str, Any]:
56
+ entry: dict[str, Any] = {
57
+ "name": pack.reference.name,
58
+ "version": pack.reference.version,
59
+ "source": pack.reference.source.value,
60
+ "checksum": pack.reference.checksum,
61
+ }
62
+ if pack.reference.path:
63
+ relative = _relative_to_repo(pack.reference.path, repo_path)
64
+ if relative:
65
+ entry["path"] = relative
66
+ return entry
67
+
68
+
69
+ def build_lockfile(root_reference: str, repo_path: str | None = None) -> dict[str, Any]:
70
+ """Resolves *root_reference* and the lockfile that would pin it.
71
+
72
+ Raises ``PolicyError`` when the pack cannot be found or its graph does not
73
+ resolve, and writes nothing. The CLI calls this first, shows the result, and
74
+ only then decides whether to call :func:`activate_policy_pack`.
75
+ """
76
+ from src.updater import __version__
77
+
78
+ repo = os.path.abspath(repo_path or os.getcwd())
79
+ name, version_range = split_reference(root_reference)
80
+ root = load_pack_by_reference(name, version_range, repo)
81
+ packs = resolve_graph(root, make_loader(repo))
82
+
83
+ return {
84
+ "schema_version": LOCKFILE_SCHEMA_VERSION,
85
+ "gitpr_version": __version__,
86
+ "activated_at": datetime.now(timezone.utc).isoformat(timespec="seconds"),
87
+ "root": _lockfile_entry(root, repo),
88
+ "packs": [_lockfile_entry(pack, repo) for pack in packs],
89
+ }
90
+
91
+
92
+ def activate_policy_pack(root_reference: str, repo_path: str | None = None) -> str:
93
+ """Pins *root_reference* for the repository and returns the lockfile path."""
94
+ data = build_lockfile(root_reference, repo_path)
95
+ return write_lockfile(data, repo_path)
96
+
97
+
98
+ def deactivate_policy_pack(repo_path: str | None = None) -> bool:
99
+ """Removes the lockfile. Returns whether there was one to remove.
100
+
101
+ The overrides file and the packs themselves are left alone: they are the
102
+ user's material, and a decision to stop following a pack is not a decision to
103
+ delete it.
104
+ """
105
+ target = lockfile_path(repo_path)
106
+ if not os.path.isfile(target):
107
+ return False
108
+ os.remove(target)
109
+ set_active_policy(None)
110
+ return True
111
+
112
+
113
+ def describe_lockfile(data: dict[str, Any]) -> str:
114
+ """A one-line summary for the confirmation prompt."""
115
+ root = data.get("root") or {}
116
+ dependencies = [p for p in data.get("packs", []) if p.get("name") != root.get("name")]
117
+ return __(
118
+ "{pack}@{version} with {count} dependency pack(s)",
119
+ pack=root.get("name", "?"),
120
+ version=root.get("version", "?"),
121
+ count=len(dependencies),
122
+ )