@entro314labs/ai-changelog-generator 3.2.1 → 3.6.0
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.
- package/CHANGELOG.md +42 -2
- package/README.md +21 -1
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +6 -3
- package/manifest.json +177 -0
- package/package.json +76 -81
- package/src/ai-changelog-generator.js +5 -4
- package/src/application/orchestrators/changelog.orchestrator.js +19 -203
- package/src/cli.js +16 -5
- package/src/domains/ai/ai-analysis.service.js +2 -0
- package/src/domains/analysis/analysis.engine.js +714 -37
- package/src/domains/changelog/changelog.service.js +623 -32
- package/src/domains/changelog/workspace-changelog.service.js +445 -622
- package/src/domains/git/commit-tagger.js +552 -0
- package/src/domains/git/git-manager.js +357 -0
- package/src/domains/git/git.service.js +865 -16
- package/src/infrastructure/cli/cli.controller.js +14 -9
- package/src/infrastructure/config/configuration.manager.js +25 -11
- package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
- package/src/infrastructure/mcp/mcp-server.service.js +105 -32
- package/src/infrastructure/providers/core/base-provider.js +1 -1
- package/src/infrastructure/providers/implementations/anthropic.js +16 -173
- package/src/infrastructure/providers/implementations/azure.js +16 -63
- package/src/infrastructure/providers/implementations/dummy.js +13 -16
- package/src/infrastructure/providers/implementations/mock.js +13 -26
- package/src/infrastructure/providers/implementations/ollama.js +12 -4
- package/src/infrastructure/providers/implementations/openai.js +13 -165
- package/src/infrastructure/providers/provider-management.service.js +126 -412
- package/src/infrastructure/providers/utils/base-provider-helpers.js +11 -0
- package/src/shared/utils/cli-ui.js +8 -10
- package/src/shared/utils/diff-processor.js +21 -19
- package/src/shared/utils/error-classes.js +33 -0
- package/src/shared/utils/utils.js +83 -63
- package/types/index.d.ts +61 -68
- package/src/domains/git/git-repository.analyzer.js +0 -678
|
@@ -24,11 +24,8 @@ export class AnalysisEngine {
|
|
|
24
24
|
|
|
25
25
|
async _ensureGitAnalyzer() {
|
|
26
26
|
if (!this.gitRepoAnalyzer && this.gitService?.gitManager) {
|
|
27
|
-
|
|
28
|
-
this.gitRepoAnalyzer =
|
|
29
|
-
this.gitService.gitManager,
|
|
30
|
-
this.aiAnalysisService
|
|
31
|
-
)
|
|
27
|
+
// Use the existing GitService instead of separate analyzer
|
|
28
|
+
this.gitRepoAnalyzer = this.gitService
|
|
32
29
|
}
|
|
33
30
|
return this.gitRepoAnalyzer
|
|
34
31
|
}
|
|
@@ -503,70 +500,750 @@ export class AnalysisEngine {
|
|
|
503
500
|
return { health: 'unknown', analysis: 'Git analyzer not available' }
|
|
504
501
|
}
|
|
505
502
|
|
|
506
|
-
//
|
|
503
|
+
// Advanced analysis methods
|
|
507
504
|
analyzeCommitPatterns(commits) {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
505
|
+
try {
|
|
506
|
+
if (!commits || commits.length === 0) {
|
|
507
|
+
return {
|
|
508
|
+
patterns: [],
|
|
509
|
+
compliance: 0,
|
|
510
|
+
suggestions: ['No commits provided for analysis'],
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
const patterns = []
|
|
515
|
+
let conventionalCount = 0
|
|
516
|
+
let semanticCount = 0
|
|
517
|
+
|
|
518
|
+
const suggestions = []
|
|
519
|
+
|
|
520
|
+
for (const commit of commits) {
|
|
521
|
+
const message = commit.subject || commit.message || ''
|
|
522
|
+
|
|
523
|
+
// Check for conventional commit format
|
|
524
|
+
if (/^(feat|fix|docs|style|refactor|test|chore|perf|build|ci)(\(.+\))?!?:/.test(message)) {
|
|
525
|
+
conventionalCount++
|
|
526
|
+
patterns.push('conventional')
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// Check for semantic patterns
|
|
530
|
+
if (/\b(add|remove|update|fix|improve|refactor)\b/i.test(message)) {
|
|
531
|
+
semanticCount++
|
|
532
|
+
patterns.push('semantic')
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const compliance = Math.round((conventionalCount / commits.length) * 100)
|
|
537
|
+
|
|
538
|
+
if (compliance < 50) {
|
|
539
|
+
suggestions.push(
|
|
540
|
+
'Consider adopting conventional commit format for better changelog generation'
|
|
541
|
+
)
|
|
542
|
+
}
|
|
543
|
+
if (conventionalCount === 0) {
|
|
544
|
+
suggestions.push('No conventional commits found. See https://www.conventionalcommits.org/')
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
patterns: [...new Set(patterns)],
|
|
549
|
+
compliance,
|
|
550
|
+
conventionalCommits: conventionalCount,
|
|
551
|
+
semanticCommits: semanticCount,
|
|
552
|
+
totalCommits: commits.length,
|
|
553
|
+
suggestions,
|
|
554
|
+
}
|
|
555
|
+
} catch (error) {
|
|
556
|
+
console.warn(`Commit pattern analysis failed: ${error.message}`)
|
|
557
|
+
return {
|
|
558
|
+
patterns: [],
|
|
559
|
+
compliance: 0,
|
|
560
|
+
suggestions: ['Analysis failed - ensure valid commits provided'],
|
|
561
|
+
}
|
|
512
562
|
}
|
|
513
563
|
}
|
|
514
564
|
|
|
515
565
|
detectChangeTypes(changes) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
566
|
+
try {
|
|
567
|
+
if (!changes || changes.length === 0) {
|
|
568
|
+
return {
|
|
569
|
+
types: [],
|
|
570
|
+
confidence: 'low',
|
|
571
|
+
details: 'No changes provided',
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
const detectedTypes = new Set()
|
|
576
|
+
const typeIndicators = {
|
|
577
|
+
feat: ['feat', 'feature', 'add', 'new', 'implement', 'create'],
|
|
578
|
+
fix: ['fix', 'bug', 'issue', 'resolve', 'correct', 'repair'],
|
|
579
|
+
docs: ['doc', 'readme', 'comment', 'documentation', 'guide'],
|
|
580
|
+
test: ['test', 'spec', 'coverage', 'unit', 'integration'],
|
|
581
|
+
refactor: ['refactor', 'cleanup', 'restructure', 'reorganize'],
|
|
582
|
+
style: ['style', 'format', 'lint', 'prettier', 'formatting'],
|
|
583
|
+
perf: ['perf', 'performance', 'optimize', 'speed', 'efficient'],
|
|
584
|
+
build: ['build', 'compile', 'bundle', 'package', 'deploy'],
|
|
585
|
+
chore: ['chore', 'maintenance', 'update', 'upgrade', 'bump'],
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
// Analyze file paths and change descriptions
|
|
589
|
+
for (const change of changes) {
|
|
590
|
+
const searchText = [
|
|
591
|
+
change.filePath || change.path || '',
|
|
592
|
+
change.diff || '',
|
|
593
|
+
change.description || '',
|
|
594
|
+
]
|
|
595
|
+
.join(' ')
|
|
596
|
+
.toLowerCase()
|
|
597
|
+
|
|
598
|
+
for (const [type, indicators] of Object.entries(typeIndicators)) {
|
|
599
|
+
if (indicators.some((indicator) => searchText.includes(indicator))) {
|
|
600
|
+
detectedTypes.add(type)
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// File-based type detection
|
|
605
|
+
const filePath = change.filePath || change.path || ''
|
|
606
|
+
if (filePath.includes('test') || filePath.includes('spec')) {
|
|
607
|
+
detectedTypes.add('test')
|
|
608
|
+
} else if (filePath.includes('doc') || filePath.endsWith('.md')) {
|
|
609
|
+
detectedTypes.add('docs')
|
|
610
|
+
} else if (filePath.includes('config') || filePath.includes('build')) {
|
|
611
|
+
detectedTypes.add('build')
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
const confidence =
|
|
616
|
+
detectedTypes.size > 0 ? (detectedTypes.size === 1 ? 'high' : 'medium') : 'low'
|
|
617
|
+
|
|
618
|
+
return {
|
|
619
|
+
types: Array.from(detectedTypes),
|
|
620
|
+
confidence,
|
|
621
|
+
totalChanges: changes.length,
|
|
622
|
+
details: `Detected ${detectedTypes.size} change types from ${changes.length} changes`,
|
|
623
|
+
}
|
|
624
|
+
} catch (error) {
|
|
625
|
+
console.warn(`Change type detection failed: ${error.message}`)
|
|
626
|
+
return {
|
|
627
|
+
types: [],
|
|
628
|
+
confidence: 'low',
|
|
629
|
+
details: `Analysis failed: ${error.message}`,
|
|
630
|
+
}
|
|
519
631
|
}
|
|
520
632
|
}
|
|
521
633
|
|
|
522
634
|
assessCodeQuality(files) {
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
635
|
+
try {
|
|
636
|
+
if (!files || files.length === 0) {
|
|
637
|
+
return {
|
|
638
|
+
score: 0,
|
|
639
|
+
issues: ['No files provided for analysis'],
|
|
640
|
+
recommendations: [],
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
let score = 10.0
|
|
645
|
+
const issues = []
|
|
646
|
+
const recommendations = []
|
|
647
|
+
|
|
648
|
+
// Analyze each file
|
|
649
|
+
for (const file of files) {
|
|
650
|
+
const filePath = file.filePath || file.path || ''
|
|
651
|
+
const diff = file.diff || ''
|
|
652
|
+
|
|
653
|
+
// Check for code quality indicators
|
|
654
|
+
if (diff.includes('TODO') || diff.includes('FIXME')) {
|
|
655
|
+
issues.push(`${filePath}: Contains TODO/FIXME comments`)
|
|
656
|
+
score -= 0.5
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
if (diff.includes('console.log') && !filePath.includes('test')) {
|
|
660
|
+
issues.push(`${filePath}: Contains console.log statements`)
|
|
661
|
+
score -= 0.3
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
if (diff.includes('debugger')) {
|
|
665
|
+
issues.push(`${filePath}: Contains debugger statements`)
|
|
666
|
+
score -= 0.5
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Check for large functions (very basic heuristic)
|
|
670
|
+
const functionMatches = diff.match(/function\s+\w+|const\s+\w+\s*=/g)
|
|
671
|
+
if (functionMatches && functionMatches.length > 5) {
|
|
672
|
+
issues.push(`${filePath}: May contain large or complex functions`)
|
|
673
|
+
score -= 0.2
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
// Check for proper error handling
|
|
677
|
+
if (diff.includes('try') && !diff.includes('catch')) {
|
|
678
|
+
issues.push(`${filePath}: Incomplete error handling detected`)
|
|
679
|
+
score -= 0.3
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// Check for documentation
|
|
683
|
+
if (!(diff.includes('/**') || diff.includes('//')) && filePath.endsWith('.js')) {
|
|
684
|
+
issues.push(`${filePath}: Lacks documentation comments`)
|
|
685
|
+
score -= 0.2
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
// Generate recommendations based on issues
|
|
690
|
+
if (issues.some((issue) => issue.includes('TODO'))) {
|
|
691
|
+
recommendations.push('Address TODO comments before releasing')
|
|
692
|
+
}
|
|
693
|
+
if (issues.some((issue) => issue.includes('console.log'))) {
|
|
694
|
+
recommendations.push('Remove debug console.log statements')
|
|
695
|
+
}
|
|
696
|
+
if (issues.some((issue) => issue.includes('documentation'))) {
|
|
697
|
+
recommendations.push('Add documentation comments to improve maintainability')
|
|
698
|
+
}
|
|
699
|
+
if (score < 7) {
|
|
700
|
+
recommendations.push('Consider code review and refactoring for better quality')
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
return {
|
|
704
|
+
score: Math.max(0, Math.min(10, score)),
|
|
705
|
+
issues: issues.slice(0, 10), // Limit to top 10 issues
|
|
706
|
+
recommendations,
|
|
707
|
+
filesAnalyzed: files.length,
|
|
708
|
+
}
|
|
709
|
+
} catch (error) {
|
|
710
|
+
console.warn(`Code quality assessment failed: ${error.message}`)
|
|
711
|
+
return {
|
|
712
|
+
score: 0,
|
|
713
|
+
issues: [`Assessment failed: ${error.message}`],
|
|
714
|
+
recommendations: ['Ensure valid file data is provided'],
|
|
715
|
+
}
|
|
527
716
|
}
|
|
528
717
|
}
|
|
529
718
|
|
|
530
719
|
identifyDependencies(changes) {
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
720
|
+
try {
|
|
721
|
+
const dependencies = {
|
|
722
|
+
added: [],
|
|
723
|
+
removed: [],
|
|
724
|
+
updated: [],
|
|
725
|
+
details: [],
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
if (!changes || changes.length === 0) {
|
|
729
|
+
return dependencies
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
// Look for package.json changes
|
|
733
|
+
const packageJsonChanges = changes.filter((change) =>
|
|
734
|
+
(change.filePath || change.path || '').includes('package.json')
|
|
735
|
+
)
|
|
736
|
+
|
|
737
|
+
for (const change of packageJsonChanges) {
|
|
738
|
+
const diff = change.diff || ''
|
|
739
|
+
|
|
740
|
+
// Parse dependency changes from diff
|
|
741
|
+
const addedDeps = diff.match(/\+\s*"([^"]+)":\s*"([^"]+)"/g) || []
|
|
742
|
+
const removedDeps = diff.match(/-\s*"([^"]+)":\s*"([^"]+)"/g) || []
|
|
743
|
+
|
|
744
|
+
for (const match of addedDeps) {
|
|
745
|
+
const [, name, version] = match.match(/\+\s*"([^"]+)":\s*"([^"]+)"/) || []
|
|
746
|
+
if (name && version) {
|
|
747
|
+
dependencies.added.push({ name, version, file: change.filePath })
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
|
|
751
|
+
for (const match of removedDeps) {
|
|
752
|
+
const [, name, version] = match.match(/-\s*"([^"]+)":\s*"([^"]+)"/) || []
|
|
753
|
+
if (name && version) {
|
|
754
|
+
dependencies.removed.push({ name, version, file: change.filePath })
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Look for other dependency files
|
|
760
|
+
const depFiles = changes.filter((change) => {
|
|
761
|
+
const path = change.filePath || change.path || ''
|
|
762
|
+
return (
|
|
763
|
+
path.includes('requirements.txt') ||
|
|
764
|
+
path.includes('Gemfile') ||
|
|
765
|
+
path.includes('go.mod') ||
|
|
766
|
+
path.includes('Cargo.toml')
|
|
767
|
+
)
|
|
768
|
+
})
|
|
769
|
+
|
|
770
|
+
dependencies.details = [
|
|
771
|
+
`Found ${packageJsonChanges.length} package.json changes`,
|
|
772
|
+
`Found ${depFiles.length} other dependency file changes`,
|
|
773
|
+
`Total: ${dependencies.added.length} added, ${dependencies.removed.length} removed`,
|
|
774
|
+
]
|
|
775
|
+
|
|
776
|
+
return dependencies
|
|
777
|
+
} catch (error) {
|
|
778
|
+
console.warn(`Dependency analysis failed: ${error.message}`)
|
|
779
|
+
return {
|
|
780
|
+
added: [],
|
|
781
|
+
removed: [],
|
|
782
|
+
updated: [],
|
|
783
|
+
details: [`Analysis failed: ${error.message}`],
|
|
784
|
+
}
|
|
535
785
|
}
|
|
536
786
|
}
|
|
537
787
|
|
|
538
788
|
evaluatePerformanceImpact(changes) {
|
|
539
|
-
|
|
540
|
-
impact
|
|
541
|
-
|
|
789
|
+
try {
|
|
790
|
+
const impact = {
|
|
791
|
+
impact: 'low',
|
|
792
|
+
metrics: {},
|
|
793
|
+
concerns: [],
|
|
794
|
+
improvements: [],
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
if (!changes || changes.length === 0) {
|
|
798
|
+
return impact
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
// Performance-related patterns
|
|
802
|
+
const performancePatterns = {
|
|
803
|
+
high: [
|
|
804
|
+
/database.*query/i,
|
|
805
|
+
/n\+1/i,
|
|
806
|
+
/memory.*leak/i,
|
|
807
|
+
/infinite.*loop/i,
|
|
808
|
+
/synchronous.*call/i,
|
|
809
|
+
],
|
|
810
|
+
medium: [/algorithm/i, /cache/i, /optimization/i, /performance/i, /async/i, /await/i],
|
|
811
|
+
improvements: [
|
|
812
|
+
/optimize/i,
|
|
813
|
+
/faster/i,
|
|
814
|
+
/efficient/i,
|
|
815
|
+
/reduce.*time/i,
|
|
816
|
+
/improve.*performance/i,
|
|
817
|
+
],
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
let riskScore = 0
|
|
821
|
+
let improvementScore = 0
|
|
822
|
+
|
|
823
|
+
for (const change of changes) {
|
|
824
|
+
const content = [
|
|
825
|
+
change.filePath || change.path || '',
|
|
826
|
+
change.diff || '',
|
|
827
|
+
change.description || '',
|
|
828
|
+
].join(' ')
|
|
829
|
+
|
|
830
|
+
// Check for performance risks
|
|
831
|
+
for (const pattern of performancePatterns.high) {
|
|
832
|
+
if (pattern.test(content)) {
|
|
833
|
+
riskScore += 3
|
|
834
|
+
impact.concerns.push(`High-risk pattern detected in ${change.filePath}`)
|
|
835
|
+
}
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
for (const pattern of performancePatterns.medium) {
|
|
839
|
+
if (pattern.test(content)) {
|
|
840
|
+
riskScore += 1
|
|
841
|
+
}
|
|
842
|
+
}
|
|
843
|
+
|
|
844
|
+
for (const pattern of performancePatterns.improvements) {
|
|
845
|
+
if (pattern.test(content)) {
|
|
846
|
+
improvementScore += 2
|
|
847
|
+
impact.improvements.push(`Performance improvement in ${change.filePath}`)
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
// Determine overall impact
|
|
853
|
+
if (riskScore > 5) {
|
|
854
|
+
impact.impact = 'high'
|
|
855
|
+
} else if (riskScore > 2 || improvementScore > 3) {
|
|
856
|
+
impact.impact = 'medium'
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
impact.metrics = {
|
|
860
|
+
riskScore,
|
|
861
|
+
improvementScore,
|
|
862
|
+
filesAnalyzed: changes.length,
|
|
863
|
+
concernsFound: impact.concerns.length,
|
|
864
|
+
improvementsFound: impact.improvements.length,
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
return impact
|
|
868
|
+
} catch (error) {
|
|
869
|
+
console.warn(`Performance impact evaluation failed: ${error.message}`)
|
|
870
|
+
return {
|
|
871
|
+
impact: 'unknown',
|
|
872
|
+
metrics: { error: error.message },
|
|
873
|
+
concerns: [],
|
|
874
|
+
improvements: [],
|
|
875
|
+
}
|
|
542
876
|
}
|
|
543
877
|
}
|
|
544
878
|
|
|
545
879
|
checkSecurityImplications(changes) {
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
880
|
+
try {
|
|
881
|
+
const security = {
|
|
882
|
+
issues: [],
|
|
883
|
+
score: 'safe',
|
|
884
|
+
warnings: [],
|
|
885
|
+
recommendations: [],
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
if (!changes || changes.length === 0) {
|
|
889
|
+
return security
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// Security-related patterns
|
|
893
|
+
const securityPatterns = {
|
|
894
|
+
critical: [
|
|
895
|
+
/password.*=.*['"][^'"]+['"]/i,
|
|
896
|
+
/api[_-]?key.*=.*['"][^'"]+['"]/i,
|
|
897
|
+
/secret.*=.*['"][^'"]+['"]/i,
|
|
898
|
+
/token.*=.*['"][^'"]+['"]/i,
|
|
899
|
+
/eval\s*\(/i,
|
|
900
|
+
/innerHTML\s*=/i,
|
|
901
|
+
/document\.write/i,
|
|
902
|
+
],
|
|
903
|
+
warning: [
|
|
904
|
+
/http:\/\//i,
|
|
905
|
+
/\.execute\(/i,
|
|
906
|
+
/shell_exec/i,
|
|
907
|
+
/system\(/i,
|
|
908
|
+
/exec\(/i,
|
|
909
|
+
/sudo/i,
|
|
910
|
+
/chmod\s+777/i,
|
|
911
|
+
],
|
|
912
|
+
auth: [/auth/i, /login/i, /permission/i, /role/i, /access/i],
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
let riskLevel = 0
|
|
916
|
+
|
|
917
|
+
for (const change of changes) {
|
|
918
|
+
const content = [
|
|
919
|
+
change.filePath || change.path || '',
|
|
920
|
+
change.diff || '',
|
|
921
|
+
change.description || '',
|
|
922
|
+
].join(' ')
|
|
923
|
+
|
|
924
|
+
// Check for critical security issues
|
|
925
|
+
for (const pattern of securityPatterns.critical) {
|
|
926
|
+
if (pattern.test(content)) {
|
|
927
|
+
security.issues.push(`Critical: Potential security issue in ${change.filePath}`)
|
|
928
|
+
riskLevel += 10
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
// Check for warnings
|
|
933
|
+
for (const pattern of securityPatterns.warning) {
|
|
934
|
+
if (pattern.test(content)) {
|
|
935
|
+
security.warnings.push(`Warning: Security concern in ${change.filePath}`)
|
|
936
|
+
riskLevel += 3
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
|
|
940
|
+
// Check for authentication changes
|
|
941
|
+
for (const pattern of securityPatterns.auth) {
|
|
942
|
+
if (pattern.test(content)) {
|
|
943
|
+
security.warnings.push(`Authentication changes detected in ${change.filePath}`)
|
|
944
|
+
riskLevel += 1
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// Determine security score
|
|
950
|
+
if (riskLevel >= 10) {
|
|
951
|
+
security.score = 'critical'
|
|
952
|
+
security.recommendations.push('Immediate security review required')
|
|
953
|
+
} else if (riskLevel >= 5) {
|
|
954
|
+
security.score = 'warning'
|
|
955
|
+
security.recommendations.push('Security review recommended')
|
|
956
|
+
} else if (riskLevel > 0) {
|
|
957
|
+
security.score = 'caution'
|
|
958
|
+
security.recommendations.push('Monitor for security implications')
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
if (security.issues.length > 0) {
|
|
962
|
+
security.recommendations.push('Remove hardcoded credentials immediately')
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
return security
|
|
966
|
+
} catch (error) {
|
|
967
|
+
console.warn(`Security analysis failed: ${error.message}`)
|
|
968
|
+
return {
|
|
969
|
+
issues: [`Analysis failed: ${error.message}`],
|
|
970
|
+
score: 'unknown',
|
|
971
|
+
warnings: [],
|
|
972
|
+
recommendations: [],
|
|
973
|
+
}
|
|
549
974
|
}
|
|
550
975
|
}
|
|
551
976
|
|
|
552
977
|
analyzeDocumentationChanges(changes) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
978
|
+
try {
|
|
979
|
+
const documentation = {
|
|
980
|
+
coverage: 'unknown',
|
|
981
|
+
changes: [],
|
|
982
|
+
statistics: {},
|
|
983
|
+
recommendations: [],
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
if (!changes || changes.length === 0) {
|
|
987
|
+
return documentation
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
let docFiles = 0
|
|
991
|
+
let codeFiles = 0
|
|
992
|
+
let docChanges = 0
|
|
993
|
+
|
|
994
|
+
for (const change of changes) {
|
|
995
|
+
const filePath = change.filePath || change.path || ''
|
|
996
|
+
const diff = change.diff || ''
|
|
997
|
+
|
|
998
|
+
if (filePath.endsWith('.md') || filePath.includes('doc') || filePath.includes('readme')) {
|
|
999
|
+
docFiles++
|
|
1000
|
+
docChanges++
|
|
1001
|
+
documentation.changes.push({
|
|
1002
|
+
file: filePath,
|
|
1003
|
+
type: 'documentation',
|
|
1004
|
+
status: change.status,
|
|
1005
|
+
})
|
|
1006
|
+
} else if (
|
|
1007
|
+
filePath.endsWith('.js') ||
|
|
1008
|
+
filePath.endsWith('.ts') ||
|
|
1009
|
+
filePath.endsWith('.py')
|
|
1010
|
+
) {
|
|
1011
|
+
codeFiles++
|
|
1012
|
+
|
|
1013
|
+
// Check for comment changes
|
|
1014
|
+
if (diff.includes('/**') || diff.includes('//') || diff.includes('#')) {
|
|
1015
|
+
documentation.changes.push({
|
|
1016
|
+
file: filePath,
|
|
1017
|
+
type: 'code-comments',
|
|
1018
|
+
status: change.status,
|
|
1019
|
+
})
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
}
|
|
1023
|
+
|
|
1024
|
+
// Calculate coverage assessment
|
|
1025
|
+
const totalFiles = docFiles + codeFiles
|
|
1026
|
+
if (totalFiles === 0) {
|
|
1027
|
+
documentation.coverage = 'unknown'
|
|
1028
|
+
} else {
|
|
1029
|
+
const docRatio = docFiles / totalFiles
|
|
1030
|
+
if (docRatio > 0.3) {
|
|
1031
|
+
documentation.coverage = 'good'
|
|
1032
|
+
} else if (docRatio > 0.1) {
|
|
1033
|
+
documentation.coverage = 'fair'
|
|
1034
|
+
} else {
|
|
1035
|
+
documentation.coverage = 'poor'
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
documentation.statistics = {
|
|
1040
|
+
documentationFiles: docFiles,
|
|
1041
|
+
codeFiles,
|
|
1042
|
+
totalChanges: documentation.changes.length,
|
|
1043
|
+
documentationRatio: totalFiles > 0 ? Math.round((docFiles / totalFiles) * 100) : 0,
|
|
1044
|
+
}
|
|
1045
|
+
|
|
1046
|
+
// Generate recommendations
|
|
1047
|
+
if (documentation.coverage === 'poor') {
|
|
1048
|
+
documentation.recommendations.push('Consider adding more documentation')
|
|
1049
|
+
}
|
|
1050
|
+
if (codeFiles > 0 && docChanges === 0) {
|
|
1051
|
+
documentation.recommendations.push('Code changes detected without documentation updates')
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
return documentation
|
|
1055
|
+
} catch (error) {
|
|
1056
|
+
console.warn(`Documentation analysis failed: ${error.message}`)
|
|
1057
|
+
return {
|
|
1058
|
+
coverage: 'unknown',
|
|
1059
|
+
changes: [],
|
|
1060
|
+
statistics: { error: error.message },
|
|
1061
|
+
recommendations: [],
|
|
1062
|
+
}
|
|
556
1063
|
}
|
|
557
1064
|
}
|
|
558
1065
|
|
|
559
1066
|
assessTestCoverage(changes) {
|
|
560
|
-
|
|
561
|
-
coverage
|
|
562
|
-
|
|
1067
|
+
try {
|
|
1068
|
+
const coverage = {
|
|
1069
|
+
coverage: 0,
|
|
1070
|
+
missing: [],
|
|
1071
|
+
statistics: {},
|
|
1072
|
+
recommendations: [],
|
|
1073
|
+
}
|
|
1074
|
+
|
|
1075
|
+
if (!changes || changes.length === 0) {
|
|
1076
|
+
return coverage
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
let testFiles = 0
|
|
1080
|
+
let codeFiles = 0
|
|
1081
|
+
const missingTests = []
|
|
1082
|
+
|
|
1083
|
+
for (const change of changes) {
|
|
1084
|
+
const filePath = change.filePath || change.path || ''
|
|
1085
|
+
|
|
1086
|
+
if (
|
|
1087
|
+
filePath.includes('test') ||
|
|
1088
|
+
filePath.includes('spec') ||
|
|
1089
|
+
filePath.endsWith('.test.js') ||
|
|
1090
|
+
filePath.endsWith('.spec.js')
|
|
1091
|
+
) {
|
|
1092
|
+
testFiles++
|
|
1093
|
+
} else if (
|
|
1094
|
+
filePath.endsWith('.js') ||
|
|
1095
|
+
filePath.endsWith('.ts') ||
|
|
1096
|
+
filePath.endsWith('.py')
|
|
1097
|
+
) {
|
|
1098
|
+
codeFiles++
|
|
1099
|
+
|
|
1100
|
+
// Check if corresponding test file exists (simplified check)
|
|
1101
|
+
const hasTest = changes.some((c) => {
|
|
1102
|
+
const testPath = c.filePath || c.path || ''
|
|
1103
|
+
return (
|
|
1104
|
+
testPath.includes(filePath.replace(/\.(js|ts|py)$/, '')) &&
|
|
1105
|
+
(testPath.includes('test') || testPath.includes('spec'))
|
|
1106
|
+
)
|
|
1107
|
+
})
|
|
1108
|
+
|
|
1109
|
+
if (!hasTest) {
|
|
1110
|
+
missingTests.push(filePath)
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
|
|
1115
|
+
// Calculate coverage estimate
|
|
1116
|
+
const totalFiles = testFiles + codeFiles
|
|
1117
|
+
if (totalFiles > 0) {
|
|
1118
|
+
coverage.coverage = Math.round((testFiles / totalFiles) * 100)
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
coverage.missing = missingTests.slice(0, 10) // Limit to top 10
|
|
1122
|
+
coverage.statistics = {
|
|
1123
|
+
testFiles,
|
|
1124
|
+
codeFiles,
|
|
1125
|
+
totalFiles,
|
|
1126
|
+
estimatedCoverage: coverage.coverage,
|
|
1127
|
+
}
|
|
1128
|
+
|
|
1129
|
+
// Generate recommendations
|
|
1130
|
+
if (coverage.coverage < 50) {
|
|
1131
|
+
coverage.recommendations.push('Low test coverage detected - consider adding more tests')
|
|
1132
|
+
}
|
|
1133
|
+
if (missingTests.length > 0) {
|
|
1134
|
+
coverage.recommendations.push(
|
|
1135
|
+
`${missingTests.length} code files may lack corresponding tests`
|
|
1136
|
+
)
|
|
1137
|
+
}
|
|
1138
|
+
|
|
1139
|
+
return coverage
|
|
1140
|
+
} catch (error) {
|
|
1141
|
+
console.warn(`Test coverage assessment failed: ${error.message}`)
|
|
1142
|
+
return {
|
|
1143
|
+
coverage: 0,
|
|
1144
|
+
missing: [],
|
|
1145
|
+
statistics: { error: error.message },
|
|
1146
|
+
recommendations: [],
|
|
1147
|
+
}
|
|
563
1148
|
}
|
|
564
1149
|
}
|
|
565
1150
|
|
|
566
1151
|
evaluateArchitecturalChanges(changes) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
1152
|
+
try {
|
|
1153
|
+
const architecture = {
|
|
1154
|
+
impact: 'minimal',
|
|
1155
|
+
changes: [],
|
|
1156
|
+
patterns: [],
|
|
1157
|
+
recommendations: [],
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1160
|
+
if (!changes || changes.length === 0) {
|
|
1161
|
+
return architecture
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
// Architectural patterns to detect
|
|
1165
|
+
const architecturalPatterns = {
|
|
1166
|
+
'database-schema': /migration|schema|table|index/i,
|
|
1167
|
+
'api-changes': /api|endpoint|route|controller/i,
|
|
1168
|
+
'dependency-injection': /inject|provider|service|factory/i,
|
|
1169
|
+
configuration: /config|setting|environment|env/i,
|
|
1170
|
+
security: /auth|security|permission|role/i,
|
|
1171
|
+
infrastructure: /docker|kubernetes|deploy|infra/i,
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
let impactScore = 0
|
|
1175
|
+
const detectedPatterns = new Set()
|
|
1176
|
+
|
|
1177
|
+
for (const change of changes) {
|
|
1178
|
+
const content = [
|
|
1179
|
+
change.filePath || change.path || '',
|
|
1180
|
+
change.diff || '',
|
|
1181
|
+
change.description || '',
|
|
1182
|
+
].join(' ')
|
|
1183
|
+
|
|
1184
|
+
// Check for architectural patterns
|
|
1185
|
+
for (const [pattern, regex] of Object.entries(architecturalPatterns)) {
|
|
1186
|
+
if (regex.test(content)) {
|
|
1187
|
+
detectedPatterns.add(pattern)
|
|
1188
|
+
impactScore += 2
|
|
1189
|
+
|
|
1190
|
+
architecture.changes.push({
|
|
1191
|
+
file: change.filePath || change.path,
|
|
1192
|
+
pattern,
|
|
1193
|
+
type: change.status,
|
|
1194
|
+
})
|
|
1195
|
+
}
|
|
1196
|
+
}
|
|
1197
|
+
|
|
1198
|
+
// Check for directory structure changes
|
|
1199
|
+
const filePath = change.filePath || change.path || ''
|
|
1200
|
+
if (filePath.includes('/') && (change.status === 'A' || change.status === 'D')) {
|
|
1201
|
+
impactScore += 1
|
|
1202
|
+
architecture.changes.push({
|
|
1203
|
+
file: filePath,
|
|
1204
|
+
pattern: 'structure-change',
|
|
1205
|
+
type: change.status,
|
|
1206
|
+
})
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
|
|
1210
|
+
// Determine impact level
|
|
1211
|
+
if (impactScore > 10) {
|
|
1212
|
+
architecture.impact = 'major'
|
|
1213
|
+
} else if (impactScore > 5) {
|
|
1214
|
+
architecture.impact = 'moderate'
|
|
1215
|
+
} else if (impactScore > 0) {
|
|
1216
|
+
architecture.impact = 'minor'
|
|
1217
|
+
}
|
|
1218
|
+
|
|
1219
|
+
architecture.patterns = Array.from(detectedPatterns)
|
|
1220
|
+
|
|
1221
|
+
// Generate recommendations
|
|
1222
|
+
if (detectedPatterns.has('database-schema')) {
|
|
1223
|
+
architecture.recommendations.push(
|
|
1224
|
+
'Database schema changes detected - ensure migration scripts are tested'
|
|
1225
|
+
)
|
|
1226
|
+
}
|
|
1227
|
+
if (detectedPatterns.has('api-changes')) {
|
|
1228
|
+
architecture.recommendations.push(
|
|
1229
|
+
'API changes detected - update documentation and versioning'
|
|
1230
|
+
)
|
|
1231
|
+
}
|
|
1232
|
+
if (architecture.impact === 'major') {
|
|
1233
|
+
architecture.recommendations.push(
|
|
1234
|
+
'Major architectural changes - consider architectural review'
|
|
1235
|
+
)
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
return architecture
|
|
1239
|
+
} catch (error) {
|
|
1240
|
+
console.warn(`Architectural analysis failed: ${error.message}`)
|
|
1241
|
+
return {
|
|
1242
|
+
impact: 'unknown',
|
|
1243
|
+
changes: [],
|
|
1244
|
+
patterns: [],
|
|
1245
|
+
recommendations: [`Analysis failed: ${error.message}`],
|
|
1246
|
+
}
|
|
570
1247
|
}
|
|
571
1248
|
}
|
|
572
1249
|
|
|
@@ -575,7 +1252,7 @@ export class AnalysisEngine {
|
|
|
575
1252
|
return {
|
|
576
1253
|
analysisCount: 0,
|
|
577
1254
|
averageTime: 0,
|
|
578
|
-
successRate: 100
|
|
1255
|
+
successRate: 100,
|
|
579
1256
|
}
|
|
580
1257
|
}
|
|
581
1258
|
}
|