@@ -217,6 +217,7 @@ def group_files_by_type(files: List[str]) -> Dict[str, List[str]]:
217
217
def enhance_ai_prompt (packages : List [Dict ], original_msg : str ) -> str :
218
218
"""Generate detailed AI prompt based on commit complexity analysis."""
219
219
complexity = calculate_commit_complexity (packages )
220
+ is_mono = is_monorepo ()
220
221
221
222
try :
222
223
diff = check_output (["git" , "diff" , "--cached" ]).decode ("utf-8" )
@@ -227,6 +228,7 @@ def enhance_ai_prompt(packages: List[Dict], original_msg: str) -> str:
227
228
analysis = {
228
229
"complexity_score" : complexity ["score" ],
229
230
"complexity_reasons" : complexity ["reasons" ],
231
+ "repository_type" : "monorepo" if is_mono else "standard" ,
230
232
"packages" : []
231
233
}
232
234
@@ -240,14 +242,19 @@ def enhance_ai_prompt(packages: List[Dict], original_msg: str) -> str:
240
242
241
243
prompt = f"""Analyze the following git changes and suggest a commit message.
242
244
245
+ Repository Type: { analysis ['repository_type' ]}
243
246
Complexity Analysis:
244
247
- Score: { complexity ['score' ]} (threshold for structured format: 5)
245
248
- Factors: { ', ' .join (complexity ['reasons' ])}
246
249
247
- Changed Packages :"""
250
+ Changed Files :"""
248
251
249
252
for pkg in analysis ["packages" ]:
250
- prompt += f"\n \n 📦 { pkg ['name' ]} ({ pkg ['scope' ]} )"
253
+ if is_mono :
254
+ prompt += f"\n \n 📦 { pkg ['name' ]} " + (f" ({ pkg ['scope' ]} )" if pkg ['scope' ] else "" )
255
+ else :
256
+ prompt += f"\n \n Directory: { pkg ['name' ]} "
257
+
251
258
for file_type , files in pkg ["files_by_type" ].items ():
252
259
prompt += f"\n { file_type } :"
253
260
for file in files :
@@ -269,11 +276,14 @@ def enhance_ai_prompt(packages: List[Dict], original_msg: str) -> str:
269
276
"message": "complete commit message",
270
277
"explanation": "reasoning",
271
278
"type": "commit type",
272
- "scope": "scope",
279
+ "scope": "{ ' scope (required for monorepo)' if is_mono else 'scope (optional)' } ",
273
280
"description": "title description"
274
281
}}
275
282
]
276
- }}"""
283
+ }}
284
+
285
+ { 'Note: This is a monorepo, so package scope is required.' if is_mono else 'Note: This is a standard repository, so scope is optional.' }
286
+ """
277
287
278
288
return prompt
279
289
@@ -439,14 +449,12 @@ def get_ai_suggestion(prompt: str, original_message: str) -> Optional[List[Dict[
439
449
440
450
def create_commit_message (commit_info : Dict [str , Any ], packages : List [Dict [str , Any ]]) -> str :
441
451
"""Create appropriate commit message based on complexity."""
442
- # Clean the description to remove any existing type prefix
443
452
description = commit_info ['description' ]
444
453
type_pattern = r'^(feat|fix|docs|style|refactor|test|chore|build|ci|perf|revert)(\([^)]+\))?:\s*'
445
454
if re .match (type_pattern , description ):
446
455
description = re .sub (type_pattern , '' , description )
447
456
commit_info ['description' ] = description .strip ()
448
457
449
- # Calculate complexity
450
458
complexity = calculate_commit_complexity (packages )
451
459
452
460
if Config ().get ("debug" ):
@@ -456,25 +464,28 @@ def create_commit_message(commit_info: Dict[str, Any], packages: List[Dict[str,
456
464
for reason in complexity ["reasons" ]:
457
465
print (f"- { reason } " )
458
466
459
- # For simple commits, just return the title
467
+ # For simple commits
460
468
if not complexity ["needs_structure" ]:
461
- return f"{ commit_info ['type' ]} ({ commit_info ['scope' ]} ): { commit_info ['description' ]} "
469
+ # Only include scope if it exists (for monorepo) or is explicitly set
470
+ if commit_info .get ('scope' ):
471
+ return f"{ commit_info ['type' ]} ({ commit_info ['scope' ]} ): { commit_info ['description' ]} "
472
+ return f"{ commit_info ['type' ]} : { commit_info ['description' ]} "
462
473
463
- # For complex commits, use structured format
474
+ # For complex commits
464
475
return create_structured_commit (commit_info , packages )
465
476
466
477
def create_structured_commit (commit_info : Dict [str , Any ], packages : List [Dict [str , Any ]]) -> str :
467
478
"""Create a structured commit message for complex changes."""
468
- # Clean the description to remove any existing type prefix
469
479
description = commit_info ['description' ]
470
480
type_pattern = r'^(feat|fix|docs|style|refactor|test|chore|build|ci|perf|revert)(\([^)]+\))?:\s*'
471
481
if re .match (type_pattern , description ):
472
482
description = re .sub (type_pattern , '' , description )
473
483
474
484
# Start with the commit title
475
- message_parts = [
476
- f"{ commit_info ['type' ]} ({ commit_info ['scope' ]} ): { description } "
477
- ]
485
+ if commit_info .get ('scope' ):
486
+ message_parts = [f"{ commit_info ['type' ]} ({ commit_info ['scope' ]} ): { description } " ]
487
+ else :
488
+ message_parts = [f"{ commit_info ['type' ]} : { description } " ]
478
489
479
490
# Add a blank line after title
480
491
message_parts .append ("" )
@@ -512,16 +523,43 @@ def get_package_json_name(package_path: Path) -> Optional[str]:
512
523
return None
513
524
return None
514
525
515
- # ... [previous code remains the same until get_changed_packages]
526
+ def is_monorepo () -> bool :
527
+ """Detect if the current repository is a monorepo."""
528
+ try :
529
+ git_root = Path (check_output (["git" , "rev-parse" , "--show-toplevel" ], text = True ).strip ())
530
+
531
+ # Common monorepo indicators
532
+ monorepo_indicators = [
533
+ git_root / "packages" ,
534
+ git_root / "apps" ,
535
+ git_root / "libs" ,
536
+ git_root / "services"
537
+ ]
538
+
539
+ # Check for package.json with workspaces
540
+ package_json = git_root / "package.json"
541
+ if package_json .exists ():
542
+ try :
543
+ data = json .loads (package_json .read_text ())
544
+ if "workspaces" in data :
545
+ return True
546
+ except json .JSONDecodeError :
547
+ pass
548
+
549
+ # Check for common monorepo directories
550
+ return any (indicator .is_dir () for indicator in monorepo_indicators )
551
+
552
+ except Exception as e :
553
+ if Config ().get ("debug" ):
554
+ print (f"Error detecting repository type: { e } " )
555
+ return False
516
556
517
557
def get_changed_packages () -> List [Dict ]:
518
558
"""Get all packages with changes in the current commit."""
519
559
try :
520
- # Get git root directory
521
560
git_root = Path (check_output (["git" , "rev-parse" , "--show-toplevel" ], text = True ).strip ())
522
561
current_dir = Path .cwd ()
523
562
524
- # Get relative path from current directory to git root
525
563
try :
526
564
rel_path = current_dir .relative_to (git_root )
527
565
except ValueError :
@@ -542,34 +580,52 @@ def get_changed_packages() -> List[Dict]:
542
580
print (f"Error getting changed files: { e } " )
543
581
return []
544
582
583
+ is_mono = is_monorepo ()
545
584
packages = {}
585
+
546
586
for file in changed_files :
547
587
if not file :
548
588
continue
549
589
550
- if file .startswith ("packages/" ):
590
+ if is_mono and file .startswith ("packages/" ):
551
591
parts = file .split ("/" )
552
592
if len (parts ) > 1 :
553
593
pkg_path = f"packages/{ parts [1 ]} "
554
594
if pkg_path not in packages :
555
595
packages [pkg_path ] = []
556
596
packages [pkg_path ].append (file )
557
597
else :
558
- if "root" not in packages :
559
- packages ["root" ] = []
560
- packages ["root" ].append (file )
598
+ # For standard repos, group by directory type
599
+ path_parts = Path (file ).parts
600
+ if not path_parts :
601
+ continue
602
+
603
+ # Determine appropriate grouping based on file type/location
604
+ if path_parts [0 ] in {"src" , "test" , "docs" , "scripts" }:
605
+ group = path_parts [0 ]
606
+ else :
607
+ group = "root"
608
+
609
+ if group not in packages :
610
+ packages [group ] = []
611
+ packages [group ].append (file )
561
612
562
613
results = []
563
614
for pkg_path , files in packages .items ():
564
- if pkg_path == "root" :
565
- scope = name = "root"
566
- else :
567
- pkg_name = get_package_json_name (Path (pkg_path ))
568
- if pkg_name :
569
- name = pkg_name
570
- scope = pkg_name .split ("/" )[- 1 ]
615
+ if is_mono :
616
+ if pkg_path == "root" :
617
+ scope = name = "root"
571
618
else :
572
- name = scope = pkg_path .split ("/" )[- 1 ]
619
+ pkg_name = get_package_json_name (Path (pkg_path ))
620
+ if pkg_name :
621
+ name = pkg_name
622
+ scope = pkg_name .split ("/" )[- 1 ]
623
+ else :
624
+ name = scope = pkg_path .split ("/" )[- 1 ]
625
+ else :
626
+ # For standard repos, scope is optional
627
+ name = pkg_path
628
+ scope = None if pkg_path == "root" else pkg_path
573
629
574
630
results .append ({
575
631
"name" : name ,
0 commit comments