gitpr-cli 0.0.28__py3-none-any.whl → 0.0.29__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-0.0.28.dist-info → gitpr_cli-0.0.29.dist-info}/METADATA +6 -2
- {gitpr_cli-0.0.28.dist-info → gitpr_cli-0.0.29.dist-info}/RECORD +9 -9
- src/mcp_server.py +259 -11
- src/spinner.py +32 -7
- src/updater.py +2 -2
- {gitpr_cli-0.0.28.dist-info → gitpr_cli-0.0.29.dist-info}/WHEEL +0 -0
- {gitpr_cli-0.0.28.dist-info → gitpr_cli-0.0.29.dist-info}/entry_points.txt +0 -0
- {gitpr_cli-0.0.28.dist-info → gitpr_cli-0.0.29.dist-info}/licenses/LICENSE +0 -0
- {gitpr_cli-0.0.28.dist-info → gitpr_cli-0.0.29.dist-info}/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: gitpr-cli
|
|
3
|
-
Version: 0.0.
|
|
3
|
+
Version: 0.0.29
|
|
4
4
|
Summary: Automação de PRs, Commits e Code Review com IA (Gemini e DeepSeek)
|
|
5
5
|
Author-email: Natan Fiuza <contato@natanfiuza.dev.br>
|
|
6
6
|
Requires-Python: >=3.10
|
|
@@ -139,7 +139,7 @@ You can pass the following *flags* for specific actions:
|
|
|
139
139
|
* `--lang <code>`: Forces the interface language for this execution (e.g.: `en_us`, `pt_br`). Overrides `GITPR_LANG` in `.env` without persisting the change.
|
|
140
140
|
* `-ch` or `--chat`: Opens the **Interactive Pair Programming Chat** — a TUI terminal where the AI sees your current diff and maintains a contextual conversation. Features memory per branch, slash commands (`/explain`, `/tests`, `/optimize`, `/clear`), auto-patching (F5), diff refresh (F2), and session export (F6).
|
|
141
141
|
* `-l` or `--linter`: Runs **only the local static linter** (no AI calls). Ideal for use in CI/CD pipelines to block non-compliant code.
|
|
142
|
-
* `--mcp`: Starts GitPR as an **MCP server** (Model Context Protocol) on stdio transport. Enables integration with VS Code, Cursor, Claude Desktop, and other MCP-compatible editors — exposing all GitPR AI capabilities as tools directly inside your IDE. Also available as the standalone `gitpr-mcp` command.
|
|
142
|
+
* `--mcp`: Starts GitPR as an **MCP server** (Model Context Protocol) on stdio transport. Enables integration with VS Code, Cursor, Claude Desktop, and other MCP-compatible editors — exposing all GitPR AI capabilities as 10 annotated tools, 15 resources, and 7 pre-built prompts directly inside your IDE. Also available as the standalone `gitpr-mcp` command.
|
|
143
143
|
* `--install`: **Interactive Setup Wizard.** Runs a guided 4-step setup: downloads skill templates, installs Git hooks, configures MCP for detected editors, and checks/requests your AI provider API key. Each step asks for confirmation before proceeding.
|
|
144
144
|
* `-ih` or `--installhooks`: Automatically installs **local Git Hooks** (`pre-commit` and `prepare-commit-msg`) in your repository.
|
|
145
145
|
* `-s` or `--skill`: Creates the AI context template files (`.gitpr.commit.md`, `.gitpr.pr.md`, `.gitpr.review.md`, `.gitpr.filereview.md`, `.gitpr.issue.md`, `.gitpr.blame.md`) and the Linter (`.gitpr.linter.yml`) at the project root.
|
|
@@ -272,6 +272,8 @@ Once configured, use natural language in your editor's AI chat:
|
|
|
272
272
|
|
|
273
273
|
📖 **Full documentation:** [docs/mcp-integration.md](https://github.com/natanfiuza/gitpr/blob/main/docs/mcp-integration.md) — available in 5 languages (EN, PT-BR, PT-PT, ES, FR).
|
|
274
274
|
|
|
275
|
+
> 💬 **MCP Prompts** — GitPR also exposes 7 pre-defined message templates (prompts) for common flows like "Review PR", "Generate Commit Message", and "Create Issue from Diff". See the [MCP Prompts guide](https://github.com/natanfiuza/gitpr/blob/main/docs/mcp-prompts.md) for the full list.
|
|
276
|
+
|
|
275
277
|
## 📚 Technical Documentation and Advanced Guides
|
|
276
278
|
|
|
277
279
|
To keep this README concise, we detail the most advanced **DevOps** and **Continuous Integration** focused implementations in separate documents.
|
|
@@ -307,6 +309,8 @@ If you want to implement GitPR as an automated quality barrier in your team, che
|
|
|
307
309
|
* [**GitHub Token (PAT) Integration and Security**](https://github.com/natanfiuza/gitpr/blob/main/docs/github-pat-integration.md) — Understand how GitPR creates issues directly in the repository with authentication.
|
|
308
310
|
* [**Internationalization (i18n)**](https://github.com/natanfiuza/gitpr/blob/main/docs/i18n_explanation.md) — Architecture, usage patterns, and how to add new languages.
|
|
309
311
|
* [**MCP Integration**](https://github.com/natanfiuza/gitpr/blob/main/docs/mcp-integration.md) — Connect GitPR to VS Code, Cursor, and Claude Desktop via Model Context Protocol.
|
|
312
|
+
* [**MCP Prompts**](https://github.com/natanfiuza/gitpr/blob/main/docs/mcp-prompts.md) — Pre-built message templates (7 prompts, 35 language variants) for common GitPR workflows in your editor's AI chat.
|
|
313
|
+
* [**MCP Tool Annotations**](https://github.com/natanfiuza/gitpr/blob/main/docs/mcp-annotations.md) — IDE integration hints (`readOnlyHint`, `destructiveHint`) for smarter UI behavior and safer tool execution.
|
|
310
314
|
|
|
311
315
|
## ⚡ Local Cache System (Quota Savings)
|
|
312
316
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
gitpr_cli-0.0.
|
|
1
|
+
gitpr_cli-0.0.29.dist-info/licenses/LICENSE,sha256=UQzLC6mwqb-ZNDkBBAqOxC-SMQcEID0gaM6oFcu6u48,27030
|
|
2
2
|
src/__init__.py,sha256=U6ksarernNcryHO20aj4rNG7w0thT7xdMs5KzBJyPvE,28
|
|
3
3
|
src/ai_providers.py,sha256=cxPuytZ9LfvhQbWFPBJ4dud3lHLpAQpa3cbY8bKESk8,10954
|
|
4
4
|
src/blame_engine.py,sha256=z-uh8mHOF--JxY_nnbp5r_9y_a_IO4Zn42P0AeLBpto,10436
|
|
@@ -10,17 +10,17 @@ src/i18n.py,sha256=QED7hTSdWCC1lSNJ-l6VuOba18OrWgFTMQcZ7cQmXbY,3599
|
|
|
10
10
|
src/issue_engine.py,sha256=XJ7WNLoeg3wct079wS7OQqU62f_avn3A8LmMuPWchzE,3642
|
|
11
11
|
src/linter_engine.py,sha256=D04wvOgRa-vZEYknm-z-Cg7V_3gnoQz_64PMorWnISo,4414
|
|
12
12
|
src/main.py,sha256=BoI2aN3l1fS4UzoWL67Yw2QtkeRxh9ITuVIQEc9JOPA,32724
|
|
13
|
-
src/mcp_server.py,sha256=
|
|
13
|
+
src/mcp_server.py,sha256=rT5ACAb0MUpvt8qfO0jIt3g4y6BKWW5YY2qRrs6hKsQ,40227
|
|
14
14
|
src/security.py,sha256=Li0zM8ZURTK2nKRHIt49TWLO4DIddBEHzXPrVycv0Rs,1343
|
|
15
|
-
src/spinner.py,sha256=
|
|
15
|
+
src/spinner.py,sha256=fnl_jVOPjlC1DmINbnJ_IKW-uujpyJNTv51jwhAMvlc,8466
|
|
16
16
|
src/tui_issue.py,sha256=gcAJCIfMcdeMjrFJUWQQvdgBv7fnOBtxQxwdBk2Sk40,1789
|
|
17
|
-
src/updater.py,sha256=
|
|
17
|
+
src/updater.py,sha256=kHzP-SyprrNQSLeOhiplFjPuswkh4enVc9TM_7LQnpU,5886
|
|
18
18
|
src/ui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
19
19
|
src/ui/chat_app.py,sha256=y-py5UpisQbPLqqDON8AIkyA7X1IM5LAn1GbeYr2p_U,25598
|
|
20
20
|
src/ui/help_screen.py,sha256=tjbokXhwyzLnkOC0S56NbpX-78hcFQnvsQMpNU1wt0c,1862
|
|
21
21
|
src/ui/issue_app.py,sha256=8MdBcOVdCHPFx6EUhs80CiZ2G-29KulskkCz9qMZ_Lg,4737
|
|
22
|
-
gitpr_cli-0.0.
|
|
23
|
-
gitpr_cli-0.0.
|
|
24
|
-
gitpr_cli-0.0.
|
|
25
|
-
gitpr_cli-0.0.
|
|
26
|
-
gitpr_cli-0.0.
|
|
22
|
+
gitpr_cli-0.0.29.dist-info/METADATA,sha256=Q77hoxR0vAluySUVwFCFuJPb3-7zozYTPFNm-y2Y1jY,23425
|
|
23
|
+
gitpr_cli-0.0.29.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
24
|
+
gitpr_cli-0.0.29.dist-info/entry_points.txt,sha256=TstGnMUzdOI5qNmpM97EPDBPuMqOZxoo6EoLxHSOMSQ,71
|
|
25
|
+
gitpr_cli-0.0.29.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
|
|
26
|
+
gitpr_cli-0.0.29.dist-info/RECORD,,
|
src/mcp_server.py
CHANGED
|
@@ -37,8 +37,9 @@ from pathlib import Path
|
|
|
37
37
|
import click
|
|
38
38
|
from dotenv import load_dotenv
|
|
39
39
|
from mcp.server.fastmcp import FastMCP
|
|
40
|
+
from mcp.types import ToolAnnotations
|
|
40
41
|
|
|
41
|
-
from src.i18n import __
|
|
42
|
+
from src.i18n import __, CURRENT_LANG
|
|
42
43
|
|
|
43
44
|
# =============================================================================
|
|
44
45
|
# Output Patching System
|
|
@@ -243,7 +244,8 @@ mcp = FastMCP(
|
|
|
243
244
|
# =============================================================================
|
|
244
245
|
|
|
245
246
|
@mcp.tool(
|
|
246
|
-
description=__("Get the current git branch, repository name, and remote origin URL.")
|
|
247
|
+
description=__("Get the current git branch, repository name, and remote origin URL."),
|
|
248
|
+
annotations=ToolAnnotations(readOnlyHint=True, idempotentHint=True),
|
|
247
249
|
)
|
|
248
250
|
def get_git_context() -> str:
|
|
249
251
|
"""Return JSON with branch name and repository info."""
|
|
@@ -264,7 +266,8 @@ def get_git_context() -> str:
|
|
|
264
266
|
|
|
265
267
|
@mcp.tool(
|
|
266
268
|
description=__("Get the current unstaged git diff (git diff HEAD). ")
|
|
267
|
-
+ __("Lists all changed files and their line-level modifications.")
|
|
269
|
+
+ __("Lists all changed files and their line-level modifications."),
|
|
270
|
+
annotations=ToolAnnotations(readOnlyHint=True, idempotentHint=True),
|
|
268
271
|
)
|
|
269
272
|
def analyze_diff() -> str:
|
|
270
273
|
"""Return the raw git diff for uncommitted local changes."""
|
|
@@ -284,7 +287,8 @@ def analyze_diff() -> str:
|
|
|
284
287
|
|
|
285
288
|
@mcp.tool(
|
|
286
289
|
description=__("Get the full diff of the current branch against the remote "
|
|
287
|
-
"base branch (origin/main or origin/master). Runs git fetch first.")
|
|
290
|
+
"base branch (origin/main or origin/master). Runs git fetch first."),
|
|
291
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False, idempotentHint=False),
|
|
288
292
|
)
|
|
289
293
|
def get_full_diff() -> str:
|
|
290
294
|
"""Return the full diff between the current branch and origin/main."""
|
|
@@ -309,7 +313,8 @@ def get_full_diff() -> str:
|
|
|
309
313
|
@mcp.tool(
|
|
310
314
|
description=__("Generate a Conventional Commits commit message from the "
|
|
311
315
|
"current git diff using AI. "
|
|
312
|
-
"Returns a message like 'feat: add user authentication'.")
|
|
316
|
+
"Returns a message like 'feat: add user authentication'."),
|
|
317
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False, idempotentHint=False),
|
|
313
318
|
)
|
|
314
319
|
def generate_commit_message(
|
|
315
320
|
provider: str = "",
|
|
@@ -351,7 +356,8 @@ def generate_commit_message(
|
|
|
351
356
|
@mcp.tool(
|
|
352
357
|
description=__("Perform an AI code review on uncommitted local changes "
|
|
353
358
|
"(git diff HEAD). Returns structured feedback with issues "
|
|
354
|
-
"and improvement suggestions.")
|
|
359
|
+
"and improvement suggestions."),
|
|
360
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False, idempotentHint=False),
|
|
355
361
|
)
|
|
356
362
|
def review_code(
|
|
357
363
|
provider: str = "",
|
|
@@ -391,7 +397,8 @@ def review_code(
|
|
|
391
397
|
|
|
392
398
|
@mcp.tool(
|
|
393
399
|
description=__("Perform a full AI code review comparing the entire current "
|
|
394
|
-
"branch against origin/main. Runs git fetch automatically.")
|
|
400
|
+
"branch against origin/main. Runs git fetch automatically."),
|
|
401
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False, idempotentHint=False),
|
|
395
402
|
)
|
|
396
403
|
def full_review(provider: str = "") -> str:
|
|
397
404
|
"""AI code review of all changes since origin/main.
|
|
@@ -426,7 +433,8 @@ def full_review(provider: str = "") -> str:
|
|
|
426
433
|
@mcp.tool(
|
|
427
434
|
description=__("Generate a complete Pull Request description (title + body) "
|
|
428
435
|
"from the full diff against origin/main. Uses AI to create a "
|
|
429
|
-
"structured, professional PR document.")
|
|
436
|
+
"structured, professional PR document."),
|
|
437
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False, idempotentHint=False),
|
|
430
438
|
)
|
|
431
439
|
def generate_pr_description(provider: str = "") -> str:
|
|
432
440
|
"""Generate a full PR description from the branch diff.
|
|
@@ -466,7 +474,8 @@ def generate_pr_description(provider: str = "") -> str:
|
|
|
466
474
|
@mcp.tool(
|
|
467
475
|
description=__("Run the static local linter (regex-based rules from "
|
|
468
476
|
".gitpr.linter.yml) on the current git diff. "
|
|
469
|
-
"Returns error and warning counts with detailed messages.")
|
|
477
|
+
"Returns error and warning counts with detailed messages."),
|
|
478
|
+
annotations=ToolAnnotations(readOnlyHint=True, idempotentHint=True),
|
|
470
479
|
)
|
|
471
480
|
def run_linter() -> str:
|
|
472
481
|
"""Analyze the current diff against .gitpr.linter.yml rules."""
|
|
@@ -499,7 +508,8 @@ def run_linter() -> str:
|
|
|
499
508
|
@mcp.tool(
|
|
500
509
|
description=__("Run AI-powered git blame analysis on a file region to trace "
|
|
501
510
|
"the origin of business rules. Classifies each commit as "
|
|
502
|
-
"ORIGIN (first introduction) or REFACTORING (later change).")
|
|
511
|
+
"ORIGIN (first introduction) or REFACTORING (later change)."),
|
|
512
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False, idempotentHint=False),
|
|
503
513
|
)
|
|
504
514
|
def analyze_blame(
|
|
505
515
|
file_path: str,
|
|
@@ -542,7 +552,8 @@ def analyze_blame(
|
|
|
542
552
|
@mcp.tool(
|
|
543
553
|
description=__("Generate a structured Issue (What / Why / Where / How) from "
|
|
544
554
|
"code context using AI. Supports three modes: diff (current "
|
|
545
|
-
"changes), history (branch history), or blame (file region).")
|
|
555
|
+
"changes), history (branch history), or blame (file region)."),
|
|
556
|
+
annotations=ToolAnnotations(readOnlyHint=False, destructiveHint=False, idempotentHint=False),
|
|
546
557
|
)
|
|
547
558
|
def generate_issue(context_type: str = "diff") -> str:
|
|
548
559
|
"""Generate an issue from code context.
|
|
@@ -592,6 +603,19 @@ SKILL_FILES = {
|
|
|
592
603
|
"blame": ".gitpr.blame.md",
|
|
593
604
|
}
|
|
594
605
|
|
|
606
|
+
# Prompt template files (message templates for common MCP flows).
|
|
607
|
+
# Each prompt has a base English file and optional language variants
|
|
608
|
+
# (e.g. gitpr.prompt.review.pt_br.md).
|
|
609
|
+
PROMPT_FILES = {
|
|
610
|
+
"review": "gitpr.prompt.review.md",
|
|
611
|
+
"commit": "gitpr.prompt.commit.md",
|
|
612
|
+
"pr": "gitpr.prompt.pr.md",
|
|
613
|
+
"linter": "gitpr.prompt.linter.md",
|
|
614
|
+
"issue": "gitpr.prompt.issue.md",
|
|
615
|
+
"blame": "gitpr.prompt.blame.md",
|
|
616
|
+
"explore": "gitpr.prompt.explore.md",
|
|
617
|
+
}
|
|
618
|
+
|
|
595
619
|
|
|
596
620
|
def _read_resource_file(filename: str) -> str:
|
|
597
621
|
"""Read a skill or config file from the project's .gitpr/skill/ directory.
|
|
@@ -613,6 +637,62 @@ def _read_resource_file(filename: str) -> str:
|
|
|
613
637
|
})
|
|
614
638
|
|
|
615
639
|
|
|
640
|
+
def _read_prompt_file(prompt_name: str) -> str:
|
|
641
|
+
"""Load prompt content from a template file with language fallback.
|
|
642
|
+
|
|
643
|
+
Tries the language-specific variant first (e.g.
|
|
644
|
+
gitpr.prompt.review.pt_br.md), falling back to the English base
|
|
645
|
+
file (gitpr.prompt.review.md). Returns an empty string when
|
|
646
|
+
neither file can be found.
|
|
647
|
+
|
|
648
|
+
Search order (first match wins):
|
|
649
|
+
1. <cwd>/templates/ — development / project-local
|
|
650
|
+
2. <cwd>/.gitpr/skill/ — downloaded via --skill
|
|
651
|
+
3. <package>/templates/ — bundled with the installation
|
|
652
|
+
"""
|
|
653
|
+
base_filename = PROMPT_FILES.get(prompt_name)
|
|
654
|
+
if not base_filename:
|
|
655
|
+
return ""
|
|
656
|
+
|
|
657
|
+
# Build language-specific filename
|
|
658
|
+
if not CURRENT_LANG.startswith("en"):
|
|
659
|
+
name_part, ext = base_filename.rsplit(".", 1)
|
|
660
|
+
lang_filename = f"{name_part}.{CURRENT_LANG}.{ext}"
|
|
661
|
+
else:
|
|
662
|
+
lang_filename = base_filename
|
|
663
|
+
|
|
664
|
+
# Search directories in priority order
|
|
665
|
+
search_dirs = [
|
|
666
|
+
os.path.join(os.getcwd(), "templates"), # dev / project-local
|
|
667
|
+
os.path.join(os.getcwd(), ".gitpr", "skill"), # downloaded
|
|
668
|
+
os.path.join(os.path.dirname(__file__), "..", "templates"), # bundled
|
|
669
|
+
]
|
|
670
|
+
|
|
671
|
+
def _try_read(search_dir, filename):
|
|
672
|
+
path = os.path.join(search_dir, filename)
|
|
673
|
+
if os.path.exists(path):
|
|
674
|
+
try:
|
|
675
|
+
with open(path, "r", encoding="utf-8", errors="replace") as f:
|
|
676
|
+
return f.read().strip()
|
|
677
|
+
except Exception:
|
|
678
|
+
pass
|
|
679
|
+
return None
|
|
680
|
+
|
|
681
|
+
for search_dir in search_dirs:
|
|
682
|
+
# Try language variant first
|
|
683
|
+
result = _try_read(search_dir, lang_filename)
|
|
684
|
+
if result:
|
|
685
|
+
return result
|
|
686
|
+
|
|
687
|
+
# Fallback to English base in all directories
|
|
688
|
+
for search_dir in search_dirs:
|
|
689
|
+
result = _try_read(search_dir, base_filename)
|
|
690
|
+
if result:
|
|
691
|
+
return result
|
|
692
|
+
|
|
693
|
+
return ""
|
|
694
|
+
|
|
695
|
+
|
|
616
696
|
@mcp.resource(
|
|
617
697
|
uri="skill://list",
|
|
618
698
|
name=__("Available Skill Templates"),
|
|
@@ -697,6 +777,174 @@ def get_linter_config() -> str:
|
|
|
697
777
|
return _read_resource_file(".gitpr.linter.yml")
|
|
698
778
|
|
|
699
779
|
|
|
780
|
+
# =============================================================================
|
|
781
|
+
# Resources — Prompt Templates
|
|
782
|
+
# =============================================================================
|
|
783
|
+
|
|
784
|
+
@mcp.resource(
|
|
785
|
+
uri="prompt://list",
|
|
786
|
+
name=__("Available Prompt Templates"),
|
|
787
|
+
description=__("Lists all available MCP prompt template URIs."),
|
|
788
|
+
mime_type="application/json",
|
|
789
|
+
)
|
|
790
|
+
def list_prompts() -> str:
|
|
791
|
+
"""Return a JSON list of available prompt resource URIs."""
|
|
792
|
+
return json.dumps({
|
|
793
|
+
"prompts": [f"prompt://{name}" for name in PROMPT_FILES],
|
|
794
|
+
})
|
|
795
|
+
|
|
796
|
+
|
|
797
|
+
@mcp.resource(
|
|
798
|
+
uri="prompt://review",
|
|
799
|
+
name=__("Review PR Prompt"),
|
|
800
|
+
description=__("Prompt template: full code review of the current branch."),
|
|
801
|
+
mime_type="text/markdown",
|
|
802
|
+
)
|
|
803
|
+
def get_prompt_review() -> str:
|
|
804
|
+
return _read_prompt_file("review")
|
|
805
|
+
|
|
806
|
+
|
|
807
|
+
@mcp.resource(
|
|
808
|
+
uri="prompt://commit",
|
|
809
|
+
name=__("Commit Message Prompt"),
|
|
810
|
+
description=__("Prompt template: generate a Conventional Commits message."),
|
|
811
|
+
mime_type="text/markdown",
|
|
812
|
+
)
|
|
813
|
+
def get_prompt_commit() -> str:
|
|
814
|
+
return _read_prompt_file("commit")
|
|
815
|
+
|
|
816
|
+
|
|
817
|
+
@mcp.resource(
|
|
818
|
+
uri="prompt://pr",
|
|
819
|
+
name=__("PR Description Prompt"),
|
|
820
|
+
description=__("Prompt template: generate a Pull Request description."),
|
|
821
|
+
mime_type="text/markdown",
|
|
822
|
+
)
|
|
823
|
+
def get_prompt_pr() -> str:
|
|
824
|
+
return _read_prompt_file("pr")
|
|
825
|
+
|
|
826
|
+
|
|
827
|
+
@mcp.resource(
|
|
828
|
+
uri="prompt://linter",
|
|
829
|
+
name=__("Linter Prompt"),
|
|
830
|
+
description=__("Prompt template: run the static linter on changes."),
|
|
831
|
+
mime_type="text/markdown",
|
|
832
|
+
)
|
|
833
|
+
def get_prompt_linter() -> str:
|
|
834
|
+
return _read_prompt_file("linter")
|
|
835
|
+
|
|
836
|
+
|
|
837
|
+
@mcp.resource(
|
|
838
|
+
uri="prompt://issue",
|
|
839
|
+
name=__("Issue Prompt"),
|
|
840
|
+
description=__("Prompt template: generate a structured issue from changes."),
|
|
841
|
+
mime_type="text/markdown",
|
|
842
|
+
)
|
|
843
|
+
def get_prompt_issue() -> str:
|
|
844
|
+
return _read_prompt_file("issue")
|
|
845
|
+
|
|
846
|
+
|
|
847
|
+
@mcp.resource(
|
|
848
|
+
uri="prompt://blame",
|
|
849
|
+
name=__("Blame Prompt"),
|
|
850
|
+
description=__("Prompt template: trace code origin with git blame + AI."),
|
|
851
|
+
mime_type="text/markdown",
|
|
852
|
+
)
|
|
853
|
+
def get_prompt_blame() -> str:
|
|
854
|
+
return _read_prompt_file("blame")
|
|
855
|
+
|
|
856
|
+
|
|
857
|
+
@mcp.resource(
|
|
858
|
+
uri="prompt://explore",
|
|
859
|
+
name=__("Explore Prompt"),
|
|
860
|
+
description=__("Prompt template: explore project context and available skills."),
|
|
861
|
+
mime_type="text/markdown",
|
|
862
|
+
)
|
|
863
|
+
def get_prompt_explore() -> str:
|
|
864
|
+
return _read_prompt_file("explore")
|
|
865
|
+
|
|
866
|
+
|
|
867
|
+
# =============================================================================
|
|
868
|
+
# Prompts — Message Templates for Common Flows
|
|
869
|
+
# =============================================================================
|
|
870
|
+
# Prompts are pre-defined message templates that users can select in their
|
|
871
|
+
# editor's AI chat. Unlike tools (which execute automatically), prompts are
|
|
872
|
+
# starter messages that guide the AI to invoke the right GitPR tools.
|
|
873
|
+
#
|
|
874
|
+
# Prompt content is loaded from template files in templates/ (with language
|
|
875
|
+
# variants), so translations can be updated independently of the Python code.
|
|
876
|
+
# =============================================================================
|
|
877
|
+
|
|
878
|
+
@mcp.prompt(
|
|
879
|
+
name=__("Review PR"),
|
|
880
|
+
description=__("Full code review of all changes in the current branch against origin/main. "
|
|
881
|
+
"Runs the full review tool and linter, then composes a comprehensive report."),
|
|
882
|
+
)
|
|
883
|
+
def review_pr_prompt() -> str:
|
|
884
|
+
"""Prompt: full code review of the current branch."""
|
|
885
|
+
return _read_prompt_file("review")
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
@mcp.prompt(
|
|
889
|
+
name=__("Generate Commit Message"),
|
|
890
|
+
description=__("Generate a Conventional Commits message (e.g., 'feat: add user auth') "
|
|
891
|
+
"from the current uncommitted changes."),
|
|
892
|
+
)
|
|
893
|
+
def generate_commit_message_prompt() -> str:
|
|
894
|
+
"""Prompt: generate a commit message from uncommitted changes."""
|
|
895
|
+
return _read_prompt_file("commit")
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
@mcp.prompt(
|
|
899
|
+
name=__("Create PR Description"),
|
|
900
|
+
description=__("Generate a complete Pull Request description (title + body) "
|
|
901
|
+
"from all changes in the current branch."),
|
|
902
|
+
)
|
|
903
|
+
def create_pr_description_prompt() -> str:
|
|
904
|
+
"""Prompt: generate a full PR description."""
|
|
905
|
+
return _read_prompt_file("pr")
|
|
906
|
+
|
|
907
|
+
|
|
908
|
+
@mcp.prompt(
|
|
909
|
+
name=__("Run Code Linter"),
|
|
910
|
+
description=__("Run the static linter (.gitpr.linter.yml rules) on current "
|
|
911
|
+
"uncommitted changes and report violations."),
|
|
912
|
+
)
|
|
913
|
+
def run_linter_prompt() -> str:
|
|
914
|
+
"""Prompt: run the static linter on current changes."""
|
|
915
|
+
return _read_prompt_file("linter")
|
|
916
|
+
|
|
917
|
+
|
|
918
|
+
@mcp.prompt(
|
|
919
|
+
name=__("Create Issue from Diff"),
|
|
920
|
+
description=__("Generate a structured issue (What / Why / Where / How) from "
|
|
921
|
+
"the current uncommitted changes."),
|
|
922
|
+
)
|
|
923
|
+
def create_issue_prompt() -> str:
|
|
924
|
+
"""Prompt: generate an issue from the current diff."""
|
|
925
|
+
return _read_prompt_file("issue")
|
|
926
|
+
|
|
927
|
+
|
|
928
|
+
@mcp.prompt(
|
|
929
|
+
name=__("Trace Code Origin"),
|
|
930
|
+
description=__("Investigate the history of a specific file region using git "
|
|
931
|
+
"blame + AI to trace where business rules came from."),
|
|
932
|
+
)
|
|
933
|
+
def trace_code_origin_prompt() -> str:
|
|
934
|
+
"""Prompt: trace the origin of code in a file region."""
|
|
935
|
+
return _read_prompt_file("blame")
|
|
936
|
+
|
|
937
|
+
|
|
938
|
+
@mcp.prompt(
|
|
939
|
+
name=__("Explore Project Context"),
|
|
940
|
+
description=__("Get current branch info, repository name, and list available "
|
|
941
|
+
"skill templates for the project."),
|
|
942
|
+
)
|
|
943
|
+
def explore_project_prompt() -> str:
|
|
944
|
+
"""Prompt: explore the current git context and available skills."""
|
|
945
|
+
return _read_prompt_file("explore")
|
|
946
|
+
|
|
947
|
+
|
|
700
948
|
# =============================================================================
|
|
701
949
|
# MCP Config Installer (gitpr-mcp --install <editor>)
|
|
702
950
|
# =============================================================================
|
src/spinner.py
CHANGED
|
@@ -135,6 +135,29 @@ class Spinner:
|
|
|
135
135
|
self._running = False
|
|
136
136
|
self._quiet = quiet
|
|
137
137
|
|
|
138
|
+
@staticmethod
|
|
139
|
+
def _adaptive_speed(word):
|
|
140
|
+
"""Calculate chars_per_letter and sleep_time based on word length.
|
|
141
|
+
|
|
142
|
+
Short words (<=15 chars) keep the original leisurely reveal.
|
|
143
|
+
Longer phrases get progressively faster so the full text is
|
|
144
|
+
displayed well before the spinner switches to the next word.
|
|
145
|
+
"""
|
|
146
|
+
length = len(word)
|
|
147
|
+
if length <= 15:
|
|
148
|
+
# Single word: original speed
|
|
149
|
+
return 4, 0.08
|
|
150
|
+
elif length <= 35:
|
|
151
|
+
# Medium phrase: moderate speed
|
|
152
|
+
return 2, 0.06
|
|
153
|
+
else:
|
|
154
|
+
# Long phrase: fast reveal
|
|
155
|
+
return 1, 0.04
|
|
156
|
+
|
|
157
|
+
def _next_word(self, current_idx):
|
|
158
|
+
"""Pick the next word index (sequential, wrapping around)."""
|
|
159
|
+
return (current_idx + 1) % len(THINKING_WORDS)
|
|
160
|
+
|
|
138
161
|
def _spin(self):
|
|
139
162
|
"""Main animation loop, runs in a separate thread."""
|
|
140
163
|
braille_idx = 0
|
|
@@ -144,7 +167,9 @@ class Spinner:
|
|
|
144
167
|
discovered = "" # Letters already "discovered" of the word
|
|
145
168
|
dots_cycle = 0 # 0 = ".", 1 = "..", 2 = "..."
|
|
146
169
|
char_step = 0 # Frame counter for revealing letters
|
|
147
|
-
|
|
170
|
+
|
|
171
|
+
# Adaptive speed based on word length
|
|
172
|
+
chars_per_letter, sleep_time = self._adaptive_speed(word)
|
|
148
173
|
|
|
149
174
|
while self._running:
|
|
150
175
|
braille_char = BRAILLE_FRAMES[braille_idx]
|
|
@@ -177,22 +202,22 @@ class Spinner:
|
|
|
177
202
|
|
|
178
203
|
# Change word and color after a few dot cycles
|
|
179
204
|
if dots_cycle == 0 and braille_idx == 0:
|
|
180
|
-
word_idx = (word_idx
|
|
205
|
+
word_idx = self._next_word(word_idx)
|
|
181
206
|
word = THINKING_WORDS[word_idx]
|
|
182
207
|
word_color = random.choice(WORD_COLORS)
|
|
183
208
|
discovered = ""
|
|
184
209
|
char_step = 0
|
|
210
|
+
# Recalculate adaptive speed for the new word
|
|
211
|
+
chars_per_letter, sleep_time = self._adaptive_speed(word)
|
|
185
212
|
|
|
186
213
|
# Build and display the line: magenta braille + colored word
|
|
187
|
-
line = f"\r {MAGENTA}{braille_char}{RESET} {word_color}{display_word}{RESET}"
|
|
188
|
-
# Pad the rest of the line with spaces
|
|
189
|
-
line = line.ljust(70 + len(MAGENTA) + len(RESET) * 2 + len(word_color))
|
|
214
|
+
line = f"\r {MAGENTA}{braille_char}{RESET} {word_color}{display_word}{RESET}\033[K"
|
|
190
215
|
|
|
191
216
|
if not self._quiet:
|
|
192
217
|
sys.stdout.write(line)
|
|
193
218
|
sys.stdout.flush()
|
|
194
219
|
|
|
195
|
-
time.sleep(
|
|
220
|
+
time.sleep(sleep_time)
|
|
196
221
|
|
|
197
222
|
def start(self):
|
|
198
223
|
"""Starts the animation in the background."""
|
|
@@ -208,5 +233,5 @@ class Spinner:
|
|
|
208
233
|
if self._thread:
|
|
209
234
|
self._thread.join(timeout=0.5)
|
|
210
235
|
if not self._quiet:
|
|
211
|
-
sys.stdout.write("\r
|
|
236
|
+
sys.stdout.write("\r\033[K")
|
|
212
237
|
sys.stdout.flush()
|
src/updater.py
CHANGED
|
@@ -7,8 +7,8 @@ import click
|
|
|
7
7
|
from datetime import datetime
|
|
8
8
|
|
|
9
9
|
# Current version of your local executable (Update this on every new build!)
|
|
10
|
-
__version__ = "0.0.
|
|
11
|
-
__lang_version__ = "v0.0.
|
|
10
|
+
__version__ = "0.0.29" # GitPR current version
|
|
11
|
+
__lang_version__ = "v0.0.7" # Language dictionary version control
|
|
12
12
|
GITHUB_API_URL = "https://api.github.com/repos/natanfiuza/gitpr/releases/latest"
|
|
13
13
|
PYPI_API_URL = "https://pypi.org/pypi/gitpr-cli/json"
|
|
14
14
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|