git-repo-analyzer-test 1.0.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/.github/copilot-instructions.md +108 -0
- package/.idea/aianalyzer.iml +9 -0
- package/.idea/misc.xml +6 -0
- package/.idea/modules.xml +8 -0
- package/.idea/vcs.xml +6 -0
- package/API_REFERENCE.md +244 -0
- package/ENHANCEMENTS.md +282 -0
- package/README.md +179 -0
- package/USAGE.md +189 -0
- package/analysis.txt +0 -0
- package/bin/cli.js +135 -0
- package/docs/SONARCLOUD_ANALYSIS_COVERED.md +144 -0
- package/docs/SonarCloud_Presentation_Points.md +81 -0
- package/docs/UI_IMPROVEMENTS.md +117 -0
- package/package-lock_cmd.json +542 -0
- package/package.json +44 -0
- package/package_command.json +16 -0
- package/public/analysis-options.json +31 -0
- package/public/images/README.txt +2 -0
- package/public/images/rws-logo.png +0 -0
- package/public/index.html +2433 -0
- package/repositories.example.txt +17 -0
- package/sample-repos.txt +20 -0
- package/src/analyzers/accessibility.js +47 -0
- package/src/analyzers/cicd-enhanced.js +113 -0
- package/src/analyzers/codeReview-enhanced.js +599 -0
- package/src/analyzers/codeReview-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/codeReview.js +171 -0
- package/src/analyzers/codeReview.js:Zone.Identifier +3 -0
- package/src/analyzers/documentation-enhanced.js +137 -0
- package/src/analyzers/performance-enhanced.js +747 -0
- package/src/analyzers/performance-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/performance.js +211 -0
- package/src/analyzers/performance.js:Zone.Identifier +3 -0
- package/src/analyzers/performance_cmd.js +216 -0
- package/src/analyzers/quality-enhanced.js +386 -0
- package/src/analyzers/quality-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/quality.js +92 -0
- package/src/analyzers/quality.js:Zone.Identifier +3 -0
- package/src/analyzers/security-enhanced.js +512 -0
- package/src/analyzers/security-enhanced.js:Zone.Identifier +3 -0
- package/src/analyzers/snyk-ai.js:Zone.Identifier +3 -0
- package/src/analyzers/sonarcloud.js +928 -0
- package/src/analyzers/vulnerability.js +185 -0
- package/src/analyzers/vulnerability.js:Zone.Identifier +3 -0
- package/src/cli.js:Zone.Identifier +3 -0
- package/src/config.js +43 -0
- package/src/core/analyzerEngine.js +68 -0
- package/src/core/reportGenerator.js +21 -0
- package/src/gemini.js +321 -0
- package/src/github/client.js +124 -0
- package/src/github/client.js:Zone.Identifier +3 -0
- package/src/index.js +93 -0
- package/src/index_cmd.js +130 -0
- package/src/openai.js +297 -0
- package/src/report/generator.js +459 -0
- package/src/report/generator_cmd.js +459 -0
- package/src/report/pdf-generator.js +387 -0
- package/src/report/pdf-generator.js:Zone.Identifier +3 -0
- package/src/server.js +431 -0
- package/src/server.js:Zone.Identifier +3 -0
- package/src/server_cmd.js +434 -0
- package/src/sonarcloud/client.js +365 -0
- package/src/sonarcloud/scanner.js +171 -0
- package/src.zip +0 -0
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
export class CodeReviewAnalyzer {
|
|
2
|
+
constructor(githubClient) {
|
|
3
|
+
this.client = githubClient;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Analyze code review practices and metrics
|
|
8
|
+
*/
|
|
9
|
+
async analyzeCodeReview(owner, repo) {
|
|
10
|
+
try {
|
|
11
|
+
const [pullRequests, commits, contributors] = await Promise.all([
|
|
12
|
+
this.client.getPullRequests(owner, repo, { state: 'all' }),
|
|
13
|
+
this.client.getCommits(owner, repo, { perPage: 100 }),
|
|
14
|
+
this.client.getContributors(owner, repo),
|
|
15
|
+
]);
|
|
16
|
+
|
|
17
|
+
const reviewMetrics = this.calculateReviewMetrics(pullRequests);
|
|
18
|
+
const commitMetrics = this.analyzeCommitPatterns(commits);
|
|
19
|
+
const collaborationScore = this.calculateCollaborationScore(
|
|
20
|
+
pullRequests,
|
|
21
|
+
contributors
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
score: collaborationScore,
|
|
26
|
+
reviewMetrics,
|
|
27
|
+
commitMetrics,
|
|
28
|
+
contributors: contributors.length,
|
|
29
|
+
recommendations: this.generateCodeReviewRecommendations(reviewMetrics),
|
|
30
|
+
};
|
|
31
|
+
} catch (error) {
|
|
32
|
+
throw new Error(`Code review analysis failed: ${error.message}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Calculate code review metrics
|
|
38
|
+
*/
|
|
39
|
+
calculateReviewMetrics(pullRequests) {
|
|
40
|
+
const totalPRs = pullRequests.length;
|
|
41
|
+
const closedPRs = pullRequests.filter((pr) => pr.state === 'closed').length;
|
|
42
|
+
const openPRs = pullRequests.filter((pr) => pr.state === 'open').length;
|
|
43
|
+
const mergedPRs = pullRequests.filter((pr) => pr.merged_at).length;
|
|
44
|
+
|
|
45
|
+
let totalReviewTime = 0;
|
|
46
|
+
let reviewedPRs = 0;
|
|
47
|
+
|
|
48
|
+
pullRequests.forEach((pr) => {
|
|
49
|
+
if (pr.merged_at && pr.created_at) {
|
|
50
|
+
const timeToMerge =
|
|
51
|
+
new Date(pr.merged_at) - new Date(pr.created_at);
|
|
52
|
+
totalReviewTime += timeToMerge;
|
|
53
|
+
reviewedPRs++;
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
const avgReviewTimeHours =
|
|
58
|
+
reviewedPRs > 0 ? Math.round(totalReviewTime / reviewedPRs / (1000 * 60 * 60)) : 0;
|
|
59
|
+
|
|
60
|
+
return {
|
|
61
|
+
totalPullRequests: totalPRs,
|
|
62
|
+
closedPullRequests: closedPRs,
|
|
63
|
+
openPullRequests: openPRs,
|
|
64
|
+
mergedPullRequests: mergedPRs,
|
|
65
|
+
averageReviewTimeHours: avgReviewTimeHours,
|
|
66
|
+
prClosureRate:
|
|
67
|
+
totalPRs > 0 ? Math.round((closedPRs / totalPRs) * 100) : 0,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Analyze commit patterns
|
|
73
|
+
*/
|
|
74
|
+
analyzeCommitPatterns(commits) {
|
|
75
|
+
const totalCommits = commits.length;
|
|
76
|
+
const authorSet = new Set();
|
|
77
|
+
let totalChanges = 0;
|
|
78
|
+
|
|
79
|
+
commits.forEach((commit) => {
|
|
80
|
+
if (commit.author) {
|
|
81
|
+
authorSet.add(commit.author.login);
|
|
82
|
+
}
|
|
83
|
+
if (commit.commit?.message) {
|
|
84
|
+
// Simple heuristic for change size based on message
|
|
85
|
+
totalChanges += commit.commit.message.length;
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
totalCommits,
|
|
91
|
+
uniqueAuthors: authorSet.size,
|
|
92
|
+
averageCommitSize: totalCommits > 0 ? Math.round(totalChanges / totalCommits) : 0,
|
|
93
|
+
commitFrequency: totalCommits > 0 ? 'Active' : 'Inactive',
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Calculate collaboration score (0-100)
|
|
99
|
+
*/
|
|
100
|
+
calculateCollaborationScore(pullRequests, contributors) {
|
|
101
|
+
let score = 50; // Base score
|
|
102
|
+
|
|
103
|
+
// Multiple contributors (0-20)
|
|
104
|
+
score += Math.min(contributors.length * 2, 20);
|
|
105
|
+
|
|
106
|
+
// PR activity (0-20)
|
|
107
|
+
const totalPRs = pullRequests.length;
|
|
108
|
+
score += Math.min(totalPRs / 5, 20);
|
|
109
|
+
|
|
110
|
+
// PR closure rate (0-20)
|
|
111
|
+
const closedPRs = pullRequests.filter((pr) => pr.state === 'closed').length;
|
|
112
|
+
const closureRate = totalPRs > 0 ? (closedPRs / totalPRs) * 100 : 0;
|
|
113
|
+
if (closureRate > 80) {
|
|
114
|
+
score += 20;
|
|
115
|
+
} else if (closureRate > 50) {
|
|
116
|
+
score += 15;
|
|
117
|
+
} else if (closureRate > 20) {
|
|
118
|
+
score += 10;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Recent PR activity (0-20)
|
|
122
|
+
const recentPRs = pullRequests.filter((pr) => {
|
|
123
|
+
const prDate = new Date(pr.created_at);
|
|
124
|
+
const daysSince = Math.floor((new Date() - prDate) / (1000 * 60 * 60 * 24));
|
|
125
|
+
return daysSince < 30;
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
if (recentPRs.length > 0) {
|
|
129
|
+
score += Math.min(recentPRs.length * 5, 20);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return Math.min(score, 100);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Generate code review recommendations
|
|
137
|
+
*/
|
|
138
|
+
generateCodeReviewRecommendations(reviewMetrics) {
|
|
139
|
+
const recommendations = [];
|
|
140
|
+
|
|
141
|
+
if (reviewMetrics.openPullRequests > 10) {
|
|
142
|
+
recommendations.push(
|
|
143
|
+
'High number of open PRs - focus on code review velocity'
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (reviewMetrics.averageReviewTimeHours > 48) {
|
|
148
|
+
recommendations.push(
|
|
149
|
+
'Review time is slow - consider implementing faster review processes'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (reviewMetrics.prClosureRate < 50) {
|
|
154
|
+
recommendations.push('Low PR closure rate - address stale pull requests');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (reviewMetrics.totalPullRequests === 0) {
|
|
158
|
+
recommendations.push(
|
|
159
|
+
'No pull requests found - encourage using PR workflow'
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (recommendations.length === 0) {
|
|
164
|
+
recommendations.push('Code review process looks healthy');
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return recommendations;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export default CodeReviewAnalyzer;
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Documentation & repo health analyzer
|
|
3
|
+
* Checks for README, LICENSE, CONTRIBUTING, SECURITY.md, CODE_OF_CONDUCT, docs, .github support
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class DocumentationAnalyzer {
|
|
7
|
+
constructor(githubClient) {
|
|
8
|
+
this.client = githubClient;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async analyzeDocumentation(owner, repo) {
|
|
12
|
+
const root = await this.client.getRepositoryContents(owner, repo, '');
|
|
13
|
+
const fileNames = (root || []).map((f) => (f.name || '').toLowerCase());
|
|
14
|
+
|
|
15
|
+
const hasReadme = fileNames.some(
|
|
16
|
+
(n) => n === 'readme' || n === 'readme.md' || n === 'readme.txt' || n.startsWith('readme.')
|
|
17
|
+
);
|
|
18
|
+
const hasLicense = fileNames.some(
|
|
19
|
+
(n) => n === 'license' || n === 'license.md' || n === 'license.txt' || n.startsWith('license.')
|
|
20
|
+
);
|
|
21
|
+
const hasContributing = fileNames.some(
|
|
22
|
+
(n) =>
|
|
23
|
+
n === 'contributing' ||
|
|
24
|
+
n === 'contributing.md' ||
|
|
25
|
+
n === 'contributing.txt' ||
|
|
26
|
+
n.startsWith('contributing.')
|
|
27
|
+
);
|
|
28
|
+
const hasSecurity = fileNames.some(
|
|
29
|
+
(n) => n === 'security.md' || n === 'security.txt' || n.startsWith('security.')
|
|
30
|
+
);
|
|
31
|
+
const hasCodeOfConduct = fileNames.some(
|
|
32
|
+
(n) =>
|
|
33
|
+
n === 'code_of_conduct.md' ||
|
|
34
|
+
n === 'code_of_conduct.txt' ||
|
|
35
|
+
n === 'codeofconduct' ||
|
|
36
|
+
n.startsWith('code_of_conduct.')
|
|
37
|
+
);
|
|
38
|
+
const hasDocs = fileNames.some((n) => n === 'docs');
|
|
39
|
+
|
|
40
|
+
let githubFiles = [];
|
|
41
|
+
try {
|
|
42
|
+
githubFiles = await this.client.getRepositoryContents(owner, repo, '.github');
|
|
43
|
+
if (!Array.isArray(githubFiles)) githubFiles = [githubFiles];
|
|
44
|
+
} catch {
|
|
45
|
+
// no .github folder
|
|
46
|
+
}
|
|
47
|
+
const githubNames = githubFiles.map((f) => (f.name || '').toLowerCase());
|
|
48
|
+
const hasWorkflows = githubNames.some((n) => n === 'workflows');
|
|
49
|
+
const hasIssueTemplate =
|
|
50
|
+
githubNames.some((n) => n === 'issue_template.md' || n === 'issue_template') ||
|
|
51
|
+
githubNames.some((n) => n === 'issues');
|
|
52
|
+
const hasPrTemplate =
|
|
53
|
+
githubNames.some((n) => n === 'pull_request_template.md' || n === 'pull_request_template');
|
|
54
|
+
|
|
55
|
+
const checks = {
|
|
56
|
+
readme: hasReadme,
|
|
57
|
+
license: hasLicense,
|
|
58
|
+
contributing: hasContributing,
|
|
59
|
+
security: hasSecurity,
|
|
60
|
+
codeOfConduct: hasCodeOfConduct,
|
|
61
|
+
docsFolder: hasDocs,
|
|
62
|
+
workflows: hasWorkflows,
|
|
63
|
+
issueTemplate: hasIssueTemplate,
|
|
64
|
+
prTemplate: hasPrTemplate,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
const score = this.calculateScore(checks);
|
|
68
|
+
const rating = this.getRating(score);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
score,
|
|
72
|
+
rating,
|
|
73
|
+
checks,
|
|
74
|
+
recommendations: this.getRecommendations(checks),
|
|
75
|
+
summary: this.getSummary(checks),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
calculateScore(checks) {
|
|
80
|
+
let s = 0;
|
|
81
|
+
if (checks.readme) s += 2;
|
|
82
|
+
if (checks.license) s += 2;
|
|
83
|
+
if (checks.contributing) s += 1.5;
|
|
84
|
+
if (checks.security) s += 1.5;
|
|
85
|
+
if (checks.codeOfConduct) s += 0.5;
|
|
86
|
+
if (checks.docsFolder) s += 1;
|
|
87
|
+
if (checks.workflows) s += 0.5;
|
|
88
|
+
if (checks.issueTemplate) s += 0.5;
|
|
89
|
+
if (checks.prTemplate) s += 0.5;
|
|
90
|
+
return Math.min(10, Math.round(s * 10) / 10);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
getRating(score) {
|
|
94
|
+
if (score >= 9) return 'A+';
|
|
95
|
+
if (score >= 8) return 'A';
|
|
96
|
+
if (score >= 7) return 'B+';
|
|
97
|
+
if (score >= 6) return 'B';
|
|
98
|
+
if (score >= 5) return 'C+';
|
|
99
|
+
if (score >= 4) return 'C';
|
|
100
|
+
return 'F';
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
getSummary(checks) {
|
|
104
|
+
const present = [];
|
|
105
|
+
const missing = [];
|
|
106
|
+
[
|
|
107
|
+
['readme', 'README'],
|
|
108
|
+
['license', 'LICENSE'],
|
|
109
|
+
['contributing', 'CONTRIBUTING'],
|
|
110
|
+
['security', 'SECURITY.md'],
|
|
111
|
+
['codeOfConduct', 'Code of Conduct'],
|
|
112
|
+
['docsFolder', 'docs/'],
|
|
113
|
+
['workflows', 'GitHub Actions'],
|
|
114
|
+
['issueTemplate', 'Issue template'],
|
|
115
|
+
['prTemplate', 'PR template'],
|
|
116
|
+
].forEach(([key, label]) => {
|
|
117
|
+
if (checks[key]) present.push(label);
|
|
118
|
+
else missing.push(label);
|
|
119
|
+
});
|
|
120
|
+
return { present, missing };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
getRecommendations(checks) {
|
|
124
|
+
const recs = [];
|
|
125
|
+
if (!checks.readme) recs.push({ priority: 'HIGH', title: 'Add a README', action: 'Create README.md with project overview, setup, and usage.' });
|
|
126
|
+
if (!checks.license) recs.push({ priority: 'HIGH', title: 'Add a LICENSE', action: 'Add a LICENSE file to clarify reuse and distribution terms.' });
|
|
127
|
+
if (!checks.contributing) recs.push({ priority: 'MEDIUM', title: 'Add CONTRIBUTING', action: 'Add CONTRIBUTING.md to guide contributors.' });
|
|
128
|
+
if (!checks.security) recs.push({ priority: 'MEDIUM', title: 'Add SECURITY.md', action: 'Document how to report security issues.' });
|
|
129
|
+
if (!checks.codeOfConduct) recs.push({ priority: 'LOW', title: 'Add Code of Conduct', action: 'Consider adding CODE_OF_CONDUCT.md for community guidelines.' });
|
|
130
|
+
if (!checks.docsFolder && checks.readme) recs.push({ priority: 'LOW', title: 'Add docs/ folder', action: 'Add a docs/ directory for detailed documentation.' });
|
|
131
|
+
if (!checks.issueTemplate) recs.push({ priority: 'LOW', title: 'Issue template', action: 'Add .github/ISSUE_TEMPLATE to standardize issue reports.' });
|
|
132
|
+
if (!checks.prTemplate) recs.push({ priority: 'LOW', title: 'PR template', action: 'Add .github/PULL_REQUEST_TEMPLATE.md for consistent PRs.' });
|
|
133
|
+
return recs;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export default DocumentationAnalyzer;
|