diffron 0.1.10__tar.gz → 0.1.11__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.
Files changed (50) hide show
  1. {diffron-0.1.10/diffron.egg-info → diffron-0.1.11}/PKG-INFO +1 -1
  2. {diffron-0.1.10 → diffron-0.1.11}/diffron/__init__.py +1 -1
  3. {diffron-0.1.10 → diffron-0.1.11}/diffron/commit_gen.py +51 -18
  4. {diffron-0.1.10 → diffron-0.1.11/diffron.egg-info}/PKG-INFO +1 -1
  5. {diffron-0.1.10 → diffron-0.1.11}/diffron.egg-info/SOURCES.txt +0 -3
  6. {diffron-0.1.10 → diffron-0.1.11}/diffron.egg-info/top_level.txt +1 -0
  7. {diffron-0.1.10 → diffron-0.1.11}/pyproject.toml +1 -1
  8. diffron-0.1.10/docs/LEMONADE_PORT_VERIFICATION_FIX.md +0 -105
  9. diffron-0.1.10/docs/LEMONADE_UNRESPONSIVE_FIX.md +0 -97
  10. diffron-0.1.10/docs/MODEL_NAME_CASE_FIX.md +0 -88
  11. {diffron-0.1.10 → diffron-0.1.11}/CHANGELOG.md +0 -0
  12. {diffron-0.1.10 → diffron-0.1.11}/LICENSE +0 -0
  13. {diffron-0.1.10 → diffron-0.1.11}/MANIFEST.in +0 -0
  14. {diffron-0.1.10 → diffron-0.1.11}/README.md +0 -0
  15. {diffron-0.1.10 → diffron-0.1.11}/diffron/__init__.pyi +0 -0
  16. {diffron-0.1.10 → diffron-0.1.11}/diffron/__main__.py +0 -0
  17. {diffron-0.1.10 → diffron-0.1.11}/diffron/agent_detect.py +0 -0
  18. {diffron-0.1.10 → diffron-0.1.11}/diffron/agent_detect.pyi +0 -0
  19. {diffron-0.1.10 → diffron-0.1.11}/diffron/cli.py +0 -0
  20. {diffron-0.1.10 → diffron-0.1.11}/diffron/cli.pyi +0 -0
  21. {diffron-0.1.10 → diffron-0.1.11}/diffron/client.py +0 -0
  22. {diffron-0.1.10 → diffron-0.1.11}/diffron/client.pyi +0 -0
  23. {diffron-0.1.10 → diffron-0.1.11}/diffron/commit_gen.pyi +0 -0
  24. {diffron-0.1.10 → diffron-0.1.11}/diffron/git_hooks.py +0 -0
  25. {diffron-0.1.10 → diffron-0.1.11}/diffron/git_hooks.pyi +0 -0
  26. {diffron-0.1.10 → diffron-0.1.11}/diffron/hooks/aipr.py +0 -0
  27. {diffron-0.1.10 → diffron-0.1.11}/diffron/hooks/prepare-commit-msg +0 -0
  28. {diffron-0.1.10 → diffron-0.1.11}/diffron/hooks/prepare-commit-msg-global.py +0 -0
  29. {diffron-0.1.10 → diffron-0.1.11}/diffron/hooks/prepare-commit-msg.py +0 -0
  30. {diffron-0.1.10 → diffron-0.1.11}/diffron/lemonade.py +0 -0
  31. {diffron-0.1.10 → diffron-0.1.11}/diffron/lemonade.pyi +0 -0
  32. {diffron-0.1.10 → diffron-0.1.11}/diffron/models.py +0 -0
  33. {diffron-0.1.10 → diffron-0.1.11}/diffron/models.pyi +0 -0
  34. {diffron-0.1.10 → diffron-0.1.11}/diffron/pr_gen.py +0 -0
  35. {diffron-0.1.10 → diffron-0.1.11}/diffron/pr_gen.pyi +0 -0
  36. {diffron-0.1.10 → diffron-0.1.11}/diffron/py.typed +0 -0
  37. {diffron-0.1.10 → diffron-0.1.11}/diffron/utils.py +0 -0
  38. {diffron-0.1.10 → diffron-0.1.11}/diffron/utils.pyi +0 -0
  39. {diffron-0.1.10 → diffron-0.1.11}/diffron.egg-info/dependency_links.txt +0 -0
  40. {diffron-0.1.10 → diffron-0.1.11}/diffron.egg-info/entry_points.txt +0 -0
  41. {diffron-0.1.10 → diffron-0.1.11}/diffron.egg-info/requires.txt +0 -0
  42. {diffron-0.1.10 → diffron-0.1.11}/docs/HOOKS.md +0 -0
  43. {diffron-0.1.10 → diffron-0.1.11}/docs/SETUP.md +0 -0
  44. {diffron-0.1.10 → diffron-0.1.11}/docs/USAGE.md +0 -0
  45. {diffron-0.1.10 → diffron-0.1.11}/hooks/aipr.py +0 -0
  46. {diffron-0.1.10 → diffron-0.1.11}/hooks/prepare-commit-msg-global.py +0 -0
  47. {diffron-0.1.10 → diffron-0.1.11}/hooks/prepare-commit-msg.py +0 -0
  48. {diffron-0.1.10 → diffron-0.1.11}/requirements.txt +0 -0
  49. {diffron-0.1.10 → diffron-0.1.11}/setup.cfg +0 -0
  50. {diffron-0.1.10 → diffron-0.1.11}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffron
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: Git commit message and PR description generator using Lemonade
5
5
  Home-page: https://github.com/diffron/diffron
6
6
  Author: Diffron Contributors
@@ -24,7 +24,7 @@ from .models import (
24
24
  get_model_config,
25
25
  )
26
26
 
27
- __version__ = "0.1.10"
27
+ __version__ = "0.1.11"
28
28
  __author__ = "Diffron Contributors"
29
29
  __all__ = [
30
30
  "DiffronClient",
@@ -28,8 +28,8 @@ COMMIT_TYPES = [
28
28
  ]
29
29
 
30
30
  DEFAULT_MAX_CHARS = 4000
31
- DEFAULT_MAX_TOKENS = 100
32
- DEFAULT_TEMPERATURE = 0.2
31
+ DEFAULT_MAX_TOKENS = 500
32
+ DEFAULT_TEMPERATURE = 0.4
33
33
 
34
34
 
35
35
  def generate_commit_message(
@@ -45,8 +45,8 @@ def generate_commit_message(
45
45
  Args:
46
46
  diff: Git diff string. If None, gets staged diff automatically.
47
47
  max_chars: Maximum characters of diff to send. Defaults to 4000.
48
- max_tokens: Maximum tokens to generate. Defaults to 100.
49
- temperature: Sampling temperature. Defaults to 0.2.
48
+ max_tokens: Maximum tokens to generate. Defaults to 500.
49
+ temperature: Sampling temperature. Defaults to 0.4.
50
50
  client: LemonadeClient instance. Creates new one if not provided.
51
51
 
52
52
  Returns:
@@ -74,10 +74,38 @@ def generate_commit_message(
74
74
  # Build prompt
75
75
  commit_types = ", ".join(COMMIT_TYPES)
76
76
  prompt = (
77
- f"Write a concise Git commit message in Conventional Commits format. "
78
- f"Use one of these types: {commit_types}. "
79
- f"Format: 'type: description' (e.g., 'feat: add user authentication'). "
80
- f"Output ONLY the commit message, nothing else. No quotes, no explanations.\n\n"
77
+ f"Write a Git commit message following the Conventional Commits specification.\n\n"
78
+ f"Format:\n"
79
+ f" type(scope): short summary\n"
80
+ f"\n"
81
+ f" Optional body explaining what changed and why.\n"
82
+ f" Use bullet points for multiple changes.\n"
83
+ f" Wrap lines at 72 characters.\n\n"
84
+ f"Rules:\n"
85
+ f"- Use one of: {commit_types}\n"
86
+ f"- Scope is optional — use it when the change affects a specific module/component\n"
87
+ f"- The summary line should be imperative mood (\"add\" not \"added\"), max 72 chars\n"
88
+ f"- The body lists WHAT changed per file/area and WHY, not a diff walkthrough\n"
89
+ f"- Separate title from body with a blank line\n"
90
+ f"- If the change is trivial (single file, <10 lines), a title-only message is fine\n\n"
91
+ f"Examples:\n"
92
+ f" feat(auth): add JWT token refresh logic\n"
93
+ f"\n"
94
+ f" Implement automatic token refresh when the access token expires.\n"
95
+ f" Uses a background thread to refresh 30 seconds before expiry,\n"
96
+ f" preventing interrupted user sessions.\n"
97
+ f"\n"
98
+ f" refactor(api): extract route handlers into separate modules\n"
99
+ f"\n"
100
+ f" router.py:\n"
101
+ f" - Move auth routes to auth_router.py\n"
102
+ f" - Move user routes to user_router.py\n"
103
+ f"\n"
104
+ f" Reduces router.py from 800 to 120 lines for easier maintenance.\n"
105
+ f"\n"
106
+ f" fix(parser): handle empty input gracefully\n"
107
+ f"\n"
108
+ f"Output ONLY the commit message, nothing else.\n\n"
81
109
  f"Diff:\n{diff}"
82
110
  )
83
111
 
@@ -97,19 +125,24 @@ def generate_commit_message(
97
125
  commit_message = re.sub(r"<think>.*?</think>", "", commit_message, flags=re.DOTALL)
98
126
  commit_message = commit_message.replace("<think>", "").replace("</think>", "").strip()
99
127
 
100
- # Remove any surrounding quotes
101
- if commit_message.startswith('"') and commit_message.endswith('"'):
102
- commit_message = commit_message[1:-1]
103
- if commit_message.startswith("'") and commit_message.endswith("'"):
104
- commit_message = commit_message[1:-1]
105
-
106
128
  # Remove markdown code blocks if present
107
129
  if commit_message.startswith("```"):
108
130
  lines = commit_message.split("\n")
109
- commit_message = "\n".join(
110
- line for line in lines
111
- if not line.startswith("```")
112
- ).strip()
131
+ # Find opening and closing ``` and strip them
132
+ start = 0
133
+ end = len(lines)
134
+ for i, line in enumerate(lines):
135
+ if line.strip().startswith("```") and i == 0:
136
+ start = 1
137
+ elif line.strip() == "```" and i > 0:
138
+ end = i
139
+ break
140
+ commit_message = "\n".join(lines[start:end]).strip()
141
+
142
+ # Remove surrounding quotes (only if entire message is wrapped)
143
+ if (commit_message.startswith('"') and commit_message.endswith('"')) or \
144
+ (commit_message.startswith("'") and commit_message.endswith("'")):
145
+ commit_message = commit_message[1:-1].strip()
113
146
 
114
147
  return commit_message
115
148
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: diffron
3
- Version: 0.1.10
3
+ Version: 0.1.11
4
4
  Summary: Git commit message and PR description generator using Lemonade
5
5
  Home-page: https://github.com/diffron/diffron
6
6
  Author: Diffron Contributors
@@ -38,9 +38,6 @@ diffron/hooks/prepare-commit-msg
38
38
  diffron/hooks/prepare-commit-msg-global.py
39
39
  diffron/hooks/prepare-commit-msg.py
40
40
  docs/HOOKS.md
41
- docs/LEMONADE_PORT_VERIFICATION_FIX.md
42
- docs/LEMONADE_UNRESPONSIVE_FIX.md
43
- docs/MODEL_NAME_CASE_FIX.md
44
41
  docs/SETUP.md
45
42
  docs/USAGE.md
46
43
  hooks/aipr.py
@@ -1,4 +1,5 @@
1
1
  build
2
+ dev-docs
2
3
  diffron
3
4
  dist
4
5
  docs
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "diffron"
7
- version = "0.1.10"
7
+ version = "0.1.11"
8
8
  description = "Git commit message and PR description generator using Lemonade"
9
9
  readme = "README.md"
10
10
  license = {text = "MIT"}
@@ -1,105 +0,0 @@
1
- # Lemonade Port Verification Fix
2
-
3
- ## Problem
4
-
5
- Diffron incorrectly connected to **port 8000** (running Axo Biomarker Analyzer) instead of **port 8020** (running Lemonade), causing:
6
-
7
- ```
8
- Error code: 404 - {'detail': 'Not Found'}
9
- ```
10
-
11
- ### Root Cause
12
-
13
- The `detect_lemonade_port()` function in `diffron/lemonade.py` only checked if a **port was open** (`is_port_open`), not whether it was actually a **Lemonade server**.
14
-
15
- When port 8000 responded (Axo Biomarker web app), Diffron mistakenly identified it as Lemonade and sent API requests to the wrong service.
16
-
17
- | Port | Service | Response |
18
- |------|---------|----------|
19
- | 8000 | Axo Biomarker Analyzer | HTML web page |
20
- | 8020 | **Lemonade** (correct) | `{"data": [...]}` API |
21
-
22
- ## Solution
23
-
24
- ### Use lemonade-python-sdk's `verify_lemonade_server`
25
-
26
- The **lemonade-python-sdk** provides `verify_lemonade_server(port, host)` which:
27
-
28
- 1. Queries the native Lemonade API endpoint `/api/v1/models`
29
- 2. Validates the response format (`{"data": [...]}` or list)
30
- 3. Distinguishes Lemonade from other services (Ollama, web apps, etc.)
31
-
32
- ### Changes
33
-
34
- **1. Added `lemonade-sdk` dependency**
35
-
36
- Updated `requirements.txt`:
37
- ```txt
38
- lemonade-sdk>=0.1.0
39
- ```
40
-
41
- **2. Updated `detect_lemonade_port()`**
42
-
43
- `diffron/lemonade.py` now:
44
-
45
- - Imports `verify_lemonade_server` and `find_available_lemonade_port` from `lemonade_sdk.port_scanner`
46
- - **Verifies** each open port is actually Lemonade before returning it
47
- - Falls back to legacy `is_port_open` behavior if SDK is not installed
48
-
49
- ```python
50
- # Import from lemonade-python-sdk for proper Lemonade verification
51
- try:
52
- from lemonade_sdk.port_scanner import verify_lemonade_server, find_available_lemonade_port
53
- HAS_LEMONADE_SDK = True
54
- except ImportError:
55
- HAS_LEMONADE_SDK = False
56
-
57
- def detect_lemonade_port(...) -> Optional[int]:
58
- # ... existing env var checks ...
59
-
60
- # Try SDK's find_available_lemonade_port first
61
- if HAS_LEMONADE_SDK:
62
- found = find_available_lemonade_port(hostname, scan_ports)
63
- if found:
64
- return found
65
-
66
- # Fallback: manual scan with SDK verification
67
- for port in ports_to_scan:
68
- if is_port_open(hostname, port):
69
- if HAS_LEMONADE_SDK:
70
- if verify_lemonade_server(port, hostname): # ← Key fix!
71
- return port
72
- else:
73
- return port # Legacy: no verification
74
- ```
75
-
76
- **3. Added graceful fallback**
77
-
78
- If `lemonade-sdk` is not installed, Diffron falls back to the original `is_port_open` behavior (first open port wins), maintaining backward compatibility.
79
-
80
- ## Testing
81
-
82
- ```bash
83
- # Before fix: returned 8000 (Axo Biomarker)
84
- # After fix: returns 8020 (Lemonade)
85
- python -c "from diffron.lemonade import detect_lemonade_port; print(detect_lemonade_port())"
86
- # Output: 8020
87
- ```
88
-
89
- ## Impact
90
-
91
- - **Fixes 404 errors** when other services run on ports 8000/8080
92
- - **No breaking changes** - existing setups continue to work
93
- - **Better diagnostics** - clear separation between Lemonade and non-Lemonade services
94
-
95
- ## Files Changed
96
-
97
- | File | Change |
98
- |------|--------|
99
- | `diffron/lemonade.py` | Use `verify_lemonade_server` for port detection |
100
- | `requirements.txt` | Add `lemonade-sdk>=0.1.0` dependency |
101
-
102
- ## Version
103
-
104
- - **Fixed in**: 0.1.3
105
- - **Date**: 2026-04-13
@@ -1,97 +0,0 @@
1
- # Lemonade Model Unresponsive Fix
2
-
3
- ## Problem
4
-
5
- Lemonade-Modell wurde manchmal unresponsive, was zu Fehlern in Diffron führte:
6
-
7
- ```
8
- Diffron error: 'NoneType' object is not subscriptable
9
- ```
10
-
11
- oder
12
-
13
- ```
14
- Lemonade model failed after 3 attempts: Lemonade model returned no response
15
- ```
16
-
17
- ## Lösung
18
-
19
- In [`diffron/lemonade.py`](diffron/lemonade.py) wurde die Retry-Logik mit automatischem Entladen und Neuladen des Modells implementiert.
20
-
21
- ### Änderungen in `LemonadeClient.chat_completion()`
22
-
23
- ```python
24
- max_retries = 3
25
- for attempt in range(1, max_retries + 1):
26
- try:
27
- response = self.client.chat.completions.create(...)
28
- # Handle None response (model unresponsive)
29
- if response is None or not response.choices:
30
- if attempt < max_retries:
31
- self._reload_model()
32
- continue
33
- raise RuntimeError("Lemonade model returned no response")
34
- return response.choices[0].message.content
35
- except Exception as e:
36
- if attempt < max_retries:
37
- self._reload_model()
38
- continue
39
- raise RuntimeError(
40
- f"Lemonade model failed after {max_retries} attempts: {e}"
41
- )
42
- ```
43
-
44
- ### Neue Methode `_reload_model()`
45
-
46
- Entlädt und lädt das Lemonade-Modell neu, wenn es unresponsive wird. Verwendet die `unload_model()` und `load_model()` Methoden des lemonade-python-sdk, um einen echten Server-seitigen Neustart zu erzwingen.
47
-
48
- ```python
49
- def _reload_model(self) -> None:
50
- """Unload and reload Lemonade model when unresponsive.
51
-
52
- Uses lemonade-python-sdk's unload_model() and load_model() methods
53
- to properly force a server-side model restart.
54
- """
55
- try:
56
- # Use SDK's LemonadeClient for server operations
57
- sdk_client = _LemonadeSDKClient(
58
- base_url=f"http://{self.host}:{self._port}"
59
- )
60
-
61
- # Unload current model from server
62
- sdk_client.unload_model()
63
-
64
- # Small delay to allow server to complete unload
65
- import time
66
- time.sleep(0.5)
67
-
68
- # Load the model back on server
69
- sdk_client.load_model(model_name=self.model)
70
-
71
- # Reinitialize our client (same port, model should be fresh)
72
- self._init_client()
73
-
74
- except Exception:
75
- pass # Continue even if reload fails
76
- ```
77
-
78
- ### Wichtige Änderungen
79
-
80
- 1. **Import-Alias**: Der SDK-Import wurde zu `_LemonadeSDKClient` umbenanmt, um Namenskonflikte mit der eigenen `LemonadeClient` Klasse zu vermeiden.
81
-
82
- 2. **Echter Server-Neustart**: Statt nur den Port zu wechseln (was nicht funktioniert hat), wird jetzt das Modell über die SDK-API entladen und neu geladen:
83
- - `sdk_client.unload_model()` - Entlädt das aktuelle Modell vom Server
84
- - `time.sleep(0.5)` - Kurze Wartezeit für sauberen unload
85
- - `sdk_client.load_model(model_name=self.model)` - Lädt das Modell neu
86
-
87
- 3. **Retry-Logik**: Bei 3 Versuchen wird jeweils versucht, das Modell neu zu laden, bevor ein Fehler geworfen wird.
88
-
89
- ## Technische Details
90
-
91
- - **Maximale Wiederholungen**: 3
92
- - **Wartezeit zwischen Reloads**: 0.5 Sekunden
93
- - **Fehlerbehandlung**: Bei jedem Reload-Versuch werden alle Exceptions abgefangen, damit der Prozess fortgesetzt werden kann
94
-
95
- ## Version
96
-
97
- Diese Änderung wurde in Version 0.1.5 veröffentlicht.
@@ -1,88 +0,0 @@
1
- # Model Name Case Sensitivity Fix
2
-
3
- ## Problem
4
-
5
- Diffron returned a **404 Not Found** error from the Lemonade server when using the `Qwen3.5-0.8B-GGUF` model.
6
-
7
- ### Root Cause
8
-
9
- The Lemonade server is **case-sensitive** when matching model names. Diffron's curated model configuration used the wrong casing:
10
-
11
- | Location | Model Name (Before) | Model Name (Expected) |
12
- |----------|---------------------|------------------------|
13
- | `diffron/models.py` | `qwen3.5-0.8b-gguf` | `Qwen3.5-0.8B-GGUF` |
14
-
15
- This caused the OpenAI SDK to send a request with the wrong model identifier, resulting in:
16
-
17
- ```
18
- Error code: 404 - {'detail': 'Not Found'}
19
- ```
20
-
21
- ## Solution
22
-
23
- ### 1. Corrected Model Name
24
-
25
- Updated the canonical model name in `diffron/models.py`:
26
-
27
- ```python
28
- # Before
29
- ModelConfig(name="qwen3.5-0.8b-gguf", ...)
30
-
31
- # After
32
- ModelConfig(name="Qwen3.5-0.8B-GGUF", ...)
33
- ```
34
-
35
- ### 2. Case-Insensitive Model Lookup
36
-
37
- Enhanced `get_model_config()` to accept case-insensitive input while always returning the **canonical name**:
38
-
39
- ```python
40
- def get_model_config(model_name: str) -> Optional[ModelConfig]:
41
- # Exact match first
42
- for model in AVAILABLE_MODELS:
43
- if model.name == model_name:
44
- return model
45
- # Case-insensitive fallback
46
- for model in AVAILABLE_MODEOLS:
47
- if model.name.lower() == model_name.lower():
48
- return model
49
- return None
50
- ```
51
-
52
- This allows users to type `qwen3.5-0.8b-gguf`, `QWEN3.5-0.8B-GGUF`, etc., but Diffron always sends the correct name to the server.
53
-
54
- ### 3. CLI Canonical Name Enforcement
55
-
56
- The `setup_model_cli()` now stores the **canonical name** from the config, not the user's raw input:
57
-
58
- ```python
59
- # Always uses config.name (canonical) instead of args.model (user input)
60
- os.environ["DIFFRON_MODEL"] = config.name
61
- subprocess.run(["setx", "DIFFRON_MODEL", config.name], check=True)
62
- ```
63
-
64
- ## Testing
65
-
66
- ```bash
67
- # All of these now work correctly:
68
- diffron-setup-model --model Qwen3.5-0.8B-GGUF # exact
69
- diffron-setup-model --model qwen3.5-0.8b-gguf # lowercase (fallback)
70
- diffron-setup-model --model QWEN3.5-0.8B-GGUF # uppercase (fallback)
71
- ```
72
-
73
- ## Impact
74
-
75
- - **No breaking changes** - existing `DIFFRON_MODEL` values with wrong casing are now resolved correctly
76
- - **Improved UX** - users don't need to memorize exact casing
77
- - **Server compatibility** - correct model name is always sent to Lemonade
78
-
79
- ## Files Changed
80
-
81
- | File | Change |
82
- |------|--------|
83
- | `diffron/models.py` | Fixed `Qwen3.5-0.8B-GGUF` name, added case-insensitive lookup |
84
-
85
- ## Version
86
-
87
- - **Fixed in**: 0.1.3
88
- - **Date**: 2026-04-13
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
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