gac 3.8.1__py3-none-any.whl → 3.10.10__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.
- gac/__init__.py +4 -6
- gac/__version__.py +1 -1
- gac/ai_utils.py +18 -49
- gac/cli.py +14 -10
- gac/commit_executor.py +59 -0
- gac/config.py +28 -3
- gac/config_cli.py +19 -7
- gac/constants/__init__.py +34 -0
- gac/constants/commit.py +63 -0
- gac/constants/defaults.py +40 -0
- gac/constants/file_patterns.py +110 -0
- gac/constants/languages.py +119 -0
- gac/diff_cli.py +0 -22
- gac/errors.py +8 -2
- gac/git.py +6 -6
- gac/git_state_validator.py +193 -0
- gac/grouped_commit_workflow.py +458 -0
- gac/init_cli.py +2 -1
- gac/interactive_mode.py +179 -0
- gac/language_cli.py +0 -1
- gac/main.py +222 -959
- gac/model_cli.py +2 -1
- gac/model_identifier.py +70 -0
- gac/oauth/claude_code.py +2 -2
- gac/oauth/qwen_oauth.py +4 -0
- gac/oauth/token_store.py +2 -2
- gac/oauth_retry.py +161 -0
- gac/postprocess.py +155 -0
- gac/prompt.py +20 -490
- gac/prompt_builder.py +88 -0
- gac/providers/README.md +437 -0
- gac/providers/__init__.py +70 -81
- gac/providers/anthropic.py +12 -56
- gac/providers/azure_openai.py +48 -92
- gac/providers/base.py +329 -0
- gac/providers/cerebras.py +10 -43
- gac/providers/chutes.py +16 -72
- gac/providers/claude_code.py +64 -97
- gac/providers/custom_anthropic.py +51 -85
- gac/providers/custom_openai.py +29 -87
- gac/providers/deepseek.py +10 -43
- gac/providers/error_handler.py +139 -0
- gac/providers/fireworks.py +10 -43
- gac/providers/gemini.py +66 -73
- gac/providers/groq.py +10 -62
- gac/providers/kimi_coding.py +19 -59
- gac/providers/lmstudio.py +62 -52
- gac/providers/minimax.py +10 -43
- gac/providers/mistral.py +10 -43
- gac/providers/moonshot.py +10 -43
- gac/providers/ollama.py +54 -41
- gac/providers/openai.py +30 -46
- gac/providers/openrouter.py +15 -62
- gac/providers/protocol.py +71 -0
- gac/providers/qwen.py +55 -67
- gac/providers/registry.py +58 -0
- gac/providers/replicate.py +137 -91
- gac/providers/streamlake.py +26 -56
- gac/providers/synthetic.py +35 -47
- gac/providers/together.py +10 -43
- gac/providers/zai.py +21 -59
- gac/py.typed +0 -0
- gac/security.py +1 -1
- gac/templates/__init__.py +1 -0
- gac/templates/question_generation.txt +60 -0
- gac/templates/system_prompt.txt +224 -0
- gac/templates/user_prompt.txt +28 -0
- gac/utils.py +6 -5
- gac/workflow_context.py +162 -0
- {gac-3.8.1.dist-info → gac-3.10.10.dist-info}/METADATA +1 -1
- gac-3.10.10.dist-info/RECORD +79 -0
- gac/constants.py +0 -328
- gac-3.8.1.dist-info/RECORD +0 -56
- {gac-3.8.1.dist-info → gac-3.10.10.dist-info}/WHEEL +0 -0
- {gac-3.8.1.dist-info → gac-3.10.10.dist-info}/entry_points.txt +0 -0
- {gac-3.8.1.dist-info → gac-3.10.10.dist-info}/licenses/LICENSE +0 -0
gac/prompt.py
CHANGED
|
@@ -7,330 +7,33 @@ formatting, and integration with diff preprocessing.
|
|
|
7
7
|
|
|
8
8
|
import logging
|
|
9
9
|
import re
|
|
10
|
-
|
|
11
|
-
from
|
|
10
|
+
from functools import lru_cache
|
|
11
|
+
from importlib.resources import files
|
|
12
12
|
|
|
13
13
|
logger = logging.getLogger(__name__)
|
|
14
14
|
|
|
15
15
|
|
|
16
16
|
# ============================================================================
|
|
17
|
-
#
|
|
17
|
+
# Template Loading from Package Resources
|
|
18
18
|
# ============================================================================
|
|
19
19
|
|
|
20
|
-
DEFAULT_SYSTEM_TEMPLATE = """<role>
|
|
21
|
-
You are an expert git commit message generator. Your task is to analyze code changes and create a concise, meaningful git commit message. You will receive git status and diff information. Your entire response will be used directly as a git commit message.
|
|
22
|
-
</role>
|
|
23
|
-
|
|
24
|
-
<focus>
|
|
25
|
-
Your commit message must reflect the core purpose and impact of these changes.
|
|
26
|
-
Prioritize the primary intent over implementation details.
|
|
27
|
-
Consider what future developers need to understand about this change.
|
|
28
|
-
Identify if this introduces new capabilities, fixes problems, or improves existing code.
|
|
29
|
-
</focus>
|
|
30
|
-
|
|
31
|
-
<mixed_changes>
|
|
32
|
-
When changes span multiple areas:
|
|
33
|
-
- Choose the commit type based on the PRIMARY purpose, not the largest file count
|
|
34
|
-
- Feature additions with supporting tests/docs should use 'feat'
|
|
35
|
-
- Bug fixes with added tests should use 'fix'
|
|
36
|
-
- Refactoring that improves multiple components should use 'refactor'
|
|
37
|
-
- Documentation updates are 'docs' only when that's the sole purpose
|
|
38
|
-
</mixed_changes>
|
|
39
|
-
|
|
40
|
-
<format>
|
|
41
|
-
<one_liner>
|
|
42
|
-
Create a single-line commit message.
|
|
43
|
-
Your message should be clear, concise, and descriptive of the core change.
|
|
44
|
-
Use present tense ("Add feature" not "Added feature").
|
|
45
|
-
</one_liner><multi_line>
|
|
46
|
-
Create a commit message with:
|
|
47
|
-
- First line: A concise summary that could stand alone
|
|
48
|
-
- Blank line after the summary
|
|
49
|
-
- Detailed body with multiple bullet points explaining the key changes
|
|
50
|
-
- Focus on WHY changes were made, not just WHAT was changed
|
|
51
|
-
- Order points from most important to least important
|
|
52
|
-
</multi_line><verbose>
|
|
53
|
-
IMPORTANT: You MUST create a MULTI-PARAGRAPH commit message with detailed sections.
|
|
54
|
-
DO NOT create a single-line commit message.
|
|
55
|
-
|
|
56
|
-
Your commit message MUST follow this structure:
|
|
57
|
-
|
|
58
|
-
Line 1: A concise summary (that could stand alone) with conventional commit prefix
|
|
59
|
-
Line 2: BLANK LINE (required)
|
|
60
|
-
Lines 3+: Detailed multi-paragraph body with the following sections:
|
|
61
|
-
|
|
62
|
-
## Motivation
|
|
63
|
-
Explain why this commit exists in 2-3 sentences. What problem does it solve? What need does it address?
|
|
64
|
-
|
|
65
|
-
## Architecture / Approach
|
|
66
|
-
Describe how it was implemented in 2-4 sentences. Include key design decisions and any rejected alternatives.
|
|
67
|
-
Reference specific modules, functions, or classes when relevant.
|
|
68
|
-
|
|
69
|
-
## Affected Components
|
|
70
|
-
List the main modules, subsystems, or directories impacted by this change.
|
|
71
|
-
|
|
72
|
-
OPTIONAL sections (include only if relevant):
|
|
73
|
-
|
|
74
|
-
## Performance / Security Impact
|
|
75
|
-
Describe any performance improvements, trade-offs, or security considerations.
|
|
76
|
-
Include concrete data such as benchmark results if available.
|
|
77
|
-
|
|
78
|
-
## Compatibility / Testing
|
|
79
|
-
Mention any compatibility considerations, known limitations, testing performed, or next steps for validation.
|
|
80
|
-
|
|
81
|
-
REQUIREMENTS:
|
|
82
|
-
- Your response MUST be at least 10 lines long with multiple paragraphs
|
|
83
|
-
- Use active voice and present tense ("Implements", "Adds", "Refactors")
|
|
84
|
-
- Provide concrete, specific information rather than vague descriptions
|
|
85
|
-
- Keep the tone professional and technical
|
|
86
|
-
- Focus on intent and reasoning, not just code changes
|
|
87
|
-
- Use markdown headers (##) for section organization
|
|
88
|
-
</verbose>
|
|
89
|
-
</format>
|
|
90
|
-
|
|
91
|
-
<conventions_no_scope>
|
|
92
|
-
You MUST start your commit message with the most appropriate conventional commit prefix.
|
|
93
|
-
|
|
94
|
-
IMPORTANT: Check file types FIRST when determining the commit type:
|
|
95
|
-
- If changes are ONLY to documentation files (*.md, *.rst, *.txt in docs/, README*, CHANGELOG*, etc.), ALWAYS use 'docs:'
|
|
96
|
-
- Use 'docs:' ONLY when ALL changes are documentation files - INCLUDING README updates, regardless of how significant the changes are
|
|
97
|
-
- If changes include both documentation and code, use the prefix for the code changes, unless it is a documentation-only change
|
|
98
|
-
|
|
99
|
-
Commit type prefixes:
|
|
100
|
-
- feat: A new feature or functionality addition
|
|
101
|
-
- fix: A bug fix or error correction
|
|
102
|
-
- docs: Documentation changes only (INCLUDING README updates, regardless of how significant)
|
|
103
|
-
- style: Changes to code style/formatting without logic changes
|
|
104
|
-
- refactor: Code restructuring without behavior changes
|
|
105
|
-
- perf: Performance improvements
|
|
106
|
-
- test: Adding/modifying tests
|
|
107
|
-
- build: Changes to build system/dependencies
|
|
108
|
-
- ci: Changes to CI configuration
|
|
109
|
-
- chore: Miscellaneous changes not affecting src/test files
|
|
110
|
-
|
|
111
|
-
Select ONE prefix that best matches the primary purpose of the changes.
|
|
112
|
-
If multiple prefixes apply, choose the one that represents the most significant change.
|
|
113
|
-
If you cannot confidently determine a type, use 'chore'.
|
|
114
|
-
|
|
115
|
-
Do NOT include a scope in your commit prefix.
|
|
116
|
-
</conventions_no_scope>
|
|
117
|
-
|
|
118
|
-
<conventions_with_scope>
|
|
119
|
-
You MUST write a conventional commit message with EXACTLY ONE type and an inferred scope.
|
|
120
|
-
|
|
121
|
-
FORMAT: type(scope): description
|
|
122
|
-
|
|
123
|
-
IMPORTANT: Check file types FIRST when determining the commit type:
|
|
124
|
-
- If changes are ONLY to documentation files (*.md, *.rst, *.txt in docs/, README*, CHANGELOG*, etc.), ALWAYS use 'docs'
|
|
125
|
-
- If changes include both documentation and code, use the prefix for the code changes, unless it is a documentation-only change
|
|
126
|
-
|
|
127
|
-
Select ONE type from this list that best matches the primary purpose of the changes:
|
|
128
|
-
- feat: A new feature or functionality addition
|
|
129
|
-
- fix: A bug fix or error correction
|
|
130
|
-
- docs: Documentation changes only (INCLUDING README and CHANGELOG updates, regardless of how significant)
|
|
131
|
-
- style: Changes to code style/formatting without logic changes
|
|
132
|
-
- refactor: Code restructuring without behavior changes
|
|
133
|
-
- perf: Performance improvements
|
|
134
|
-
- test: Adding/modifying tests
|
|
135
|
-
- build: Changes to build system/dependencies
|
|
136
|
-
- ci: Changes to CI configuration
|
|
137
|
-
- chore: Miscellaneous changes not affecting src/test files
|
|
138
|
-
|
|
139
|
-
You MUST infer an appropriate scope from the changes. A good scope is concise (usually one word) and indicates the component or area that was changed.
|
|
140
|
-
|
|
141
|
-
<scope_rules>
|
|
142
|
-
For scope inference, select the most specific component affected:
|
|
143
|
-
- Use module/component names from the codebase (auth, api, cli, core)
|
|
144
|
-
- Use functional areas for cross-cutting changes (config, build, test)
|
|
145
|
-
- Keep scopes consistent with existing commit history when possible
|
|
146
|
-
- Prefer established patterns over creating new scope names
|
|
147
|
-
- Use singular form (auth, not auths; test, not tests)
|
|
148
|
-
</scope_rules>
|
|
149
|
-
|
|
150
|
-
Examples of good scopes: api, auth, ui, core, docs, build, prompt, config
|
|
151
|
-
|
|
152
|
-
CORRECT EXAMPLES (these formats are correct):
|
|
153
|
-
✅ feat(auth): add login functionality
|
|
154
|
-
✅ fix(api): resolve null response issue
|
|
155
|
-
✅ refactor(core): improve data processing
|
|
156
|
-
✅ docs(readme): update installation instructions
|
|
157
|
-
|
|
158
|
-
INCORRECT EXAMPLES (these formats are wrong and must NOT be used):
|
|
159
|
-
❌ chore: feat(component): description
|
|
160
|
-
❌ fix: refactor(component): description
|
|
161
|
-
❌ feat: feat(component): description
|
|
162
|
-
❌ chore: chore(component): description
|
|
163
|
-
|
|
164
|
-
You MUST NOT prefix the type(scope) with another type. Use EXACTLY ONE type, which MUST include the scope in parentheses.
|
|
165
|
-
</conventions_with_scope>
|
|
166
|
-
|
|
167
|
-
<examples_no_scope>
|
|
168
|
-
Good commit messages (no scope):
|
|
169
|
-
[OK] feat: add OAuth2 integration with Google and GitHub
|
|
170
|
-
[OK] fix: resolve race condition in user session management
|
|
171
|
-
[OK] docs: add troubleshooting section for common installation issues
|
|
172
|
-
[OK] refactor: extract validation logic into reusable utilities
|
|
173
|
-
[OK] test: add comprehensive unit tests for token validation
|
|
174
|
-
[OK] build: upgrade to latest security patches
|
|
175
|
-
|
|
176
|
-
Bad commit messages:
|
|
177
|
-
[ERROR] fix stuff
|
|
178
|
-
[ERROR] update code
|
|
179
|
-
[ERROR] feat(auth): add login (scope included when not requested)
|
|
180
|
-
[ERROR] WIP: still working on this
|
|
181
|
-
[ERROR] Fixed bug
|
|
182
|
-
[ERROR] Changes
|
|
183
|
-
</examples_no_scope>
|
|
184
|
-
|
|
185
|
-
<examples_verbose_no_scope>
|
|
186
|
-
Example of a good VERBOSE commit message (without scope):
|
|
187
|
-
|
|
188
|
-
feat: add verbose mode for detailed commit message generation
|
|
189
|
-
|
|
190
|
-
## Motivation
|
|
191
|
-
Users need the ability to generate comprehensive commit messages that follow best practices for code review and documentation. The existing one-liner and multi-line modes don't provide sufficient structure for complex changes that require detailed explanations of motivation, architecture decisions, and impact.
|
|
192
|
-
|
|
193
|
-
## Architecture / Approach
|
|
194
|
-
Adds a new --verbose/-v flag to the CLI that modifies the prompt generation in build_prompt(). When enabled, the prompt instructs the AI to generate commit messages with structured sections including Motivation, Architecture/Approach, Affected Components, and optional Performance/Testing sections. The implementation uses the existing format selection logic with verbose taking priority over one_liner and multi_line modes.
|
|
195
|
-
|
|
196
|
-
## Affected Components
|
|
197
|
-
- src/gac/cli.py: Added --verbose flag and parameter passing
|
|
198
|
-
- src/gac/main.py: Extended main() to accept and pass verbose parameter
|
|
199
|
-
- src/gac/prompt.py: Added <verbose> template section with detailed instructions
|
|
200
|
-
- tests/test_prompt.py: Added test coverage for verbose mode
|
|
201
|
-
|
|
202
|
-
## Compatibility / Testing
|
|
203
|
-
Added new test test_build_prompt_verbose_mode to verify the verbose template generation. All existing tests pass. The verbose mode is opt-in via the -v flag, maintaining backward compatibility.
|
|
204
|
-
</examples_verbose_no_scope>
|
|
205
|
-
|
|
206
|
-
<examples_verbose_with_scope>
|
|
207
|
-
Example of a good VERBOSE commit message (with scope):
|
|
208
|
-
|
|
209
|
-
feat(cli): add verbose mode for detailed commit message generation
|
|
210
|
-
|
|
211
|
-
## Motivation
|
|
212
|
-
Users need the ability to generate comprehensive commit messages that follow best practices for code review and documentation. The existing one-liner and multi-line modes don't provide sufficient structure for complex changes that require detailed explanations of motivation, architecture decisions, and impact.
|
|
213
|
-
|
|
214
|
-
## Architecture / Approach
|
|
215
|
-
Adds a new --verbose/-v flag to the CLI that modifies the prompt generation in build_prompt(). When enabled, the prompt instructs the AI to generate commit messages with structured sections including Motivation, Architecture/Approach, Affected Components, and optional Performance/Testing sections. The implementation uses the existing format selection logic with verbose taking priority over one_liner and multi_line modes.
|
|
216
|
-
|
|
217
|
-
## Affected Components
|
|
218
|
-
- src/gac/cli.py: Added --verbose flag and parameter passing
|
|
219
|
-
- src/gac/main.py: Extended main() to accept and pass verbose parameter
|
|
220
|
-
- src/gac/prompt.py: Added <verbose> template section with detailed instructions
|
|
221
|
-
- tests/test_prompt.py: Added test coverage for verbose mode
|
|
222
|
-
|
|
223
|
-
## Compatibility / Testing
|
|
224
|
-
Added new test test_build_prompt_verbose_mode to verify the verbose template generation. All existing tests pass. The verbose mode is opt-in via the -v flag, maintaining backward compatibility.
|
|
225
|
-
</examples_verbose_with_scope>
|
|
226
|
-
|
|
227
|
-
<examples_with_scope>
|
|
228
|
-
Good commit message top lines (with scope):
|
|
229
|
-
[OK] feat(auth): add OAuth2 integration with Google and GitHub
|
|
230
|
-
[OK] fix(api): resolve race condition in user session management
|
|
231
|
-
[OK] docs(readme): add troubleshooting section for common installation issues
|
|
232
|
-
[OK] refactor(core): extract validation logic into reusable utilities
|
|
233
|
-
[OK] test(auth): add comprehensive unit tests for token validation
|
|
234
|
-
[OK] build(deps): upgrade to latest security patches
|
|
235
|
-
|
|
236
|
-
Bad commit messages:
|
|
237
|
-
[ERROR] fix stuff
|
|
238
|
-
[ERROR] update code
|
|
239
|
-
[ERROR] feat: fix(auth): add login (double prefix)
|
|
240
|
-
[ERROR] WIP: still working on this
|
|
241
|
-
[ERROR] Fixed bug
|
|
242
|
-
[ERROR] Changes
|
|
243
|
-
</examples_with_scope>"""
|
|
244
|
-
|
|
245
|
-
DEFAULT_USER_TEMPLATE = """<hint>
|
|
246
|
-
Additional context provided by the user: <hint_text></hint_text>
|
|
247
|
-
</hint>
|
|
248
20
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
21
|
+
@lru_cache(maxsize=1)
|
|
22
|
+
def _load_default_system_template() -> str:
|
|
23
|
+
"""Load the default system prompt template from package resources."""
|
|
24
|
+
return files("gac.templates").joinpath("system_prompt.txt").read_text(encoding="utf-8")
|
|
252
25
|
|
|
253
|
-
<git_diff_stat>
|
|
254
|
-
<diff_stat></diff_stat>
|
|
255
|
-
</git_diff_stat>
|
|
256
26
|
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
27
|
+
@lru_cache(maxsize=1)
|
|
28
|
+
def _load_default_user_template() -> str:
|
|
29
|
+
"""Load the default user prompt template from package resources."""
|
|
30
|
+
return files("gac.templates").joinpath("user_prompt.txt").read_text(encoding="utf-8")
|
|
260
31
|
|
|
261
|
-
<language_instructions>
|
|
262
|
-
IMPORTANT: You MUST write the entire commit message in <language_name></language_name>.
|
|
263
|
-
All text in the commit message, including the summary line and body, must be in <language_name></language_name>.
|
|
264
|
-
<prefix_instruction></prefix_instruction>
|
|
265
|
-
</language_instructions>
|
|
266
32
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
The entire response will be passed directly to 'git commit -m'.
|
|
272
|
-
</format_instructions>"""
|
|
273
|
-
|
|
274
|
-
QUESTION_GENERATION_TEMPLATE = """<role>
|
|
275
|
-
You are an expert code reviewer specializing in identifying missing context and intent in code changes. Your task is to analyze git diffs and generate focused questions that clarify the "why" behind the changes.
|
|
276
|
-
</role>
|
|
277
|
-
|
|
278
|
-
<focus>
|
|
279
|
-
Analyze the git diff and determine the appropriate number of questions based on change complexity. Generate 1-5 focused questions to clarify intent, motivation, and impact. Your questions should help the developer provide the essential context needed for a meaningful commit message.
|
|
280
|
-
</focus>
|
|
281
|
-
|
|
282
|
-
<adaptive_guidelines>
|
|
283
|
-
- For very small changes (single file, <10 lines): Ask 1-2 essential questions about core purpose
|
|
284
|
-
- For small changes (few files, <50 lines): Ask 1-3 questions covering intent and impact
|
|
285
|
-
- For medium changes (multiple files, <200 lines): Ask 2-4 questions covering scope, intent, and impact
|
|
286
|
-
- For large changes (many files or substantial modifications): Ask 3-5 questions covering all aspects
|
|
287
|
-
- Always prioritize questions that would most help generate an informative commit message
|
|
288
|
-
- Lean toward fewer questions for straightforward changes
|
|
289
|
-
</adaptive_guidelines>
|
|
290
|
-
|
|
291
|
-
<guidelines>
|
|
292
|
-
- Focus on WHY the changes were made, not just WHAT was changed
|
|
293
|
-
- Ask about the intent, motivation, or business purpose behind the changes
|
|
294
|
-
- Consider what future developers need to understand about this change
|
|
295
|
-
- Ask about the broader impact or consequences of the changes
|
|
296
|
-
- Target areas where technical implementation doesn't reveal the underlying purpose
|
|
297
|
-
- Keep questions concise and specific
|
|
298
|
-
- Format as a clean list for easy parsing
|
|
299
|
-
</guidelines>
|
|
300
|
-
|
|
301
|
-
<rules>
|
|
302
|
-
NEVER write or rewrite the commit message; only ask questions.
|
|
303
|
-
DO NOT suggest specific commit message formats or wording.
|
|
304
|
-
DO NOT ask about implementation details that are already clear from the diff.
|
|
305
|
-
DO NOT include any explanations or preamble with your response.
|
|
306
|
-
</rules>
|
|
307
|
-
|
|
308
|
-
<output_format>
|
|
309
|
-
Respond with ONLY a numbered list of questions, one per line:
|
|
310
|
-
1. First focused question?
|
|
311
|
-
2. Second focused question?
|
|
312
|
-
3. Third focused question?
|
|
313
|
-
4. [etc...]
|
|
314
|
-
</output_format>
|
|
315
|
-
|
|
316
|
-
<examples>
|
|
317
|
-
Good example questions for small changes:
|
|
318
|
-
1. What problem does this fix?
|
|
319
|
-
2. Why was this approach chosen?
|
|
320
|
-
|
|
321
|
-
Good example questions for larger changes:
|
|
322
|
-
1. What problem or user need does this change address?
|
|
323
|
-
2. Why was this particular approach chosen over alternatives?
|
|
324
|
-
3. What impact will this have on existing functionality?
|
|
325
|
-
4. What motivated the addition of these new error cases?
|
|
326
|
-
5. Why are these validation rules being added now?
|
|
327
|
-
|
|
328
|
-
Bad examples (violates rules):
|
|
329
|
-
❌ feat: add user authentication - This is a commit message, not a question
|
|
330
|
-
❌ Should I use "feat" or "fix" for this change? - This asks about formatting, not context
|
|
331
|
-
❌ Why did you rename the variable from x to y? - Too implementation-specific
|
|
332
|
-
❌ You should reformat this as "fix: resolve authentication issue" - This rewrites the message
|
|
333
|
-
</examples>"""
|
|
33
|
+
@lru_cache(maxsize=1)
|
|
34
|
+
def _load_question_generation_template() -> str:
|
|
35
|
+
"""Load the question generation template from package resources."""
|
|
36
|
+
return files("gac.templates").joinpath("question_generation.txt").read_text(encoding="utf-8")
|
|
334
37
|
|
|
335
38
|
|
|
336
39
|
# ============================================================================
|
|
@@ -350,8 +53,8 @@ def load_system_template(custom_path: str | None = None) -> str:
|
|
|
350
53
|
if custom_path:
|
|
351
54
|
return load_custom_system_template(custom_path)
|
|
352
55
|
|
|
353
|
-
logger.debug("Using default system template")
|
|
354
|
-
return
|
|
56
|
+
logger.debug("Using default system template from package resources")
|
|
57
|
+
return _load_default_system_template()
|
|
355
58
|
|
|
356
59
|
|
|
357
60
|
def load_user_template() -> str:
|
|
@@ -360,8 +63,8 @@ def load_user_template() -> str:
|
|
|
360
63
|
Returns:
|
|
361
64
|
User template content as string
|
|
362
65
|
"""
|
|
363
|
-
logger.debug("Using default user template")
|
|
364
|
-
return
|
|
66
|
+
logger.debug("Using default user template from package resources")
|
|
67
|
+
return _load_default_user_template()
|
|
365
68
|
|
|
366
69
|
|
|
367
70
|
def load_custom_system_template(path: str) -> str:
|
|
@@ -690,7 +393,7 @@ def build_question_generation_prompt(
|
|
|
690
393
|
Returns:
|
|
691
394
|
Tuple of (system_prompt, user_prompt) ready to be sent to an AI model
|
|
692
395
|
"""
|
|
693
|
-
system_prompt =
|
|
396
|
+
system_prompt = _load_question_generation_template()
|
|
694
397
|
|
|
695
398
|
# Build user prompt with git context
|
|
696
399
|
user_prompt = f"""<git_diff>
|
|
@@ -720,176 +423,3 @@ Analyze the changes above and determine the appropriate number of questions base
|
|
|
720
423
|
</format_instructions>"""
|
|
721
424
|
|
|
722
425
|
return system_prompt.strip(), user_prompt.strip()
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
# ============================================================================
|
|
726
|
-
# Message Cleaning Helpers
|
|
727
|
-
# ============================================================================
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
def _remove_think_tags(message: str) -> str:
|
|
731
|
-
"""Remove AI reasoning <think> tags and their content from the message.
|
|
732
|
-
|
|
733
|
-
Args:
|
|
734
|
-
message: The message to clean
|
|
735
|
-
|
|
736
|
-
Returns:
|
|
737
|
-
Message with <think> tags removed
|
|
738
|
-
"""
|
|
739
|
-
while re.search(r"<think>(?:(?!</think>)[^\n])*\n.*?</think>", message, flags=re.DOTALL | re.IGNORECASE):
|
|
740
|
-
message = re.sub(
|
|
741
|
-
r"<think>(?:(?!</think>)[^\n])*\n.*?</think>\s*", "", message, flags=re.DOTALL | re.IGNORECASE, count=1
|
|
742
|
-
)
|
|
743
|
-
|
|
744
|
-
message = re.sub(r"\n\n+\s*<think>.*?</think>\s*", "", message, flags=re.DOTALL | re.IGNORECASE)
|
|
745
|
-
message = re.sub(r"<think>.*?</think>\s*\n\n+", "", message, flags=re.DOTALL | re.IGNORECASE)
|
|
746
|
-
|
|
747
|
-
message = re.sub(r"<think>\s*\n.*$", "", message, flags=re.DOTALL | re.IGNORECASE)
|
|
748
|
-
|
|
749
|
-
conventional_prefixes_pattern = r"(" + "|".join(CommitMessageConstants.CONVENTIONAL_PREFIXES) + r")[\(:)]"
|
|
750
|
-
if re.search(r"^.*?</think>", message, flags=re.DOTALL | re.IGNORECASE):
|
|
751
|
-
prefix_match = re.search(conventional_prefixes_pattern, message, flags=re.IGNORECASE)
|
|
752
|
-
think_match = re.search(r"</think>", message, flags=re.IGNORECASE)
|
|
753
|
-
|
|
754
|
-
if not prefix_match or (think_match and think_match.start() < prefix_match.start()):
|
|
755
|
-
message = re.sub(r"^.*?</think>\s*", "", message, flags=re.DOTALL | re.IGNORECASE)
|
|
756
|
-
|
|
757
|
-
message = re.sub(r"</think>\s*$", "", message, flags=re.IGNORECASE)
|
|
758
|
-
|
|
759
|
-
return message
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
def _remove_code_blocks(message: str) -> str:
|
|
763
|
-
"""Remove markdown code blocks from the message.
|
|
764
|
-
|
|
765
|
-
Args:
|
|
766
|
-
message: The message to clean
|
|
767
|
-
|
|
768
|
-
Returns:
|
|
769
|
-
Message with code blocks removed
|
|
770
|
-
"""
|
|
771
|
-
return re.sub(r"```[\w]*\n|```", "", message)
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
def _extract_commit_from_reasoning(message: str) -> str:
|
|
775
|
-
"""Extract the actual commit message from reasoning/preamble text.
|
|
776
|
-
|
|
777
|
-
Args:
|
|
778
|
-
message: The message potentially containing reasoning
|
|
779
|
-
|
|
780
|
-
Returns:
|
|
781
|
-
Extracted commit message
|
|
782
|
-
"""
|
|
783
|
-
for indicator in CommitMessageConstants.COMMIT_INDICATORS:
|
|
784
|
-
if indicator.lower() in message.lower():
|
|
785
|
-
message = message.split(indicator, 1)[1].strip()
|
|
786
|
-
break
|
|
787
|
-
|
|
788
|
-
lines = message.split("\n")
|
|
789
|
-
for i, line in enumerate(lines):
|
|
790
|
-
if any(line.strip().startswith(f"{prefix}:") for prefix in CommitMessageConstants.CONVENTIONAL_PREFIXES):
|
|
791
|
-
message = "\n".join(lines[i:])
|
|
792
|
-
break
|
|
793
|
-
|
|
794
|
-
return message
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
def _remove_xml_tags(message: str) -> str:
|
|
798
|
-
"""Remove XML tags that might have leaked into the message.
|
|
799
|
-
|
|
800
|
-
Args:
|
|
801
|
-
message: The message to clean
|
|
802
|
-
|
|
803
|
-
Returns:
|
|
804
|
-
Message with XML tags removed
|
|
805
|
-
"""
|
|
806
|
-
for tag in CommitMessageConstants.XML_TAGS_TO_REMOVE:
|
|
807
|
-
message = message.replace(tag, "")
|
|
808
|
-
return message
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
def _fix_double_prefix(message: str) -> str:
|
|
812
|
-
"""Fix double type prefix issues like 'chore: feat(scope):' to 'feat(scope):'.
|
|
813
|
-
|
|
814
|
-
Args:
|
|
815
|
-
message: The message to fix
|
|
816
|
-
|
|
817
|
-
Returns:
|
|
818
|
-
Message with double prefix corrected
|
|
819
|
-
"""
|
|
820
|
-
double_prefix_pattern = re.compile(
|
|
821
|
-
r"^("
|
|
822
|
-
+ r"|\s*".join(CommitMessageConstants.CONVENTIONAL_PREFIXES)
|
|
823
|
-
+ r"):\s*("
|
|
824
|
-
+ r"|\s*".join(CommitMessageConstants.CONVENTIONAL_PREFIXES)
|
|
825
|
-
+ r")\(([^)]+)\):"
|
|
826
|
-
)
|
|
827
|
-
match = double_prefix_pattern.match(message)
|
|
828
|
-
|
|
829
|
-
if match:
|
|
830
|
-
second_type = match.group(2)
|
|
831
|
-
scope = match.group(3)
|
|
832
|
-
description = message[match.end() :].strip()
|
|
833
|
-
message = f"{second_type}({scope}): {description}"
|
|
834
|
-
|
|
835
|
-
return message
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
def _ensure_conventional_prefix(message: str) -> str:
|
|
839
|
-
"""Ensure the message starts with a conventional commit prefix.
|
|
840
|
-
|
|
841
|
-
Args:
|
|
842
|
-
message: The message to check
|
|
843
|
-
|
|
844
|
-
Returns:
|
|
845
|
-
Message with conventional prefix ensured
|
|
846
|
-
"""
|
|
847
|
-
if not any(
|
|
848
|
-
message.strip().startswith(prefix + ":") or message.strip().startswith(prefix + "(")
|
|
849
|
-
for prefix in CommitMessageConstants.CONVENTIONAL_PREFIXES
|
|
850
|
-
):
|
|
851
|
-
message = f"chore: {message.strip()}"
|
|
852
|
-
return message
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
def _normalize_whitespace(message: str) -> str:
|
|
856
|
-
"""Normalize whitespace, ensuring no more than one blank line between paragraphs.
|
|
857
|
-
|
|
858
|
-
Args:
|
|
859
|
-
message: The message to normalize
|
|
860
|
-
|
|
861
|
-
Returns:
|
|
862
|
-
Message with normalized whitespace
|
|
863
|
-
"""
|
|
864
|
-
return re.sub(r"\n(?:[ \t]*\n){2,}", "\n\n", message).strip()
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
# ============================================================================
|
|
868
|
-
# Message Cleaning
|
|
869
|
-
# ============================================================================
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
def clean_commit_message(message: str) -> str:
|
|
873
|
-
"""Clean up a commit message generated by an AI model.
|
|
874
|
-
|
|
875
|
-
This function:
|
|
876
|
-
1. Removes any preamble or reasoning text
|
|
877
|
-
2. Removes code block markers and formatting
|
|
878
|
-
3. Removes XML tags that might have leaked into the response
|
|
879
|
-
4. Fixes double type prefix issues (e.g., "chore: feat(scope):")
|
|
880
|
-
5. Normalizes whitespace
|
|
881
|
-
|
|
882
|
-
Args:
|
|
883
|
-
message: Raw commit message from AI
|
|
884
|
-
|
|
885
|
-
Returns:
|
|
886
|
-
Cleaned commit message ready for use
|
|
887
|
-
"""
|
|
888
|
-
message = message.strip()
|
|
889
|
-
message = _remove_think_tags(message)
|
|
890
|
-
message = _remove_code_blocks(message)
|
|
891
|
-
message = _extract_commit_from_reasoning(message)
|
|
892
|
-
message = _remove_xml_tags(message)
|
|
893
|
-
message = _fix_double_prefix(message)
|
|
894
|
-
message = _normalize_whitespace(message)
|
|
895
|
-
return message
|
gac/prompt_builder.py
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Prompt building logic for gac."""
|
|
3
|
+
|
|
4
|
+
from typing import NamedTuple
|
|
5
|
+
|
|
6
|
+
from rich.console import Console
|
|
7
|
+
from rich.panel import Panel
|
|
8
|
+
|
|
9
|
+
from gac.config import GACConfig
|
|
10
|
+
from gac.git_state_validator import GitState
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class PromptBundle(NamedTuple):
|
|
14
|
+
"""Bundle of system and user prompts."""
|
|
15
|
+
|
|
16
|
+
system_prompt: str
|
|
17
|
+
user_prompt: str
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class PromptBuilder:
|
|
21
|
+
"""Builds prompts for AI commit message generation."""
|
|
22
|
+
|
|
23
|
+
def __init__(self, config: GACConfig):
|
|
24
|
+
self.config = config
|
|
25
|
+
|
|
26
|
+
def build_prompts(
|
|
27
|
+
self,
|
|
28
|
+
git_state: GitState,
|
|
29
|
+
group: bool = False,
|
|
30
|
+
one_liner: bool = False,
|
|
31
|
+
hint: str = "",
|
|
32
|
+
infer_scope: bool = False,
|
|
33
|
+
verbose: bool = False,
|
|
34
|
+
language: str | None = None,
|
|
35
|
+
) -> PromptBundle:
|
|
36
|
+
"""Build prompts from git state."""
|
|
37
|
+
from gac.prompt import build_group_prompt, build_prompt
|
|
38
|
+
|
|
39
|
+
# Get language and translate prefixes from config
|
|
40
|
+
if language is None:
|
|
41
|
+
language_value = self.config.get("language")
|
|
42
|
+
language = language_value if isinstance(language_value, str) else None
|
|
43
|
+
|
|
44
|
+
translate_prefixes_value = self.config.get("translate_prefixes")
|
|
45
|
+
translate_prefixes: bool = (
|
|
46
|
+
bool(translate_prefixes_value) if isinstance(translate_prefixes_value, bool) else False
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Get system template path from config
|
|
50
|
+
system_template_path_value = self.config.get("system_prompt_path")
|
|
51
|
+
system_template_path: str | None = (
|
|
52
|
+
system_template_path_value if isinstance(system_template_path_value, str) else None
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
if group:
|
|
56
|
+
system_prompt, user_prompt = build_group_prompt(
|
|
57
|
+
status=git_state.status,
|
|
58
|
+
processed_diff=git_state.processed_diff,
|
|
59
|
+
diff_stat=git_state.diff_stat,
|
|
60
|
+
one_liner=one_liner,
|
|
61
|
+
hint=hint,
|
|
62
|
+
infer_scope=infer_scope,
|
|
63
|
+
verbose=verbose,
|
|
64
|
+
system_template_path=system_template_path,
|
|
65
|
+
language=language,
|
|
66
|
+
translate_prefixes=translate_prefixes,
|
|
67
|
+
)
|
|
68
|
+
else:
|
|
69
|
+
system_prompt, user_prompt = build_prompt(
|
|
70
|
+
status=git_state.status,
|
|
71
|
+
processed_diff=git_state.processed_diff,
|
|
72
|
+
diff_stat=git_state.diff_stat,
|
|
73
|
+
one_liner=one_liner,
|
|
74
|
+
hint=hint,
|
|
75
|
+
infer_scope=infer_scope,
|
|
76
|
+
verbose=verbose,
|
|
77
|
+
system_template_path=system_template_path,
|
|
78
|
+
language=language,
|
|
79
|
+
translate_prefixes=translate_prefixes,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
return PromptBundle(system_prompt=system_prompt, user_prompt=user_prompt)
|
|
83
|
+
|
|
84
|
+
def display_prompts(self, system_prompt: str, user_prompt: str) -> None:
|
|
85
|
+
"""Display prompts for debugging purposes."""
|
|
86
|
+
console = Console()
|
|
87
|
+
full_prompt = f"SYSTEM PROMPT:\n{system_prompt}\n\nUSER PROMPT:\n{user_prompt}"
|
|
88
|
+
console.print(Panel(full_prompt, title="Prompt for LLM", border_style="bright_blue"))
|