fm-smart-commit 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.
@@ -0,0 +1,98 @@
1
+ Metadata-Version: 2.4
2
+ Name: fm-smart-commit
3
+ Version: 1.1.0
4
+ Summary: AI-powered Git commit message generator using Apple Intelligence
5
+ Author-email: Maverick Brazill <brazillmav@gmail.com>
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Environment :: Console
8
+ Classifier: Intended Audience :: Developers
9
+ Classifier: Programming Language :: Python :: 3
10
+ Classifier: Programming Language :: Python :: 3.10
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Classifier: Programming Language :: Python :: 3.14
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: apple_fm_sdk
19
+ Provides-Extra: dev
20
+ Requires-Dist: pytest; extra == "dev"
21
+ Requires-Dist: pytest-asyncio; extra == "dev"
22
+ Dynamic: license-file
23
+
24
+ # Git SmartCommit CLI
25
+
26
+ **AI-powered Git commit generation running *100% locally* on your Mac using Apple Intelligence.**
27
+
28
+ SmartCommit is a lightweight CLI tool that analyzes your staged Git changes and generates concise, professional [Conventional Commits](https://www.conventionalcommits.org/) (`feat:`, `fix:`, `refactor:`, etc.).
29
+
30
+ Because it runs completely on-device using the Apple Foundation Models SDK, it offers massive advantages over cloud-based AI tools:
31
+
32
+ * **Privacy-First:** Your codebase never leaves your machine. Perfect for enterprise, proprietary, or sensitive code.
33
+ * **Zero API Costs:** No OpenAI API keys or GitHub Copilot subscriptions required. It uses the AI already built into your Mac.
34
+ * ️**Fast:** Powered natively by Apple Silicon neural engines for instant inference.
35
+
36
+ ---
37
+
38
+ ## Prerequisites
39
+
40
+ Before installing, ensure your machine meets the hardware and software requirements for local Apple Intelligence:
41
+ * **Hardware:** Apple Silicon Mac (M1 chip or newer).
42
+ * **OS:** macOS 15.0 (Sequoia) or newer.
43
+ * **Settings:** Apple Intelligence must be enabled on your Mac.
44
+
45
+ ---
46
+
47
+ ## Installation
48
+
49
+ ```bash
50
+ # 1. Download the latest release
51
+ curl -fsSL -o smartcommit https://github.com/brazill7/smart-commit/releases/latest/download/smartcommit
52
+
53
+ # 2. Make the file executable
54
+ chmod +x smartcommit
55
+
56
+ # 3. Clear the macOS Gatekeeper quarantine flag (required for unsigned binaries)
57
+ xattr -d com.apple.quarantine smartcommit
58
+
59
+ # 4. Move it to your local bin so it can be run from anywhere
60
+ sudo mv smartcommit /usr/local/bin/
61
+
62
+ ### (Optional) Set up a Git Alias
63
+ If you want to use this tool natively within Git (e.g., typing `git sc` or `git smart-commit` etc. ), you can add a global alias:
64
+
65
+ git config --global alias.sc '!smartcommit'
66
+ git config --global alias.smart-commit '!smartcommit'
67
+ ```
68
+
69
+ ---
70
+
71
+ ## Usage
72
+
73
+ Make sure you have staged your changes (`git add .`) before running the tool.
74
+
75
+ ### Option A: Using the Standalone Command
76
+ If you skipped the alias step, you can just call the tool directly in your repository:
77
+
78
+ `smartcommit`
79
+
80
+ To provide custom context to the AI (like explaining *why* you made a change), use the `-c` flag:
81
+
82
+ `smartcommit -c "race condition on the login screen"`
83
+
84
+
85
+ ### Option B: Using the Git Alias
86
+ If you configured the `git smart-commit` alias, you can use it just like a native Git command:
87
+
88
+ `git smart-commit`
89
+
90
+ With custom context:
91
+
92
+ `git smart-commit -c "refactored the login auth flow"`
93
+
94
+
95
+ **Example Output:**
96
+ > Analyzing diff...
97
+ > Suggested commit: **fix: resolve race condition in login flow**
98
+ > Accept this commit message? (y/n):
@@ -0,0 +1,7 @@
1
+ fm_smart_commit-1.1.0.dist-info/licenses/LICENSE,sha256=cqOdyLYvw3vvxQ-sN2LPGRop9Z2bb2dvSSQeV76Shn0,1068
2
+ smartcommit/__init__.py,sha256=k27q3P5V2kgzwYOiX1DCqsHdzXq0Ke5yvxtP9nOiHfM,7197
3
+ fm_smart_commit-1.1.0.dist-info/METADATA,sha256=89r73zn5NoPMDZ_dQB5tQd5eGthgTjvwpzKgp7_N36M,3434
4
+ fm_smart_commit-1.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
5
+ fm_smart_commit-1.1.0.dist-info/entry_points.txt,sha256=5p-H3LMlDnsl3h5tEzHwrq3Kx3k5GIEXZZ4-zXm8TCQ,49
6
+ fm_smart_commit-1.1.0.dist-info/top_level.txt,sha256=f1KG6zVyN4mqVSJasNRmNGmGSyfQm6xTSZLa63pr-ow,12
7
+ fm_smart_commit-1.1.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (82.0.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ smartcommit = smartcommit:main
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Maverick B.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1 @@
1
+ smartcommit
@@ -0,0 +1,211 @@
1
+ import subprocess
2
+ import asyncio
3
+ import argparse
4
+ from typing import Optional
5
+ import apple_fm_sdk as fm
6
+
7
+
8
+ async def analyze_scope(diff: str, session: fm.LanguageModelSession) -> str:
9
+ prompt = f"""
10
+ Analyze this git diff and identify the SCOPE of changes: what files, components, or modules are affected?
11
+
12
+ Diff:
13
+ {diff}
14
+
15
+ Output ONLY a brief list of affected areas (e.g., "user_auth.py, login component, API routes"). No other text.
16
+ """
17
+ response = await session.respond(prompt)
18
+ return response.strip()
19
+
20
+
21
+ async def analyze_intent(diff: str, session: fm.LanguageModelSession, developer_context: Optional[str] = None) -> str:
22
+ context = f"\nDeveloper context: {developer_context}" if developer_context else ""
23
+ prompt = f"""
24
+ Analyze this git diff and identify the INTENT: why was this change made? What problem does it solve?{context}
25
+
26
+ Diff:
27
+ {diff}
28
+
29
+ Output ONLY a one-sentence intent summary. No other text.
30
+ """
31
+ response = await session.respond(prompt)
32
+ return response.strip()
33
+
34
+
35
+ async def analyze_changes(diff: str, session: fm.LanguageModelSession) -> str:
36
+ prompt = f"""
37
+ Analyze this git diff and identify the SPECIFIC CHANGES: what functions, logic, or code patterns changed?
38
+
39
+ Diff:
40
+ {diff}
41
+
42
+ Output ONLY a brief list of what changed (e.g., "added calculateTotal function, refactored validation logic"). No other text.
43
+ """
44
+ response = await session.respond(prompt)
45
+ return response.strip()
46
+
47
+
48
+ async def synthesize_message(scope: str, intent: str, changes: str, session: fm.LanguageModelSession) -> str:
49
+ prompt = f"""
50
+ You are a Git commit message generator. Using the following analysis, generate a SINGLE Conventional Commit message.
51
+
52
+ Scope (what files/components): {scope}
53
+ Intent (why): {intent}
54
+ Changes (what): {changes}
55
+
56
+ Rules:
57
+ - Use prefix: feat:, fix:, docs:, style:, refactor:, chore:
58
+ - Keep it under 72 characters if possible
59
+ - ONLY output the commit message, no quotes, no explanation
60
+
61
+ Output:
62
+ """
63
+ response = await session.respond(prompt)
64
+ return response.strip().strip('"').strip("'")
65
+
66
+
67
+ async def quick_mode(developer_context: Optional[str] = None) -> Optional[str]:
68
+ diff_process = subprocess.run(['git', 'diff', '--staged'], capture_output=True, text=True)
69
+ diff = diff_process.stdout.strip()
70
+
71
+ if not diff:
72
+ print("No staged changes found. Run `git add` first!")
73
+ return None
74
+
75
+ model = fm.SystemLanguageModel()
76
+ is_available, reason = model.is_available()
77
+ if not is_available:
78
+ print(f"Apple Intelligence unavailable: {reason}")
79
+ return None
80
+
81
+ session = fm.LanguageModelSession()
82
+
83
+ context_instruction = ""
84
+ if developer_context:
85
+ context_instruction = f"\nAdditional context from the developer to include/consider:\n\"{developer_context}\"\n"
86
+
87
+ prompt = f"""
88
+ You are a strictly formatted Git commit generator. Analyze the following code diff and generate a single commit message using the Conventional Commits standard.
89
+
90
+ Allowed prefixes: feat:, fix:, docs:, style:, refactor:, chore:
91
+
92
+ Rules:
93
+ - ONLY output the commit message. No conversational text.
94
+ - Do not wrap the output in quotes.
95
+ - Incorporate any additional context provided by the developer.
96
+
97
+ Examples:
98
+ Diff: + function calculateTotal(a, b) {{ return a + b; }}
99
+ Output: feat: add calculateTotal function, which adds a and b and returns the total.
100
+
101
+ Actual Diff to analyze:
102
+ {diff}
103
+ {context_instruction}
104
+
105
+ Analyze the following code diff and generate a single commit message using the Conventional Commits standard.
106
+
107
+ Allowed prefixes: feat:, fix:, docs:, style:, refactor:, chore:
108
+
109
+ Output:
110
+ """
111
+
112
+ print("Analyzing diff (quick mode)...")
113
+ response = await session.respond(prompt)
114
+ return response.strip().strip('"').strip("'")
115
+
116
+
117
+ async def detailed_mode(developer_context: Optional[str] = None) -> Optional[str]:
118
+ diff_process = subprocess.run(['git', 'diff', '--staged'], capture_output=True, text=True)
119
+ diff = diff_process.stdout.strip()
120
+
121
+ if not diff:
122
+ print("No staged changes found. Run `git add` first!")
123
+ return None
124
+
125
+ model = fm.SystemLanguageModel()
126
+ is_available, reason = model.is_available()
127
+ if not is_available:
128
+ print(f"Apple Intelligence unavailable: {reason}")
129
+ return None
130
+
131
+ session = fm.LanguageModelSession()
132
+
133
+ print("Analyzing diff (detailed mode - 3 parallel agents)...")
134
+
135
+ commit_msg = None
136
+ try:
137
+ async with asyncio.TaskGroup() as tg:
138
+ task_scope = tg.create_task(analyze_scope(diff, session))
139
+ task_intent = tg.create_task(analyze_intent(diff, session, developer_context))
140
+ task_changes = tg.create_task(analyze_changes(diff, session))
141
+
142
+ scope_result = task_scope.result()
143
+ intent_result = task_intent.result()
144
+ changes_result = task_changes.result()
145
+
146
+ print(f" Scope: {scope_result[:50]}...")
147
+ print(f" Intent: {intent_result[:50]}...")
148
+ print(f" Changes: {changes_result[:50]}...")
149
+
150
+ print("Synthesizing results...")
151
+ commit_msg = await synthesize_message(scope_result, intent_result, changes_result, session)
152
+
153
+ except* Exception as e:
154
+ print(f"Error in parallel analysis: {e}")
155
+
156
+ return commit_msg
157
+
158
+
159
+ async def commit_flow(developer_context: Optional[str] = None, quick: bool = False) -> None:
160
+ commit_msg = None
161
+ max_retries = 3
162
+
163
+ for attempt in range(max_retries):
164
+ if quick:
165
+ commit_msg = await quick_mode(developer_context)
166
+ else:
167
+ commit_msg = await detailed_mode(developer_context)
168
+
169
+ if not commit_msg:
170
+ return
171
+
172
+ print(f"\nSuggested commit: \033[92m{commit_msg}\033[0m")
173
+ user_input = input("Accept commit? (y/n/r): ")
174
+
175
+ if user_input.lower() == 'y':
176
+ subprocess.run(['git', 'commit', '-m', commit_msg])
177
+ print("Committed successfully!")
178
+ return
179
+ elif user_input.lower() == 'r':
180
+ print("Retrying...\n")
181
+ continue
182
+ else:
183
+ print("Commit aborted.")
184
+ return
185
+
186
+ print("Max retries reached. Commit aborted.")
187
+
188
+
189
+ def main():
190
+ parser = argparse.ArgumentParser(description="Generate smart Git commits using Apple Intelligence.")
191
+ parser.add_argument(
192
+ '-c', '--context',
193
+ type=str,
194
+ help='Additional context or intent to guide the AI (e.g., "fixes ticket #123")'
195
+ )
196
+ parser.add_argument(
197
+ '-q', '--quick',
198
+ action='store_true',
199
+ help='Use quick single-pass mode (faster but less detailed)'
200
+ )
201
+
202
+ args = parser.parse_args()
203
+
204
+ if args.quick:
205
+ print("Running in --quick mode\n")
206
+
207
+ asyncio.run(commit_flow(developer_context=args.context, quick=args.quick))
208
+
209
+
210
+ if __name__ == "__main__":
211
+ main()