gitpr-cli 1.5.0__py3-none-any.whl → 1.7.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {gitpr_cli-1.5.0.dist-info → gitpr_cli-1.7.0.dist-info}/METADATA +2 -2
- {gitpr_cli-1.5.0.dist-info → gitpr_cli-1.7.0.dist-info}/RECORD +43 -40
- src/application/use_cases/create_baseline.py +2 -1
- src/application/use_cases/run_check.py +2 -1
- src/ascii_chart.py +248 -0
- src/config.py +1266 -1383
- src/config_schema.py +1730 -1357
- src/core.py +2769 -2328
- src/diff_parser.py +102 -0
- src/doc_links.py +53 -28
- src/i18n.py +142 -136
- src/infrastructure/scm/azure_devops_provider.py +491 -464
- src/infrastructure/scm/base.py +1 -0
- src/infrastructure/scm/bitbucket_provider.py +443 -402
- src/infrastructure/scm/factory.py +15 -3
- src/infrastructure/scm/gitlab_provider.py +469 -435
- src/issue_autoclose/issue_registry.py +285 -235
- src/issue_autoclose/register_created_issue.py +81 -80
- src/issue_autoclose/resolve_close_issue.py +236 -226
- src/ledger.py +7 -2
- src/main.py +6277 -45
- src/mcp_server.py +21 -5
- src/metrics.py +249 -2
- src/project_config.py +484 -426
- src/release_engine.py +134 -64
- src/review/diff_normalizer.py +84 -84
- src/reviewer_suggestion.py +2 -0
- src/scm_settings.py +441 -0
- src/skill_frontmatter.py +70 -0
- src/spinner.py +260 -257
- src/suggest_reviewers.py +155 -146
- src/tui_issue.py +44 -24
- src/ui/chat_app.py +4 -3
- src/ui/config_app.py +2332 -2043
- src/ui/help_screen.py +46 -46
- src/ui/issue_app.py +260 -258
- src/ui/linter_app.py +48 -46
- src/ui/metrics_app.py +129 -22
- src/updater.py +2 -2
- {gitpr_cli-1.5.0.dist-info → gitpr_cli-1.7.0.dist-info}/WHEEL +0 -0
- {gitpr_cli-1.5.0.dist-info → gitpr_cli-1.7.0.dist-info}/entry_points.txt +0 -0
- {gitpr_cli-1.5.0.dist-info → gitpr_cli-1.7.0.dist-info}/licenses/LICENSE +0 -0
- {gitpr_cli-1.5.0.dist-info → gitpr_cli-1.7.0.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitpr-cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.7.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
|
|
@@ -499,7 +499,7 @@ This is useful for scripting, CI/CD pipelines, and one-off queries where you don
|
|
|
499
499
|
|
|
500
500
|
📖 **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).
|
|
501
501
|
|
|
502
|
-
> 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics?
|
|
502
|
+
> 🧩 **Official VS Code & Cursor Extension** — Prefer native editor commands, SCM toolbar buttons, and inline `Problems` diagnostics? Download the ready-to-use `.vsix` from [GitHub Releases](https://github.com/natanfiuza/gitpr/releases) or see the [Editor Extension Guide](https://gitpr.natanfiuza.dev.br/docs/editor-extension) for 1-click install in VS Code and Cursor.
|
|
503
503
|
|
|
504
504
|
> 💬 **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.
|
|
505
505
|
|
|
@@ -1,36 +1,39 @@
|
|
|
1
|
-
gitpr_cli-1.
|
|
1
|
+
gitpr_cli-1.7.0.dist-info/licenses/LICENSE,sha256=UQzLC6mwqb-ZNDkBBAqOxC-SMQcEID0gaM6oFcu6u48,27030
|
|
2
2
|
src/__init__.py,sha256=ZHymXsocMDazI_fjOIgAQsRQvFPN2tV8FOuv0qptI_o,30
|
|
3
3
|
src/ai_providers.py,sha256=QyFEOsXyA59lZziO5bl-4nlYFJpxBcxU9aLhcTBnkWE,14633
|
|
4
|
+
src/ascii_chart.py,sha256=WFQclaxm5W6bPBBNVlImyrnrJYCSj5gRgEpldYzI7wk,8279
|
|
4
5
|
src/blame_engine.py,sha256=Zov1hxrt7Dcmf56ovJKz3QmHIfzDKtSub2iItXeg9-A,17104
|
|
5
6
|
src/cache.py,sha256=frROc69NyTn3TdJIOPctvuH9z_sh2pdKxL4ekMtyh6k,6572
|
|
6
7
|
src/changelog_builder.py,sha256=Ea0coS6LCqsdxTWrKW8SZyVsD02HuNNYzCoggI0loi8,9922
|
|
7
8
|
src/chat_memory.py,sha256=PcohnyspAfIwwwbsp9U2Wk_1AylJMFEMoWqRSJe_w30,8949
|
|
8
9
|
src/commit_classifier.py,sha256=HQHxPbCfgW_GD1_4mbO2No16jh_Ci5RTSoLbTU4IWYI,6920
|
|
9
|
-
src/config.py,sha256=
|
|
10
|
-
src/config_schema.py,sha256=
|
|
11
|
-
src/core.py,sha256=
|
|
12
|
-
src/diff_parser.py,sha256=
|
|
13
|
-
src/doc_links.py,sha256=
|
|
10
|
+
src/config.py,sha256=MrfZW27x-41gI9P8gtAyMuTryzeluBtvZfxRZkVIiBk,47122
|
|
11
|
+
src/config_schema.py,sha256=VATmOHWs_eeMLVhrtGFANcOVdrYwSadiFPUUhZM33OE,58091
|
|
12
|
+
src/core.py,sha256=RmwqnEa0-lEHKpxCIH95JLJkJQGsfx_QC8n8H_uJitQ,99037
|
|
13
|
+
src/diff_parser.py,sha256=JHeaTJflNOEbR9SYK_Ljr2hctrrABUIb31IFHAbyIDo,15405
|
|
14
|
+
src/doc_links.py,sha256=GdANhE6XbEnVDsbpCf22eUZnqFXor_2SU1O4S-pteIE,2028
|
|
14
15
|
src/github_api.py,sha256=i1o58oKWWmMCerP-g-hOQHGCwPWd01ooKntTKjpFhIA,3948
|
|
15
|
-
src/i18n.py,sha256=
|
|
16
|
+
src/i18n.py,sha256=Xp9c1dNGmTHbo8SyXa-LQuO71nWx3RP-Us7jJ7ARtUY,4804
|
|
16
17
|
src/issue_engine.py,sha256=RmZsQVg6N1vCcpMdv_5FX-cMGgQ3I0vwMN4parQ4zFg,10974
|
|
17
|
-
src/ledger.py,sha256=
|
|
18
|
+
src/ledger.py,sha256=3p5nX9gPGHgANQxBxEywrKo3TNb5KdWDeTI2mslYopo,43787
|
|
18
19
|
src/linter_engine.py,sha256=hIxht8M09Fnm6NiJZOqjI6dyqnFoHUDVA8Ve4dWZV4s,25214
|
|
19
20
|
src/linter_wizard.py,sha256=mo8aSTZmVLluCOskWv3nji74WPJh_6c122zwADN9aCc,7634
|
|
20
|
-
src/main.py,sha256=
|
|
21
|
-
src/mcp_server.py,sha256=
|
|
22
|
-
src/metrics.py,sha256=
|
|
21
|
+
src/main.py,sha256=zo0PEpxWzrsP8o61AAFDWwoThfWj5YLl_75WmKDT12I,246130
|
|
22
|
+
src/mcp_server.py,sha256=z3yf2eyKPxq4trAaeKrLQNQiN_rjeWYzDgbvAY5ziXc,112996
|
|
23
|
+
src/metrics.py,sha256=Pk8-HkK9dvr8MgBx6QJ_5iTLVgpohPww9C4pQ6wWLzM,48650
|
|
23
24
|
src/net.py,sha256=CjhgmQnp4OHVmO-3Lm-DLhNfo2RW0M9FnD_HYPKcnNI,2848
|
|
24
|
-
src/project_config.py,sha256=
|
|
25
|
-
src/release_engine.py,sha256=
|
|
25
|
+
src/project_config.py,sha256=r-gzjKeFuTYvUzOflQlpbijn6uLXtPEUBzA-1PzUR_k,16749
|
|
26
|
+
src/release_engine.py,sha256=S0kgO-a7RSC07AzvRWRlGo32jx38PAQGIzgvYXOfthk,41274
|
|
26
27
|
src/reviewer_resolution.py,sha256=bFRdf4GVWIpz4kk7cyF7EYWV43aP7V8sAlNGqKRPd7A,5913
|
|
27
|
-
src/reviewer_suggestion.py,sha256=
|
|
28
|
+
src/reviewer_suggestion.py,sha256=YeXJLjJjn1YS69FChWZBF4qHUuIwvv_n-GPiymLN8LQ,8904
|
|
29
|
+
src/scm_settings.py,sha256=3pXgBbxqekvD4LM-I3GgfV4IOa6QJn4x3-DPAThCris,14156
|
|
28
30
|
src/security.py,sha256=fkWy-r4tdISH0xOyWZyR8ZCFyLuYqu8fn9sKsIeNAso,1351
|
|
29
31
|
src/security_ruleset.py,sha256=4mqmqtWLHuhuCcjjaoKUM_YFzil4cWZ_ubjlOLgbM_4,4874
|
|
30
|
-
src/
|
|
31
|
-
src/
|
|
32
|
-
src/
|
|
33
|
-
src/
|
|
32
|
+
src/skill_frontmatter.py,sha256=K-HsjwSYPHUsDRjIiugBE3koxVdGf38L5d4l8eYVqEo,2167
|
|
33
|
+
src/spinner.py,sha256=JGOO_taTXbrQK0KnNxUQ40PcpS802bYYBosWWm_NG9o,9012
|
|
34
|
+
src/suggest_reviewers.py,sha256=LTOtwDIjaBS_FGDU6bjN6oGy-YD112hIPugYytNKD_A,5821
|
|
35
|
+
src/tui_issue.py,sha256=yaNpj9SSXiWWjMJ7a9FABcNWSffCkA1tjUSnJ1hN-MQ,11477
|
|
36
|
+
src/updater.py,sha256=8OwxL8hhh-hzKyQo5sjckw_e4bNlmlKeegwwH35nrG8,9560
|
|
34
37
|
src/usage_log.py,sha256=CBCNlUm_xftXsBaGRHvsEjTENvZfE-lW70HGLMkisSg,4508
|
|
35
38
|
src/version_bump.py,sha256=Dw1LZ_c6VzaO_s9cDeYSevoWtXVr_oP70eS_mkrOm8s,3041
|
|
36
39
|
src/application/__init__.py,sha256=3YXFcmxJh0f7BwryKXxUrOhGrLnZLsagV-VbBIk0eGk,31
|
|
@@ -40,14 +43,14 @@ src/application/use_cases/baseline_gate.py,sha256=-3c5iy7i-HJ7_lsgHGftc8JDYOct0O
|
|
|
40
43
|
src/application/use_cases/baseline_summary.py,sha256=BK5LibkNbQsIRmYvqv8AcS_kkC1b_Lt9Q6ZiOMzYCO0,10550
|
|
41
44
|
src/application/use_cases/calculate_risk.py,sha256=Kgd6YVTrt6DtizXAZQ97XCYd0TBXXytiloauke-L-ko,13513
|
|
42
45
|
src/application/use_cases/compare_against_baseline.py,sha256=hePa-yidv9OfBe8AZoNhu6FA5MBg5izZyL5K9ZAOWAI,1916
|
|
43
|
-
src/application/use_cases/create_baseline.py,sha256=
|
|
46
|
+
src/application/use_cases/create_baseline.py,sha256=ZJKO_hTmC01CLJ-cnbDVddY3PPOgWobNMYq5U94YwG4,26715
|
|
44
47
|
src/application/use_cases/generate_mentor_explanation.py,sha256=pPXSLYBJwum6a2P1TYv_B1zwAuM3xYtT8T2LhKO60rY,9707
|
|
45
48
|
src/application/use_cases/generate_pr_explanation.py,sha256=vlR6Lw7XBnHD7Cloz2gZMrW-kx31cBeC2kSM0qSnUjk,3418
|
|
46
49
|
src/application/use_cases/generate_test_file.py,sha256=U6EyPbZSXEv9A34PYHkQDToenjqu5Fu1h-nIBqcT9Ds,9657
|
|
47
50
|
src/application/use_cases/install_policy_pack.py,sha256=7s8wUL_SticgRnUMpzLJCmHso6y6tDHLibSdBLyXb0M,2816
|
|
48
51
|
src/application/use_cases/publish_pull_request.py,sha256=sBQTBhFGJ98YKTqatZdEccTHfNrp3rxZ_qbKUR9nZrw,9762
|
|
49
52
|
src/application/use_cases/resolve_effective_policy.py,sha256=HQruehhgqzT5cX2YcVftm1cZqiwDS5cAkRO0txj3hns,7771
|
|
50
|
-
src/application/use_cases/run_check.py,sha256=
|
|
53
|
+
src/application/use_cases/run_check.py,sha256=40txVJrLPmRjAJLMVnlYGVm8qo68Cnx5awmVFnnW8fM,15921
|
|
51
54
|
src/application/use_cases/suppress_finding.py,sha256=a9oXVFZmvNFrKbEMNXepAu9dEIUqqBb-H1ZSjM0hcAs,18292
|
|
52
55
|
src/application/use_cases/validate_policy_pack.py,sha256=OaHfs_coNFR9s6c2I7MKFVZRy2Hw2q_y9_B48poOZrE,9906
|
|
53
56
|
src/branding/__init__.py,sha256=BJ7o5Z13LvWnUVb1aN1tz2XlFk7h89MQGkB2nJuJO0s,81
|
|
@@ -122,18 +125,18 @@ src/infrastructure/policy/__init__.py,sha256=mgznDOqCM9iazgY8DTi3uJqrc9wqIQp9D5e
|
|
|
122
125
|
src/infrastructure/policy/local_policy_repository.py,sha256=-dW4QR-rzUM6qwOSC5UhA51tg1iWAXkTxKcZJPK0eBo,5786
|
|
123
126
|
src/infrastructure/policy/policy_pack_loader.py,sha256=uRTp64ksVkdC3GBYgaHxkXUjeTk7UWPy0ePWVRo92k4,9559
|
|
124
127
|
src/infrastructure/scm/__init__.py,sha256=CbW2p9DsYpwwUqy4xdtgoPugsLijopfNwLT-IAfEcqA,1887
|
|
125
|
-
src/infrastructure/scm/azure_devops_provider.py,sha256=
|
|
126
|
-
src/infrastructure/scm/base.py,sha256=
|
|
127
|
-
src/infrastructure/scm/bitbucket_provider.py,sha256=
|
|
128
|
-
src/infrastructure/scm/factory.py,sha256=
|
|
128
|
+
src/infrastructure/scm/azure_devops_provider.py,sha256=9IpgB-Vqzgj0VDFXaYs4PFRiecsQGU9svW5m3OpefsI,18790
|
|
129
|
+
src/infrastructure/scm/base.py,sha256=9tN2IivEngHoo1pDK3PivWHvHSkXokuyQQUJ680gsXU,17198
|
|
130
|
+
src/infrastructure/scm/bitbucket_provider.py,sha256=A58dMDa60lLUynENrinWENFz5dSA_lb9VJSg22ptbkI,16895
|
|
131
|
+
src/infrastructure/scm/factory.py,sha256=O3bRT6f15AozcldZfiH2LgOQ4g4GqMb0sbEWCIeF0HE,4487
|
|
129
132
|
src/infrastructure/scm/github_provider.py,sha256=vdJDTTYd-CYkAQos-6IZ2hFOJkXG09SrkIcZzHbuuIc,21316
|
|
130
|
-
src/infrastructure/scm/gitlab_provider.py,sha256=
|
|
133
|
+
src/infrastructure/scm/gitlab_provider.py,sha256=VAYjdg5gU43FDamXUNMdFzs9MwIGyNRqMzEnLTacRE8,17951
|
|
131
134
|
src/infrastructure/scm/web_links.py,sha256=-7oF7oUwK_UvKVJZWDPbTnKBi9JEQu2dMDYtgqH10iM,3724
|
|
132
135
|
src/issue_autoclose/__init__.py,sha256=9IuLWTrq7Vvk2pf3BWMtxVFJ0ALmmHOpYjG8Rwd_x5U,651
|
|
133
136
|
src/issue_autoclose/insert_close_line.py,sha256=MY8Hr1HWbdIflI6L0Drg9x3ouEJy9BbYxnm1PNguzxY,1779
|
|
134
|
-
src/issue_autoclose/issue_registry.py,sha256=
|
|
135
|
-
src/issue_autoclose/register_created_issue.py,sha256=
|
|
136
|
-
src/issue_autoclose/resolve_close_issue.py,sha256=
|
|
137
|
+
src/issue_autoclose/issue_registry.py,sha256=ttZ8hjuBRWn3iAK9s-KYBybpsW7NRlVxpLyHzD8eKoY,10297
|
|
138
|
+
src/issue_autoclose/register_created_issue.py,sha256=6RwTYepPu1mvuF2T-RcKL3r9THk9zNwCthjg__uQ9JI,2871
|
|
139
|
+
src/issue_autoclose/resolve_close_issue.py,sha256=IS-F39Kp8yvQJhKozIwy2gFEtCy5m1geCwf4cdcdsWo,8094
|
|
137
140
|
src/policy_packs/laravel-quality/CHANGELOG.md,sha256=g7f9coAAKmPMa3IxpI1bduvr1WfapgfM6KE_kBs5vLM,1154
|
|
138
141
|
src/policy_packs/laravel-quality/README.md,sha256=MhSQV-p8F6jmBEenvDvklseQ3FDvnvvmN6A16L6-x4s,1852
|
|
139
142
|
src/policy_packs/laravel-quality/linter.yml,sha256=bzA9yWf1obx3qDs4TCPzfkinOUJaScDI7mK8BlAm8xo,3191
|
|
@@ -148,7 +151,7 @@ src/policy_packs/vue-quality/README.md,sha256=2CCPyZbePU4Aiq06lygcVlOcFtNALJaxh0
|
|
|
148
151
|
src/policy_packs/vue-quality/linter.yml,sha256=_G_CMBB4hvo-mkRI0rv77530Es5GnXoNipD5wpfPwvg,2508
|
|
149
152
|
src/policy_packs/vue-quality/policy.yml,sha256=FyVL17Fl_bgVjxZU5lO7MME5w04vjKV3UfISOBkGBXc,3099
|
|
150
153
|
src/review/__init__.py,sha256=ux2rkwebfnzuQpFc57eEceOC4CZkojPOtfp951Ux5Gc,700
|
|
151
|
-
src/review/diff_normalizer.py,sha256=
|
|
154
|
+
src/review/diff_normalizer.py,sha256=8KnFbauGK-fuLGSSL_ox4kAwCE048MuTmorPwBrhlEU,3675
|
|
152
155
|
src/review/diff_source.py,sha256=V6fMtgxNJbMBL_qRifkLGfzFwtaTCbFFKFvcutHZjLk,2733
|
|
153
156
|
src/review/remote_pr.py,sha256=9oEEo-v-D08egxwN_EINh5Ju74vow91-CAH1kAb2ql0,13669
|
|
154
157
|
src/review/render.py,sha256=l63vQ3WiGxPSzCk4JxyQoFNfIyG0AdHgMobAjMeaOcY,3194
|
|
@@ -159,13 +162,13 @@ src/split/hunk_grouper.py,sha256=35PU7I_9iUzaN9xaJvoK2y9KGkeKjKofO_7l6xOWWks,145
|
|
|
159
162
|
src/split/hunk_parser.py,sha256=DYc82pCKnuquJJwB7nm44WtZKK22lv-d75g9EeBhCm8,10266
|
|
160
163
|
src/split/split_plan.py,sha256=Y0Ah3LbzixKgAy74dh7XiQImsw6-f2dxwfJSkQHreCw,6572
|
|
161
164
|
src/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
162
|
-
src/ui/chat_app.py,sha256=
|
|
165
|
+
src/ui/chat_app.py,sha256=eOhGsaC2EweYNJe9kdCbN9AMYcN_LI2BCj_tmOYZ7Bs,26789
|
|
163
166
|
src/ui/commit_app.py,sha256=mggIsdHkg3EIvQoSc11XL7tpkSGJ25WWszHdgmJnMjc,8304
|
|
164
|
-
src/ui/config_app.py,sha256=
|
|
165
|
-
src/ui/help_screen.py,sha256=
|
|
166
|
-
src/ui/issue_app.py,sha256=
|
|
167
|
-
src/ui/linter_app.py,sha256=
|
|
168
|
-
src/ui/metrics_app.py,sha256=
|
|
167
|
+
src/ui/config_app.py,sha256=thlCw2E8DungQf2W8DoATT2RFioq_vmsyS_IM2nsybo,94671
|
|
168
|
+
src/ui/help_screen.py,sha256=PxdLLey13DycZpIY0nj2Zd0DSF1uV02Gcrq3Ny88CuU,1723
|
|
169
|
+
src/ui/issue_app.py,sha256=ea8RYK0QcS9izoJ9e60TTTznOX8QVhMREW_GQEyi_xM,10192
|
|
170
|
+
src/ui/linter_app.py,sha256=xVMMZJhVQAo6XKI_MrRQvnGXNoDjELRs7k8rNaF4yws,1602
|
|
171
|
+
src/ui/metrics_app.py,sha256=8zbCVwTMN8k6uDHpbXlzBxd1EFIFaDTPDyH4h-0bdXY,20618
|
|
169
172
|
src/ui/metrics_migration_app.py,sha256=U6l-ORUk1NlC2aIQSLvFjahMVM1dat-wFjWlct0fRYQ,6096
|
|
170
173
|
src/ui/pr_base_app.py,sha256=OvPcQVeyBrGBH8lhlVy0Ks1kVdvqcYHl7Zc3P2tXPGI,3857
|
|
171
174
|
src/ui/pr_publish_app.py,sha256=TU5IrSB3bkQEhl2N0W8KCwHfRGGacijqziMD9I7By04,71802
|
|
@@ -174,8 +177,8 @@ src/ui/demo/__init__.py,sha256=aOdUN0izuwW2FfLAHG9Qga-DJG5NBVqP0gYNO8OiKls,59
|
|
|
174
177
|
src/ui/demo/demo_app.py,sha256=4t_Mct-BJXpe5tlt1XmtRyN1IT0OL6U3WSmGqOJqMG4,3165
|
|
175
178
|
src/ui/demo/demo_help_screen.py,sha256=I_LELswhPtbHjfMWGVUqLtOwxGbzSYJsVWXjqZrLoLA,2108
|
|
176
179
|
src/ui/demo/demo_screens.py,sha256=GXaQFTgTn9awSP-EYiBXsCZqB583WrSQcuNYoBK12g8,1997
|
|
177
|
-
gitpr_cli-1.
|
|
178
|
-
gitpr_cli-1.
|
|
179
|
-
gitpr_cli-1.
|
|
180
|
-
gitpr_cli-1.
|
|
181
|
-
gitpr_cli-1.
|
|
180
|
+
gitpr_cli-1.7.0.dist-info/METADATA,sha256=hpoCCrRp4v_yMf_v7Pqsg4YtA0nVWFha4N2JugWwr7E,54113
|
|
181
|
+
gitpr_cli-1.7.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
182
|
+
gitpr_cli-1.7.0.dist-info/entry_points.txt,sha256=TstGnMUzdOI5qNmpM97EPDBPuMqOZxoo6EoLxHSOMSQ,71
|
|
183
|
+
gitpr_cli-1.7.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
184
|
+
gitpr_cli-1.7.0.dist-info/RECORD,,
|
|
@@ -107,7 +107,8 @@ def current_diff(base: str | None = None, quiet: bool = True) -> str:
|
|
|
107
107
|
encoding="utf-8",
|
|
108
108
|
errors="replace",
|
|
109
109
|
)
|
|
110
|
-
|
|
110
|
+
from src.diff_parser import collapse_binary_hunks
|
|
111
|
+
return collapse_binary_hunks(result.stdout) if result.returncode == 0 else ""
|
|
111
112
|
|
|
112
113
|
|
|
113
114
|
def diff_files(diff_text: str) -> list[str]:
|
|
@@ -108,7 +108,8 @@ def get_check_diff(
|
|
|
108
108
|
cwd=repo_path or None,
|
|
109
109
|
)
|
|
110
110
|
if proc.returncode == 0:
|
|
111
|
-
|
|
111
|
+
from src.diff_parser import collapse_binary_hunks
|
|
112
|
+
return collapse_binary_hunks(proc.stdout or "")
|
|
112
113
|
except OSError:
|
|
113
114
|
pass
|
|
114
115
|
return ""
|
src/ascii_chart.py
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
"""
|
|
2
|
+
ASCII / Unicode 2D Chart Rendering Engine for GitPR Metrics.
|
|
3
|
+
|
|
4
|
+
Provides pure functions to render 2D terminal charts (vertical histograms,
|
|
5
|
+
time-series charts) without external dependencies or UI framework coupling.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
import math
|
|
9
|
+
from typing import Any, Dict, List, Optional
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _fmt_tokens(val: float) -> str:
|
|
13
|
+
"""Format token count in human-readable compact form (e.g. 1.2M, 45.0k, 500)."""
|
|
14
|
+
if val >= 1_000_000:
|
|
15
|
+
return f"{val / 1_000_000:.1f}M"
|
|
16
|
+
if val >= 1_000:
|
|
17
|
+
return f"{val / 1_000:.1f}k"
|
|
18
|
+
return str(int(round(val)))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _duration_ms_label(ms: Optional[float]) -> str:
|
|
22
|
+
"""Format duration in milliseconds into ms, s, or min."""
|
|
23
|
+
if ms is None:
|
|
24
|
+
return "0 ms"
|
|
25
|
+
if ms < 1000:
|
|
26
|
+
return f"{ms:.0f} ms"
|
|
27
|
+
seconds = ms / 1000.0
|
|
28
|
+
if seconds < 60:
|
|
29
|
+
return f"{seconds:.1f} s"
|
|
30
|
+
return f"{seconds / 60.0:.1f} min"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def render_empty_card(title: str, message: str, width: int = 60) -> str:
|
|
34
|
+
"""Render a framed empty-state banner with informative text."""
|
|
35
|
+
w = max(40, width)
|
|
36
|
+
inner_w = w - 4
|
|
37
|
+
top_border = f"┌{'─' * (w - 2)}┐"
|
|
38
|
+
bottom_border = f"└{'─' * (w - 2)}┘"
|
|
39
|
+
empty_line = f"│ {' ' * inner_w} │"
|
|
40
|
+
|
|
41
|
+
title_line = f"│ [bold cyan]{title:<{inner_w}}[/bold cyan] │"
|
|
42
|
+
msg_line = f"│ [yellow]{message:<{inner_w}}[/yellow] │"
|
|
43
|
+
|
|
44
|
+
return "\n".join([top_border, title_line, empty_line, msg_line, bottom_border])
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def render_vertical_histogram(
|
|
48
|
+
latency: Optional[Dict[str, Any]],
|
|
49
|
+
height: int = 7,
|
|
50
|
+
col_width: int = 7,
|
|
51
|
+
) -> str:
|
|
52
|
+
"""Render a 2D vertical ASCII histogram for latency distributions.
|
|
53
|
+
|
|
54
|
+
Args:
|
|
55
|
+
latency: Dict from latency_report() containing 'count', 'average_ms',
|
|
56
|
+
'median_ms', 'p95_ms', and 'buckets'.
|
|
57
|
+
height: Height in lines for the plotting area.
|
|
58
|
+
col_width: Character width of each column bar.
|
|
59
|
+
|
|
60
|
+
Returns:
|
|
61
|
+
Formatted multi-line string containing the 2D chart.
|
|
62
|
+
"""
|
|
63
|
+
if not latency or not latency.get("count") or not latency.get("buckets"):
|
|
64
|
+
return render_empty_card(
|
|
65
|
+
"📊 Latency Distribution",
|
|
66
|
+
"No timed executions recorded in the selected window.",
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
buckets: List[Dict[str, Any]] = latency["buckets"]
|
|
70
|
+
counts = [b["count"] for b in buckets]
|
|
71
|
+
max_count = max(counts) if counts else 0
|
|
72
|
+
if max_count <= 0:
|
|
73
|
+
max_count = 1
|
|
74
|
+
|
|
75
|
+
count_total = latency.get("count", 0)
|
|
76
|
+
avg_lbl = _duration_ms_label(latency.get("average_ms"))
|
|
77
|
+
med_lbl = _duration_ms_label(latency.get("median_ms"))
|
|
78
|
+
p95_lbl = _duration_ms_label(latency.get("p95_ms"))
|
|
79
|
+
|
|
80
|
+
lines: List[str] = []
|
|
81
|
+
|
|
82
|
+
# Title & Summary header
|
|
83
|
+
lines.append("[bold cyan]📊 Latency Histogram[/bold cyan]")
|
|
84
|
+
lines.append(
|
|
85
|
+
f" Timed runs: [bold]{count_total}[/bold] │ "
|
|
86
|
+
f"Average: [cyan]{avg_lbl}[/cyan] │ "
|
|
87
|
+
f"Median (P50): [green]{med_lbl}[/green] │ "
|
|
88
|
+
f"P95: [yellow]{p95_lbl}[/yellow]"
|
|
89
|
+
)
|
|
90
|
+
lines.append("")
|
|
91
|
+
|
|
92
|
+
# Y-axis label formatting width
|
|
93
|
+
y_label_width = max(len(str(max_count)), 3)
|
|
94
|
+
|
|
95
|
+
# Plot rows from top (height) down to 1
|
|
96
|
+
for r in range(height, 0, -1):
|
|
97
|
+
# Y-axis tick mark and label
|
|
98
|
+
ratio = r / height
|
|
99
|
+
level_val = round(ratio * max_count)
|
|
100
|
+
if r == height:
|
|
101
|
+
y_tick = f"{max_count:>{y_label_width}} ┤"
|
|
102
|
+
elif r == math.ceil(height / 2):
|
|
103
|
+
half_val = round(max_count / 2)
|
|
104
|
+
y_tick = f"{half_val:>{y_label_width}} ┤"
|
|
105
|
+
else:
|
|
106
|
+
y_tick = f"{' ' * y_label_width} │"
|
|
107
|
+
|
|
108
|
+
# Build column cells
|
|
109
|
+
cells = []
|
|
110
|
+
for b_count in counts:
|
|
111
|
+
# Thresholds for full block and half block
|
|
112
|
+
val_ratio = b_count / max_count
|
|
113
|
+
if val_ratio >= (r / height):
|
|
114
|
+
# Full block
|
|
115
|
+
bar_chars = "█" * col_width
|
|
116
|
+
elif val_ratio >= ((r - 0.5) / height):
|
|
117
|
+
# Half block
|
|
118
|
+
bar_chars = "▄" * col_width
|
|
119
|
+
else:
|
|
120
|
+
bar_chars = " " * col_width
|
|
121
|
+
|
|
122
|
+
cells.append(bar_chars)
|
|
123
|
+
|
|
124
|
+
row_content = " ".join(cells)
|
|
125
|
+
lines.append(f" {y_tick} {row_content}")
|
|
126
|
+
|
|
127
|
+
# X-axis Baseline
|
|
128
|
+
base_col = "─" * col_width
|
|
129
|
+
x_axis_line = "───".join([base_col] * len(buckets))
|
|
130
|
+
lines.append(f" {0:>{y_label_width}} ┴─{x_axis_line}─")
|
|
131
|
+
|
|
132
|
+
# Bucket Labels
|
|
133
|
+
label_cells = []
|
|
134
|
+
for b in buckets:
|
|
135
|
+
lbl = b.get("label", "")
|
|
136
|
+
label_cells.append(f"{lbl:^{col_width}}")
|
|
137
|
+
lines.append(f" {' ' * y_label_width} " + " ".join(label_cells))
|
|
138
|
+
|
|
139
|
+
# Counts and percentages below labels
|
|
140
|
+
stat_cells = []
|
|
141
|
+
for b in buckets:
|
|
142
|
+
c = b.get("count", 0)
|
|
143
|
+
rate = b.get("rate", 0.0)
|
|
144
|
+
txt = f"{c} ({rate:.0%})"
|
|
145
|
+
stat_cells.append(f"{txt:^{col_width}}")
|
|
146
|
+
lines.append(f" {' ' * y_label_width} " + " ".join(stat_cells))
|
|
147
|
+
|
|
148
|
+
return "\n".join(lines)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def render_token_timeseries(
|
|
152
|
+
trend: Optional[Dict[str, Any]],
|
|
153
|
+
height: int = 7,
|
|
154
|
+
col_width: int = 5,
|
|
155
|
+
) -> str:
|
|
156
|
+
"""Render a 2D continuous ASCII time-series chart for daily token trends.
|
|
157
|
+
|
|
158
|
+
Args:
|
|
159
|
+
trend: Dict from token_trend_report() containing 'days', 'total_tokens',
|
|
160
|
+
'peak_tokens', 'average_tokens', and 'sparkline'.
|
|
161
|
+
height: Height in lines for the plotting area.
|
|
162
|
+
col_width: Character width allocated per day column.
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Formatted multi-line string containing the 2D chart.
|
|
166
|
+
"""
|
|
167
|
+
if not trend or not trend.get("days") or not trend.get("total_tokens"):
|
|
168
|
+
return render_empty_card(
|
|
169
|
+
"📈 Token Trend & Consumption",
|
|
170
|
+
"No token consumption recorded in the selected window.",
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
days: List[Dict[str, Any]] = trend["days"]
|
|
174
|
+
peak_tokens = trend.get("peak_tokens", 0)
|
|
175
|
+
total_tokens = trend.get("total_tokens", 0)
|
|
176
|
+
avg_tokens = trend.get("average_tokens", 0.0)
|
|
177
|
+
sparkline = trend.get("sparkline", "")
|
|
178
|
+
|
|
179
|
+
if peak_tokens <= 0:
|
|
180
|
+
peak_tokens = 1
|
|
181
|
+
|
|
182
|
+
lines: List[str] = []
|
|
183
|
+
|
|
184
|
+
# Title & Summary header
|
|
185
|
+
lines.append("[bold cyan]📈 Token Consumption Trend[/bold cyan]")
|
|
186
|
+
lines.append(
|
|
187
|
+
f" Total: [bold]{total_tokens:,}[/bold] │ "
|
|
188
|
+
f"Peak: [yellow]{peak_tokens:,}[/yellow] │ "
|
|
189
|
+
f"Daily Avg: [green]{avg_tokens:,.0f}[/green] │ "
|
|
190
|
+
f"Sparkline: {sparkline}"
|
|
191
|
+
)
|
|
192
|
+
lines.append("")
|
|
193
|
+
|
|
194
|
+
y_label_top = _fmt_tokens(peak_tokens)
|
|
195
|
+
y_label_mid = _fmt_tokens(peak_tokens / 2)
|
|
196
|
+
y_label_width = max(len(y_label_top), len(y_label_mid), 4)
|
|
197
|
+
|
|
198
|
+
# Plot matrix from top down
|
|
199
|
+
for r in range(height, 0, -1):
|
|
200
|
+
if r == height:
|
|
201
|
+
y_tick = f"{y_label_top:>{y_label_width}} ┼"
|
|
202
|
+
grid_char = "┈"
|
|
203
|
+
elif r == math.ceil(height / 2):
|
|
204
|
+
y_tick = f"{y_label_mid:>{y_label_width}} ┼"
|
|
205
|
+
grid_char = "┈"
|
|
206
|
+
else:
|
|
207
|
+
y_tick = f"{' ' * y_label_width} │"
|
|
208
|
+
grid_char = " "
|
|
209
|
+
|
|
210
|
+
day_cells = []
|
|
211
|
+
for d in days:
|
|
212
|
+
tokens = d.get("tokens", 0)
|
|
213
|
+
ratio = tokens / peak_tokens
|
|
214
|
+
if ratio >= (r / height):
|
|
215
|
+
cell_str = "█" * col_width
|
|
216
|
+
elif ratio >= ((r - 0.5) / height):
|
|
217
|
+
cell_str = "▄" * col_width
|
|
218
|
+
elif grid_char != " ":
|
|
219
|
+
cell_str = grid_char * col_width
|
|
220
|
+
else:
|
|
221
|
+
cell_str = " " * col_width
|
|
222
|
+
day_cells.append(cell_str)
|
|
223
|
+
|
|
224
|
+
lines.append(f" {y_tick} " + " ".join(day_cells))
|
|
225
|
+
|
|
226
|
+
# X-axis Baseline
|
|
227
|
+
base_col = "─" * col_width
|
|
228
|
+
x_axis_line = "─".join([base_col] * len(days))
|
|
229
|
+
lines.append(f" {'0':>{y_label_width}} ┴─{x_axis_line}─")
|
|
230
|
+
|
|
231
|
+
# Date labels (MM-DD)
|
|
232
|
+
date_cells = []
|
|
233
|
+
for d in days:
|
|
234
|
+
raw_day = d.get("day", "")
|
|
235
|
+
# Format YYYY-MM-DD -> MM-DD
|
|
236
|
+
short_date = raw_day[5:] if len(raw_day) >= 10 else raw_day
|
|
237
|
+
date_cells.append(f"{short_date:^{col_width}}")
|
|
238
|
+
lines.append(f" {' ' * y_label_width} " + " ".join(date_cells))
|
|
239
|
+
|
|
240
|
+
# Compact token labels below dates
|
|
241
|
+
tok_cells = []
|
|
242
|
+
for d in days:
|
|
243
|
+
t = d.get("tokens", 0)
|
|
244
|
+
tok_str = _fmt_tokens(t) if t > 0 else "·"
|
|
245
|
+
tok_cells.append(f"{tok_str:^{col_width}}")
|
|
246
|
+
lines.append(f" {' ' * y_label_width} " + " ".join(tok_cells))
|
|
247
|
+
|
|
248
|
+
return "\n".join(lines)
|