git-alchemist 1.0.2__tar.gz → 1.1.0__tar.gz
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.
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/PKG-INFO +3 -2
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/README.md +1 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/git_alchemist.egg-info/PKG-INFO +3 -2
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/git_alchemist.egg-info/SOURCES.txt +1 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/pyproject.toml +2 -2
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/architect.py +8 -6
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/cli.py +6 -0
- git_alchemist-1.1.0/src/forge.py +145 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/LICENSE +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/git_alchemist.egg-info/dependency_links.txt +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/git_alchemist.egg-info/entry_points.txt +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/git_alchemist.egg-info/requires.txt +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/git_alchemist.egg-info/top_level.txt +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/setup.cfg +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/audit.py +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/committer.py +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/core.py +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/issue_gen.py +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/profile_gen.py +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/promote.py +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/repo_tools.py +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/sage.py +0 -0
- {git_alchemist-1.0.2 → git_alchemist-1.1.0}/src/utils.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-alchemist
|
|
3
|
-
Version: 1.0
|
|
4
|
-
Summary: A unified AI stack to optimize, describe, and
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: A unified AI stack to optimize, describe, architect, and forge pull requests for your GitHub repositories.
|
|
5
5
|
Author: abduznik
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: >=3.10
|
|
@@ -32,6 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
* **Gold Score Audit:** Measure your repository's professional quality and health.
|
|
33
33
|
* **The Sage:** Contextual codebase chat to answer deep technical questions about your code.
|
|
34
34
|
* **Commit Alchemist:** Automated semantic commit message suggestions from staged changes.
|
|
35
|
+
* **Forge:** Automated PR creation from local changes.
|
|
35
36
|
|
|
36
37
|
## Model Tiers
|
|
37
38
|
|
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
* **Gold Score Audit:** Measure your repository's professional quality and health.
|
|
18
18
|
* **The Sage:** Contextual codebase chat to answer deep technical questions about your code.
|
|
19
19
|
* **Commit Alchemist:** Automated semantic commit message suggestions from staged changes.
|
|
20
|
+
* **Forge:** Automated PR creation from local changes.
|
|
20
21
|
|
|
21
22
|
## Model Tiers
|
|
22
23
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: git-alchemist
|
|
3
|
-
Version: 1.0
|
|
4
|
-
Summary: A unified AI stack to optimize, describe, and
|
|
3
|
+
Version: 1.1.0
|
|
4
|
+
Summary: A unified AI stack to optimize, describe, architect, and forge pull requests for your GitHub repositories.
|
|
5
5
|
Author: abduznik
|
|
6
6
|
License: MIT
|
|
7
7
|
Requires-Python: >=3.10
|
|
@@ -32,6 +32,7 @@ Dynamic: license-file
|
|
|
32
32
|
* **Gold Score Audit:** Measure your repository's professional quality and health.
|
|
33
33
|
* **The Sage:** Contextual codebase chat to answer deep technical questions about your code.
|
|
34
34
|
* **Commit Alchemist:** Automated semantic commit message suggestions from staged changes.
|
|
35
|
+
* **Forge:** Automated PR creation from local changes.
|
|
35
36
|
|
|
36
37
|
## Model Tiers
|
|
37
38
|
|
|
@@ -4,8 +4,8 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "git-alchemist"
|
|
7
|
-
version = "1.0
|
|
8
|
-
description = "A unified AI stack to optimize, describe, and
|
|
7
|
+
version = "1.1.0"
|
|
8
|
+
description = "A unified AI stack to optimize, describe, architect, and forge pull requests for your GitHub repositories."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [{ name = "abduznik" }]
|
|
11
11
|
license = { text = "MIT" }
|
|
@@ -70,26 +70,28 @@ Do NOT use markdown blocks.
|
|
|
70
70
|
|
|
71
71
|
# NEW: Auto-Deployment Logic
|
|
72
72
|
if Confirm.ask("Initialize Git and deploy to GitHub?"):
|
|
73
|
-
repo_name = os.path.basename(
|
|
73
|
+
repo_name = os.path.basename(os.getcwd())
|
|
74
74
|
console.print(f"[cyan]Initializing repository: {repo_name}...[/cyan]")
|
|
75
|
-
run_shell("git init")
|
|
76
75
|
|
|
77
|
-
#
|
|
76
|
+
# 1. Initialize and set identity if missing
|
|
77
|
+
run_shell("git init")
|
|
78
78
|
try:
|
|
79
79
|
run_shell("git config user.name", check=True)
|
|
80
80
|
except:
|
|
81
|
-
run_shell(f'git config user.name "
|
|
82
|
-
run_shell(f'git config user.email "
|
|
81
|
+
run_shell(f'git config user.name "abduznik"')
|
|
82
|
+
run_shell(f'git config user.email "abduznik@users.noreply.github.com"')
|
|
83
83
|
|
|
84
|
+
# 2. Add and Commit
|
|
84
85
|
run_shell("git add .")
|
|
85
86
|
run_shell('git commit -m "feat: Initial scaffold by Git-Alchemist"')
|
|
86
87
|
|
|
88
|
+
# 3. Create and Push
|
|
87
89
|
try:
|
|
88
90
|
console.print("[magenta]Creating GitHub repository...[/magenta]")
|
|
89
91
|
run_shell(f"gh repo create {repo_name} --public --source=. --remote=origin --push")
|
|
90
92
|
console.print(f"[bold yellow]✨ Project deployed to GitHub: {repo_name}[/bold yellow]")
|
|
91
93
|
except Exception as e:
|
|
92
|
-
console.print(f"[red]GitHub deployment failed
|
|
94
|
+
console.print(f"[red]GitHub deployment failed:[/red] {e}")
|
|
93
95
|
else:
|
|
94
96
|
console.print("[yellow]Discarding workspace.[/yellow]")
|
|
95
97
|
|
|
@@ -8,6 +8,7 @@ from .issue_gen import create_issue
|
|
|
8
8
|
from .audit import run_audit
|
|
9
9
|
from .sage import ask_sage
|
|
10
10
|
from .committer import suggest_commits
|
|
11
|
+
from .forge import forge_pr
|
|
11
12
|
|
|
12
13
|
console = Console()
|
|
13
14
|
|
|
@@ -16,6 +17,9 @@ def main():
|
|
|
16
17
|
parser.add_argument("--smart", action="store_true", help="Use high-end Gemini Pro models (slower/lower quota)")
|
|
17
18
|
subparsers = parser.add_subparsers(dest="command", help="Available commands")
|
|
18
19
|
|
|
20
|
+
# Forge Command
|
|
21
|
+
forge_parser = subparsers.add_parser("forge", help="Automatically generate and open a PR from the current branch")
|
|
22
|
+
|
|
19
23
|
# Commit Command
|
|
20
24
|
commit_parser = subparsers.add_parser("commit", help="Generate semantic commit messages from changes")
|
|
21
25
|
|
|
@@ -77,6 +81,8 @@ def main():
|
|
|
77
81
|
ask_sage(args.question, mode=mode)
|
|
78
82
|
elif args.command == "commit":
|
|
79
83
|
suggest_commits(mode=mode)
|
|
84
|
+
elif args.command == "forge":
|
|
85
|
+
forge_pr(mode=mode)
|
|
80
86
|
else:
|
|
81
87
|
parser.print_help()
|
|
82
88
|
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from rich.console import Console
|
|
5
|
+
from rich.prompt import Confirm
|
|
6
|
+
from .core import generate_content
|
|
7
|
+
from .utils import run_shell, check_gh_auth
|
|
8
|
+
|
|
9
|
+
console = Console()
|
|
10
|
+
|
|
11
|
+
def get_branch_diff(base_branch="master"):
|
|
12
|
+
"""Gets the diff between the current branch and the base branch."""
|
|
13
|
+
try:
|
|
14
|
+
# If base_branch is not provided or invalid, try to detect it
|
|
15
|
+
if not base_branch:
|
|
16
|
+
base_branch = run_shell("git remote show origin | grep 'HEAD branch' | cut -d' ' -f5", check=False) or "master"
|
|
17
|
+
|
|
18
|
+
return run_shell(f"git diff {base_branch}...HEAD", check=False), base_branch
|
|
19
|
+
except:
|
|
20
|
+
return None, "master"
|
|
21
|
+
|
|
22
|
+
def handle_uncommitted_changes(mode="fast"):
|
|
23
|
+
"""
|
|
24
|
+
Checks for uncommitted changes, creates a branch, and commits them.
|
|
25
|
+
Returns True if a new branch was created and changes committed.
|
|
26
|
+
"""
|
|
27
|
+
status = run_shell("git status --porcelain", check=False)
|
|
28
|
+
if not status:
|
|
29
|
+
return False
|
|
30
|
+
|
|
31
|
+
console.print("[yellow]Uncommitted changes detected. Forging a new branch...[/yellow]")
|
|
32
|
+
|
|
33
|
+
# Generate unique branch name
|
|
34
|
+
timestamp = int(time.time())
|
|
35
|
+
new_branch = f"forge-{timestamp}"
|
|
36
|
+
|
|
37
|
+
# Create and switch to new branch
|
|
38
|
+
run_shell(f"git checkout -b {new_branch}")
|
|
39
|
+
console.print(f"[green]Switched to new branch: {new_branch}[/green]")
|
|
40
|
+
|
|
41
|
+
# Stage all changes
|
|
42
|
+
run_shell("git add .")
|
|
43
|
+
|
|
44
|
+
# Generate commit message
|
|
45
|
+
diff = run_shell("git diff --staged", check=False)
|
|
46
|
+
if not diff:
|
|
47
|
+
# Fallback if diff is somehow empty or large binary
|
|
48
|
+
commit_msg = f"wip: auto-commit changes {timestamp}"
|
|
49
|
+
else:
|
|
50
|
+
prompt = f"""
|
|
51
|
+
Task: Generate a concise, semantic git commit message for the following changes.
|
|
52
|
+
Diff:
|
|
53
|
+
'''
|
|
54
|
+
{diff[:3000]}
|
|
55
|
+
'''
|
|
56
|
+
Constraint: Max 70 characters. No quotes. Start with a verb (e.g., 'fix:', 'feat:', 'chore:').
|
|
57
|
+
"""
|
|
58
|
+
commit_msg = generate_content(prompt, mode=mode)
|
|
59
|
+
if commit_msg:
|
|
60
|
+
commit_msg = commit_msg.strip().replace('"', '').replace("'", "")
|
|
61
|
+
else:
|
|
62
|
+
commit_msg = f"wip: auto-commit changes {timestamp}"
|
|
63
|
+
|
|
64
|
+
# Commit
|
|
65
|
+
run_shell(f'git commit -m "{commit_msg}"')
|
|
66
|
+
console.print(f"[green]Committed changes:[/green] {commit_msg}")
|
|
67
|
+
|
|
68
|
+
return True
|
|
69
|
+
|
|
70
|
+
def forge_pr(mode="fast"):
|
|
71
|
+
"""
|
|
72
|
+
Analyzes changes (committed or uncommitted) and opens a professional PR on GitHub.
|
|
73
|
+
"""
|
|
74
|
+
username = check_gh_auth()
|
|
75
|
+
if not username:
|
|
76
|
+
console.print("[red]Not authenticated with gh CLI.[/red]")
|
|
77
|
+
return
|
|
78
|
+
|
|
79
|
+
# Handle uncommitted changes first
|
|
80
|
+
handle_uncommitted_changes(mode=mode)
|
|
81
|
+
|
|
82
|
+
# Detect base branch
|
|
83
|
+
base_branch = run_shell("git remote show origin | grep 'HEAD branch' | cut -d' ' -f5", check=False)
|
|
84
|
+
if not base_branch:
|
|
85
|
+
base_branch = "master"
|
|
86
|
+
# Try main if master doesn't look right, but remote show origin is best source
|
|
87
|
+
# or check local branches
|
|
88
|
+
|
|
89
|
+
diff, base = get_branch_diff(base_branch)
|
|
90
|
+
|
|
91
|
+
# If no diff, maybe we are on the base branch and just committed?
|
|
92
|
+
# get_branch_diff compares base...HEAD. If we are on a new branch that branched from base, it should work.
|
|
93
|
+
|
|
94
|
+
if not diff:
|
|
95
|
+
console.print("[yellow]No changes detected between current branch and base branch.[/yellow]")
|
|
96
|
+
return
|
|
97
|
+
|
|
98
|
+
console.print(f"[cyan]Forging Pull Request details for branch relative to {base}...[/cyan]")
|
|
99
|
+
|
|
100
|
+
prompt = f"""
|
|
101
|
+
Task: Generate a professional GitHub Pull Request title and technical description.
|
|
102
|
+
Context: Below is the git diff for the current branch.
|
|
103
|
+
|
|
104
|
+
DIFF:
|
|
105
|
+
'''
|
|
106
|
+
{diff[:5000]}
|
|
107
|
+
'''
|
|
108
|
+
|
|
109
|
+
Instructions:
|
|
110
|
+
1. Title: Concise, semantic (e.g., feat: add X, fix: handle Y).
|
|
111
|
+
2. Body:
|
|
112
|
+
- **Summary**: 1-2 sentences on what this PR does.
|
|
113
|
+
- **Technical Changes**: Bullet points explaining the logic changes.
|
|
114
|
+
3. Return ONLY a JSON object with "title" and "body" keys. No markdown blocks.
|
|
115
|
+
"""
|
|
116
|
+
|
|
117
|
+
result = generate_content(prompt, mode=mode)
|
|
118
|
+
if not result:
|
|
119
|
+
return
|
|
120
|
+
|
|
121
|
+
try:
|
|
122
|
+
import json
|
|
123
|
+
clean_json = result.replace("```json", "").replace("```", "").strip()
|
|
124
|
+
pr_data = json.loads(clean_json)
|
|
125
|
+
|
|
126
|
+
title = pr_data.get("title", "AI PR Update")
|
|
127
|
+
body = pr_data.get("body", "Automated PR created by Git-Alchemist.")
|
|
128
|
+
|
|
129
|
+
console.print(f"\n[bold green]Forged PR Title:[/bold green] {title}")
|
|
130
|
+
console.print(f"[bold green]Forged PR Body:[/bold green]\n{body}\n")
|
|
131
|
+
|
|
132
|
+
if os.getenv("FORGE_NO_CONFIRM") or Confirm.ask("Forge and open this PR on GitHub?"):
|
|
133
|
+
# Ensure branch is pushed
|
|
134
|
+
current_branch = run_shell("git rev-parse --abbrev-ref HEAD")
|
|
135
|
+
console.print(f"[gray]Pushing {current_branch} to origin...[/gray]")
|
|
136
|
+
run_shell(f"git push -u origin {current_branch} --force")
|
|
137
|
+
|
|
138
|
+
# Create PR
|
|
139
|
+
cmd = f'gh pr create --title "{title}" --body "{body}\n\n> Forged by Git-Alchemist ⚗️"'
|
|
140
|
+
run_shell(cmd)
|
|
141
|
+
console.print("[bold yellow]✨ PR successfully forged and opened![/bold yellow]")
|
|
142
|
+
|
|
143
|
+
except Exception as e:
|
|
144
|
+
console.print(f"[red]Failed to forge PR:[/red] {e}")
|
|
145
|
+
console.print(f"[gray]Raw AI response:[/gray] {result}")
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|