@entro314labs/ai-changelog-generator 3.2.0 → 3.3.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 +41 -10
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +0 -0
- package/package.json +72 -80
- package/src/ai-changelog-generator.js +11 -2
- package/src/application/orchestrators/changelog.orchestrator.js +12 -202
- package/src/cli.js +4 -5
- package/src/domains/ai/ai-analysis.service.js +2 -0
- package/src/domains/analysis/analysis.engine.js +758 -5
- package/src/domains/changelog/changelog.service.js +711 -13
- package/src/domains/changelog/workspace-changelog.service.js +429 -571
- 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 +24 -2
- package/src/infrastructure/interactive/interactive-workflow.service.js +8 -1
- package/src/infrastructure/mcp/mcp-server.service.js +35 -11
- 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 +1 -1
- package/src/shared/utils/diff-processor.js +21 -19
- package/src/shared/utils/error-classes.js +33 -0
- package/src/shared/utils/utils.js +65 -60
- 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
|
}
|
|
@@ -502,4 +499,760 @@ export class AnalysisEngine {
|
|
|
502
499
|
}
|
|
503
500
|
return { health: 'unknown', analysis: 'Git analyzer not available' }
|
|
504
501
|
}
|
|
502
|
+
|
|
503
|
+
// Advanced analysis methods
|
|
504
|
+
analyzeCommitPatterns(commits) {
|
|
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
|
+
}
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
detectChangeTypes(changes) {
|
|
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
|
+
}
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
assessCodeQuality(files) {
|
|
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
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
identifyDependencies(changes) {
|
|
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
|
+
}
|
|
785
|
+
}
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
evaluatePerformanceImpact(changes) {
|
|
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
|
+
}
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
checkSecurityImplications(changes) {
|
|
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
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
analyzeDocumentationChanges(changes) {
|
|
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
|
+
}
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
assessTestCoverage(changes) {
|
|
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
|
+
}
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
|
|
1151
|
+
evaluateArchitecturalChanges(changes) {
|
|
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
|
+
}
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1250
|
+
// Metrics method expected by tests
|
|
1251
|
+
getMetrics() {
|
|
1252
|
+
return {
|
|
1253
|
+
analysisCount: 0,
|
|
1254
|
+
averageTime: 0,
|
|
1255
|
+
successRate: 100,
|
|
1256
|
+
}
|
|
1257
|
+
}
|
|
505
1258
|
}
|