diffron 0.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.
- diffron/__init__.py +26 -0
- diffron/__init__.pyi +28 -0
- diffron/cli.py +347 -0
- diffron/cli.pyi +30 -0
- diffron/client.py +175 -0
- diffron/client.pyi +80 -0
- diffron/commit_gen.py +151 -0
- diffron/commit_gen.pyi +41 -0
- diffron/git_hooks.py +292 -0
- diffron/git_hooks.pyi +52 -0
- diffron/lemonade.py +192 -0
- diffron/lemonade.pyi +66 -0
- diffron/pr_gen.py +224 -0
- diffron/pr_gen.pyi +56 -0
- diffron/py.typed +0 -0
- diffron/utils.py +236 -0
- diffron/utils.pyi +58 -0
- diffron-0.1.0.dist-info/METADATA +373 -0
- diffron-0.1.0.dist-info/RECORD +28 -0
- diffron-0.1.0.dist-info/WHEEL +5 -0
- diffron-0.1.0.dist-info/entry_points.txt +5 -0
- diffron-0.1.0.dist-info/licenses/LICENSE +21 -0
- diffron-0.1.0.dist-info/top_level.txt +2 -0
- docs/HOOKS.md +442 -0
- docs/PLAN.md +378 -0
- docs/PYPI_RELEASE.md +487 -0
- docs/SETUP.md +524 -0
- docs/USAGE.md +515 -0
diffron/utils.py
ADDED
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared utilities for Diffron.
|
|
3
|
+
|
|
4
|
+
Provides common functions for port scanning, Git operations, and file handling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import subprocess
|
|
8
|
+
import socket
|
|
9
|
+
from typing import List, Optional
|
|
10
|
+
import psutil
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
COMMON_PORTS = [8000, 8001, 8080, 8081, 5000, 5001]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def scan_ports(ports: Optional[List[int]] = None, host: str = "localhost") -> List[int]:
|
|
17
|
+
"""
|
|
18
|
+
Scan for open ports on the given host.
|
|
19
|
+
|
|
20
|
+
Args:
|
|
21
|
+
ports: List of ports to scan. Defaults to COMMON_PORTS.
|
|
22
|
+
host: Host to scan. Defaults to localhost.
|
|
23
|
+
|
|
24
|
+
Returns:
|
|
25
|
+
List of open ports.
|
|
26
|
+
"""
|
|
27
|
+
if ports is None:
|
|
28
|
+
ports = COMMON_PORTS
|
|
29
|
+
|
|
30
|
+
open_ports = []
|
|
31
|
+
for port in ports:
|
|
32
|
+
if is_port_open(host, port):
|
|
33
|
+
open_ports.append(port)
|
|
34
|
+
|
|
35
|
+
return open_ports
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def is_port_open(host: str, port: int, timeout: float = 1.0) -> bool:
|
|
39
|
+
"""
|
|
40
|
+
Check if a port is open on the given host.
|
|
41
|
+
|
|
42
|
+
Args:
|
|
43
|
+
host: Host to check.
|
|
44
|
+
port: Port number to check.
|
|
45
|
+
timeout: Connection timeout in seconds.
|
|
46
|
+
|
|
47
|
+
Returns:
|
|
48
|
+
True if port is open, False otherwise.
|
|
49
|
+
"""
|
|
50
|
+
try:
|
|
51
|
+
with socket.create_connection((host, port), timeout=timeout):
|
|
52
|
+
return True
|
|
53
|
+
except (socket.timeout, ConnectionRefusedError, OSError):
|
|
54
|
+
return False
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def get_staged_diff(max_chars: int = 4000) -> str:
|
|
58
|
+
"""
|
|
59
|
+
Get the staged git diff.
|
|
60
|
+
|
|
61
|
+
Args:
|
|
62
|
+
max_chars: Maximum number of characters to return.
|
|
63
|
+
|
|
64
|
+
Returns:
|
|
65
|
+
Staged diff as string, truncated to max_chars.
|
|
66
|
+
"""
|
|
67
|
+
try:
|
|
68
|
+
result = subprocess.run(
|
|
69
|
+
["git", "diff", "--cached"],
|
|
70
|
+
capture_output=True,
|
|
71
|
+
text=True,
|
|
72
|
+
errors="ignore",
|
|
73
|
+
timeout=30,
|
|
74
|
+
)
|
|
75
|
+
diff = result.stdout[:max_chars]
|
|
76
|
+
return diff
|
|
77
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
78
|
+
return ""
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def get_branch_diff(branch: str, base: str = "main", max_chars: int = 5000) -> str:
|
|
82
|
+
"""
|
|
83
|
+
Get the diff between a branch and its base.
|
|
84
|
+
|
|
85
|
+
Args:
|
|
86
|
+
branch: Branch name to compare.
|
|
87
|
+
base: Base branch to compare against.
|
|
88
|
+
max_chars: Maximum number of characters to return.
|
|
89
|
+
|
|
90
|
+
Returns:
|
|
91
|
+
Diff as string, truncated to max_chars.
|
|
92
|
+
"""
|
|
93
|
+
try:
|
|
94
|
+
result = subprocess.run(
|
|
95
|
+
["git", "diff", f"{base}..{branch}"],
|
|
96
|
+
capture_output=True,
|
|
97
|
+
text=True,
|
|
98
|
+
errors="ignore",
|
|
99
|
+
timeout=30,
|
|
100
|
+
)
|
|
101
|
+
diff = result.stdout[:max_chars]
|
|
102
|
+
return diff
|
|
103
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
104
|
+
return ""
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def get_commit_log(branch: str, base: str = "main") -> str:
|
|
108
|
+
"""
|
|
109
|
+
Get the commit log between a branch and its base.
|
|
110
|
+
|
|
111
|
+
Args:
|
|
112
|
+
branch: Branch name.
|
|
113
|
+
base: Base branch.
|
|
114
|
+
|
|
115
|
+
Returns:
|
|
116
|
+
Commit log as string (oneline format).
|
|
117
|
+
"""
|
|
118
|
+
try:
|
|
119
|
+
result = subprocess.run(
|
|
120
|
+
["git", "log", "--oneline", f"{base}..{branch}"],
|
|
121
|
+
capture_output=True,
|
|
122
|
+
text=True,
|
|
123
|
+
errors="ignore",
|
|
124
|
+
timeout=30,
|
|
125
|
+
)
|
|
126
|
+
return result.stdout
|
|
127
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
128
|
+
return ""
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def get_current_branch() -> Optional[str]:
|
|
132
|
+
"""
|
|
133
|
+
Get the current git branch name.
|
|
134
|
+
|
|
135
|
+
Returns:
|
|
136
|
+
Branch name or None if not in a git repo.
|
|
137
|
+
"""
|
|
138
|
+
try:
|
|
139
|
+
result = subprocess.run(
|
|
140
|
+
["git", "symbolic-ref", "--short", "HEAD"],
|
|
141
|
+
capture_output=True,
|
|
142
|
+
text=True,
|
|
143
|
+
errors="ignore",
|
|
144
|
+
timeout=10,
|
|
145
|
+
)
|
|
146
|
+
if result.returncode == 0:
|
|
147
|
+
return result.stdout.strip()
|
|
148
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
149
|
+
pass
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
def is_git_repo(path: str = ".") -> bool:
|
|
154
|
+
"""
|
|
155
|
+
Check if the given path is inside a git repository.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
path: Path to check.
|
|
159
|
+
|
|
160
|
+
Returns:
|
|
161
|
+
True if inside a git repo, False otherwise.
|
|
162
|
+
"""
|
|
163
|
+
try:
|
|
164
|
+
result = subprocess.run(
|
|
165
|
+
["git", "rev-parse", "--git-dir"],
|
|
166
|
+
capture_output=True,
|
|
167
|
+
text=True,
|
|
168
|
+
cwd=path,
|
|
169
|
+
timeout=10,
|
|
170
|
+
)
|
|
171
|
+
return result.returncode == 0
|
|
172
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
173
|
+
return False
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def get_git_dir(path: str = ".") -> Optional[str]:
|
|
177
|
+
"""
|
|
178
|
+
Get the .git directory path.
|
|
179
|
+
|
|
180
|
+
Args:
|
|
181
|
+
path: Path to check.
|
|
182
|
+
|
|
183
|
+
Returns:
|
|
184
|
+
Absolute path to .git directory or None.
|
|
185
|
+
"""
|
|
186
|
+
try:
|
|
187
|
+
result = subprocess.run(
|
|
188
|
+
["git", "rev-parse", "--git-dir"],
|
|
189
|
+
capture_output=True,
|
|
190
|
+
text=True,
|
|
191
|
+
cwd=path,
|
|
192
|
+
timeout=10,
|
|
193
|
+
)
|
|
194
|
+
if result.returncode == 0:
|
|
195
|
+
git_dir = result.stdout.strip()
|
|
196
|
+
# Convert to absolute path
|
|
197
|
+
import os
|
|
198
|
+
if not os.path.isabs(git_dir):
|
|
199
|
+
git_dir = os.path.abspath(os.path.join(path, git_dir))
|
|
200
|
+
return git_dir
|
|
201
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
202
|
+
pass
|
|
203
|
+
return None
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def find_default_branch() -> str:
|
|
207
|
+
"""
|
|
208
|
+
Find the default branch (main or master).
|
|
209
|
+
|
|
210
|
+
Returns:
|
|
211
|
+
Default branch name.
|
|
212
|
+
"""
|
|
213
|
+
try:
|
|
214
|
+
# Try main first (modern default)
|
|
215
|
+
result = subprocess.run(
|
|
216
|
+
["git", "rev-parse", "--verify", "main"],
|
|
217
|
+
capture_output=True,
|
|
218
|
+
text=True,
|
|
219
|
+
timeout=10,
|
|
220
|
+
)
|
|
221
|
+
if result.returncode == 0:
|
|
222
|
+
return "main"
|
|
223
|
+
|
|
224
|
+
# Fall back to master
|
|
225
|
+
result = subprocess.run(
|
|
226
|
+
["git", "rev-parse", "--verify", "master"],
|
|
227
|
+
capture_output=True,
|
|
228
|
+
text=True,
|
|
229
|
+
timeout=10,
|
|
230
|
+
)
|
|
231
|
+
if result.returncode == 0:
|
|
232
|
+
return "master"
|
|
233
|
+
except (subprocess.SubprocessError, FileNotFoundError):
|
|
234
|
+
pass
|
|
235
|
+
|
|
236
|
+
return "main" # Default to main
|
diffron/utils.pyi
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Shared utilities for Diffron.
|
|
3
|
+
|
|
4
|
+
Provides common functions for port scanning, Git operations, and file handling.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from typing import List, Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
COMMON_PORTS: List[int]
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def scan_ports(
|
|
14
|
+
ports: Optional[List[int]] = None,
|
|
15
|
+
host: str = "localhost"
|
|
16
|
+
) -> List[int]:
|
|
17
|
+
"""Scan for open ports on the given host."""
|
|
18
|
+
...
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def is_port_open(host: str, port: int, timeout: float = 1.0) -> bool:
|
|
22
|
+
"""Check if a port is open on the given host."""
|
|
23
|
+
...
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_staged_diff(max_chars: int = 4000) -> str:
|
|
27
|
+
"""Get the staged git diff."""
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def get_branch_diff(branch: str, base: str = "main", max_chars: int = 5000) -> str:
|
|
32
|
+
"""Get the diff between a branch and its base."""
|
|
33
|
+
...
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def get_commit_log(branch: str, base: str = "main") -> str:
|
|
37
|
+
"""Get the commit log between a branch and its base."""
|
|
38
|
+
...
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def get_current_branch() -> Optional[str]:
|
|
42
|
+
"""Get the current git branch name."""
|
|
43
|
+
...
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def is_git_repo(path: str = ".") -> bool:
|
|
47
|
+
"""Check if the given path is inside a git repository."""
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def get_git_dir(path: str = ".") -> Optional[str]:
|
|
52
|
+
"""Get the .git directory path."""
|
|
53
|
+
...
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def find_default_branch() -> str:
|
|
57
|
+
"""Find the default branch (main or master)."""
|
|
58
|
+
...
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: diffron
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Git commit message and PR description generator using Lemonade
|
|
5
|
+
Home-page: https://github.com/diffron/diffron
|
|
6
|
+
Author: Diffron Contributors
|
|
7
|
+
Author-email: Diffron Contributors <diffron@example.com>
|
|
8
|
+
License: MIT
|
|
9
|
+
Project-URL: Homepage, https://github.com/diffron/diffron
|
|
10
|
+
Project-URL: Documentation, https://github.com/diffron/diffron/docs
|
|
11
|
+
Project-URL: Repository, https://github.com/diffron/diffron
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
17
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
18
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
25
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
26
|
+
Requires-Python: >=3.9
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
License-File: LICENSE
|
|
29
|
+
Requires-Dist: openai>=1.0.0
|
|
30
|
+
Requires-Dist: psutil>=5.9.0
|
|
31
|
+
Provides-Extra: git
|
|
32
|
+
Requires-Dist: gitpython>=3.1.0; extra == "git"
|
|
33
|
+
Provides-Extra: dev
|
|
34
|
+
Requires-Dist: pytest>=7.0.0; extra == "dev"
|
|
35
|
+
Requires-Dist: pytest-cov>=4.0.0; extra == "dev"
|
|
36
|
+
Requires-Dist: black>=23.0.0; extra == "dev"
|
|
37
|
+
Requires-Dist: isort>=5.12.0; extra == "dev"
|
|
38
|
+
Requires-Dist: mypy>=1.0.0; extra == "dev"
|
|
39
|
+
Dynamic: author
|
|
40
|
+
Dynamic: home-page
|
|
41
|
+
Dynamic: license-file
|
|
42
|
+
Dynamic: requires-python
|
|
43
|
+
|
|
44
|
+
# Diffron
|
|
45
|
+
|
|
46
|
+
Git commit message and PR description generator using AMD Lemonade via lemonade-python-sdk.
|
|
47
|
+
|
|
48
|
+
**This is a demo project showcasing the lemonade-python-sdk - submitted to the AMD Lemonade Developer Challenge 2026.**
|
|
49
|
+
|
|
50
|
+

|
|
51
|
+

|
|
52
|
+

|
|
53
|
+

|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Features
|
|
58
|
+
|
|
59
|
+
- 🤖 **Auto Commit Messages** - Generates Conventional Commits format messages from your staged changes
|
|
60
|
+
- 📝 **PR Descriptions** - Creates detailed PR titles and descriptions from branch diffs
|
|
61
|
+
- 🔌 **Lemonade Integration** - Works with your local Lemonade LLM server (no cloud required)
|
|
62
|
+
- 🪟 **Windows Ready** - Fully compatible with GitHub Desktop 3.5.5+ hooks support
|
|
63
|
+
- ⚡ **Auto-Detection** - Automatically finds your running Lemonade instance
|
|
64
|
+
- 🎯 **Model Flexible** - Use any Lemonade-compatible model (default: qwen2.5-it-3b-FLM)
|
|
65
|
+
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
## Quick Start
|
|
69
|
+
|
|
70
|
+
### 1. Install AMD Lemonade Server
|
|
71
|
+
|
|
72
|
+
**Lemonade is AMD's local LLM server for Ryzen AI PCs.**
|
|
73
|
+
|
|
74
|
+
1. Download the installer from [AMD Lemonade Releases](https://github.com/AMD-AI-Software/lemonade/releases)
|
|
75
|
+
2. Run `Lemonade_Server_Installer.exe`
|
|
76
|
+
3. Launch Lemonade Server from the desktop shortcut
|
|
77
|
+
4. Download a model via the Lemonade UI (e.g., `qwen2.5-it-3b-FLM`)
|
|
78
|
+
|
|
79
|
+
📚 **Documentation:** [AMD Ryzen AI - Lemonade Setup](https://ryzenai.docs.amd.com/en/latest/llm/server_interface.html)
|
|
80
|
+
|
|
81
|
+
### 2. Install lemonade-python-sdk
|
|
82
|
+
|
|
83
|
+
**Our Python SDK for AMD Lemonade API:**
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
pip install lemonade-sdk
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
🔗 **Source:** [github.com/Tetramatrix/lemonade-python-sdk](https://github.com/Tetramatrix/lemonade-python-sdk)
|
|
90
|
+
|
|
91
|
+
### 3. Configure Environment
|
|
92
|
+
|
|
93
|
+
**Set Lemonade Server URL (Permanent):**
|
|
94
|
+
1. System Properties → Environment Variables
|
|
95
|
+
2. New User Variable:
|
|
96
|
+
- Name: `LEMONADE_SERVER_URL`
|
|
97
|
+
- Value: `http://localhost:8020`
|
|
98
|
+
|
|
99
|
+
**Or Temporary (current session):**
|
|
100
|
+
```cmd
|
|
101
|
+
set LEMONADE_SERVER_URL=http://localhost:8020
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
### 4. Install Diffron
|
|
105
|
+
|
|
106
|
+
```bash
|
|
107
|
+
pip install diffron
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### 5. Install Git Hooks
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
python -c "from diffron.git_hooks import install_hooks; install_hooks(global_install=True)"
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### 6. Test It
|
|
117
|
+
|
|
118
|
+
```bash
|
|
119
|
+
# Make a change
|
|
120
|
+
echo "test" > test.txt
|
|
121
|
+
git add test.txt
|
|
122
|
+
|
|
123
|
+
# Commit - hooks generate the message automatically!
|
|
124
|
+
git commit -m "anything"
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
Expected output:
|
|
128
|
+
```
|
|
129
|
+
[master abc123] feat: add test.txt file
|
|
130
|
+
1 file changed, 1 insertion(+)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## Installation
|
|
136
|
+
|
|
137
|
+
### Requirements
|
|
138
|
+
|
|
139
|
+
| Software | Version | Purpose |
|
|
140
|
+
|----------|---------|---------|
|
|
141
|
+
| Python | 3.9+ | Runtime |
|
|
142
|
+
| Git | 2.0+ | Version control |
|
|
143
|
+
| GitHub Desktop | 3.5.5+ | Git GUI (Windows) |
|
|
144
|
+
| lemonade-sdk | Latest | AMD Lemonade API client |
|
|
145
|
+
| Lemonade | Latest | Local LLM server |
|
|
146
|
+
|
|
147
|
+
### Full Installation Guide
|
|
148
|
+
|
|
149
|
+
See [docs/SETUP.md](docs/SETUP.md) for detailed Windows-specific instructions.
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Usage
|
|
154
|
+
|
|
155
|
+
### CLI Commands
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
# Install hooks globally
|
|
159
|
+
python -c "from diffron.git_hooks import install_hooks; install_hooks(global_install=True)"
|
|
160
|
+
|
|
161
|
+
# Generate PR description
|
|
162
|
+
python -c "from diffron import generate_pr_description; pr = generate_pr_description(); print(pr.format_output())"
|
|
163
|
+
|
|
164
|
+
# Check status
|
|
165
|
+
python -c "from diffron import is_lemonade_running, is_hooks_installed; print('Lemonade:', is_lemonade_running()); print('Hooks:', is_hooks_installed(check_global=True))"
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Python API
|
|
169
|
+
|
|
170
|
+
```python
|
|
171
|
+
from diffron import DiffronClient
|
|
172
|
+
|
|
173
|
+
# Create client
|
|
174
|
+
client = DiffronClient()
|
|
175
|
+
|
|
176
|
+
# Generate commit message
|
|
177
|
+
msg = client.generate_commit_message()
|
|
178
|
+
print(msg) # "feat: add user authentication"
|
|
179
|
+
|
|
180
|
+
# Generate PR description
|
|
181
|
+
pr = client.generate_pr_description(branch="feature/my-feature")
|
|
182
|
+
print(f"TITLE: {pr.title}")
|
|
183
|
+
print(f"DESCRIPTION: {pr.description}")
|
|
184
|
+
|
|
185
|
+
# Install hooks
|
|
186
|
+
client.install_hooks(global_install=True)
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
### GitHub Desktop Workflow
|
|
190
|
+
|
|
191
|
+
1. Make changes to your files
|
|
192
|
+
2. Open GitHub Desktop
|
|
193
|
+
3. Enter any commit message (e.g., "auto")
|
|
194
|
+
4. Click "Commit to main"
|
|
195
|
+
5. **Diffron replaces** your message with AI-generated message
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Configuration
|
|
200
|
+
|
|
201
|
+
### Environment Variables
|
|
202
|
+
|
|
203
|
+
| Variable | Default | Description |
|
|
204
|
+
|----------|---------|-------------|
|
|
205
|
+
| `LEMONADE_SERVER_URL` | `http://localhost:8020` | Lemonade server URL |
|
|
206
|
+
| `DIFFRON_MODEL` | `qwen2.5-it-3b-FLM` | Model name to use |
|
|
207
|
+
| `DIFFRON_MAX_DIFF_CHARS` | `4000` | Max diff characters |
|
|
208
|
+
|
|
209
|
+
### Change Model
|
|
210
|
+
|
|
211
|
+
```cmd
|
|
212
|
+
# System-wide
|
|
213
|
+
setx DIFFRON_MODEL "qwen2.5-coder-7b"
|
|
214
|
+
|
|
215
|
+
# Current session
|
|
216
|
+
set DIFFRON_MODEL=qwen2.5-coder-7b
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
### Python API
|
|
220
|
+
|
|
221
|
+
```python
|
|
222
|
+
from diffron import DiffronClient
|
|
223
|
+
|
|
224
|
+
# Use different model
|
|
225
|
+
client = DiffronClient(model="your-model-name")
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Documentation
|
|
231
|
+
|
|
232
|
+
| Document | Description |
|
|
233
|
+
|----------|-------------|
|
|
234
|
+
| [SETUP.md](docs/SETUP.md) | Complete installation guide for Windows |
|
|
235
|
+
| [HOOKS.md](docs/HOOKS.md) | Git hooks architecture and internals |
|
|
236
|
+
| [USAGE.md](docs/USAGE.md) | Detailed usage examples |
|
|
237
|
+
|
|
238
|
+
---
|
|
239
|
+
|
|
240
|
+
## Related Projects
|
|
241
|
+
|
|
242
|
+
### Tetramatrix Projects
|
|
243
|
+
|
|
244
|
+
| Project | Description |
|
|
245
|
+
|---------|-------------|
|
|
246
|
+
| **lemonade-python-sdk** | 🍋 **AMD Lemonade Challenge Submission** - Python SDK for AMD Lemonade API |
|
|
247
|
+
| **Diffron** | Demo project showcasing lemonade-python-sdk (this repo) |
|
|
248
|
+
| **Aicono** | AI Assistant (Desktop App) |
|
|
249
|
+
| **TabNeuron** | Browser Connector / Memory |
|
|
250
|
+
| **Sorana** | Advanced AI Interface |
|
|
251
|
+
| **RyzenPilot** | Hardware Optimization |
|
|
252
|
+
|
|
253
|
+
**Note:** Lemonade is AMD's local LLM server for Ryzen AI PCs. Diffron uses `lemonade-python-sdk` to communicate with Lemonade's API.
|
|
254
|
+
|
|
255
|
+
🏆 **AMD Lemonade Developer Challenge 2026:** This project demonstrates the capabilities of lemonade-python-sdk as a real-world application built on AMD Lemonade.
|
|
256
|
+
|
|
257
|
+
---
|
|
258
|
+
|
|
259
|
+
## How It Works
|
|
260
|
+
|
|
261
|
+
```
|
|
262
|
+
┌─────────────────────────────────────────────────────────┐
|
|
263
|
+
│ 1. User makes changes and runs: git commit │
|
|
264
|
+
└─────────────────────────────────────────────────────────┘
|
|
265
|
+
↓
|
|
266
|
+
┌─────────────────────────────────────────────────────────┐
|
|
267
|
+
│ 2. Git hook executes prepare-commit-msg │
|
|
268
|
+
│ - Location: C:/Users/Name/.diffron-hooks/ │
|
|
269
|
+
└─────────────────────────────────────────────────────────┘
|
|
270
|
+
↓
|
|
271
|
+
┌─────────────────────────────────────────────────────────┐
|
|
272
|
+
│ 3. Hook reads staged diff: git diff --cached │
|
|
273
|
+
└─────────────────────────────────────────────────────────┘
|
|
274
|
+
↓
|
|
275
|
+
┌─────────────────────────────────────────────────────────┐
|
|
276
|
+
│ 4. Hook calls Lemonade API │
|
|
277
|
+
│ - URL: http://localhost:8020/api/v1 │
|
|
278
|
+
│ - Model: qwen2.5-it-3b-FLM │
|
|
279
|
+
└─────────────────────────────────────────────────────────┘
|
|
280
|
+
↓
|
|
281
|
+
┌─────────────────────────────────────────────────────────┐
|
|
282
|
+
│ 5. AI generates Conventional Commit message │
|
|
283
|
+
│ - "feat: add user authentication module" │
|
|
284
|
+
└─────────────────────────────────────────────────────────┘
|
|
285
|
+
↓
|
|
286
|
+
┌─────────────────────────────────────────────────────────┐
|
|
287
|
+
│ 6. Git opens editor with generated message │
|
|
288
|
+
│ - User can review/modify before saving │
|
|
289
|
+
└─────────────────────────────────────────────────────────┘
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Troubleshooting
|
|
295
|
+
|
|
296
|
+
### Lemonade Not Detected
|
|
297
|
+
|
|
298
|
+
```bash
|
|
299
|
+
# Start Lemonade
|
|
300
|
+
lemonade serve qwen2.5-it-3b-FLM
|
|
301
|
+
|
|
302
|
+
# Verify URL
|
|
303
|
+
echo %LEMONADE_SERVER_URL%
|
|
304
|
+
```
|
|
305
|
+
|
|
306
|
+
### Hooks Not Working
|
|
307
|
+
|
|
308
|
+
```bash
|
|
309
|
+
# Check GitHub Desktop version (must be 3.5.5+)
|
|
310
|
+
# Help → About
|
|
311
|
+
|
|
312
|
+
# Verify hooks path
|
|
313
|
+
git config --global core.hooksPath
|
|
314
|
+
|
|
315
|
+
# Reinstall hooks
|
|
316
|
+
python -c "from diffron.git_hooks import install_hooks; install_hooks(global_install=True)"
|
|
317
|
+
```
|
|
318
|
+
|
|
319
|
+
### Model Not Found (404)
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# Download model
|
|
323
|
+
lemonade pull qwen2.5-it-3b-FLM
|
|
324
|
+
|
|
325
|
+
# Verify model name
|
|
326
|
+
set DIFFRON_MODEL=qwen2.5-it-3b-FLM
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
See [docs/SETUP.md](docs/SETUP.md) for complete troubleshooting guide.
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## License
|
|
334
|
+
|
|
335
|
+
MIT License - see [LICENSE](LICENSE) for details.
|
|
336
|
+
|
|
337
|
+
---
|
|
338
|
+
|
|
339
|
+
## Contributing
|
|
340
|
+
|
|
341
|
+
Contributions welcome! This is an open source project.
|
|
342
|
+
|
|
343
|
+
1. Fork the repository
|
|
344
|
+
2. Create a feature branch
|
|
345
|
+
3. Make your changes
|
|
346
|
+
4. Run tests
|
|
347
|
+
5. Submit a pull request
|
|
348
|
+
|
|
349
|
+
### Development Setup
|
|
350
|
+
|
|
351
|
+
```bash
|
|
352
|
+
git clone https://github.com/diffron/diffron.git
|
|
353
|
+
cd diffron
|
|
354
|
+
pip install -e ".[dev]"
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### Run Tests
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
pytest tests/
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
---
|
|
364
|
+
|
|
365
|
+
## Acknowledgments
|
|
366
|
+
|
|
367
|
+
- **Lemonade** - Local LLM server by the Lemonade team
|
|
368
|
+
- **GitHub Desktop** - Git GUI with hooks support (3.5.5+)
|
|
369
|
+
- **Conventional Commits** - Commit message format specification
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
*Version: 0.1.0 | Last updated: 2026-03-28*
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
diffron/__init__.py,sha256=zWh5clvGKqkTWP4i5BuG4uzzBAV0WOz5cb66noH3n6w,730
|
|
2
|
+
diffron/__init__.pyi,sha256=narREZbkyqZeyb6uFKMqSJDjdisbP7bwdH6I_qpk8Ik,712
|
|
3
|
+
diffron/cli.py,sha256=-bVwq_gdepyBgGiNutSO7VZOkzR56bBdF_eCa5ldo6Y,9673
|
|
4
|
+
diffron/cli.pyi,sha256=DRx5Uvg9YoOfLIzhCRArLHXpddaADZoose94oOSxHu8,550
|
|
5
|
+
diffron/client.py,sha256=6C69FGEiqMPhL_qVfpXjV-V4tsIyTssxwXKdzF-FPzA,5122
|
|
6
|
+
diffron/client.pyi,sha256=eEcSY2kJHiuYcKGJ7KnaIcvY92xlm4b-yXV9ITf4GBg,2087
|
|
7
|
+
diffron/commit_gen.py,sha256=IJTadg5ttvWFl0X1aLREprHvBr4AAHlhKLMxN2fMjT4,4560
|
|
8
|
+
diffron/commit_gen.pyi,sha256=-3MZezLNCfXIHzsIQdiWYVZ_1gMBzH51vH9pnICpKmc,949
|
|
9
|
+
diffron/git_hooks.py,sha256=ScyEfwZVID4yuniRVEEBOoVWqQ2tzvoR2kJPLTjoqFQ,8218
|
|
10
|
+
diffron/git_hooks.pyi,sha256=HPMhxzGG3-Jqsnn95fwWU535-IiQAR1faRD9HcpJvAI,1027
|
|
11
|
+
diffron/lemonade.py,sha256=y1YUWLWlrEiyTnn_YJVrc8qU3-2990hORGj-IMNoTlA,5600
|
|
12
|
+
diffron/lemonade.pyi,sha256=Z3dvqsstOwA5eHTu2oBXmaECyCBZLz-RmzsHDwRXlUM,1350
|
|
13
|
+
diffron/pr_gen.py,sha256=84qZUf1IXsxv27HxCfqi5yFvJ6lOeCaK87KlDVVNwVA,6560
|
|
14
|
+
diffron/pr_gen.pyi,sha256=oMm9Q9cNwzT9kpAhaWqiGMZxnpdCteyPQdV-eai0c98,1317
|
|
15
|
+
diffron/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
16
|
+
diffron/utils.py,sha256=-YbCIm8rAGiDBYZahMZgUJ17r3NlpEX-FX97nsz5mp0,5831
|
|
17
|
+
diffron/utils.pyi,sha256=LnxeTcOSucvfg4jszVBI8cYHoqUnkgaOiK4PmUJKPU8,1251
|
|
18
|
+
diffron-0.1.0.dist-info/licenses/LICENSE,sha256=Lyind8rORjrnAcM4YCvqvlklEUaeZSTzqmA2QiRrAGY,1077
|
|
19
|
+
docs/HOOKS.md,sha256=2l138u33rcQeBFEIhpOcECneg2HHMs7EdQ1JCDtWxKQ,13778
|
|
20
|
+
docs/PLAN.md,sha256=_fBS3zWcaxcW4Npss66-YLDn_lrAZyhSnjreTLK8cfU,11217
|
|
21
|
+
docs/PYPI_RELEASE.md,sha256=sl_vT6x_piPNDfymfCHNpM_j0OwY3ZGbxRAMUy8RCa4,8729
|
|
22
|
+
docs/SETUP.md,sha256=Uy_1_AGN0i4JPigPquZWb2YXijd5Yzz-Fn5zwcgTfEs,13023
|
|
23
|
+
docs/USAGE.md,sha256=hYDQQZdKcUIUUrdDG51LVzHduMQgxwNNdYGBvOgMea8,9878
|
|
24
|
+
diffron-0.1.0.dist-info/METADATA,sha256=tAYHQcHWd1zDPdpRpkmOkFRPXgsXcK_1Z_1IXrxkF9Q,12136
|
|
25
|
+
diffron-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
26
|
+
diffron-0.1.0.dist-info/entry_points.txt,sha256=IRWRl2dBVxsXKNcV45trz4E22WjDDAI1xr5U8_CILmA,214
|
|
27
|
+
diffron-0.1.0.dist-info/top_level.txt,sha256=xRWBliiMzDBMb-K_izgUOMcVqj88-EXLhZbJU4Nl-EA,13
|
|
28
|
+
diffron-0.1.0.dist-info/RECORD,,
|