gitpr-cli 1.4.3__py3-none-any.whl → 1.5.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 (171) hide show
  1. {gitpr_cli-1.4.3.dist-info → gitpr_cli-1.5.0.dist-info}/METADATA +20 -1
  2. gitpr_cli-1.5.0.dist-info/RECORD +181 -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 +709 -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 +448 -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 +14 -0
  27. src/config_schema.py +1357 -1270
  28. src/core.py +82 -8
  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 +321 -321
  36. src/doc_links.py +28 -28
  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/infrastructure/__init__.py +1 -1
  80. src/infrastructure/baseline/__init__.py +28 -28
  81. src/infrastructure/baseline/local_baseline_repository.py +355 -355
  82. src/infrastructure/ci/__init__.py +11 -11
  83. src/infrastructure/ci/junit_builder.py +102 -102
  84. src/infrastructure/ci/masking.py +65 -65
  85. src/infrastructure/ci/sarif_builder.py +152 -152
  86. src/infrastructure/git/__init__.py +1 -1
  87. src/infrastructure/git/identity.py +155 -155
  88. src/infrastructure/git/patch_applier.py +209 -209
  89. src/infrastructure/git/risk_history_reader.py +93 -93
  90. src/infrastructure/git/selective_stager.py +198 -198
  91. src/infrastructure/git/test_matcher.py +78 -78
  92. src/infrastructure/linter/external/__init__.py +9 -9
  93. src/infrastructure/linter/external/bandit_bridge.py +127 -127
  94. src/infrastructure/linter/external/base_bridge.py +95 -95
  95. src/infrastructure/linter/external/gitleaks_bridge.py +168 -168
  96. src/infrastructure/linter/external/semgrep_bridge.py +139 -139
  97. src/infrastructure/policy/__init__.py +45 -45
  98. src/infrastructure/policy/local_policy_repository.py +142 -142
  99. src/infrastructure/policy/policy_pack_loader.py +240 -240
  100. src/infrastructure/scm/__init__.py +62 -62
  101. src/infrastructure/scm/azure_devops_provider.py +464 -464
  102. src/infrastructure/scm/base.py +420 -352
  103. src/infrastructure/scm/bitbucket_provider.py +402 -402
  104. src/infrastructure/scm/factory.py +97 -97
  105. src/infrastructure/scm/github_provider.py +564 -531
  106. src/infrastructure/scm/gitlab_provider.py +435 -435
  107. src/infrastructure/scm/web_links.py +103 -103
  108. src/issue_autoclose/__init__.py +12 -0
  109. src/issue_autoclose/insert_close_line.py +46 -0
  110. src/issue_autoclose/issue_registry.py +235 -0
  111. src/issue_autoclose/register_created_issue.py +80 -0
  112. src/issue_autoclose/resolve_close_issue.py +226 -0
  113. src/linter_wizard.py +210 -210
  114. src/main.py +363 -15
  115. src/mcp_server.py +52 -31
  116. src/metrics.py +1105 -1105
  117. src/net.py +74 -74
  118. src/policy_packs/laravel-quality/CHANGELOG.md +25 -25
  119. src/policy_packs/laravel-quality/README.md +41 -41
  120. src/policy_packs/laravel-quality/linter.yml +61 -61
  121. src/policy_packs/laravel-quality/policy.yml +110 -110
  122. src/policy_packs/node-quality/README.md +27 -27
  123. src/policy_packs/node-quality/linter.yml +57 -57
  124. src/policy_packs/node-quality/policy.yml +82 -82
  125. src/policy_packs/php-security/README.md +33 -33
  126. src/policy_packs/php-security/linter.yml +69 -69
  127. src/policy_packs/php-security/policy.yml +75 -75
  128. src/policy_packs/vue-quality/README.md +29 -29
  129. src/policy_packs/vue-quality/linter.yml +50 -50
  130. src/policy_packs/vue-quality/policy.yml +78 -78
  131. src/project_config.py +426 -0
  132. src/release_engine.py +990 -990
  133. src/review/__init__.py +13 -13
  134. src/review/diff_normalizer.py +84 -84
  135. src/review/diff_source.py +69 -69
  136. src/review/remote_pr.py +350 -350
  137. src/review/render.py +83 -83
  138. src/reviewer_resolution.py +159 -159
  139. src/reviewer_suggestion.py +258 -258
  140. src/security_ruleset.py +115 -108
  141. src/spinner.py +257 -257
  142. src/split/__init__.py +17 -17
  143. src/split/apply_split_plan.py +251 -251
  144. src/split/generate_split_plan.py +222 -222
  145. src/split/hunk_grouper.py +347 -347
  146. src/split/hunk_parser.py +256 -256
  147. src/split/split_plan.py +153 -153
  148. src/suggest_reviewers.py +146 -146
  149. src/tui_issue.py +281 -281
  150. src/ui/chat_app.py +11 -11
  151. src/ui/commit_app.py +220 -0
  152. src/ui/config_app.py +2043 -1835
  153. src/ui/demo/__init__.py +1 -1
  154. src/ui/demo/demo_app.py +91 -91
  155. src/ui/demo/demo_help_screen.py +52 -52
  156. src/ui/demo/demo_screens.py +60 -60
  157. src/ui/issue_app.py +22 -0
  158. src/ui/linter_app.py +46 -46
  159. src/ui/metrics_app.py +456 -456
  160. src/ui/metrics_migration_app.py +173 -173
  161. src/ui/pr_base_app.py +98 -0
  162. src/ui/pr_publish_app.py +1783 -1775
  163. src/ui/pr_publish_help.py +50 -50
  164. src/updater.py +274 -274
  165. src/usage_log.py +127 -127
  166. src/version_bump.py +87 -87
  167. gitpr_cli-1.4.3.dist-info/RECORD +0 -173
  168. {gitpr_cli-1.4.3.dist-info → gitpr_cli-1.5.0.dist-info}/WHEEL +0 -0
  169. {gitpr_cli-1.4.3.dist-info → gitpr_cli-1.5.0.dist-info}/entry_points.txt +0 -0
  170. {gitpr_cli-1.4.3.dist-info → gitpr_cli-1.5.0.dist-info}/licenses/LICENSE +0 -0
  171. {gitpr_cli-1.4.3.dist-info → gitpr_cli-1.5.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.3
3
+ Version: 1.5.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,181 @@
1
+ gitpr_cli-1.5.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=3O6h-A_gVJLxxG9M1L6fHCk-6UeP4tscONepZHInWHM,54130
10
+ src/config_schema.py,sha256=hqfiNQgyJQ7l_f4VU315gMEUUG_D8490aXDPwMmFw4I,48087
11
+ src/core.py,sha256=w29W2Tyx4DqWfLcZ1svMDyEwLfFpVZbFmFi79QE-mLc,88666
12
+ src/diff_parser.py,sha256=ZJxiDMWLglk6QADS37S-oMDhuCvZ5eiKOnCWy6E-Avg,11573
13
+ src/doc_links.py,sha256=xc9An9_MyLRYGo_4K5qXRqemG5t_QmWTik8lh8Rlpjo,1185
14
+ src/github_api.py,sha256=i1o58oKWWmMCerP-g-hOQHGCwPWd01ooKntTKjpFhIA,3948
15
+ src/i18n.py,sha256=PdUmtZlH1zogbiDc3j6xiwtnvUd7XiNbiS-xKjYK4WM,4844
16
+ src/issue_engine.py,sha256=RmZsQVg6N1vCcpMdv_5FX-cMGgQ3I0vwMN4parQ4zFg,10974
17
+ src/ledger.py,sha256=s10Sm0qcA1b916X7m8d2osDThCmSK0UQnmUrkyj8Eao,43663
18
+ src/linter_engine.py,sha256=hIxht8M09Fnm6NiJZOqjI6dyqnFoHUDVA8Ve4dWZV4s,25214
19
+ src/linter_wizard.py,sha256=mo8aSTZmVLluCOskWv3nji74WPJh_6c122zwADN9aCc,7634
20
+ src/main.py,sha256=W7AWcM4vnaqwXSeg30Sc5_uBTK9-HGWFsUVPwOFiGaY,229742
21
+ src/mcp_server.py,sha256=SoTS23WbkmE6VowIdQOcUiFkXKlP5aDrr7MSJX5KD5o,112417
22
+ src/metrics.py,sha256=g39FYKx-LAuVyjZLxZCU66iulne8mhTLzPbINk5TFnU,40665
23
+ src/net.py,sha256=CjhgmQnp4OHVmO-3Lm-DLhNfo2RW0M9FnD_HYPKcnNI,2848
24
+ src/project_config.py,sha256=9mXC5GKLv3pgQvlc50XCBnMVvTtbWCwIHAi-J48XXyA,15278
25
+ src/release_engine.py,sha256=gowcInpeAA581YheeBbRk9dKz3CL-K-zY7kEdEw6DqA,38423
26
+ src/reviewer_resolution.py,sha256=bFRdf4GVWIpz4kk7cyF7EYWV43aP7V8sAlNGqKRPd7A,5913
27
+ src/reviewer_suggestion.py,sha256=4tFwfltsEvx3s8RnUDKMaFGhdtWPWfgM0wqCTzEzWjE,8759
28
+ src/security.py,sha256=fkWy-r4tdISH0xOyWZyR8ZCFyLuYqu8fn9sKsIeNAso,1351
29
+ src/security_ruleset.py,sha256=4mqmqtWLHuhuCcjjaoKUM_YFzil4cWZ_ubjlOLgbM_4,4874
30
+ src/spinner.py,sha256=8w3_BpIi-EkqmjP2OspALx9lWNyn8XwLw44gCTBeQv8,9221
31
+ src/suggest_reviewers.py,sha256=WITj8lZOomwSL703Rw-cFBr59LnQ9t0Pl2kALCh9AA8,5327
32
+ src/tui_issue.py,sha256=zNGglq_Xk8qTmoFmoQpIZjgIUz7fX7rvyp1fnBuUbME,10612
33
+ src/updater.py,sha256=7UeSsDUbYT4OrvX8UnEP0WmUWYZ58oYBS0huCPZn9mo,9560
34
+ src/usage_log.py,sha256=CBCNlUm_xftXsBaGRHvsEjTENvZfE-lW70HGLMkisSg,4508
35
+ src/version_bump.py,sha256=Dw1LZ_c6VzaO_s9cDeYSevoWtXVr_oP70eS_mkrOm8s,3041
36
+ src/application/__init__.py,sha256=3YXFcmxJh0f7BwryKXxUrOhGrLnZLsagV-VbBIk0eGk,31
37
+ src/application/use_cases/__init__.py,sha256=jgOjxxTdvgGTmaHA7VIcAnNoDKIdvqOs8IYhCV3N3aE,292
38
+ src/application/use_cases/activate_policy_pack.py,sha256=U6nNsESRDJv4bW_v__qc7zSo2LxiLcBkkppievfiSls,4627
39
+ src/application/use_cases/baseline_gate.py,sha256=-3c5iy7i-HJ7_lsgHGftc8JDYOct0OnrkppQEJn47HE,16734
40
+ src/application/use_cases/baseline_summary.py,sha256=BK5LibkNbQsIRmYvqv8AcS_kkC1b_Lt9Q6ZiOMzYCO0,10550
41
+ src/application/use_cases/calculate_risk.py,sha256=Kgd6YVTrt6DtizXAZQ97XCYd0TBXXytiloauke-L-ko,13513
42
+ src/application/use_cases/compare_against_baseline.py,sha256=hePa-yidv9OfBe8AZoNhu6FA5MBg5izZyL5K9ZAOWAI,1916
43
+ src/application/use_cases/create_baseline.py,sha256=amVigQG-E3yCqzPNmCK9wazSC3Rh4sXJgntqhgg6cUU,26637
44
+ src/application/use_cases/generate_mentor_explanation.py,sha256=pPXSLYBJwum6a2P1TYv_B1zwAuM3xYtT8T2LhKO60rY,9707
45
+ src/application/use_cases/generate_pr_explanation.py,sha256=vlR6Lw7XBnHD7Cloz2gZMrW-kx31cBeC2kSM0qSnUjk,3418
46
+ src/application/use_cases/generate_test_file.py,sha256=U6EyPbZSXEv9A34PYHkQDToenjqu5Fu1h-nIBqcT9Ds,9657
47
+ src/application/use_cases/install_policy_pack.py,sha256=7s8wUL_SticgRnUMpzLJCmHso6y6tDHLibSdBLyXb0M,2816
48
+ src/application/use_cases/publish_pull_request.py,sha256=sBQTBhFGJ98YKTqatZdEccTHfNrp3rxZ_qbKUR9nZrw,9762
49
+ src/application/use_cases/resolve_effective_policy.py,sha256=HQruehhgqzT5cX2YcVftm1cZqiwDS5cAkRO0txj3hns,7771
50
+ src/application/use_cases/run_check.py,sha256=fWzMj91Kp5SXgmCwdWS5IjALXX0wNa3-M7yoHWID27g,15835
51
+ src/application/use_cases/suppress_finding.py,sha256=a9oXVFZmvNFrKbEMNXepAu9dEIUqqBb-H1ZSjM0hcAs,18292
52
+ src/application/use_cases/validate_policy_pack.py,sha256=OaHfs_coNFR9s6c2I7MKFVZRy2Hw2q_y9_B48poOZrE,9906
53
+ src/branding/__init__.py,sha256=BJ7o5Z13LvWnUVb1aN1tz2XlFk7h89MQGkB2nJuJO0s,81
54
+ src/branding/badge_builder.py,sha256=y-YYE3UCWuZ6aBte857TIgaFXJ4rpgqDLr5qBnP45jI,5038
55
+ src/branding/badge_data.py,sha256=ZJGQsbguX3p9n6_XQbyO7UBEc-Guub731Q1Vbe8YCSM,1978
56
+ src/demo/__init__.py,sha256=0sJ_1xV65ND-HNZjUBMFtIFdiqfskl5BR_BDkR9Yoq4,377
57
+ src/demo/demo_runner.py,sha256=s3ghZ6VJLlol6qPjNiuHV0fvSJd64gqACTgfC08nOzU,11057
58
+ src/demo/fake_ai_provider.py,sha256=PbdZoY0rlWoA-LUx0oD9ERjZL69N1BoohQbgRYLcLLk,4150
59
+ src/demo/scenarios/__init__.py,sha256=OSXSvpqm4xi6HElA0-xZjUcvTFgMZQ91aHMHwwvZyiM,4169
60
+ src/demo/scenarios/laravel_bug_fix.py,sha256=OOh4AVWYDah0sCQ2kU4UaqRM5I56nSqhG5EYOhVQQSY,20904
61
+ src/demo/scenarios/security_issue.py,sha256=dnrnWyUDOfUTk2ANbwXQTnuXk50tohG7AtdPdthVjyE,24332
62
+ src/domain/baseline/__init__.py,sha256=mHQRdNMh_VMUzm2mVFqwHdcqI_d9ipxEA0CJwH010Cs,3945
63
+ src/domain/baseline/baseline_comparator.py,sha256=G0QUBpDpxUHjIepevOp9S8RQ3wpG1OmEEMYEX2ST6Yc,8970
64
+ src/domain/baseline/baseline_fingerprint.py,sha256=8RqBJMnz9yqRvqzpldV7_ynv_hHOiu-Hi1n8ubomV0A,4473
65
+ src/domain/baseline/baseline_manifest.py,sha256=q3svlaqso0mYGIP0xQQSmA_oWLDbQmfl3AmAbawYDvE,10720
66
+ src/domain/baseline/baseline_types.py,sha256=oRMdXxAdpx94Ur-n9XwSSO--d-CC4w2-k6M549h6ADI,11509
67
+ src/domain/baseline/suppression_policy.py,sha256=x6W-4_Hl16q5lA3wgXWa0BkIkE4J5W3k0MCzhQ49dhM,23150
68
+ src/domain/check/__init__.py,sha256=OA8heAdclSkZRPY6pumHGPPq9yC6XL5FLD__JHUo85A,712
69
+ src/domain/check/check_config.py,sha256=b8--2g4PVu_CxnTvPhRagNpxFr7I3YtQFlRp4ox8zZI,3801
70
+ src/domain/check/check_output.py,sha256=6kAf0zkuQZi6J79fL7JrwEvbF8sQbly557rxt_Caz4Q,3946
71
+ src/domain/check/check_types.py,sha256=TS7oZEg82geIn-q601CV6NP4co1laN_HnqAhnE7aeYw,5221
72
+ src/domain/finding/__init__.py,sha256=j-veoGxtyF0TVJTQxV2jy-Jgy5k3u6dAzg6d-i8g2nI,171
73
+ src/domain/finding/finding_types.py,sha256=_ZVC-KB48qXlD1Q5y8aSwnfQD5GSeeKZUIkfJoSmHJU,858
74
+ src/domain/linter/__init__.py,sha256=Ic3z4fXCxESzLPZF7LLEzgyWjrEF_A6vh3QEL0ScYOo,244
75
+ src/domain/linter/sast_finding_mapper.py,sha256=oD7wLH0LlMcC-_6Luv6rKPNfynBRcDCoa-zRFx0M1dU,3419
76
+ src/domain/mentor/__init__.py,sha256=w1_wx0el3z03QwSiKB6mr8dyprML-AFUsE99Ez8_wUg,372
77
+ src/domain/mentor/mentor_explanation_builder.py,sha256=6eON3x20BEpi7bVdu3TCJh_qstkMYUoLpz-VfORVTqQ,7004
78
+ src/domain/policy/__init__.py,sha256=n15hFYtfI-mg0_Z47A6Bm4rqbjUklQXAxaHTTdr27F0,3617
79
+ src/domain/policy/policy_checksum.py,sha256=5rzJo31iAkbbuJREyNKm5FVOyL6SH6roYlxDXc9YYRg,2342
80
+ src/domain/policy/policy_compatibility.py,sha256=_optz7r56tF_rmq42DeA-34VbUolUcP6kvo5TcPJdDQ,8477
81
+ src/domain/policy/policy_manifest.py,sha256=d9PEvZKaGMg3koGEJ6XIwNevJGssrw3Q6VqUkvvcwq4,26986
82
+ src/domain/policy/policy_resolver.py,sha256=xduWDDaqU1T-BDSckhmyOViXwXZQiXLrPyOsh9qvB3U,29589
83
+ src/domain/policy/policy_types.py,sha256=IOgGVM6oIDrFmSdOkkNpwxkQpTgXIBqY1hxVY-RxMwo,10112
84
+ src/domain/pr/__init__.py,sha256=zCKL3_JVnOAfAoKQGT4dbpJmpF2mU9KKeumxIN50MzE,288
85
+ src/domain/pr/explain_section_builder.py,sha256=Nhnmm7166V1rEft4uRaNxx5_gV0MoCJOQkMOogbWzHg,5176
86
+ src/domain/risk/__init__.py,sha256=wvsE1QhTUVSwcnwDDMEnQTgdbzurCFJDxsbrjKjbve4,301
87
+ src/domain/risk/risk_calculator.py,sha256=ZSJeU6YaQAxQfo5mLyAqtvPVLuWT4kYKqFId8bXtq4M,5181
88
+ src/domain/risk/risk_explanation.py,sha256=JIoeP_GYvVe-1ul-HU43qedoCpgAjWuVtH0WvFdfTrM,3083
89
+ src/domain/risk/risk_rules.py,sha256=6dP4QVaaDwJbrdX401ahG_p_wNCG5JKIrvWAsq1X93g,8966
90
+ src/domain/risk/risk_types.py,sha256=-kgr_a1ky4RKhP17tm2Q33VD4C_pdz9GD5FK3Be2zow,4154
91
+ src/domain/tests_generation/__init__.py,sha256=TqKcuJhvH3v_S_1R8ju1dRFx-cmp9v0ewPYjyQsqxOU,265
92
+ src/domain/tests_generation/framework_detector.py,sha256=s4yN5ZjtCGX8hDiMHDPO9dZs8jWU2m8bBMP_Iihx0MM,4559
93
+ src/domain/tests_generation/test_content_types.py,sha256=tfBGn26jx5VMK2PxRkJf3Md25Z4ofCkLaNktvyzKDns,1062
94
+ src/domain/tests_generation/test_scaffold_builder.py,sha256=t3CUBuUchTPgKVgZicjCz-VS5Ea7WUhvd4dbbi2UV6g,3892
95
+ src/fix/__init__.py,sha256=tkdj-xF8PCJ6PaIRmH61W3vUn8vnRJXz00Atnf61Yo8,953
96
+ src/fix/apply_fix.py,sha256=XURlvTnkBjwLzdwixEy5v8PBAKCTam6B3CCSNMFEhTE,18391
97
+ src/fix/fix_history.py,sha256=jxUbwsSi4lqw-64zlVVj2suVnOrvRPrrgWVlzjRO4Gw,5779
98
+ src/fix/patch_applier.py,sha256=DJ5rS5tOM3v5g8l9dWVpNoalWhukj6wVRUwdTkXJACg,1277
99
+ src/fix/patch_extractor.py,sha256=8SdltGtbccBowHbnYd4BU-oUcEQegel_tB7o_2uK6wo,2575
100
+ src/fix/patch_provenance.py,sha256=7PlygeSHp4qSa-XmUUkEkV-N5aSeaHhRvNQA35aA-MQ,3291
101
+ src/fix/patch_safety_classifier.py,sha256=cEkzMyQkXQeCE-btY1Tl9xdvah6RMioQ_L5HAoqzqDs,4222
102
+ src/fix/rollback_fix.py,sha256=jwmISZP-EiaNFEKlSNdhiKWuPytV635BdDmzAurmegY,2570
103
+ src/infrastructure/__init__.py,sha256=FDQv7sZaZ3bZFyIm7MJap-99SdeDRqiLcXhZudjDYnk,82
104
+ src/infrastructure/baseline/__init__.py,sha256=FIG9NT1dTUA4i59xXIhwzLKmNCa7sOIf1KRWHO8-lE0,642
105
+ src/infrastructure/baseline/local_baseline_repository.py,sha256=6r4_qpzEGUFClD1Yt-Vm-ws5AQrDpzqsq70J3V0uBHA,13815
106
+ src/infrastructure/ci/__init__.py,sha256=epqkB76zTFODGQ2ZknO-x3-F83WOQ-ZtJXFaFYoPX70,345
107
+ src/infrastructure/ci/junit_builder.py,sha256=uxII_90biDKOm-l0xn2Q4QAAxDt0kl0oYAsQk4etBUQ,3921
108
+ src/infrastructure/ci/masking.py,sha256=neKqDWAlnlg2n78vCdb8v78mNylXxpSWxpsqNCJaKb0,2218
109
+ src/infrastructure/ci/sarif_builder.py,sha256=fbAshYS0BTBH3Vg_FEkzK8o3rSD6xOvHrq0g2K08wsc,5385
110
+ src/infrastructure/git/__init__.py,sha256=YERpBrzf1S76qi9rHqDuaq9_X5pDeKKXaAmw9C6SOuI,88
111
+ src/infrastructure/git/identity.py,sha256=SdcNyPF_eISGGye0PtxiSWAU4YyDcTIFYtQyKa6590Q,5877
112
+ src/infrastructure/git/patch_applier.py,sha256=x7xU2mdq6eEDJtpO7G4ZZKq1hsIJ81HiFCwdF2360Oc,8197
113
+ src/infrastructure/git/risk_history_reader.py,sha256=YtKSguDZgMqVXx3IpEXQCGB1yzuCro1S8KjaU8VkgpE,2826
114
+ src/infrastructure/git/selective_stager.py,sha256=yjztG4fV1mp-KHCvF7a-caGtcg_WUSrPAtYH96tGq0k,8236
115
+ src/infrastructure/git/test_matcher.py,sha256=Uk4f7f2XeVuUbLyQ7miGwAhOAN0m_YS3stqrumNPhRc,2664
116
+ src/infrastructure/linter/external/__init__.py,sha256=OOIw9u587FnscgyPBnR2X3cvNjT0iiNH9XY45DI5FOU,326
117
+ src/infrastructure/linter/external/bandit_bridge.py,sha256=U_e-h74S9LcnRMJxoJxPPzN9YKb69NZOrJjpFugBKCs,4457
118
+ src/infrastructure/linter/external/base_bridge.py,sha256=iMQwRWy5BibgWEDs-Pi3-YvKEmy452u5qG8yHYtmEhI,3158
119
+ src/infrastructure/linter/external/gitleaks_bridge.py,sha256=9v3jnDcgwVfJQS4M6jTvt69X061S7YshmiecVjaWLiY,6049
120
+ src/infrastructure/linter/external/semgrep_bridge.py,sha256=7iZMm2QY44_dUnpqeBIXLMkn0SAuuiswaSvGVeg-VtU,4643
121
+ src/infrastructure/policy/__init__.py,sha256=mgznDOqCM9iazgY8DTi3uJqrc9wqIQp9D5eCDa5tyAk,1084
122
+ src/infrastructure/policy/local_policy_repository.py,sha256=-dW4QR-rzUM6qwOSC5UhA51tg1iWAXkTxKcZJPK0eBo,5786
123
+ src/infrastructure/policy/policy_pack_loader.py,sha256=uRTp64ksVkdC3GBYgaHxkXUjeTk7UWPy0ePWVRo92k4,9559
124
+ src/infrastructure/scm/__init__.py,sha256=CbW2p9DsYpwwUqy4xdtgoPugsLijopfNwLT-IAfEcqA,1887
125
+ src/infrastructure/scm/azure_devops_provider.py,sha256=5k3w6HWpZm2hlv_00E5sQYV7OgZcQh1XwJsNlyPa51k,18384
126
+ src/infrastructure/scm/base.py,sha256=SJlrjktVUJapTLxWmtT652e2yvuK0kuMOTNCI5_Wleo,17172
127
+ src/infrastructure/scm/bitbucket_provider.py,sha256=gGZo6YSUt2HkVdJdKVizkEF9bv4X0bhrzKGST_iybdQ,15553
128
+ src/infrastructure/scm/factory.py,sha256=U-1IkVaWxBCb1-8dbyGBJKCFwQWSqLE2P6ZG4_cad1c,3953
129
+ src/infrastructure/scm/github_provider.py,sha256=vdJDTTYd-CYkAQos-6IZ2hFOJkXG09SrkIcZzHbuuIc,21316
130
+ src/infrastructure/scm/gitlab_provider.py,sha256=8AimDKgVPXkYmvZXUYD-NBWfAk-EjhhSgvVVMiUAoBU,17030
131
+ src/infrastructure/scm/web_links.py,sha256=-7oF7oUwK_UvKVJZWDPbTnKBi9JEQu2dMDYtgqH10iM,3724
132
+ src/issue_autoclose/__init__.py,sha256=9IuLWTrq7Vvk2pf3BWMtxVFJ0ALmmHOpYjG8Rwd_x5U,651
133
+ src/issue_autoclose/insert_close_line.py,sha256=MY8Hr1HWbdIflI6L0Drg9x3ouEJy9BbYxnm1PNguzxY,1779
134
+ src/issue_autoclose/issue_registry.py,sha256=UONwAwVEENh3AA527rs-tme-LBALVnZHbVq8WPTI95c,8731
135
+ src/issue_autoclose/register_created_issue.py,sha256=jCSDQWVN6WEhMj8s_Ggkx2FsFQPq2m2ehQom3GI6s2I,3006
136
+ src/issue_autoclose/resolve_close_issue.py,sha256=clGc2g1vsmCJ4fxieDsN-zfNyemZ1aYvRStHZlMkFZg,7951
137
+ src/policy_packs/laravel-quality/CHANGELOG.md,sha256=g7f9coAAKmPMa3IxpI1bduvr1WfapgfM6KE_kBs5vLM,1154
138
+ src/policy_packs/laravel-quality/README.md,sha256=MhSQV-p8F6jmBEenvDvklseQ3FDvnvvmN6A16L6-x4s,1852
139
+ src/policy_packs/laravel-quality/linter.yml,sha256=bzA9yWf1obx3qDs4TCPzfkinOUJaScDI7mK8BlAm8xo,3191
140
+ src/policy_packs/laravel-quality/policy.yml,sha256=mQFsaJn1_QZ7lfHB24Sz3qvAN6CsEoI9oYwNDD5ARdg,3813
141
+ src/policy_packs/node-quality/README.md,sha256=1C_o-8y6jsI2x963KjQ0VeSB4MlxDHK3ACEYO81rmPI,1313
142
+ src/policy_packs/node-quality/linter.yml,sha256=0211mNdOU70N5C-U4tT1qivkis5_bptU_y6fLGnRc1Q,2874
143
+ src/policy_packs/node-quality/policy.yml,sha256=7BByVT_qE9q5YIT0Tt2_7eM9eTMdcFF_HsCugc-q0uQ,3219
144
+ src/policy_packs/php-security/README.md,sha256=wVOu2uPyDR0sAjo605jTijJ6AprYKNRma5MRFqAIEqw,1489
145
+ src/policy_packs/php-security/linter.yml,sha256=2WTX_2YajaH7wwB7t-A-W4u317jG7wkf5jW5qrhsvdQ,3260
146
+ src/policy_packs/php-security/policy.yml,sha256=cQkffWrRgLu-KUyDaLuTuJ_9ahwLySNbAV0OO9GP8kY,3258
147
+ src/policy_packs/vue-quality/README.md,sha256=2CCPyZbePU4Aiq06lygcVlOcFtNALJaxh0-xt7t4ZPM,1293
148
+ src/policy_packs/vue-quality/linter.yml,sha256=_G_CMBB4hvo-mkRI0rv77530Es5GnXoNipD5wpfPwvg,2508
149
+ src/policy_packs/vue-quality/policy.yml,sha256=FyVL17Fl_bgVjxZU5lO7MME5w04vjKV3UfISOBkGBXc,3099
150
+ src/review/__init__.py,sha256=ux2rkwebfnzuQpFc57eEceOC4CZkojPOtfp951Ux5Gc,700
151
+ src/review/diff_normalizer.py,sha256=ITSoJc1s_SKDjSRjFM7pFvotDO9kclW0wf9XSZZcgC4,3635
152
+ src/review/diff_source.py,sha256=V6fMtgxNJbMBL_qRifkLGfzFwtaTCbFFKFvcutHZjLk,2733
153
+ src/review/remote_pr.py,sha256=9oEEo-v-D08egxwN_EINh5Ju74vow91-CAH1kAb2ql0,13669
154
+ src/review/render.py,sha256=l63vQ3WiGxPSzCk4JxyQoFNfIyG0AdHgMobAjMeaOcY,3194
155
+ src/split/__init__.py,sha256=8XZn4jwPhQ0pJyNEMTBfgbGTMJkQUvePNp5ZncWCJ-Q,929
156
+ src/split/apply_split_plan.py,sha256=ZKoACWLCaBndLzvRl8wHyAyPlalTrjrPJ2ep_AjpWCE,10929
157
+ src/split/generate_split_plan.py,sha256=60nMWOsokcweWE6tX2RJqeJ_8o-XByAMtg2Zcmwumt0,9008
158
+ src/split/hunk_grouper.py,sha256=35PU7I_9iUzaN9xaJvoK2y9KGkeKjKofO_7l6xOWWks,14596
159
+ src/split/hunk_parser.py,sha256=DYc82pCKnuquJJwB7nm44WtZKK22lv-d75g9EeBhCm8,10266
160
+ src/split/split_plan.py,sha256=Y0Ah3LbzixKgAy74dh7XiQImsw6-f2dxwfJSkQHreCw,6572
161
+ src/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
162
+ src/ui/chat_app.py,sha256=y6hDKGgSaJDfp1rZBWxwQVQHzP_xx6-3q-oxxWnTUfg,26839
163
+ src/ui/commit_app.py,sha256=mggIsdHkg3EIvQoSc11XL7tpkSGJ25WWszHdgmJnMjc,8304
164
+ src/ui/config_app.py,sha256=YTQSkSWh97UqIWTcwu2_bAbra9C4pt2vgth8F-L-Ad0,84484
165
+ src/ui/help_screen.py,sha256=EvbP8kFZ6Sdehi7bJ6Wo_YcTTk5MTkgneeidWwefYrA,1890
166
+ src/ui/issue_app.py,sha256=ao2PDiT539Ppc2LItjesmMG7I5Mj96DIy4fFU7UCw_g,10378
167
+ src/ui/linter_app.py,sha256=kXhcHlhOMRCKTdfnXlCU34oDvHkm1aRivGWlz0AkeKM,1612
168
+ src/ui/metrics_app.py,sha256=fLDVWFVkJSZcDKfx2t5njtjGR_3laVeHu4rm19ByW7E,16759
169
+ src/ui/metrics_migration_app.py,sha256=U6l-ORUk1NlC2aIQSLvFjahMVM1dat-wFjWlct0fRYQ,6096
170
+ src/ui/pr_base_app.py,sha256=OvPcQVeyBrGBH8lhlVy0Ks1kVdvqcYHl7Zc3P2tXPGI,3857
171
+ src/ui/pr_publish_app.py,sha256=TU5IrSB3bkQEhl2N0W8KCwHfRGGacijqziMD9I7By04,71802
172
+ src/ui/pr_publish_help.py,sha256=3fcTbrQIoPM72kTbNjFu4PAin592DuNQlNIn_AeRmVk,1941
173
+ src/ui/demo/__init__.py,sha256=aOdUN0izuwW2FfLAHG9Qga-DJG5NBVqP0gYNO8OiKls,59
174
+ src/ui/demo/demo_app.py,sha256=4t_Mct-BJXpe5tlt1XmtRyN1IT0OL6U3WSmGqOJqMG4,3165
175
+ src/ui/demo/demo_help_screen.py,sha256=I_LELswhPtbHjfMWGVUqLtOwxGbzSYJsVWXjqZrLoLA,2108
176
+ src/ui/demo/demo_screens.py,sha256=GXaQFTgTn9awSP-EYiBXsCZqB583WrSQcuNYoBK12g8,1997
177
+ gitpr_cli-1.5.0.dist-info/METADATA,sha256=xkoqpc842w0Y6RcI0MdeH6VUbn0QvGPKYB821hnIpBE,54016
178
+ gitpr_cli-1.5.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
179
+ gitpr_cli-1.5.0.dist-info/entry_points.txt,sha256=TstGnMUzdOI5qNmpM97EPDBPuMqOZxoo6EoLxHSOMSQ,71
180
+ gitpr_cli-1.5.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
181
+ gitpr_cli-1.5.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
+ )