ngpt 2.14.1__py3-none-any.whl → 2.15.1__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.
ngpt/cli/args.py CHANGED
@@ -98,8 +98,12 @@ def setup_argument_parser():
98
98
  help='Use diff from specified file instead of staged changes. If used without a path, uses the path from CLI config.')
99
99
  gitcommsg_group.add_argument('--chunk-size', type=int, default=200,
100
100
  help='Number of lines per chunk when chunking is enabled (default: 200)')
101
- gitcommsg_group.add_argument('--max-depth', type=int, default=3,
102
- help='Maximum recursion depth for recursive chunking (default: 3)')
101
+ gitcommsg_group.add_argument('--analyses-chunk-size', type=int, default=200,
102
+ help='Number of lines per chunk when recursively chunking analyses (default: 200)')
103
+ gitcommsg_group.add_argument('--max-msg-lines', type=int, default=20,
104
+ help='Maximum number of lines in commit message before condensing (default: 20)')
105
+ gitcommsg_group.add_argument('--max-recursion-depth', type=int, default=3,
106
+ help='Maximum recursion depth for commit message condensing (default: 3)')
103
107
 
104
108
  # Mode flags (mutually exclusive)
105
109
  mode_group = parser.add_argument_group('Modes (mutually exclusive)')
@@ -68,56 +68,57 @@ def split_into_chunks(content, chunk_size=200):
68
68
 
69
69
  return chunks
70
70
 
71
- def process_context(context):
72
- """Process context string to extract directives and filters.
71
+ def create_technical_analysis_system_prompt(context=None):
72
+ """Create system prompt for technical analysis based on context data.
73
73
 
74
74
  Args:
75
- context: The context string provided with -m/--message-context
75
+ context: The raw context string from -m flag
76
76
 
77
77
  Returns:
78
- dict: Extracted context data
78
+ str: System prompt for the technical analysis stage
79
79
  """
80
- context_data = {
81
- "file_type_filter": None,
82
- "commit_type": None,
83
- "focus": None,
84
- "exclusions": [],
85
- "raw_context": context
86
- }
87
-
88
- if not context:
89
- return context_data
90
-
91
- # Extract commit type directive (e.g., "type:feat")
92
- if "type:" in context:
93
- match = re.search(r"type:(\w+)", context)
94
- if match:
95
- context_data["commit_type"] = match.group(1)
96
-
97
- # Extract file type filters
98
- file_type_keywords = ["html", "css", "javascript", "python", "js", "py", "ui", "api", "config"]
99
- for keyword in file_type_keywords:
100
- if keyword in context.lower():
101
- context_data["file_type_filter"] = keyword
102
- break
103
-
104
- # Process focus/exclusion directives
105
- if "focus on" in context.lower() or "only mention" in context.lower():
106
- focus_match = re.search(r"focus(?:\s+on)?\s+(\w+)", context.lower())
107
- if focus_match:
108
- context_data["focus"] = focus_match.group(1)
109
-
110
- if any(x in context.lower() for x in ["ignore", "don't include", "exclude"]):
111
- exclusion_matches = re.findall(r"(?:ignore|don't include|exclude)\s+(\w+)", context.lower())
112
- context_data["exclusions"] = exclusion_matches
113
-
114
- return context_data
115
-
116
- def create_system_prompt(context_data=None):
117
- """Create system prompt based on context data.
80
+ base_prompt = """You are an expert at analyzing git diffs and extracting precise technical details. Your task is to analyze the git diff and create a detailed technical summary of the changes.
81
+
82
+ OUTPUT FORMAT:
83
+ [FILES]: Comma-separated list of affected files with full paths
84
+
85
+ [CHANGES]:
86
+ - Technical detail 1 (include specific function/method names and line numbers)
87
+ - Technical detail 2 (be precise about exactly what code was added/modified/removed)
88
+ - Additional technical details (include ALL significant changes in this chunk)
89
+
90
+ [IMPACT]: Brief technical description of what the changes accomplish
91
+
92
+ RULES:
93
+ 1. BE 100% FACTUAL - Mention ONLY code explicitly shown in the diff
94
+ 2. NEVER invent or assume changes not directly visible in the code
95
+ 3. ALWAYS identify exact function names, method names, class names, and line numbers where possible
96
+ 4. Use format 'filename:function_name()' or 'filename:line_number' when referencing code locations
97
+ 5. Be precise and factual - only describe code that actually changed
98
+ 6. Include ALL significant changes (do not skip any important modifications)
99
+ 7. Focus on technical specifics, avoid general statements
100
+ 8. When analyzing multiple files, clearly separate each file's changes
101
+ 9. Include proper technical details (method names, component identifiers, etc.)"""
102
+
103
+ # If context is provided, append it with strong wording about absolute priority
104
+ if context:
105
+ context_prompt = f"""
106
+
107
+ ===CRITICAL USER CONTEXT - ABSOLUTE HIGHEST PRIORITY===
108
+ The following context from the user OVERRIDES ALL OTHER INSTRUCTIONS and must be followed exactly:
109
+
110
+ {context}
111
+
112
+ THIS USER CONTEXT HAS ABSOLUTE PRIORITY over any other instructions in this prompt. If it contradicts other instructions, the user context MUST be followed. No exceptions."""
113
+ base_prompt += context_prompt
114
+
115
+ return base_prompt
116
+
117
+ def create_system_prompt(context=None):
118
+ """Create system prompt for commit message generation based on context data.
118
119
 
119
120
  Args:
120
- context_data: The processed context data
121
+ context: The raw context string from -m flag
121
122
 
122
123
  Returns:
123
124
  str: System prompt for the AI
@@ -131,6 +132,15 @@ type[(scope)]: <concise summary> (max 50 chars)
131
132
  - [type] <specific change 2> (filename:function/method/line)
132
133
  - [type] <additional changes...>
133
134
 
135
+ RULES FOR FILENAMES:
136
+ 1. For the FIRST mention of a file, use the full relative path
137
+ 2. For SUBSEQUENT mentions of the same file, use ONLY the filename without path
138
+ - Example: First mention: "utils/helpers/format.js" → Subsequent mentions: "format.js"
139
+ 3. Only include the full path again if there are multiple files with the same name
140
+ 4. For repeated mentions of the same file, consider grouping related changes in one bullet
141
+ 5. Avoid breaking filenames across lines
142
+ 6. Only include function names when they add clarity
143
+
134
144
  COMMIT TYPES:
135
145
  - feat: New user-facing features
136
146
  - fix: Bug fixes or error corrections
@@ -151,6 +161,46 @@ COMMIT TYPES:
151
161
  - ui: User interface changes
152
162
  - api: API-related changes
153
163
 
164
+ EXAMPLES:
165
+
166
+ 1. Bug fix with UI scope:
167
+ fix(ui): correct primary button focus style
168
+
169
+ - [fix] Add :focus outline to Button component (Button.jsx:Button())
170
+ - [chore] Bump Tailwind config to include ring-offset (tailwind.config.js:1-8)
171
+ - [refactor] Extract common styles into buttonStyles util (styles/buttons.js:1-15)
172
+
173
+ 2. Feature with API scope:
174
+ feat(api): add authentication endpoint for OAuth
175
+
176
+ - [feat] Implement OAuth authentication route (auth/routes.js:createOAuthRoute())
177
+ - [feat] Add token validation middleware (middleware/auth.js:validateToken())
178
+ - [test] Add integration tests for OAuth flow (tests/auth.test.js:45-87)
179
+
180
+ 3. Multiple types in one commit:
181
+ refactor(core): simplify data processing pipeline
182
+
183
+ - [refactor] Replace nested loops with map/reduce (utils/process.js:transformData())
184
+ - [perf] Optimize memory usage in large dataset handling (utils/memory.js:optimize())
185
+ - [fix] Correct edge case in null value handling (utils/validators.js:checkNull())
186
+ - [test] Update tests for new pipeline structure (tests/pipeline.test.js)
187
+
188
+ 4. Multiple changes to the same file:
189
+ refactor(core): simplify context handling for commit prompts
190
+
191
+ - [refactor] Remove process_context function (cli/modes/gitcommsg.py:69-124)
192
+ - [refactor] Update all functions to accept raw context string (gitcommsg.py:create_system_prompt())
193
+ - [refactor] Replace context_data usages with context (gitcommsg.py)
194
+ - [docs] Update library usage doc (docs/usage/library_usage.md:516,531-537)
195
+ - [chore] Bump project version to 2.15.1 (pyproject.toml:3, uv.lock:137)
196
+
197
+ BULLET POINT FORMAT:
198
+ - Each bullet MUST start with a type in square brackets: [type]
199
+ - DO NOT use the format "- type: description" (without square brackets)
200
+ - Instead, ALWAYS use "- [type] description" (with square brackets)
201
+ - Example: "- [feat] Add new login component" (correct)
202
+ - Not: "- feat: Add new login component" (incorrect)
203
+
154
204
  RULES:
155
205
  1. BE 100% FACTUAL - Mention ONLY code explicitly shown in the diff
156
206
  2. NEVER invent or assume changes not directly visible in the code
@@ -164,48 +214,17 @@ RULES:
164
214
  10. Include proper technical details (method names, component identifiers, etc.)
165
215
  11. When all changes are to the same file, mention it once in the summary"""
166
216
 
167
- if not context_data:
168
- return base_prompt
169
-
170
- # Add file type filtering instructions
171
- if context_data.get("file_type_filter"):
172
- file_type = context_data["file_type_filter"]
173
- file_type_prompt = f"""
174
-
175
- CRITICAL FILE TYPE FILTERING:
176
- You MUST INCLUDE ONLY changes to {file_type} files or files related to {file_type}.
177
- You MUST EXCLUDE ALL other files completely from your output.
178
- This is a strict filter - no exceptions allowed."""
179
- base_prompt += file_type_prompt
180
-
181
- # Add commit type directive
182
- if context_data.get("commit_type"):
183
- commit_type = context_data["commit_type"]
184
- commit_type_prompt = f"""
185
-
186
- CRITICAL COMMIT TYPE DIRECTIVE:
187
- You MUST use exactly "{commit_type}:" as the commit type prefix.
188
- This takes highest priority over any other commit type you might determine.
189
- Do not override this commit type based on your own analysis."""
190
- base_prompt += commit_type_prompt
191
-
192
- # Add focus/exclusion directives
193
- if context_data.get("focus"):
194
- focus = context_data["focus"]
195
- focus_prompt = f"""
196
-
197
- FOCUS DIRECTIVE:
198
- Focus exclusively on changes related to {focus}.
199
- Exclude everything else from your analysis."""
200
- base_prompt += focus_prompt
201
-
202
- if context_data.get("exclusions"):
203
- exclusions = ", ".join(context_data["exclusions"])
204
- exclusion_prompt = f"""
205
-
206
- EXCLUSION DIRECTIVE:
207
- Completely ignore and exclude any mentions of: {exclusions}."""
208
- base_prompt += exclusion_prompt
217
+ # If context is provided, append it with strong wording about absolute priority
218
+ if context:
219
+ context_prompt = f"""
220
+
221
+ ===CRITICAL USER CONTEXT - ABSOLUTE HIGHEST PRIORITY===
222
+ The following context from the user OVERRIDES ALL OTHER INSTRUCTIONS and must be followed exactly:
223
+
224
+ {context}
225
+
226
+ THIS USER CONTEXT HAS ABSOLUTE PRIORITY over any other instructions in this prompt. If it contradicts other instructions, the user context MUST be followed. No exceptions."""
227
+ base_prompt += context_prompt
209
228
 
210
229
  return base_prompt
211
230
 
@@ -218,21 +237,14 @@ def create_chunk_prompt(chunk):
218
237
  Returns:
219
238
  str: Prompt for the AI
220
239
  """
221
- return f"""Analyze this PARTIAL git diff and create a detailed technical summary with this EXACT format:
222
-
223
- [FILES]: Comma-separated list of affected files with full paths
240
+ return f"""Analyze this PARTIAL git diff and create a detailed technical summary.
224
241
 
225
- [CHANGES]:
226
- - Technical detail 1 (include specific function/method names and line numbers)
227
- - Technical detail 2 (be precise about exactly what code was added/modified/removed)
228
- - Additional technical details (include ALL significant changes in this chunk)
229
-
230
- [IMPACT]: Brief technical description of what the changes accomplish
242
+ The system prompt already contains your output format instructions with [FILES], [CHANGES], and [IMPACT] sections.
231
243
 
232
- CRITICALLY IMPORTANT: Be extremely specific with technical details.
233
- ALWAYS identify exact function names, method names, class names, and line numbers where possible.
234
- Use format 'filename:function_name()' or 'filename:line_number' when referencing code locations.
235
- Be precise and factual - only describe code that actually changed.
244
+ REMINDER:
245
+ - Identify exact function names, method names, class names, and line numbers
246
+ - Use format 'filename:function_name()' or 'filename:line_number' for references
247
+ - Be precise and factual - only describe code that actually changed
236
248
 
237
249
  Diff chunk:
238
250
 
@@ -251,61 +263,15 @@ def create_rechunk_prompt(combined_analysis, depth):
251
263
  return f"""IMPORTANT: You are analyzing SUMMARIES of git changes, not raw git diff.
252
264
 
253
265
  You are in a re-chunking process (depth: {depth}) where the input is already summarized changes.
254
- Create a TERSE summary of these summaries focusing ONLY ON TECHNICAL CHANGES:
255
-
256
- [CHANGES]:
257
- - Technical change 1 (specific file and function)
258
- - Technical change 2 (specific file and function)
259
- - Additional relevant changes
266
+ Create a terse technical summary following the format in the system prompt.
260
267
 
261
268
  DO NOT ask for raw git diff. These summaries are all you need to work with.
262
- Keep your response FACTUAL and SPECIFIC to what's in the summaries.
269
+ Keep your response factual and specific to what's in the summaries.
263
270
 
264
271
  Section to summarize:
265
272
 
266
273
  {combined_analysis}"""
267
274
 
268
- def create_combine_prompt(partial_analyses):
269
- """Create prompt for combining partial analyses.
270
-
271
- Args:
272
- partial_analyses: List of partial analyses to combine
273
-
274
- Returns:
275
- str: Prompt for the AI
276
- """
277
- all_analyses = "\n\n".join(partial_analyses)
278
-
279
- return f"""===CRITICAL INSTRUCTION===
280
- You are working with ANALYZED SUMMARIES of git changes, NOT raw git diff.
281
- The raw git diff has ALREADY been processed into these summaries.
282
- DO NOT ask for or expect to see the original git diff.
283
-
284
- TASK: Synthesize these partial analyses into a complete conventional commit message:
285
-
286
- {all_analyses}
287
-
288
- Create a CONVENTIONAL COMMIT MESSAGE with:
289
- 1. First line: "type[(scope)]: brief summary" (50 chars max)
290
- - Include scope ONLY if you are 100% confident about the affected area
291
- - Omit scope if changes affect multiple areas or scope is unclear
292
- 2. ⚠️ ONE BLANK LINE IS MANDATORY - NEVER SKIP THIS STEP ⚠️
293
- - This blank line MUST be present in EVERY commit message
294
- - The blank line separates the summary from the detailed changes
295
- - Without this blank line, the commit message format is invalid
296
- 3. Bullet points with specific changes, each with appropriate [type] tag
297
- 4. Reference files in EACH bullet point with function names or line numbers
298
-
299
- FILENAME & FUNCTION HANDLING RULES:
300
- - Include SPECIFIC function names, method names, or line numbers when available
301
- - Format as filename:function() or filename:line_number
302
- - Use short relative paths for files
303
- - Group related changes to the same file when appropriate
304
- - Avoid breaking long filenames across lines
305
-
306
- STRICTLY follow this format with NO EXPLANATION or additional commentary.
307
- DO NOT mention insufficient information or ask for the original diff."""
308
-
309
275
  def create_final_prompt(diff_content):
310
276
  """Create prompt for direct processing without chunking.
311
277
 
@@ -325,11 +291,13 @@ type[(scope)]: <concise summary> (max 50 chars)
325
291
  - [type] <additional changes...>
326
292
 
327
293
  RULES FOR FILENAMES:
328
- 1. Use short relative paths when possible
329
- 2. For multiple changes to the same file, consider grouping them
330
- 3. Abbreviate long paths when they're repeated (e.g., 'commit.zsh' instead of full path)
331
- 4. Avoid breaking filenames across lines
332
- 5. Only include function names when they add clarity
294
+ 1. For the FIRST mention of a file, use the full relative path
295
+ 2. For SUBSEQUENT mentions of the same file, use ONLY the filename without path
296
+ - Example: First mention: "utils/helpers/format.js" Subsequent mentions: "format.js"
297
+ 3. Only include the full path again if there are multiple files with the same name
298
+ 4. For repeated mentions of the same file, consider grouping related changes in one bullet
299
+ 5. Avoid breaking filenames across lines
300
+ 6. Only include function names when they add clarity
333
301
 
334
302
  COMMIT TYPES:
335
303
  - feat: New user-facing features
@@ -351,6 +319,46 @@ COMMIT TYPES:
351
319
  - ui: User interface changes
352
320
  - api: API-related changes
353
321
 
322
+ EXAMPLES:
323
+
324
+ 1. Bug fix with UI scope:
325
+ fix(ui): correct primary button focus style
326
+
327
+ - [fix] Add :focus outline to Button component (Button.jsx:Button())
328
+ - [chore] Bump Tailwind config to include ring-offset (tailwind.config.js:1-8)
329
+ - [refactor] Extract common styles into buttonStyles util (styles/buttons.js:1-15)
330
+
331
+ 2. Feature with API scope:
332
+ feat(api): add authentication endpoint for OAuth
333
+
334
+ - [feat] Implement OAuth authentication route (auth/routes.js:createOAuthRoute())
335
+ - [feat] Add token validation middleware (middleware/auth.js:validateToken())
336
+ - [test] Add integration tests for OAuth flow (tests/auth.test.js:45-87)
337
+
338
+ 3. Multiple types in one commit:
339
+ refactor(core): simplify data processing pipeline
340
+
341
+ - [refactor] Replace nested loops with map/reduce (utils/process.js:transformData())
342
+ - [perf] Optimize memory usage in large dataset handling (utils/memory.js:optimize())
343
+ - [fix] Correct edge case in null value handling (utils/validators.js:checkNull())
344
+ - [test] Update tests for new pipeline structure (tests/pipeline.test.js)
345
+
346
+ 4. Multiple changes to the same file:
347
+ refactor(core): simplify context handling for commit prompts
348
+
349
+ - [refactor] Remove process_context function (cli/modes/gitcommsg.py:69-124)
350
+ - [refactor] Update all functions to accept raw context string (gitcommsg.py:create_system_prompt())
351
+ - [refactor] Replace context_data usages with context (gitcommsg.py)
352
+ - [docs] Update library usage doc (docs/usage/library_usage.md:516,531-537)
353
+ - [chore] Bump project version to 2.15.1 (pyproject.toml:3, uv.lock:137)
354
+
355
+ BULLET POINT FORMAT:
356
+ - Each bullet MUST start with a type in square brackets: [type]
357
+ - DO NOT use the format "- type: description" (without square brackets)
358
+ - Instead, ALWAYS use "- [type] description" (with square brackets)
359
+ - Example: "- [feat] Add new login component" (correct)
360
+ - Not: "- feat: Add new login component" (incorrect)
361
+
354
362
  RULES:
355
363
  1. BE 100% FACTUAL - Mention ONLY code explicitly shown in the diff
356
364
  2. NEVER invent or assume changes not directly visible in the code
@@ -441,23 +449,30 @@ def handle_api_call(client, prompt, system_prompt=None, logger=None, max_retries
441
449
  # Exponential backoff
442
450
  wait_seconds *= 2
443
451
 
444
- def process_with_chunking(client, diff_content, context_data, chunk_size=200, recursive=False, max_depth=3, logger=None):
452
+ def process_with_chunking(client, diff_content, context, chunk_size=200, recursive=False, logger=None, max_msg_lines=20, max_recursion_depth=3, analyses_chunk_size=None):
445
453
  """Process diff with chunking to handle large diffs.
446
454
 
447
455
  Args:
448
456
  client: The NGPTClient instance
449
457
  diff_content: The diff content to process
450
- context_data: The processed context data
458
+ context: The raw context string
451
459
  chunk_size: Maximum number of lines per chunk
452
460
  recursive: Whether to use recursive chunking
453
- max_depth: Maximum recursion depth
454
461
  logger: Optional logger instance
462
+ max_msg_lines: Maximum number of lines in commit message before condensing
463
+ max_recursion_depth: Maximum recursion depth for message condensing
464
+ analyses_chunk_size: Maximum number of lines per chunk for recursive analysis chunking
455
465
 
456
466
  Returns:
457
467
  str: Generated commit message
458
468
  """
459
- # Create system prompt
460
- system_prompt = create_system_prompt(context_data)
469
+ # If analyses_chunk_size not provided, default to chunk_size
470
+ if analyses_chunk_size is None:
471
+ analyses_chunk_size = chunk_size
472
+
473
+ # Create different system prompts for different stages
474
+ technical_system_prompt = create_technical_analysis_system_prompt(context)
475
+ commit_system_prompt = create_system_prompt(context)
461
476
 
462
477
  # Log initial diff content
463
478
  if logger:
@@ -488,10 +503,10 @@ def process_with_chunking(client, diff_content, context_data, chunk_size=200, re
488
503
  if logger:
489
504
  logger.log_template("DEBUG", "CHUNK", chunk_prompt)
490
505
 
491
- # Process chunk
506
+ # Process chunk - use technical system prompt for analysis
492
507
  print(f"{COLORS['yellow']}Analyzing changes...{COLORS['reset']}")
493
508
  try:
494
- result = handle_api_call(client, chunk_prompt, system_prompt, logger)
509
+ result = handle_api_call(client, chunk_prompt, technical_system_prompt, logger)
495
510
  partial_analyses.append(result)
496
511
  print(f"{COLORS['green']}✓ Chunk {i+1} processed{COLORS['reset']}")
497
512
  except Exception as e:
@@ -517,11 +532,20 @@ def process_with_chunking(client, diff_content, context_data, chunk_size=200, re
517
532
  combined_analyses = "\n\n".join(partial_analyses)
518
533
  combined_line_count = len(combined_analyses.splitlines())
519
534
 
520
- if recursive and combined_line_count > 50 and max_depth > 0:
521
- # Use recursive chunking
522
- return recursive_process(client, combined_analyses, context_data, max_depth, logger)
535
+ if recursive and combined_line_count > analyses_chunk_size:
536
+ # Use recursive analysis chunking
537
+ return recursive_chunk_analysis(
538
+ client,
539
+ combined_analyses,
540
+ context,
541
+ analyses_chunk_size,
542
+ logger,
543
+ max_msg_lines,
544
+ max_recursion_depth
545
+ )
523
546
  else:
524
- # Use direct combination
547
+ # Combined analysis is under the chunk size limit, generate the commit message
548
+ print(f"{COLORS['green']}Generating commit message from combined analysis...{COLORS['reset']}")
525
549
  combine_prompt = create_combine_prompt(partial_analyses)
526
550
 
527
551
  # Log combine template
@@ -529,66 +553,62 @@ def process_with_chunking(client, diff_content, context_data, chunk_size=200, re
529
553
  logger.log_template("DEBUG", "COMBINE", combine_prompt)
530
554
 
531
555
  try:
532
- result = handle_api_call(client, combine_prompt, system_prompt, logger)
533
- return result
556
+ # Use commit message system prompt for final generation
557
+ commit_message = handle_api_call(client, combine_prompt, commit_system_prompt, logger)
558
+
559
+ # If the commit message is too long, we need to condense it
560
+ if len(commit_message.splitlines()) > max_msg_lines:
561
+ return condense_commit_message(
562
+ client,
563
+ commit_message,
564
+ commit_system_prompt,
565
+ max_msg_lines,
566
+ max_recursion_depth,
567
+ 1, # Start at depth 1
568
+ logger
569
+ )
570
+ return commit_message
534
571
  except Exception as e:
535
572
  print(f"{COLORS['red']}Error combining analyses: {str(e)}{COLORS['reset']}")
536
573
  if logger:
537
574
  logger.error(f"Error combining analyses: {str(e)}")
538
575
  return None
539
576
 
540
- def recursive_process(client, combined_analysis, context_data, max_depth, logger=None, current_depth=1):
541
- """Process large analysis results recursively.
577
+ def recursive_chunk_analysis(client, combined_analysis, context, chunk_size, logger=None, max_msg_lines=20, max_recursion_depth=3, current_depth=1):
578
+ """Recursively chunk and process large analysis results until they're small enough.
542
579
 
543
580
  Args:
544
581
  client: The NGPTClient instance
545
582
  combined_analysis: The combined analysis to process
546
- context_data: The processed context data
547
- max_depth: Maximum recursion depth
583
+ context: The raw context string
584
+ chunk_size: Maximum number of lines per chunk
548
585
  logger: Optional logger instance
549
- current_depth: Current recursion depth
586
+ max_msg_lines: Maximum number of lines in commit message before condensing
587
+ max_recursion_depth: Maximum recursion depth for message condensing
588
+ current_depth: Current recursive analysis depth
550
589
 
551
590
  Returns:
552
591
  str: Generated commit message
553
592
  """
554
- system_prompt = create_system_prompt(context_data)
593
+ # Create different system prompts for different stages
594
+ technical_system_prompt = create_technical_analysis_system_prompt(context)
595
+ commit_system_prompt = create_system_prompt(context)
555
596
 
556
- print(f"\n{COLORS['cyan']}Recursive chunking level {current_depth}/{max_depth}...{COLORS['reset']}")
597
+ print(f"\n{COLORS['cyan']}Recursive analysis chunking level {current_depth}...{COLORS['reset']}")
557
598
 
558
599
  if logger:
559
- logger.info(f"Starting recursive chunking at depth {current_depth}/{max_depth}")
600
+ logger.info(f"Starting recursive analysis chunking at depth {current_depth}")
560
601
  logger.debug(f"Combined analysis size: {len(combined_analysis.splitlines())} lines")
561
602
  logger.log_content("DEBUG", f"COMBINED_ANALYSIS_DEPTH_{current_depth}", combined_analysis)
562
603
 
563
- # Create rechunk prompt
564
- rechunk_prompt = create_rechunk_prompt(combined_analysis, current_depth)
565
-
566
- # Log rechunk template
567
- if logger:
568
- logger.log_template("DEBUG", f"RECHUNK_DEPTH_{current_depth}", rechunk_prompt)
569
-
570
- # Process rechunk
571
- try:
572
- result = handle_api_call(client, rechunk_prompt, system_prompt, logger)
573
-
574
- # Check if further recursive chunking is needed
575
- result_line_count = len(result.splitlines())
604
+ # If analysis is under chunk size, generate the commit message
605
+ if len(combined_analysis.splitlines()) <= chunk_size:
606
+ print(f"{COLORS['green']}Analysis is small enough, generating commit message...{COLORS['reset']}")
576
607
 
577
- if result_line_count > 50 and current_depth < max_depth:
578
- # Need another level of chunking
579
- print(f"{COLORS['yellow']}Result still too large ({result_line_count} lines), continuing recursion...{COLORS['reset']}")
580
- if logger:
581
- logger.info(f"Result still too large ({result_line_count} lines), depth {current_depth}/{max_depth}")
582
-
583
- return recursive_process(client, result, context_data, max_depth, logger, current_depth + 1)
584
- else:
585
- # Final processing
586
- print(f"{COLORS['green']}Recursion complete, generating final commit message...{COLORS['reset']}")
587
-
588
- # Create final combine prompt
589
- final_prompt = f"""Create a CONVENTIONAL COMMIT MESSAGE based on these analyzed git changes:
608
+ # Create final prompt
609
+ final_prompt = f"""Create a CONVENTIONAL COMMIT MESSAGE based on these analyzed git changes:
590
610
 
591
- {result}
611
+ {combined_analysis}
592
612
 
593
613
  FORMAT:
594
614
  type[(scope)]: <concise summary> (max 50 chars)
@@ -597,6 +617,13 @@ type[(scope)]: <concise summary> (max 50 chars)
597
617
  - [type] <specific change 2> (filename:function/method/line)
598
618
  - [type] <additional changes...>
599
619
 
620
+ BULLET POINT FORMAT:
621
+ - Each bullet MUST start with a type in square brackets: [type]
622
+ - DO NOT use the format "- type: description" (without square brackets)
623
+ - Instead, ALWAYS use "- [type] description" (with square brackets)
624
+ - Example: "- [feat] Add new login component" (correct)
625
+ - Not: "- feat: Add new login component" (incorrect)
626
+
600
627
  RULES:
601
628
  1. First line must be under 50 characters
602
629
  2. Include a blank line after the first line
@@ -604,17 +631,281 @@ RULES:
604
631
  4. BE SPECIFIC - mention technical details and function names
605
632
 
606
633
  DO NOT include any explanation or commentary outside the commit message format."""
634
+
635
+ # Log final template
636
+ if logger:
637
+ logger.log_template("DEBUG", f"FINAL_PROMPT_DEPTH_{current_depth}", final_prompt)
638
+
639
+ # Generate the commit message - use commit message system prompt
640
+ commit_message = handle_api_call(client, final_prompt, commit_system_prompt, logger)
641
+
642
+ if logger:
643
+ logger.log_content("DEBUG", f"COMMIT_MESSAGE_DEPTH_{current_depth}", commit_message)
644
+
645
+ # If the commit message is too long, we need to condense it
646
+ if len(commit_message.splitlines()) > max_msg_lines:
647
+ return condense_commit_message(
648
+ client,
649
+ commit_message,
650
+ commit_system_prompt,
651
+ max_msg_lines,
652
+ max_recursion_depth,
653
+ 1, # Start at depth 1
654
+ logger
655
+ )
656
+ return commit_message
657
+
658
+ # Analysis is still too large, need to chunk it
659
+ print(f"{COLORS['yellow']}Analysis still too large ({len(combined_analysis.splitlines())} lines), chunking...{COLORS['reset']}")
660
+
661
+ # Split the analysis into chunks
662
+ analysis_chunks = split_into_chunks(combined_analysis, chunk_size)
663
+ analysis_chunk_count = len(analysis_chunks)
664
+
665
+ if logger:
666
+ logger.info(f"Split analysis into {analysis_chunk_count} chunks at depth {current_depth}")
667
+
668
+ # Process each analysis chunk and get a condensed version
669
+ condensed_chunks = []
670
+ for i, analysis_chunk in enumerate(analysis_chunks):
671
+ print(f"\n{COLORS['cyan']}[Analysis chunk {i+1}/{analysis_chunk_count} at depth {current_depth}]{COLORS['reset']}")
672
+
673
+ # Create a target size based on how many chunks we have
674
+ target_size = min(int(chunk_size / analysis_chunk_count), 100) # Make sure it's not too small
675
+
676
+ # Create a prompt to condense this analysis chunk
677
+ condense_prompt = f"""You are analyzing a PORTION of already analyzed git changes. This is analysis data, not raw git diff.
678
+
679
+ Take this SECTION of technical analysis and condense it to be UNDER {target_size} lines while preserving the most important technical details.
680
+
681
+ Keep the format consistent with the system prompt.
682
+ Preserve full file paths, function names, and technical changes.
683
+ Group related changes when appropriate.
684
+
685
+ SECTION OF ANALYSIS TO CONDENSE:
686
+
687
+ {analysis_chunk}"""
688
+
689
+ if logger:
690
+ logger.log_template("DEBUG", f"CONDENSE_ANALYSIS_DEPTH_{current_depth}_CHUNK_{i+1}", condense_prompt)
691
+
692
+ print(f"{COLORS['yellow']}Condensing analysis chunk {i+1}/{analysis_chunk_count}...{COLORS['reset']}")
693
+
694
+ # Condense this analysis chunk - use technical system prompt for condensing analysis
695
+ try:
696
+ condensed_chunk = handle_api_call(client, condense_prompt, technical_system_prompt, logger)
697
+ condensed_chunks.append(condensed_chunk)
698
+
699
+ if logger:
700
+ logger.log_content("DEBUG", f"CONDENSED_ANALYSIS_DEPTH_{current_depth}_CHUNK_{i+1}", condensed_chunk)
701
+
702
+ print(f"{COLORS['green']}✓ Analysis chunk {i+1}/{analysis_chunk_count} condensed{COLORS['reset']}")
703
+ except Exception as e:
704
+ print(f"{COLORS['red']}Error condensing analysis chunk {i+1}: {str(e)}{COLORS['reset']}")
705
+ if logger:
706
+ logger.error(f"Error condensing analysis chunk {i+1} at depth {current_depth}: {str(e)}")
707
+ return None
708
+
709
+ # Rate limit protection between chunks
710
+ if i < analysis_chunk_count - 1:
711
+ print(f"{COLORS['yellow']}Waiting to avoid rate limits...{COLORS['reset']}")
712
+ time.sleep(5)
713
+
714
+ # Combine condensed chunks
715
+ combined_condensed = "\n\n".join(condensed_chunks)
716
+ condensed_line_count = len(combined_condensed.splitlines())
717
+
718
+ print(f"\n{COLORS['cyan']}Condensed analysis to {condensed_line_count} lines at depth {current_depth}{COLORS['reset']}")
719
+
720
+ if logger:
721
+ logger.info(f"Combined condensed analysis: {condensed_line_count} lines at depth {current_depth}")
722
+ logger.log_content("DEBUG", f"COMBINED_CONDENSED_DEPTH_{current_depth}", combined_condensed)
723
+
724
+ # Recursively process the combined condensed analysis
725
+ return recursive_chunk_analysis(
726
+ client,
727
+ combined_condensed,
728
+ context,
729
+ chunk_size,
730
+ logger,
731
+ max_msg_lines,
732
+ max_recursion_depth,
733
+ current_depth + 1
734
+ )
735
+
736
+ def condense_commit_message(client, commit_message, system_prompt, max_msg_lines, max_recursion_depth, current_depth=1, logger=None):
737
+ """Recursively condense a commit message to be under the maximum length.
738
+
739
+ Args:
740
+ client: The NGPTClient instance
741
+ commit_message: The commit message to condense
742
+ system_prompt: The system prompt
743
+ max_msg_lines: Maximum number of lines in commit message
744
+ max_recursion_depth: Maximum recursion depth for condensing
745
+ current_depth: Current recursion depth
746
+ logger: Optional logger instance
747
+
748
+ Returns:
749
+ str: Condensed commit message
750
+ """
751
+ # Always use commit message system prompt for condensing commit messages
752
+ if not isinstance(system_prompt, str) or not system_prompt.startswith("You are an expert Git commit message writer"):
753
+ system_prompt = create_system_prompt(None) # Use default commit message system prompt
754
+
755
+ commit_lines = len(commit_message.splitlines())
756
+ print(f"\n{COLORS['cyan']}Commit message has {commit_lines} lines (depth {current_depth}/{max_recursion_depth}){COLORS['reset']}")
757
+
758
+ if logger:
759
+ logger.info(f"Commit message has {commit_lines} lines at depth {current_depth}/{max_recursion_depth}")
760
+ logger.log_content("DEBUG", f"COMMIT_MESSAGE_DEPTH_{current_depth}", commit_message)
761
+
762
+ # If already under the limit, return as is
763
+ if commit_lines <= max_msg_lines:
764
+ return commit_message
765
+
766
+ # Check if we've reached the maximum recursion depth
767
+ is_final_depth = current_depth >= max_recursion_depth
768
+
769
+ # Create the condense prompt - only mention the specific max_msg_lines at final depth
770
+ if is_final_depth:
771
+ condense_prompt = f"""Rewrite this git commit message to be MUST BE AT MOST {max_msg_lines} LINES TOTAL.
772
+ PRESERVE the first line exactly as is, and keep the most important changes in the bullet points.
773
+ Group related changes when possible.
774
+
775
+ CURRENT MESSAGE (TOO LONG):
776
+ {commit_message}
777
+
778
+ BULLET POINT FORMAT:
779
+ - Each bullet MUST start with a type in square brackets: [type]
780
+ - DO NOT use the format "- type: description" (without square brackets)
781
+ - Instead, ALWAYS use "- [type] description" (with square brackets)
782
+ - Example: "- [feat] Add new login component" (correct)
783
+ - Not: "- feat: Add new login component" (incorrect)
784
+
785
+ REQUIREMENTS:
786
+ 1. First line must be preserved exactly as is
787
+ 2. MUST BE AT MOST {max_msg_lines} LINES TOTAL including blank lines - THIS IS A HARD REQUIREMENT
788
+ 3. Include the most significant changes
789
+ 4. Group related changes when possible
790
+ 5. Keep proper formatting with bullet points
791
+ 6. Maintain detailed file/function references in each bullet point
792
+ 7. KEEP TYPE TAGS IN SQUARE BRACKETS: [type]"""
793
+ else:
794
+ # At earlier depths, don't specify the exact line count limit
795
+ condense_prompt = f"""Rewrite this git commit message to be more concise.
796
+ PRESERVE the first line exactly as is, and keep the most important changes in the bullet points.
797
+ Group related changes when possible.
798
+
799
+ CURRENT MESSAGE (TOO LONG):
800
+ {commit_message}
801
+
802
+ BULLET POINT FORMAT:
803
+ - Each bullet MUST start with a type in square brackets: [type]
804
+ - DO NOT use the format "- type: description" (without square brackets)
805
+ - Instead, ALWAYS use "- [type] description" (with square brackets)
806
+ - Example: "- [feat] Add new login component" (correct)
807
+ - Not: "- feat: Add new login component" (incorrect)
808
+
809
+ REQUIREMENTS:
810
+ 1. First line must be preserved exactly as is
811
+ 2. Make the message significantly shorter while preserving key information
812
+ 3. Include the most significant changes
813
+ 4. Group related changes when possible
814
+ 5. Keep proper formatting with bullet points
815
+ 6. Maintain detailed file/function references in each bullet point
816
+ 7. KEEP TYPE TAGS IN SQUARE BRACKETS: [type]"""
817
+
818
+ if logger:
819
+ logger.log_template("DEBUG", f"CONDENSE_PROMPT_DEPTH_{current_depth}", condense_prompt)
820
+
821
+ print(f"{COLORS['yellow']}Condensing commit message (depth {current_depth}/{max_recursion_depth})...{COLORS['reset']}")
822
+
823
+ try:
824
+ condensed_result = handle_api_call(client, condense_prompt, system_prompt, logger)
825
+
826
+ if logger:
827
+ logger.log_content("DEBUG", f"CONDENSED_RESULT_DEPTH_{current_depth}", condensed_result)
828
+
829
+ # Check if we need to condense further
830
+ condensed_lines = len(condensed_result.splitlines())
831
+
832
+ if condensed_lines > max_msg_lines and current_depth < max_recursion_depth:
833
+ print(f"{COLORS['yellow']}Commit message still has {condensed_lines} lines. Further condensing...{COLORS['reset']}")
607
834
 
608
- # Log final template
609
835
  if logger:
610
- logger.log_template("DEBUG", "FINAL", final_prompt)
836
+ logger.info(f"Commit message still has {condensed_lines} lines after condensing at depth {current_depth}")
611
837
 
612
- return handle_api_call(client, final_prompt, system_prompt, logger)
838
+ # Try again at the next depth
839
+ return condense_commit_message(
840
+ client,
841
+ condensed_result,
842
+ system_prompt,
843
+ max_msg_lines,
844
+ max_recursion_depth,
845
+ current_depth + 1,
846
+ logger
847
+ )
848
+ else:
849
+ return condensed_result
613
850
  except Exception as e:
614
- print(f"{COLORS['red']}Error in recursive processing: {str(e)}{COLORS['reset']}")
851
+ print(f"{COLORS['red']}Error condensing commit message: {str(e)}{COLORS['reset']}")
615
852
  if logger:
616
- logger.error(f"Error in recursive processing at depth {current_depth}: {str(e)}")
617
- return None
853
+ logger.error(f"Error condensing commit message at depth {current_depth}: {str(e)}")
854
+ # Return the original message if condensing fails
855
+ return commit_message
856
+
857
+ def create_combine_prompt(partial_analyses):
858
+ """Create prompt for combining partial analyses.
859
+
860
+ Args:
861
+ partial_analyses: List of partial analyses to combine
862
+
863
+ Returns:
864
+ str: Prompt for the AI
865
+ """
866
+ all_analyses = "\n\n".join(partial_analyses)
867
+
868
+ return f"""===CRITICAL INSTRUCTION===
869
+ You are working with ANALYZED SUMMARIES of git changes, NOT raw git diff.
870
+ The raw git diff has ALREADY been processed into these summaries.
871
+
872
+ TASK: Synthesize these partial analyses into a complete conventional commit message
873
+ following the format specified in the system prompt.
874
+
875
+ The analyses to combine:
876
+
877
+ {all_analyses}
878
+
879
+ RULES FOR FILENAMES:
880
+ 1. For the FIRST mention of a file, use the full relative path
881
+ 2. For SUBSEQUENT mentions of the same file, use ONLY the filename without path
882
+ - Example: First mention: "utils/helpers/format.js" → Subsequent mentions: "format.js"
883
+ 3. Only include the full path again if there are multiple files with the same name
884
+ 4. For repeated mentions of the same file, consider grouping related changes in one bullet
885
+
886
+ BULLET POINT FORMAT:
887
+ - Each bullet MUST start with a type in square brackets: [type]
888
+ - DO NOT use the format "- type: description" (without square brackets)
889
+ - Instead, ALWAYS use "- [type] description" (with square brackets)
890
+ - Example: "- [feat] Add new login component" (correct)
891
+ - Not: "- feat: Add new login component" (incorrect)
892
+
893
+ EXAMPLE OF PROPERLY FORMATTED COMMIT MESSAGE:
894
+ refactor(core): simplify context handling for commit prompts
895
+
896
+ - [refactor] Remove process_context function (cli/modes/gitcommsg.py:69-124)
897
+ - [refactor] Update all functions to accept raw context string (gitcommsg.py:create_system_prompt())
898
+ - [refactor] Replace context_data usages with context (gitcommsg.py)
899
+ - [docs] Update library usage doc (docs/usage/library_usage.md:516,531-537)
900
+ - [chore] Bump project version to 2.15.1 (pyproject.toml:3, uv.lock:137)
901
+
902
+ REMINDER:
903
+ - First line must be under 50 characters
904
+ - Include a blank line after the first line
905
+ - Each bullet must include specific file references with format [type]
906
+ - Include specific technical details in each bullet point
907
+
908
+ DO NOT ask for the original diff or add explanations outside the commit message format."""
618
909
 
619
910
  def gitcommsg_mode(client, args, logger=None):
620
911
  """Handle the Git commit message generation mode.
@@ -678,19 +969,21 @@ def gitcommsg_mode(client, args, logger=None):
678
969
  active_logger.log_diff("DEBUG", diff_content)
679
970
 
680
971
  # Process context if provided
681
- context_data = None
972
+ context = None
682
973
  if args.message_context:
683
- context_data = process_context(args.message_context)
974
+ context = args.message_context
684
975
  if active_logger:
685
- active_logger.debug(f"Processed context: {context_data}")
686
- active_logger.log_content("DEBUG", "CONTEXT_DATA", str(context_data))
976
+ active_logger.debug(f"Using raw context: {context}")
977
+ active_logger.log_content("DEBUG", "CONTEXT", context)
687
978
 
688
- # Create system prompt
689
- system_prompt = create_system_prompt(context_data)
979
+ # Create system prompts for different stages
980
+ technical_system_prompt = create_technical_analysis_system_prompt(context)
981
+ commit_system_prompt = create_system_prompt(context)
690
982
 
691
- # Log system prompt
983
+ # Log system prompts
692
984
  if active_logger:
693
- active_logger.log_template("DEBUG", "SYSTEM", system_prompt)
985
+ active_logger.log_template("DEBUG", "TECHNICAL_SYSTEM", technical_system_prompt)
986
+ active_logger.log_template("DEBUG", "COMMIT_SYSTEM", commit_system_prompt)
694
987
 
695
988
  print(f"\n{COLORS['green']}Generating commit message...{COLORS['reset']}")
696
989
 
@@ -701,19 +994,36 @@ def gitcommsg_mode(client, args, logger=None):
701
994
  if active_logger:
702
995
  active_logger.info(f"Using chunk size: {chunk_size}")
703
996
 
997
+ # Get max_msg_lines from args or use default
998
+ max_msg_lines = getattr(args, 'max_msg_lines', 20) # Default to 20 if not specified
999
+ if active_logger:
1000
+ active_logger.info(f"Maximum commit message lines: {max_msg_lines}")
1001
+
1002
+ # Get max_recursion_depth from args or use default
1003
+ max_recursion_depth = getattr(args, 'max_recursion_depth', 3) # Default to 3 if not specified
1004
+ if active_logger:
1005
+ active_logger.info(f"Maximum recursion depth for message condensing: {max_recursion_depth}")
1006
+
1007
+ # Get analyses_chunk_size from args or use default
1008
+ analyses_chunk_size = getattr(args, 'analyses_chunk_size', args.chunk_size) # Default to chunk_size if not specified
1009
+ if active_logger:
1010
+ active_logger.info(f"Analyses chunk size: {analyses_chunk_size}")
1011
+
704
1012
  if args.recursive_chunk:
705
1013
  # Use chunking with recursive processing
706
1014
  if active_logger:
707
- active_logger.info(f"Using recursive chunking with max_depth: {args.max_depth}")
1015
+ active_logger.info(f"Using recursive chunking with max_recursion_depth: {max_recursion_depth}")
708
1016
 
709
1017
  result = process_with_chunking(
710
1018
  client,
711
1019
  diff_content,
712
- context_data,
1020
+ context,
713
1021
  chunk_size=args.chunk_size,
714
1022
  recursive=True,
715
- max_depth=args.max_depth,
716
- logger=active_logger
1023
+ logger=active_logger,
1024
+ max_msg_lines=max_msg_lines,
1025
+ max_recursion_depth=max_recursion_depth,
1026
+ analyses_chunk_size=analyses_chunk_size
717
1027
  )
718
1028
  else:
719
1029
  # Direct processing without chunking
@@ -726,7 +1036,25 @@ def gitcommsg_mode(client, args, logger=None):
726
1036
  if active_logger:
727
1037
  active_logger.log_template("DEBUG", "DIRECT_PROCESSING", prompt)
728
1038
 
729
- result = handle_api_call(client, prompt, system_prompt, active_logger)
1039
+ # Use commit message system prompt for direct processing
1040
+ result = handle_api_call(client, prompt, commit_system_prompt, active_logger)
1041
+
1042
+ # Check if the result exceeds max_msg_lines and recursive_chunk is enabled
1043
+ if result and len(result.splitlines()) > max_msg_lines:
1044
+ print(f"{COLORS['yellow']}Commit message exceeds {max_msg_lines} lines, condensing...{COLORS['reset']}")
1045
+ if active_logger:
1046
+ active_logger.info(f"Commit message exceeds {max_msg_lines} lines, starting condensing process")
1047
+
1048
+ # Use our condense_commit_message function with commit message system prompt
1049
+ result = condense_commit_message(
1050
+ client,
1051
+ result,
1052
+ commit_system_prompt,
1053
+ max_msg_lines,
1054
+ max_recursion_depth,
1055
+ 1, # Start at depth 1
1056
+ active_logger
1057
+ )
730
1058
 
731
1059
  if not result:
732
1060
  print(f"{COLORS['red']}Failed to generate commit message.{COLORS['reset']}")
ngpt/utils/cli_config.py CHANGED
@@ -24,7 +24,9 @@ CLI_CONFIG_OPTIONS = {
24
24
  "recursive-chunk": {"type": "bool", "default": False, "context": ["gitcommsg"]},
25
25
  "diff": {"type": "str", "default": None, "context": ["gitcommsg"]},
26
26
  "chunk-size": {"type": "int", "default": 200, "context": ["gitcommsg"]},
27
- "max-depth": {"type": "int", "default": 3, "context": ["gitcommsg"]},
27
+ "analyses-chunk-size": {"type": "int", "default": 200, "context": ["gitcommsg"]},
28
+ "max-msg-lines": {"type": "int", "default": 20, "context": ["gitcommsg"]},
29
+ "max-recursion-depth": {"type": "int", "default": 3, "context": ["gitcommsg"]},
28
30
  }
29
31
 
30
32
  def get_cli_config_dir() -> Path:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ngpt
3
- Version: 2.14.1
3
+ Version: 2.15.1
4
4
  Summary: A lightweight Python CLI and library for interacting with OpenAI-compatible APIs, supporting both official and self-hosted LLM endpoints.
5
5
  Project-URL: Homepage, https://github.com/nazdridoy/ngpt
6
6
  Project-URL: Repository, https://github.com/nazdridoy/ngpt
@@ -154,6 +154,13 @@ ngpt --log "Tell me about quantum computing"
154
154
 
155
155
  # Process text from stdin using the {} placeholder
156
156
  cat README.md | ngpt --stdin "Summarize this document: {}"
157
+
158
+ # Use different model providers by specifying the provider name
159
+ ngpt --provider Groq "Explain quantum computing"
160
+
161
+ # Compare outputs from different providers
162
+ ngpt --provider OpenAI "Explain quantum physics" > openai_response.txt
163
+ ngpt --provider Ollama "Explain quantum physics" > ollama_response.txt
157
164
  ```
158
165
 
159
166
  For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdridoy.github.io/ngpt/usage/cli_usage.html).
@@ -162,7 +169,7 @@ For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdri
162
169
 
163
170
  - ✅ **Versatile**: Use as a CLI tool, Python library, or CLI framework for building custom tools
164
171
  - 🪶 **Lightweight**: Minimal dependencies with everything you need included
165
- - 🔄 **API Flexibility**: Works with OpenAI, Ollama, Groq, and any compatible endpoint
172
+ - 🔄 **API Flexibility**: Works with OpenAI, Ollama, Groq, Claude, and any compatible endpoint
166
173
  - 💬 **Interactive Chat**: Continuous conversation with memory in modern UI
167
174
  - 📊 **Streaming Responses**: Real-time output for better user experience
168
175
  - 🔍 **Web Search**: Integrated with compatible API endpoints
@@ -179,6 +186,8 @@ For more examples and detailed usage, visit the [CLI Usage Guide](https://nazdri
179
186
  - 📃 **Conversation Logging**: Save your conversations to text files for later reference
180
187
  - 🧰 **CLI Components**: Reusable components for building custom AI-powered command-line tools
181
188
  - 🔌 **Modular Architecture**: Well-structured codebase with clean separation of concerns
189
+ - 🔄 **Provider Switching**: Easily switch between different LLM providers with a single parameter
190
+ - 🚀 **Performance Optimized**: Fast response times and minimal resource usage
182
191
 
183
192
  See the [Feature Overview](https://nazdridoy.github.io/ngpt/overview.html) for more details.
184
193
 
@@ -327,6 +336,16 @@ print(result.stdout)
327
336
  # Returns only code without markdown or explanations
328
337
  code = client.generate_code("function that converts Celsius to Fahrenheit")
329
338
  print(code)
339
+
340
+ # Compare responses from different providers
341
+ openai_config = load_config(config_index=0) # OpenAI
342
+ groq_config = load_config(config_index=1) # Groq
343
+
344
+ openai_client = NGPTClient(**openai_config)
345
+ groq_client = NGPTClient(**groq_config)
346
+
347
+ openai_response = openai_client.chat("Explain quantum computing")
348
+ groq_response = groq_client.chat("Explain quantum computing")
330
349
  ```
331
350
 
332
351
  For advanced usage patterns and integrations, check out the [Advanced Examples](https://nazdridoy.github.io/ngpt/examples/advanced.html).
@@ -392,6 +411,7 @@ You can configure nGPT using the following options:
392
411
  | `-t, --text` | Open interactive multiline editor for complex prompts with syntax highlighting |
393
412
  | `--stdin` | Read from stdin and use content with prompt. Use {} in prompt as placeholder for stdin content |
394
413
  | `--rewrite` | Rewrite text to improve quality while preserving original tone and meaning |
414
+ | `--gitcommsg` | Generate AI-powered git commit messages from staged changes or diff files |
395
415
 
396
416
  #### Global Options
397
417
 
@@ -2,7 +2,7 @@ ngpt/__init__.py,sha256=kpKhViLakwMdHZkuLht2vWcjt0uD_5gR33gvMhfXr6w,664
2
2
  ngpt/__main__.py,sha256=j3eFYPOtCCFBOGh7NK5IWEnADnTMMSEB9GLyIDoW724,66
3
3
  ngpt/client.py,sha256=rLgDPmJe8_yi13-XUiHJ45z54rJVrupxWmeb-fQZGF4,15129
4
4
  ngpt/cli/__init__.py,sha256=hebbDSMGiOd43YNnQP67uzr67Ue6rZPwm2czynr5iZY,43
5
- ngpt/cli/args.py,sha256=fTX3ozqMT5fCcxlyrPQEEHeWxSpgDvdWSM4AtdIAIE8,11152
5
+ ngpt/cli/args.py,sha256=VJM6ySMnVrXgKaGb7Qb3AQPYxcQCv3FfCI4x8YkAsLQ,11534
6
6
  ngpt/cli/config_manager.py,sha256=NQQcWnjUppAAd0s0p9YAf8EyKS1ex5-0EB4DvKdB4dk,3662
7
7
  ngpt/cli/formatters.py,sha256=HBYGlx_7eoAKyzfy0Vq5L0yn8yVKjngqYBukMmXCcz0,9401
8
8
  ngpt/cli/interactive.py,sha256=DZFbExcXd7RylkpBiZBhiI6N8FBaT0m_lBes0Pvhi48,10894
@@ -12,16 +12,16 @@ ngpt/cli/ui.py,sha256=iMinm_QdsmwrEUpb7CBRexyyBqf4sviFI9M3E8D-hhA,5303
12
12
  ngpt/cli/modes/__init__.py,sha256=R3aO662RIzWEOvr3moTrEI8Tpg0zDDyMGGh1-OxiRgM,285
13
13
  ngpt/cli/modes/chat.py,sha256=4a5EgM_5A1zCSrLrjgQMDnBwIHd1Rnu5_BjSKSm7p24,4255
14
14
  ngpt/cli/modes/code.py,sha256=RjOAj7BDO5vLUdIPkUfPtyIkI_W6qEHsZvYh-sIdVaM,4293
15
- ngpt/cli/modes/gitcommsg.py,sha256=eme_E1JFOndh1yWP90ryYcrsx6K5VLQqPoTIJDKwCsQ,28164
15
+ ngpt/cli/modes/gitcommsg.py,sha256=Kx9pWGWB2bMNFhpBPAa4q7VwHmJQwnZ2qoFrWK7t3gs,45080
16
16
  ngpt/cli/modes/rewrite.py,sha256=Zb0PFvWRKXs4xJCF3GEdYc-LSmy6qRszz8-QJuldHc0,8595
17
17
  ngpt/cli/modes/shell.py,sha256=lF9f7w-0bl_FdZl-WJnZuV736BKrWQtrwoKr3ejPXFE,2682
18
18
  ngpt/cli/modes/text.py,sha256=ncYnfLFMdTPuHiOvAaHNiOWhox6GF6S-2fTwMIrAz-g,3140
19
19
  ngpt/utils/__init__.py,sha256=E46suk2-QgYBI0Qrs6WXOajOUOebF3ETAFY7ah8DTWs,942
20
- ngpt/utils/cli_config.py,sha256=b7cXTxbRA-tQWgaehP_uRm_L8-677elPUXk290uzsTs,11110
20
+ ngpt/utils/cli_config.py,sha256=tQxR3a2iXyc5TfRBPQHSUXPInO2dv_zTPGn04eWfmoo,11285
21
21
  ngpt/utils/config.py,sha256=WYOk_b1eiYjo6hpV3pfXr2RjqhOnmKqwZwKid1T41I4,10363
22
22
  ngpt/utils/log.py,sha256=f1jg2iFo35PAmsarH8FVL_62plq4VXH0Mu2QiP6RJGw,15934
23
- ngpt-2.14.1.dist-info/METADATA,sha256=MRsUrLvG2mRWtZ7ZxhbTzf1GQ43gY9E_LjVIPIr_NSU,22573
24
- ngpt-2.14.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
- ngpt-2.14.1.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
26
- ngpt-2.14.1.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
27
- ngpt-2.14.1.dist-info/RECORD,,
23
+ ngpt-2.15.1.dist-info/METADATA,sha256=vKoeQ_IrjV2UtPpEGsThb-i8wg6OE46qXw-UDZdj9YQ,23523
24
+ ngpt-2.15.1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
25
+ ngpt-2.15.1.dist-info/entry_points.txt,sha256=SqAAvLhMrsEpkIr4YFRdUeyuXQ9o0IBCeYgE6AVojoI,44
26
+ ngpt-2.15.1.dist-info/licenses/LICENSE,sha256=mQkpWoADxbHqE0HRefYLJdm7OpdrXBr3vNv5bZ8w72M,1065
27
+ ngpt-2.15.1.dist-info/RECORD,,
File without changes