@andrebuzeli/git-mcp 5.1.0 → 5.2.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/dist/__tests__/setup.d.ts +10 -0
- package/dist/__tests__/setup.d.ts.map +1 -0
- package/dist/__tests__/setup.js +105 -0
- package/dist/__tests__/setup.js.map +1 -0
- package/dist/index.js +1 -1
- package/dist/server.d.ts +8 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +100 -4
- package/dist/server.js.map +1 -1
- package/dist/tools/git-archive.d.ts.map +1 -1
- package/dist/tools/git-archive.js +1 -3
- package/dist/tools/git-archive.js.map +1 -1
- package/dist/tools/git-automations.d.ts +257 -0
- package/dist/tools/git-automations.d.ts.map +1 -0
- package/dist/tools/git-automations.js +878 -0
- package/dist/tools/git-automations.js.map +1 -0
- package/dist/tools/git-bisect.d.ts +158 -0
- package/dist/tools/git-bisect.d.ts.map +1 -0
- package/dist/tools/git-bisect.js +571 -0
- package/dist/tools/git-bisect.js.map +1 -0
- package/dist/tools/git-changelog.d.ts +253 -0
- package/dist/tools/git-changelog.d.ts.map +1 -0
- package/dist/tools/git-changelog.js +728 -0
- package/dist/tools/git-changelog.js.map +1 -0
- package/dist/tools/git-cherry-pick.d.ts +150 -0
- package/dist/tools/git-cherry-pick.d.ts.map +1 -0
- package/dist/tools/git-cherry-pick.js +455 -0
- package/dist/tools/git-cherry-pick.js.map +1 -0
- package/dist/tools/git-dependencies.d.ts +233 -0
- package/dist/tools/git-dependencies.d.ts.map +1 -0
- package/dist/tools/git-dependencies.js +761 -0
- package/dist/tools/git-dependencies.js.map +1 -0
- package/dist/tools/git-hooks.d.ts +146 -0
- package/dist/tools/git-hooks.d.ts.map +1 -0
- package/dist/tools/git-hooks.js +634 -0
- package/dist/tools/git-hooks.js.map +1 -0
- package/dist/tools/git-stats-personal.d.ts +210 -0
- package/dist/tools/git-stats-personal.d.ts.map +1 -0
- package/dist/tools/git-stats-personal.js +718 -0
- package/dist/tools/git-stats-personal.js.map +1 -0
- package/dist/tools/git-tags.d.ts.map +1 -1
- package/dist/tools/git-tags.js +11 -3
- package/dist/tools/git-tags.js.map +1 -1
- package/dist/utils/parameter-validator.d.ts +9 -0
- package/dist/utils/parameter-validator.d.ts.map +1 -1
- package/dist/utils/parameter-validator.js +390 -14
- package/dist/utils/parameter-validator.js.map +1 -1
- package/package.json +81 -78
|
@@ -0,0 +1,728 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Git Changelog Tool
|
|
4
|
+
*
|
|
5
|
+
* Changelog generation tool providing comprehensive changelog operations.
|
|
6
|
+
* Supports conventional commits parsing, multiple formats, and semantic versioning.
|
|
7
|
+
*
|
|
8
|
+
* Operations: generate, update, preview, formats
|
|
9
|
+
*/
|
|
10
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
11
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
12
|
+
};
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.GitChangelogTool = void 0;
|
|
15
|
+
const git_command_executor_js_1 = require("../utils/git-command-executor.js");
|
|
16
|
+
const parameter_validator_js_1 = require("../utils/parameter-validator.js");
|
|
17
|
+
const operation_error_handler_js_1 = require("../utils/operation-error-handler.js");
|
|
18
|
+
const fs_1 = __importDefault(require("fs"));
|
|
19
|
+
const path_1 = __importDefault(require("path"));
|
|
20
|
+
class GitChangelogTool {
|
|
21
|
+
gitExecutor;
|
|
22
|
+
// Conventional commit types and their display names
|
|
23
|
+
commitTypes = {
|
|
24
|
+
feat: { emoji: '✨', name: 'Features', description: 'New features' },
|
|
25
|
+
fix: { emoji: '🐛', name: 'Bug Fixes', description: 'Bug fixes' },
|
|
26
|
+
docs: { emoji: '📚', name: 'Documentation', description: 'Documentation changes' },
|
|
27
|
+
style: { emoji: '💎', name: 'Styles', description: 'Code style changes' },
|
|
28
|
+
refactor: { emoji: '📦', name: 'Code Refactoring', description: 'Code refactoring' },
|
|
29
|
+
perf: { emoji: '⚡️', name: 'Performance Improvements', description: 'Performance improvements' },
|
|
30
|
+
test: { emoji: '✅', name: 'Tests', description: 'Test changes' },
|
|
31
|
+
build: { emoji: '🏗️', name: 'Build System', description: 'Build system changes' },
|
|
32
|
+
ci: { emoji: '👷', name: 'Continuous Integration', description: 'CI changes' },
|
|
33
|
+
chore: { emoji: '🔧', name: 'Chores', description: 'Chore changes' },
|
|
34
|
+
revert: { emoji: '⏪️', name: 'Reverts', description: 'Reverted changes' }
|
|
35
|
+
};
|
|
36
|
+
constructor() {
|
|
37
|
+
this.gitExecutor = new git_command_executor_js_1.GitCommandExecutor();
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Execute git-changelog operation
|
|
41
|
+
*/
|
|
42
|
+
async execute(params) {
|
|
43
|
+
const startTime = Date.now();
|
|
44
|
+
try {
|
|
45
|
+
// Validate basic parameters
|
|
46
|
+
const validation = parameter_validator_js_1.ParameterValidator.validateToolParams('git-changelog', params);
|
|
47
|
+
if (!validation.isValid) {
|
|
48
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('VALIDATION_ERROR', `Parameter validation failed: ${validation.errors.join(', ')}`, params.action, { validationErrors: validation.errors }, validation.suggestions);
|
|
49
|
+
}
|
|
50
|
+
// Validate operation-specific parameters
|
|
51
|
+
const operationValidation = parameter_validator_js_1.ParameterValidator.validateOperationParams('git-changelog', params.action, params);
|
|
52
|
+
if (!operationValidation.isValid) {
|
|
53
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('VALIDATION_ERROR', `Operation validation failed: ${operationValidation.errors.join(', ')}`, params.action, { validationErrors: operationValidation.errors }, operationValidation.suggestions);
|
|
54
|
+
}
|
|
55
|
+
// Route to appropriate handler
|
|
56
|
+
switch (params.action) {
|
|
57
|
+
case 'generate':
|
|
58
|
+
return await this.handleGenerate(params, startTime);
|
|
59
|
+
case 'update':
|
|
60
|
+
return await this.handleUpdate(params, startTime);
|
|
61
|
+
case 'preview':
|
|
62
|
+
return await this.handlePreview(params, startTime);
|
|
63
|
+
case 'formats':
|
|
64
|
+
return await this.handleFormats(params, startTime);
|
|
65
|
+
default:
|
|
66
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('INVALID_ACTION', `Unknown action: ${params.action}`, params.action, { supportedActions: ['generate', 'update', 'preview', 'formats'] }, ['Use one of the supported actions']);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
71
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('EXECUTION_ERROR', `Failed to execute ${params.action}: ${errorMessage}`, params.action, { error: errorMessage }, ['Check the error details and try again']);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Generate changelog
|
|
76
|
+
*/
|
|
77
|
+
async handleGenerate(params, startTime) {
|
|
78
|
+
try {
|
|
79
|
+
const commits = await this.getCommits(params);
|
|
80
|
+
const changelog = await this.generateChangelog(commits, params);
|
|
81
|
+
// Write to file if output path is specified
|
|
82
|
+
if (params.stdout) {
|
|
83
|
+
const outputPath = path_1.default.resolve(params.projectPath, params.stdout);
|
|
84
|
+
fs_1.default.writeFileSync(outputPath, changelog, 'utf8');
|
|
85
|
+
}
|
|
86
|
+
const executionTime = Date.now() - startTime;
|
|
87
|
+
return {
|
|
88
|
+
success: true,
|
|
89
|
+
data: {
|
|
90
|
+
changelog: {
|
|
91
|
+
content: changelog,
|
|
92
|
+
format: params.format || 'markdown',
|
|
93
|
+
outputPath: params.stdout,
|
|
94
|
+
version: params.version,
|
|
95
|
+
commits: commits.length,
|
|
96
|
+
stats: this.getChangelogStats(commits)
|
|
97
|
+
},
|
|
98
|
+
message: `Changelog generated successfully${params.stdout ? ` and saved to ${params.stdout}` : ''}`,
|
|
99
|
+
executionTime
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
105
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('GENERATE_ERROR', `Failed to generate changelog: ${errorMessage}`, params.action, { error: errorMessage }, ['Check repository state', 'Verify date/tag parameters']);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Update existing changelog
|
|
110
|
+
*/
|
|
111
|
+
async handleUpdate(params, startTime) {
|
|
112
|
+
try {
|
|
113
|
+
if (!params.stdout) {
|
|
114
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('MISSING_OUTPUT', 'Output path is required for update operation', params.action, { output: params.stdout }, ['Provide output parameter with changelog file path']);
|
|
115
|
+
}
|
|
116
|
+
const outputPath = path_1.default.resolve(params.projectPath, params.stdout);
|
|
117
|
+
// Read existing changelog
|
|
118
|
+
let existingContent = '';
|
|
119
|
+
if (fs_1.default.existsSync(outputPath)) {
|
|
120
|
+
existingContent = fs_1.default.readFileSync(outputPath, 'utf8');
|
|
121
|
+
}
|
|
122
|
+
// Generate new changelog
|
|
123
|
+
const commits = await this.getCommits(params);
|
|
124
|
+
const newChangelog = await this.generateChangelog(commits, params);
|
|
125
|
+
// Merge with existing content
|
|
126
|
+
const updatedContent = this.mergeChangelog(existingContent, newChangelog, params);
|
|
127
|
+
// Write updated content
|
|
128
|
+
fs_1.default.writeFileSync(outputPath, updatedContent, 'utf8');
|
|
129
|
+
const executionTime = Date.now() - startTime;
|
|
130
|
+
return {
|
|
131
|
+
success: true,
|
|
132
|
+
data: {
|
|
133
|
+
changelog: {
|
|
134
|
+
content: updatedContent,
|
|
135
|
+
format: params.format || 'markdown',
|
|
136
|
+
outputPath: params.stdout,
|
|
137
|
+
version: params.version,
|
|
138
|
+
commits: commits.length,
|
|
139
|
+
stats: this.getChangelogStats(commits),
|
|
140
|
+
updated: true
|
|
141
|
+
},
|
|
142
|
+
message: `Changelog updated successfully in ${params.stdout}`,
|
|
143
|
+
executionTime
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
149
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('UPDATE_ERROR', `Failed to update changelog: ${errorMessage}`, params.action, { error: errorMessage }, ['Check output file permissions', 'Verify changelog format']);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Preview changelog
|
|
154
|
+
*/
|
|
155
|
+
async handlePreview(params, startTime) {
|
|
156
|
+
try {
|
|
157
|
+
const commits = await this.getCommits(params);
|
|
158
|
+
const preview = await this.generateChangelog(commits, params);
|
|
159
|
+
const executionTime = Date.now() - startTime;
|
|
160
|
+
return {
|
|
161
|
+
success: true,
|
|
162
|
+
data: {
|
|
163
|
+
changelog: {
|
|
164
|
+
content: preview,
|
|
165
|
+
format: params.format || 'markdown',
|
|
166
|
+
version: params.version,
|
|
167
|
+
commits: commits.length,
|
|
168
|
+
stats: this.getChangelogStats(commits),
|
|
169
|
+
preview: true
|
|
170
|
+
},
|
|
171
|
+
message: 'Changelog preview generated successfully',
|
|
172
|
+
executionTime
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
catch (error) {
|
|
177
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
178
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('PREVIEW_ERROR', `Failed to preview changelog: ${errorMessage}`, params.action, { error: errorMessage }, ['Check repository state', 'Verify date/tag parameters']);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get available formats
|
|
183
|
+
*/
|
|
184
|
+
async handleFormats(params, startTime) {
|
|
185
|
+
try {
|
|
186
|
+
const formats = [
|
|
187
|
+
{
|
|
188
|
+
name: 'markdown',
|
|
189
|
+
description: 'Markdown format with headers and lists',
|
|
190
|
+
extension: '.md',
|
|
191
|
+
template: 'markdown'
|
|
192
|
+
},
|
|
193
|
+
{
|
|
194
|
+
name: 'json',
|
|
195
|
+
description: 'JSON format with structured data',
|
|
196
|
+
extension: '.json',
|
|
197
|
+
template: 'json'
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'html',
|
|
201
|
+
description: 'HTML format with styling',
|
|
202
|
+
extension: '.html',
|
|
203
|
+
template: 'html'
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
name: 'plain',
|
|
207
|
+
description: 'Plain text format',
|
|
208
|
+
extension: '.txt',
|
|
209
|
+
template: 'plain'
|
|
210
|
+
}
|
|
211
|
+
];
|
|
212
|
+
const executionTime = Date.now() - startTime;
|
|
213
|
+
return {
|
|
214
|
+
success: true,
|
|
215
|
+
data: {
|
|
216
|
+
formats,
|
|
217
|
+
message: 'Available changelog formats retrieved successfully',
|
|
218
|
+
executionTime
|
|
219
|
+
}
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
224
|
+
return operation_error_handler_js_1.OperationErrorHandler.createToolError('FORMATS_ERROR', `Failed to get formats: ${errorMessage}`, params.action, { error: errorMessage }, ['Check tool configuration']);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get commits for changelog
|
|
229
|
+
*/
|
|
230
|
+
async getCommits(params) {
|
|
231
|
+
let command = 'git log --pretty=format:"%H|%an|%ae|%ad|%s|%b" --date=iso';
|
|
232
|
+
// Add date/tag filters
|
|
233
|
+
if (params.fromTag && params.toTag) {
|
|
234
|
+
command += ` ${params.toTag}..${params.fromTag}`;
|
|
235
|
+
}
|
|
236
|
+
else if (params.fromTag) {
|
|
237
|
+
command += ` ${params.fromTag}..HEAD`;
|
|
238
|
+
}
|
|
239
|
+
else if (params.since && params.until) {
|
|
240
|
+
command += ` --since="${params.since}" --until="${params.until}"`;
|
|
241
|
+
}
|
|
242
|
+
else if (params.since) {
|
|
243
|
+
command += ` --since="${params.since}"`;
|
|
244
|
+
}
|
|
245
|
+
else if (params.until) {
|
|
246
|
+
command += ` --until="${params.until}"`;
|
|
247
|
+
}
|
|
248
|
+
// Add merge commit filter
|
|
249
|
+
if (!params.includeMergeCommits) {
|
|
250
|
+
command += ' --no-merges';
|
|
251
|
+
}
|
|
252
|
+
// Add commit limit
|
|
253
|
+
if (params.maxCommits) {
|
|
254
|
+
command += ` --max-count=${params.maxCommits}`;
|
|
255
|
+
}
|
|
256
|
+
const result = await this.gitExecutor.executeGitCommand(command, [], params.projectPath);
|
|
257
|
+
if (!result.success) {
|
|
258
|
+
return [];
|
|
259
|
+
}
|
|
260
|
+
const commits = [];
|
|
261
|
+
const lines = result.stdout.trim().split('\n').filter(line => line.trim());
|
|
262
|
+
for (const line of lines) {
|
|
263
|
+
const [hash, author, email, date, subject, body] = line.split('|', 6);
|
|
264
|
+
const commit = {
|
|
265
|
+
hash,
|
|
266
|
+
author,
|
|
267
|
+
email,
|
|
268
|
+
date: new Date(date),
|
|
269
|
+
subject,
|
|
270
|
+
body: body || '',
|
|
271
|
+
type: this.parseCommitType(subject),
|
|
272
|
+
scope: this.parseCommitScope(subject),
|
|
273
|
+
breaking: this.isBreakingChange(subject, body),
|
|
274
|
+
issues: this.extractIssues(subject, body)
|
|
275
|
+
};
|
|
276
|
+
// Apply filters
|
|
277
|
+
if (this.shouldIncludeCommit(commit, params)) {
|
|
278
|
+
commits.push(commit);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return commits;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Generate changelog content
|
|
285
|
+
*/
|
|
286
|
+
async generateChangelog(commits, params) {
|
|
287
|
+
const format = params.format || 'markdown';
|
|
288
|
+
switch (format) {
|
|
289
|
+
case 'markdown':
|
|
290
|
+
return this.generateMarkdownChangelog(commits, params);
|
|
291
|
+
case 'json':
|
|
292
|
+
return this.generateJsonChangelog(commits, params);
|
|
293
|
+
case 'html':
|
|
294
|
+
return this.generateHtmlChangelog(commits, params);
|
|
295
|
+
case 'plain':
|
|
296
|
+
return this.generatePlainChangelog(commits, params);
|
|
297
|
+
default:
|
|
298
|
+
return this.generateMarkdownChangelog(commits, params);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Generate Markdown changelog
|
|
303
|
+
*/
|
|
304
|
+
generateMarkdownChangelog(commits, params) {
|
|
305
|
+
let changelog = '';
|
|
306
|
+
// Header
|
|
307
|
+
const version = params.version || 'Unreleased';
|
|
308
|
+
const date = new Date().toISOString().split('T')[0];
|
|
309
|
+
changelog += `# Changelog\n\n`;
|
|
310
|
+
changelog += `## [${version}] - ${date}\n\n`;
|
|
311
|
+
// Breaking changes
|
|
312
|
+
const breakingChanges = commits.filter(c => c.breaking);
|
|
313
|
+
if (breakingChanges.length > 0) {
|
|
314
|
+
changelog += `### ⚠️ Breaking Changes\n\n`;
|
|
315
|
+
for (const commit of breakingChanges) {
|
|
316
|
+
changelog += `- ${commit.subject.replace(/^BREAKING CHANGE: /, '')}\n`;
|
|
317
|
+
}
|
|
318
|
+
changelog += '\n';
|
|
319
|
+
}
|
|
320
|
+
// Group by type
|
|
321
|
+
const grouped = this.groupCommitsByType(commits);
|
|
322
|
+
for (const [type, typeCommits] of Object.entries(grouped)) {
|
|
323
|
+
if (typeCommits.length === 0)
|
|
324
|
+
continue;
|
|
325
|
+
const typeInfo = this.commitTypes[type];
|
|
326
|
+
if (typeInfo) {
|
|
327
|
+
changelog += `### ${typeInfo.emoji} ${typeInfo.name}\n\n`;
|
|
328
|
+
for (const commit of typeCommits) {
|
|
329
|
+
const scope = commit.scope ? `**${commit.scope}**: ` : '';
|
|
330
|
+
const description = this.formatCommitDescription(commit.subject, type);
|
|
331
|
+
changelog += `- ${scope}${description}\n`;
|
|
332
|
+
}
|
|
333
|
+
changelog += '\n';
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Footer
|
|
337
|
+
if (params.includeIssues) {
|
|
338
|
+
const allIssues = commits.flatMap(c => c.issues);
|
|
339
|
+
if (allIssues.length > 0) {
|
|
340
|
+
changelog += `### 🔗 Issues & Pull Requests\n\n`;
|
|
341
|
+
const uniqueIssues = [...new Set(allIssues)];
|
|
342
|
+
for (const issue of uniqueIssues) {
|
|
343
|
+
changelog += `- ${issue}\n`;
|
|
344
|
+
}
|
|
345
|
+
changelog += '\n';
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
return changelog;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Generate JSON changelog
|
|
352
|
+
*/
|
|
353
|
+
generateJsonChangelog(commits, params) {
|
|
354
|
+
const changelog = {
|
|
355
|
+
version: params.version || 'Unreleased',
|
|
356
|
+
date: new Date().toISOString(),
|
|
357
|
+
changes: {
|
|
358
|
+
breaking: commits.filter(c => c.breaking),
|
|
359
|
+
features: commits.filter(c => c.type === 'feat'),
|
|
360
|
+
fixes: commits.filter(c => c.type === 'fix'),
|
|
361
|
+
other: commits.filter(c => !['feat', 'fix'].includes(c.type) && !c.breaking)
|
|
362
|
+
},
|
|
363
|
+
stats: this.getChangelogStats(commits),
|
|
364
|
+
commits: commits.map(c => ({
|
|
365
|
+
hash: c.hash,
|
|
366
|
+
type: c.type,
|
|
367
|
+
scope: c.scope,
|
|
368
|
+
subject: c.subject,
|
|
369
|
+
author: c.author,
|
|
370
|
+
date: c.date.toISOString(),
|
|
371
|
+
breaking: c.breaking,
|
|
372
|
+
issues: c.issues
|
|
373
|
+
}))
|
|
374
|
+
};
|
|
375
|
+
return JSON.stringify(changelog, null, 2);
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Generate HTML changelog
|
|
379
|
+
*/
|
|
380
|
+
generateHtmlChangelog(commits, params) {
|
|
381
|
+
let html = `
|
|
382
|
+
<!DOCTYPE html>
|
|
383
|
+
<html>
|
|
384
|
+
<head>
|
|
385
|
+
<title>Changelog</title>
|
|
386
|
+
<style>
|
|
387
|
+
body { font-family: Arial, sans-serif; max-width: 800px; margin: 0 auto; padding: 20px; }
|
|
388
|
+
.version { color: #0366d6; border-bottom: 2px solid #e1e4e8; padding-bottom: 10px; }
|
|
389
|
+
.breaking { background: #fff5f5; border-left: 4px solid #d73a49; padding: 10px; margin: 10px 0; }
|
|
390
|
+
.feature { color: #28a745; }
|
|
391
|
+
.fix { color: #d73a49; }
|
|
392
|
+
.other { color: #6a737d; }
|
|
393
|
+
.commit { margin: 5px 0; }
|
|
394
|
+
.scope { font-weight: bold; }
|
|
395
|
+
ul { list-style-type: none; padding-left: 0; }
|
|
396
|
+
</style>
|
|
397
|
+
</head>
|
|
398
|
+
<body>
|
|
399
|
+
<h1>Changelog</h1>
|
|
400
|
+
<div class="version">
|
|
401
|
+
<h2>${params.version || 'Unreleased'} - ${new Date().toISOString().split('T')[0]}</h2>
|
|
402
|
+
</div>
|
|
403
|
+
`;
|
|
404
|
+
// Breaking changes
|
|
405
|
+
const breakingChanges = commits.filter(c => c.breaking);
|
|
406
|
+
if (breakingChanges.length > 0) {
|
|
407
|
+
html += `<div class="breaking">
|
|
408
|
+
<h3>⚠️ Breaking Changes</h3>
|
|
409
|
+
<ul>`;
|
|
410
|
+
for (const commit of breakingChanges) {
|
|
411
|
+
html += `<li class="commit">${commit.subject.replace(/^BREAKING CHANGE: /, '')}</li>`;
|
|
412
|
+
}
|
|
413
|
+
html += `</ul></div>`;
|
|
414
|
+
}
|
|
415
|
+
// Group by type
|
|
416
|
+
const grouped = this.groupCommitsByType(commits);
|
|
417
|
+
for (const [type, typeCommits] of Object.entries(grouped)) {
|
|
418
|
+
if (typeCommits.length === 0)
|
|
419
|
+
continue;
|
|
420
|
+
const typeInfo = this.commitTypes[type];
|
|
421
|
+
if (typeInfo) {
|
|
422
|
+
html += `<h3>${typeInfo.emoji} ${typeInfo.name}</h3>
|
|
423
|
+
<ul>`;
|
|
424
|
+
for (const commit of typeCommits) {
|
|
425
|
+
const scope = commit.scope ? `<span class="scope">${commit.scope}:</span> ` : '';
|
|
426
|
+
const description = this.formatCommitDescription(commit.subject, type);
|
|
427
|
+
html += `<li class="commit ${type}">${scope}${description}</li>`;
|
|
428
|
+
}
|
|
429
|
+
html += `</ul>`;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
html += `</body></html>`;
|
|
433
|
+
return html;
|
|
434
|
+
}
|
|
435
|
+
/**
|
|
436
|
+
* Generate plain text changelog
|
|
437
|
+
*/
|
|
438
|
+
generatePlainChangelog(commits, params) {
|
|
439
|
+
let changelog = `CHANGELOG\n`;
|
|
440
|
+
changelog += `${'='.repeat(50)}\n\n`;
|
|
441
|
+
changelog += `${params.version || 'Unreleased'} - ${new Date().toISOString().split('T')[0]}\n\n`;
|
|
442
|
+
// Group by type
|
|
443
|
+
const grouped = this.groupCommitsByType(commits);
|
|
444
|
+
for (const [type, typeCommits] of Object.entries(grouped)) {
|
|
445
|
+
if (typeCommits.length === 0)
|
|
446
|
+
continue;
|
|
447
|
+
const typeInfo = this.commitTypes[type];
|
|
448
|
+
if (typeInfo) {
|
|
449
|
+
changelog += `${typeInfo.name.toUpperCase()}\n`;
|
|
450
|
+
changelog += `${'-'.repeat(typeInfo.name.length)}\n`;
|
|
451
|
+
for (const commit of typeCommits) {
|
|
452
|
+
const scope = commit.scope ? `${commit.scope}: ` : '';
|
|
453
|
+
const description = this.formatCommitDescription(commit.subject, type);
|
|
454
|
+
changelog += ` - ${scope}${description}\n`;
|
|
455
|
+
}
|
|
456
|
+
changelog += '\n';
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
return changelog;
|
|
460
|
+
}
|
|
461
|
+
/**
|
|
462
|
+
* Parse commit type from subject
|
|
463
|
+
*/
|
|
464
|
+
parseCommitType(subject) {
|
|
465
|
+
const match = subject.match(/^(\w+)(?:\(.+\))?:/);
|
|
466
|
+
return match ? match[1].toLowerCase() : 'other';
|
|
467
|
+
}
|
|
468
|
+
/**
|
|
469
|
+
* Parse commit scope from subject
|
|
470
|
+
*/
|
|
471
|
+
parseCommitScope(subject) {
|
|
472
|
+
const match = subject.match(/^\w+\((.+)\):/);
|
|
473
|
+
return match ? match[1] : null;
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Check if commit is a breaking change
|
|
477
|
+
*/
|
|
478
|
+
isBreakingChange(subject, body) {
|
|
479
|
+
return subject.includes('BREAKING CHANGE:') ||
|
|
480
|
+
subject.includes('!') ||
|
|
481
|
+
body.includes('BREAKING CHANGE:');
|
|
482
|
+
}
|
|
483
|
+
/**
|
|
484
|
+
* Extract issues and PRs from commit
|
|
485
|
+
*/
|
|
486
|
+
extractIssues(subject, body) {
|
|
487
|
+
const issues = [];
|
|
488
|
+
const text = `${subject} ${body}`;
|
|
489
|
+
// GitHub issues/PRs
|
|
490
|
+
const githubMatches = text.match(/#\d+/g);
|
|
491
|
+
if (githubMatches) {
|
|
492
|
+
issues.push(...githubMatches);
|
|
493
|
+
}
|
|
494
|
+
// Jira issues
|
|
495
|
+
const jiraMatches = text.match(/[A-Z]+-\d+/g);
|
|
496
|
+
if (jiraMatches) {
|
|
497
|
+
issues.push(...jiraMatches);
|
|
498
|
+
}
|
|
499
|
+
return issues;
|
|
500
|
+
}
|
|
501
|
+
/**
|
|
502
|
+
* Check if commit should be included
|
|
503
|
+
*/
|
|
504
|
+
shouldIncludeCommit(commit, params) {
|
|
505
|
+
// Filter by type
|
|
506
|
+
if (params.includeTypes && !params.includeTypes.includes(commit.type)) {
|
|
507
|
+
return false;
|
|
508
|
+
}
|
|
509
|
+
if (params.excludeTypes && params.excludeTypes.includes(commit.type)) {
|
|
510
|
+
return false;
|
|
511
|
+
}
|
|
512
|
+
// Filter by scope
|
|
513
|
+
if (params.includeScopes && commit.scope && !params.includeScopes.includes(commit.scope)) {
|
|
514
|
+
return false;
|
|
515
|
+
}
|
|
516
|
+
if (params.excludeScopes && commit.scope && params.excludeScopes.includes(commit.scope)) {
|
|
517
|
+
return false;
|
|
518
|
+
}
|
|
519
|
+
// Filter reverts
|
|
520
|
+
if (!params.includeReverts && commit.type === 'revert') {
|
|
521
|
+
return false;
|
|
522
|
+
}
|
|
523
|
+
return true;
|
|
524
|
+
}
|
|
525
|
+
/**
|
|
526
|
+
* Group commits by type
|
|
527
|
+
*/
|
|
528
|
+
groupCommitsByType(commits) {
|
|
529
|
+
const grouped = {};
|
|
530
|
+
for (const commit of commits) {
|
|
531
|
+
if (!grouped[commit.type]) {
|
|
532
|
+
grouped[commit.type] = [];
|
|
533
|
+
}
|
|
534
|
+
grouped[commit.type].push(commit);
|
|
535
|
+
}
|
|
536
|
+
return grouped;
|
|
537
|
+
}
|
|
538
|
+
/**
|
|
539
|
+
* Format commit description
|
|
540
|
+
*/
|
|
541
|
+
formatCommitDescription(subject, type) {
|
|
542
|
+
// Remove type and scope from subject
|
|
543
|
+
let description = subject.replace(/^\w+(?:\(.+\))?: ?/, '');
|
|
544
|
+
// Remove breaking change prefix
|
|
545
|
+
description = description.replace(/^BREAKING CHANGE: ?/, '');
|
|
546
|
+
return description;
|
|
547
|
+
}
|
|
548
|
+
/**
|
|
549
|
+
* Get changelog statistics
|
|
550
|
+
*/
|
|
551
|
+
getChangelogStats(commits) {
|
|
552
|
+
const stats = {
|
|
553
|
+
totalCommits: commits.length,
|
|
554
|
+
breakingChanges: commits.filter(c => c.breaking).length,
|
|
555
|
+
features: commits.filter(c => c.type === 'feat').length,
|
|
556
|
+
fixes: commits.filter(c => c.type === 'fix').length,
|
|
557
|
+
types: {},
|
|
558
|
+
scopes: {},
|
|
559
|
+
authors: {},
|
|
560
|
+
issues: []
|
|
561
|
+
};
|
|
562
|
+
for (const commit of commits) {
|
|
563
|
+
// Count types
|
|
564
|
+
stats.types[commit.type] = (stats.types[commit.type] || 0) + 1;
|
|
565
|
+
// Count scopes
|
|
566
|
+
if (commit.scope) {
|
|
567
|
+
stats.scopes[commit.scope] = (stats.scopes[commit.scope] || 0) + 1;
|
|
568
|
+
}
|
|
569
|
+
// Count authors
|
|
570
|
+
stats.authors[commit.author] = (stats.authors[commit.author] || 0) + 1;
|
|
571
|
+
// Collect issues
|
|
572
|
+
stats.issues.push(...commit.issues);
|
|
573
|
+
}
|
|
574
|
+
// Remove duplicates from issues
|
|
575
|
+
stats.issues = [...new Set(stats.issues)];
|
|
576
|
+
return stats;
|
|
577
|
+
}
|
|
578
|
+
/**
|
|
579
|
+
* Merge new changelog with existing content
|
|
580
|
+
*/
|
|
581
|
+
mergeChangelog(existing, newContent, params) {
|
|
582
|
+
if (!existing.trim()) {
|
|
583
|
+
return newContent;
|
|
584
|
+
}
|
|
585
|
+
// Simple merge: prepend new content
|
|
586
|
+
return `${newContent}\n\n---\n\n${existing}`;
|
|
587
|
+
}
|
|
588
|
+
/**
|
|
589
|
+
* Get tool schema for MCP registration
|
|
590
|
+
*/
|
|
591
|
+
static getToolSchema() {
|
|
592
|
+
return {
|
|
593
|
+
name: 'git-changelog',
|
|
594
|
+
description: 'Automatic changelog generation tool. Creates changelogs from conventional commits with multiple output formats and filtering options.',
|
|
595
|
+
inputSchema: {
|
|
596
|
+
type: 'object',
|
|
597
|
+
properties: {
|
|
598
|
+
"action": {
|
|
599
|
+
"type": "string",
|
|
600
|
+
"enum": [
|
|
601
|
+
"generate",
|
|
602
|
+
"update",
|
|
603
|
+
"preview",
|
|
604
|
+
"formats"
|
|
605
|
+
],
|
|
606
|
+
"description": "The operation to perform"
|
|
607
|
+
},
|
|
608
|
+
"projectPath": {
|
|
609
|
+
"type": "string",
|
|
610
|
+
"description": "Path to the Git repository"
|
|
611
|
+
},
|
|
612
|
+
"format": {
|
|
613
|
+
"type": "string",
|
|
614
|
+
"description": "format parameter"
|
|
615
|
+
},
|
|
616
|
+
"output": {
|
|
617
|
+
"type": "string",
|
|
618
|
+
"description": "output parameter"
|
|
619
|
+
},
|
|
620
|
+
"fromTag": {
|
|
621
|
+
"type": "string",
|
|
622
|
+
"description": "fromTag parameter"
|
|
623
|
+
},
|
|
624
|
+
"toTag": {
|
|
625
|
+
"type": "string",
|
|
626
|
+
"description": "toTag parameter"
|
|
627
|
+
},
|
|
628
|
+
"since": {
|
|
629
|
+
"type": "string",
|
|
630
|
+
"description": "since parameter"
|
|
631
|
+
},
|
|
632
|
+
"until": {
|
|
633
|
+
"type": "string",
|
|
634
|
+
"description": "until parameter"
|
|
635
|
+
},
|
|
636
|
+
"groupBy": {
|
|
637
|
+
"type": "string",
|
|
638
|
+
"description": "groupBy parameter"
|
|
639
|
+
},
|
|
640
|
+
"includeTypes": {
|
|
641
|
+
"type": "array",
|
|
642
|
+
"items": {
|
|
643
|
+
"type": "string"
|
|
644
|
+
},
|
|
645
|
+
"description": "includeTypes parameter"
|
|
646
|
+
},
|
|
647
|
+
"excludeTypes": {
|
|
648
|
+
"type": "array",
|
|
649
|
+
"items": {
|
|
650
|
+
"type": "string"
|
|
651
|
+
},
|
|
652
|
+
"description": "excludeTypes parameter"
|
|
653
|
+
},
|
|
654
|
+
"includeScopes": {
|
|
655
|
+
"type": "array",
|
|
656
|
+
"items": {
|
|
657
|
+
"type": "string"
|
|
658
|
+
},
|
|
659
|
+
"description": "includeScopes parameter"
|
|
660
|
+
},
|
|
661
|
+
"excludeScopes": {
|
|
662
|
+
"type": "array",
|
|
663
|
+
"items": {
|
|
664
|
+
"type": "string"
|
|
665
|
+
},
|
|
666
|
+
"description": "excludeScopes parameter"
|
|
667
|
+
},
|
|
668
|
+
"includeBreakingChanges": {
|
|
669
|
+
"type": "string",
|
|
670
|
+
"description": "includeBreakingChanges parameter"
|
|
671
|
+
},
|
|
672
|
+
"includeMergeCommits": {
|
|
673
|
+
"type": "string",
|
|
674
|
+
"description": "includeMergeCommits parameter"
|
|
675
|
+
},
|
|
676
|
+
"includeReverts": {
|
|
677
|
+
"type": "string",
|
|
678
|
+
"description": "includeReverts parameter"
|
|
679
|
+
},
|
|
680
|
+
"includeIssues": {
|
|
681
|
+
"type": "string",
|
|
682
|
+
"description": "includeIssues parameter"
|
|
683
|
+
},
|
|
684
|
+
"template": {
|
|
685
|
+
"type": "string",
|
|
686
|
+
"description": "template parameter"
|
|
687
|
+
},
|
|
688
|
+
"headerTemplate": {
|
|
689
|
+
"type": "string",
|
|
690
|
+
"description": "headerTemplate parameter"
|
|
691
|
+
},
|
|
692
|
+
"sectionTemplate": {
|
|
693
|
+
"type": "string",
|
|
694
|
+
"description": "sectionTemplate parameter"
|
|
695
|
+
},
|
|
696
|
+
"commitTemplate": {
|
|
697
|
+
"type": "string",
|
|
698
|
+
"description": "commitTemplate parameter"
|
|
699
|
+
},
|
|
700
|
+
"version": {
|
|
701
|
+
"type": "string",
|
|
702
|
+
"description": "version parameter"
|
|
703
|
+
},
|
|
704
|
+
"unreleased": {
|
|
705
|
+
"type": "string",
|
|
706
|
+
"description": "unreleased parameter"
|
|
707
|
+
},
|
|
708
|
+
"showStats": {
|
|
709
|
+
"type": "string",
|
|
710
|
+
"description": "showStats parameter"
|
|
711
|
+
},
|
|
712
|
+
"maxCommits": {
|
|
713
|
+
"type": "string",
|
|
714
|
+
"description": "maxCommits parameter"
|
|
715
|
+
}
|
|
716
|
+
},
|
|
717
|
+
required: ['action', 'projectPath'],
|
|
718
|
+
additionalProperties: false
|
|
719
|
+
},
|
|
720
|
+
errorCodes: {
|
|
721
|
+
'VALIDATION_ERROR': 'Parameter validation failed',
|
|
722
|
+
'EXECUTION_ERROR': 'Tool execution failed'
|
|
723
|
+
}
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
exports.GitChangelogTool = GitChangelogTool;
|
|
728
|
+
//# sourceMappingURL=git-changelog.js.map
|