git-alchemist 1.0.2__py3-none-any.whl → 1.1.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.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: git-alchemist
3
- Version: 1.0.2
4
- Summary: A unified AI stack to optimize, describe, and architect your GitHub repositories.
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
 
@@ -1,17 +1,18 @@
1
- git_alchemist-1.0.2.dist-info/licenses/LICENSE,sha256=m0_AWYEhbGuv2DeqHpVotJvfJSbWafyGQwivleB3tUI,1065
2
- src/architect.py,sha256=AJ1sGS5Bfz4ZCdMSNFfszjGae9xDROmW3M2AeatgYWs,6793
1
+ git_alchemist-1.1.0.dist-info/licenses/LICENSE,sha256=m0_AWYEhbGuv2DeqHpVotJvfJSbWafyGQwivleB3tUI,1065
2
+ src/architect.py,sha256=grjRnyXXJ-1hE0xrOUyJgsX2HA1xNm2Ydgngb2DrowQ,6887
3
3
  src/audit.py,sha256=Pehy1LiMZKPOquAj9G50IQ7EAp_HyNXErtSqInt58kY,2790
4
- src/cli.py,sha256=-_NksBB4jG2NKZmg-5OYfkPbqIQKhOKFORfdWiu0czQ,3688
4
+ src/cli.py,sha256=zVmInpNQqFTYyGsgl7o2wGtpIYq4N6stIX_oJRpmYIo,3918
5
5
  src/committer.py,sha256=O9KFavCgJa8mbsFGZoLBdtMEccwlN7yKNuaxhPtdQ-c,2249
6
6
  src/core.py,sha256=mwz5QgTIyIMKVHvfcP2Ndol_DHdZz5azx-zT_FE3VOY,1806
7
+ src/forge.py,sha256=37siXhT71e-YvqM7x3c0O8agx4713iqEcTowz81UJEc,5238
7
8
  src/issue_gen.py,sha256=Sdn2DKnihWIDwRJIRmrql-9q8mqLy7LscEuk4celt0c,2474
8
9
  src/profile_gen.py,sha256=i6N-gTE0rM1hIxWfN8sEBLItm1gu2_6xE94YaeHRa7g,6772
9
10
  src/promote.py,sha256=nSmFywPI5mmHJO15dTpwZ2TO_IZT9w04XOJWF3BE0xM,3224
10
11
  src/repo_tools.py,sha256=QGkCDYRBsl4g1DAecNJtOj91nLYZkJriadY3rCLfzE4,3929
11
12
  src/sage.py,sha256=nxUEc146pKO_6TR4nBONKkRdREY08NEa2ir4BF--vX8,2227
12
13
  src/utils.py,sha256=Ncz4vczfIXcQGgXaFa-8bL3SD3Sykg46yiHDScV8-CA,1187
13
- git_alchemist-1.0.2.dist-info/METADATA,sha256=lLusOpZG555oEwHF7c5u-ttFDppZgRr7P9caw_LbpFI,3231
14
- git_alchemist-1.0.2.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
15
- git_alchemist-1.0.2.dist-info/entry_points.txt,sha256=OcOmJI8khwZFsdRSUGGppfKdVfdGOKRRkwcSlZ-rJTI,43
16
- git_alchemist-1.0.2.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
17
- git_alchemist-1.0.2.dist-info/RECORD,,
14
+ git_alchemist-1.1.0.dist-info/METADATA,sha256=Eka6429mqqj-jGD6jBxVKcbsJnWySyj8cjRDhqp_CU8,3313
15
+ git_alchemist-1.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
16
+ git_alchemist-1.1.0.dist-info/entry_points.txt,sha256=OcOmJI8khwZFsdRSUGGppfKdVfdGOKRRkwcSlZ-rJTI,43
17
+ git_alchemist-1.1.0.dist-info/top_level.txt,sha256=74rtVfumQlgAPzR5_2CgYN24MB0XARCg0t-gzk6gTrM,4
18
+ git_alchemist-1.1.0.dist-info/RECORD,,
src/architect.py CHANGED
@@ -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(cwd)
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
- # Ensure identity is set
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 "Git-Alchemist"')
82
- run_shell(f'git config user.email "alchemist@localhost"')
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 (check if repo already exists):[/red] {e}")
94
+ console.print(f"[red]GitHub deployment failed:[/red] {e}")
93
95
  else:
94
96
  console.print("[yellow]Discarding workspace.[/yellow]")
95
97
 
src/cli.py CHANGED
@@ -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
 
src/forge.py ADDED
@@ -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}")