@andrebuzeli/git-mcp 5.4.8 → 5.4.9

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.
@@ -0,0 +1,820 @@
1
+ "use strict";
2
+ /**
3
+ * Git History Tool
4
+ *
5
+ * Comprehensive change tracking tool that records all repository modifications
6
+ * with detailed timestamps, storing history locally in JSON and syncing to remote providers.
7
+ *
8
+ * Operations: log, track, sync, export, auto
9
+ */
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.GitHistoryTool = void 0;
45
+ const git_command_executor_js_1 = require("../utils/git-command-executor.js");
46
+ const parameter_validator_js_1 = require("../utils/parameter-validator.js");
47
+ const operation_error_handler_js_1 = require("../utils/operation-error-handler.js");
48
+ const provider_operation_handler_js_1 = require("../providers/provider-operation-handler.js");
49
+ const fs = __importStar(require("fs"));
50
+ const path = __importStar(require("path"));
51
+ class GitHistoryTool {
52
+ gitExecutor;
53
+ providerHandler;
54
+ historyPath;
55
+ configPath;
56
+ constructor(providerConfig) {
57
+ this.gitExecutor = new git_command_executor_js_1.GitCommandExecutor();
58
+ this.historyPath = path.join('.git-history', 'history.json');
59
+ this.configPath = path.join('.git-history', 'config.json');
60
+ if (providerConfig) {
61
+ this.providerHandler = new provider_operation_handler_js_1.ProviderOperationHandler(providerConfig);
62
+ }
63
+ this.ensureHistoryDirectory();
64
+ }
65
+ /**
66
+ * Execute git-history operation
67
+ */
68
+ async execute(params) {
69
+ const startTime = Date.now();
70
+ try {
71
+ // Validate basic parameters
72
+ const validation = parameter_validator_js_1.ParameterValidator.validateToolParams('git-history', params);
73
+ if (!validation.isValid) {
74
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('VALIDATION_ERROR', `Parameter validation failed: ${validation.errors.join(', ')}`, params.action, { validationErrors: validation.errors }, validation.suggestions);
75
+ }
76
+ // Validate operation-specific parameters
77
+ const operationValidation = this.validateOperationParams(params);
78
+ if (!operationValidation.isValid) {
79
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('VALIDATION_ERROR', `Operation validation failed: ${operationValidation.errors.join(', ')}`, params.action, { validationErrors: operationValidation.errors }, operationValidation.suggestions);
80
+ }
81
+ // Route to appropriate handler
82
+ switch (params.action) {
83
+ case 'log':
84
+ return await this.handleLog(params, startTime);
85
+ case 'track':
86
+ return await this.handleTrack(params, startTime);
87
+ case 'sync':
88
+ return await this.handleSync(params, startTime);
89
+ case 'export':
90
+ return await this.handleExport(params, startTime);
91
+ case 'auto':
92
+ return await this.handleAuto(params, startTime);
93
+ default:
94
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('UNSUPPORTED_OPERATION', `Unsupported operation: ${params.action}`, params.action, { supportedOperations: ['log', 'track', 'sync', 'export', 'auto'] }, ['Use one of the supported operations: log, track, sync, export, auto']);
95
+ }
96
+ }
97
+ catch (error) {
98
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
99
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('EXECUTION_ERROR', `Failed to execute git-history operation: ${errorMessage}`, params.action, { error: errorMessage });
100
+ }
101
+ }
102
+ /**
103
+ * Validate operation-specific parameters
104
+ */
105
+ validateOperationParams(params) {
106
+ const errors = [];
107
+ const suggestions = [];
108
+ switch (params.action) {
109
+ case 'track':
110
+ if (!params.message) {
111
+ errors.push('Message is required for track operation');
112
+ suggestions.push('Provide a message describing the change');
113
+ }
114
+ break;
115
+ case 'sync':
116
+ if (!this.providerHandler && params.syncMethod === 'api') {
117
+ errors.push('Provider configuration required for API sync');
118
+ suggestions.push('Configure GitHub/Gitea provider or use file sync method');
119
+ }
120
+ break;
121
+ case 'export':
122
+ if (params.outputPath && !path.isAbsolute(params.outputPath)) {
123
+ errors.push('Output path must be absolute');
124
+ suggestions.push('Provide an absolute path for export file');
125
+ }
126
+ break;
127
+ }
128
+ return {
129
+ isValid: errors.length === 0,
130
+ errors,
131
+ suggestions
132
+ };
133
+ }
134
+ /**
135
+ * Handle log operation - View history entries
136
+ */
137
+ async handleLog(params, startTime) {
138
+ try {
139
+ const history = await this.loadHistory();
140
+ let entries = [...history.entries];
141
+ // Apply filters
142
+ if (params.since) {
143
+ const sinceDate = new Date(params.since);
144
+ entries = entries.filter(entry => new Date(entry.timestamp) >= sinceDate);
145
+ }
146
+ if (params.until) {
147
+ const untilDate = new Date(params.until);
148
+ entries = entries.filter(entry => new Date(entry.timestamp) <= untilDate);
149
+ }
150
+ if (params.author) {
151
+ entries = entries.filter(entry => entry.author.toLowerCase().includes(params.author.toLowerCase()) ||
152
+ entry.authorEmail.toLowerCase().includes(params.author.toLowerCase()));
153
+ }
154
+ if (params.filePath) {
155
+ entries = entries.filter(entry => entry.filesChanged.some(file => file.path.includes(params.filePath)));
156
+ }
157
+ if (params.branch) {
158
+ entries = entries.filter(entry => entry.branch === params.branch);
159
+ }
160
+ // Sort by timestamp (newest first)
161
+ entries.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
162
+ // Apply limit
163
+ const limit = params.limit || 50;
164
+ entries = entries.slice(0, limit);
165
+ // Format output
166
+ const format = params.format || 'json';
167
+ let formattedData;
168
+ if (format === 'markdown') {
169
+ formattedData = this.formatHistoryAsMarkdown(entries);
170
+ }
171
+ else {
172
+ formattedData = {
173
+ totalEntries: history.entries.length,
174
+ filteredEntries: entries.length,
175
+ entries: entries
176
+ };
177
+ }
178
+ return {
179
+ success: true,
180
+ data: {
181
+ ...formattedData,
182
+ filters: {
183
+ since: params.since,
184
+ until: params.until,
185
+ author: params.author,
186
+ filePath: params.filePath,
187
+ branch: params.branch,
188
+ limit: limit
189
+ }
190
+ },
191
+ metadata: {
192
+ operation: params.action,
193
+ executionTime: Date.now() - startTime,
194
+ timestamp: new Date().toISOString()
195
+ }
196
+ };
197
+ }
198
+ catch (error) {
199
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
200
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('LOG_FAILED', `Failed to retrieve history: ${errorMessage}`, params.action, { error: errorMessage });
201
+ }
202
+ }
203
+ /**
204
+ * Handle track operation - Manually record a change
205
+ */
206
+ async handleTrack(params, startTime) {
207
+ try {
208
+ const currentBranch = await this.getCurrentBranch(params.projectPath);
209
+ const timestamp = params.timestamp || new Date().toISOString();
210
+ const entry = {
211
+ id: `manual-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
212
+ timestamp,
213
+ author: await this.getGitUserName() || 'Unknown',
214
+ authorEmail: await this.getGitUserEmail() || '',
215
+ message: params.message,
216
+ filesChanged: params.files ? params.files.map(file => ({
217
+ path: file,
218
+ status: 'modified',
219
+ additions: params.additions || 0,
220
+ deletions: params.deletions || 0
221
+ })) : [],
222
+ additions: params.additions || 0,
223
+ deletions: params.deletions || 0,
224
+ branch: currentBranch || 'main',
225
+ manual: true,
226
+ synced: false
227
+ };
228
+ await this.addHistoryEntry(entry);
229
+ return {
230
+ success: true,
231
+ data: {
232
+ entry: entry,
233
+ message: 'Change tracked successfully'
234
+ },
235
+ metadata: {
236
+ operation: params.action,
237
+ executionTime: Date.now() - startTime,
238
+ timestamp: new Date().toISOString()
239
+ }
240
+ };
241
+ }
242
+ catch (error) {
243
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
244
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('TRACK_FAILED', `Failed to track change: ${errorMessage}`, params.action, { error: errorMessage });
245
+ }
246
+ }
247
+ /**
248
+ * Handle sync operation - Sync local history to remote
249
+ */
250
+ async handleSync(params, startTime) {
251
+ try {
252
+ const history = await this.loadHistory();
253
+ const unsyncedEntries = history.entries.filter(entry => !entry.synced);
254
+ if (unsyncedEntries.length === 0) {
255
+ return {
256
+ success: true,
257
+ data: {
258
+ message: 'All entries are already synced',
259
+ totalEntries: history.entries.length,
260
+ syncedEntries: history.entries.length - unsyncedEntries.length,
261
+ unsyncedEntries: 0
262
+ },
263
+ metadata: {
264
+ operation: params.action,
265
+ executionTime: Date.now() - startTime,
266
+ timestamp: new Date().toISOString()
267
+ }
268
+ };
269
+ }
270
+ const syncMethod = params.syncMethod || 'file';
271
+ let syncResult = {};
272
+ if (syncMethod === 'file') {
273
+ syncResult = await this.syncViaFile(params, unsyncedEntries);
274
+ }
275
+ else if (syncMethod === 'api') {
276
+ syncResult = await this.syncViaApi(params, unsyncedEntries);
277
+ }
278
+ else {
279
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('INVALID_SYNC_METHOD', `Invalid sync method: ${syncMethod}`, params.action, { supportedMethods: ['file', 'api'] }, ['Use "file" to commit history.json or "api" to use provider API']);
280
+ }
281
+ // Mark entries as synced
282
+ const updatedHistory = await this.loadHistory();
283
+ updatedHistory.entries.forEach(entry => {
284
+ if (!entry.synced && unsyncedEntries.some(u => u.id === entry.id)) {
285
+ entry.synced = true;
286
+ }
287
+ });
288
+ await this.saveHistory(updatedHistory);
289
+ return {
290
+ success: true,
291
+ data: {
292
+ ...syncResult,
293
+ syncedEntries: unsyncedEntries.length,
294
+ totalEntries: history.entries.length,
295
+ syncMethod
296
+ },
297
+ metadata: {
298
+ operation: params.action,
299
+ executionTime: Date.now() - startTime,
300
+ timestamp: new Date().toISOString()
301
+ }
302
+ };
303
+ }
304
+ catch (error) {
305
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
306
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('SYNC_FAILED', `Failed to sync history: ${errorMessage}`, params.action, { error: errorMessage });
307
+ }
308
+ }
309
+ /**
310
+ * Handle export operation - Export history to file
311
+ */
312
+ async handleExport(params, startTime) {
313
+ try {
314
+ const history = await this.loadHistory();
315
+ const outputPath = params.outputPath || path.join(params.projectPath, 'HISTORY.json');
316
+ let exportData;
317
+ if (path.extname(outputPath).toLowerCase() === '.md') {
318
+ // Export as Markdown
319
+ exportData = this.formatHistoryAsMarkdown(history.entries);
320
+ }
321
+ else {
322
+ // Export as JSON
323
+ exportData = {
324
+ exportedAt: new Date().toISOString(),
325
+ totalEntries: history.entries.length,
326
+ includeDiffs: params.includeDiffs || false,
327
+ entries: params.includeDiffs ? history.entries : history.entries.map(entry => {
328
+ const { diff, ...entryWithoutDiff } = entry;
329
+ return entryWithoutDiff;
330
+ })
331
+ };
332
+ }
333
+ // Write to file
334
+ const outputDir = path.dirname(outputPath);
335
+ if (!fs.existsSync(outputDir)) {
336
+ fs.mkdirSync(outputDir, { recursive: true });
337
+ }
338
+ if (typeof exportData === 'string') {
339
+ fs.writeFileSync(outputPath, exportData, 'utf8');
340
+ }
341
+ else {
342
+ fs.writeFileSync(outputPath, JSON.stringify(exportData, null, 2), 'utf8');
343
+ }
344
+ return {
345
+ success: true,
346
+ data: {
347
+ exportedPath: outputPath,
348
+ format: path.extname(outputPath).toLowerCase() === '.md' ? 'markdown' : 'json',
349
+ totalEntries: history.entries.length,
350
+ includeDiffs: params.includeDiffs || false
351
+ },
352
+ metadata: {
353
+ operation: params.action,
354
+ executionTime: Date.now() - startTime,
355
+ timestamp: new Date().toISOString()
356
+ }
357
+ };
358
+ }
359
+ catch (error) {
360
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
361
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('EXPORT_FAILED', `Failed to export history: ${errorMessage}`, params.action, { error: errorMessage });
362
+ }
363
+ }
364
+ /**
365
+ * Handle auto operation - Enable/disable automatic tracking
366
+ */
367
+ async handleAuto(params, startTime) {
368
+ try {
369
+ const config = await this.loadConfig();
370
+ const wasEnabled = config.autoTracking;
371
+ if (params.enabled !== undefined) {
372
+ config.autoTracking = params.enabled;
373
+ await this.saveConfig(config);
374
+ if (params.enabled && !wasEnabled) {
375
+ // Enable auto-tracking - sync current commits
376
+ await this.syncRecentCommits();
377
+ }
378
+ }
379
+ return {
380
+ success: true,
381
+ data: {
382
+ autoTrackingEnabled: config.autoTracking,
383
+ message: `Auto-tracking ${config.autoTracking ? 'enabled' : 'disabled'}`
384
+ },
385
+ metadata: {
386
+ operation: params.action,
387
+ executionTime: Date.now() - startTime,
388
+ timestamp: new Date().toISOString()
389
+ }
390
+ };
391
+ }
392
+ catch (error) {
393
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
394
+ return operation_error_handler_js_1.OperationErrorHandler.createToolError('AUTO_CONFIG_FAILED', `Failed to configure auto-tracking: ${errorMessage}`, params.action, { error: errorMessage });
395
+ }
396
+ }
397
+ /**
398
+ * Sync via file commit
399
+ */
400
+ async syncViaFile(params, entries) {
401
+ try {
402
+ const historyFile = path.join(params.projectPath, 'HISTORY.json');
403
+ // Write history to repository
404
+ const historyData = {
405
+ lastUpdated: new Date().toISOString(),
406
+ entries: entries
407
+ };
408
+ fs.writeFileSync(historyFile, JSON.stringify(historyData, null, 2), 'utf8');
409
+ // Commit the file
410
+ const addResult = await this.gitExecutor.executeGitCommand('add', ['HISTORY.json'], params.projectPath);
411
+ if (!addResult.success) {
412
+ throw new Error(`Failed to add HISTORY.json: ${addResult.stderr}`);
413
+ }
414
+ const commitResult = await this.gitExecutor.executeGitCommand('commit', ['-m', `Update history: ${entries.length} new entries`], params.projectPath);
415
+ if (!commitResult.success && !commitResult.stderr.includes('nothing to commit')) {
416
+ throw new Error(`Failed to commit HISTORY.json: ${commitResult.stderr}`);
417
+ }
418
+ return {
419
+ method: 'file',
420
+ committed: commitResult.success,
421
+ message: 'History synced via file commit'
422
+ };
423
+ }
424
+ catch (error) {
425
+ throw new Error(`File sync failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
426
+ }
427
+ }
428
+ /**
429
+ * Sync via provider API
430
+ */
431
+ async syncViaApi(params, entries) {
432
+ if (!this.providerHandler) {
433
+ throw new Error('Provider handler not configured');
434
+ }
435
+ try {
436
+ const provider = params.provider || 'github';
437
+ const repo = params.repo || await this.getRepoName();
438
+ // Create a summary entry for the API
439
+ const summary = {
440
+ period: `${entries[0]?.timestamp} to ${entries[entries.length - 1]?.timestamp}`,
441
+ totalEntries: entries.length,
442
+ authors: [...new Set(entries.map(e => e.author))],
443
+ filesChanged: entries.reduce((sum, e) => sum + e.filesChanged.length, 0),
444
+ totalAdditions: entries.reduce((sum, e) => sum + e.additions, 0),
445
+ totalDeletions: entries.reduce((sum, e) => sum + e.deletions, 0),
446
+ entries: entries.slice(0, 10) // Include first 10 entries in detail
447
+ };
448
+ const operation = {
449
+ provider,
450
+ operation: 'create',
451
+ parameters: {
452
+ repo,
453
+ title: `History Update: ${entries.length} changes`,
454
+ body: JSON.stringify(summary, null, 2),
455
+ type: 'issue' // Create as issue for tracking
456
+ },
457
+ requiresAuth: true,
458
+ isRemoteOperation: true
459
+ };
460
+ const result = await this.providerHandler.executeOperation(operation);
461
+ return {
462
+ method: 'api',
463
+ provider,
464
+ success: result.success,
465
+ message: result.success ? 'History synced via API' : 'API sync failed'
466
+ };
467
+ }
468
+ catch (error) {
469
+ throw new Error(`API sync failed: ${error instanceof Error ? error.message : 'Unknown error'}`);
470
+ }
471
+ }
472
+ /**
473
+ * Format history as Markdown
474
+ */
475
+ formatHistoryAsMarkdown(entries) {
476
+ let markdown = '# Repository History\n\n';
477
+ markdown += `Generated on: ${new Date().toISOString()}\n\n`;
478
+ markdown += `Total entries: ${entries.length}\n\n`;
479
+ for (const entry of entries) {
480
+ markdown += `## ${entry.message}\n\n`;
481
+ markdown += `- **Date:** ${new Date(entry.timestamp).toLocaleString()}\n`;
482
+ markdown += `- **Author:** ${entry.author} <${entry.authorEmail}>\n`;
483
+ markdown += `- **Branch:** ${entry.branch}\n`;
484
+ markdown += `- **Commit:** ${entry.commitHash || 'Manual entry'}\n`;
485
+ markdown += `- **Changes:** +${entry.additions} -${entry.deletions}\n`;
486
+ if (entry.filesChanged.length > 0) {
487
+ markdown += `- **Files changed:**\n`;
488
+ for (const file of entry.filesChanged) {
489
+ markdown += ` - ${file.status.toUpperCase()}: ${file.path} (+${file.additions} -${file.deletions})\n`;
490
+ }
491
+ }
492
+ if (entry.tags && entry.tags.length > 0) {
493
+ markdown += `- **Tags:** ${entry.tags.join(', ')}\n`;
494
+ }
495
+ markdown += '\n---\n\n';
496
+ }
497
+ return markdown;
498
+ }
499
+ /**
500
+ * Load history from file
501
+ */
502
+ async loadHistory() {
503
+ try {
504
+ if (fs.existsSync(this.historyPath)) {
505
+ const data = fs.readFileSync(this.historyPath, 'utf8');
506
+ return JSON.parse(data);
507
+ }
508
+ }
509
+ catch (error) {
510
+ console.warn('Failed to load history file:', error);
511
+ }
512
+ return { entries: [] };
513
+ }
514
+ /**
515
+ * Save history to file
516
+ */
517
+ async saveHistory(history) {
518
+ try {
519
+ fs.writeFileSync(this.historyPath, JSON.stringify(history, null, 2), 'utf8');
520
+ }
521
+ catch (error) {
522
+ throw new Error(`Failed to save history: ${error instanceof Error ? error.message : 'Unknown error'}`);
523
+ }
524
+ }
525
+ /**
526
+ * Add entry to history
527
+ */
528
+ async addHistoryEntry(entry) {
529
+ const history = await this.loadHistory();
530
+ history.entries.push(entry);
531
+ await this.saveHistory(history);
532
+ }
533
+ /**
534
+ * Load configuration
535
+ */
536
+ async loadConfig() {
537
+ try {
538
+ if (fs.existsSync(this.configPath)) {
539
+ const data = fs.readFileSync(this.configPath, 'utf8');
540
+ return JSON.parse(data);
541
+ }
542
+ }
543
+ catch (error) {
544
+ console.warn('Failed to load config file:', error);
545
+ }
546
+ return { autoTracking: false };
547
+ }
548
+ /**
549
+ * Save configuration
550
+ */
551
+ async saveConfig(config) {
552
+ try {
553
+ fs.writeFileSync(this.configPath, JSON.stringify(config, null, 2), 'utf8');
554
+ }
555
+ catch (error) {
556
+ throw new Error(`Failed to save config: ${error instanceof Error ? error.message : 'Unknown error'}`);
557
+ }
558
+ }
559
+ /**
560
+ * Sync recent commits to history
561
+ */
562
+ async syncRecentCommits() {
563
+ try {
564
+ const config = await this.loadConfig();
565
+ const lastHash = config.lastCommitHash;
566
+ // Get commits since last sync
567
+ const logArgs = ['--oneline', '--numstat', '--format=format:%H|%an|%ae|%ad|%s'];
568
+ if (lastHash) {
569
+ logArgs.push(`${lastHash}..HEAD`);
570
+ }
571
+ const logResult = await this.gitExecutor.executeGitCommand('log', logArgs);
572
+ if (!logResult.success) {
573
+ console.warn('Failed to get git log:', logResult.stderr);
574
+ return;
575
+ }
576
+ const lines = logResult.stdout.split('\n').filter(line => line.trim());
577
+ let currentEntry = null;
578
+ for (const line of lines) {
579
+ if (line.includes('|')) {
580
+ // Commit header line
581
+ if (currentEntry) {
582
+ await this.addHistoryEntry(currentEntry);
583
+ }
584
+ const [hash, author, email, date, ...messageParts] = line.split('|');
585
+ const message = messageParts.join('|');
586
+ currentEntry = {
587
+ id: `commit-${hash}`,
588
+ timestamp: new Date(date).toISOString(),
589
+ commitHash: hash,
590
+ author,
591
+ authorEmail: email,
592
+ message,
593
+ filesChanged: [],
594
+ additions: 0,
595
+ deletions: 0,
596
+ branch: await this.getCurrentBranch() || 'main',
597
+ synced: false
598
+ };
599
+ }
600
+ else if (line.trim() && currentEntry) {
601
+ // File change line (from --numstat)
602
+ const parts = line.trim().split('\t');
603
+ if (parts.length >= 3) {
604
+ const additions = parseInt(parts[0]) || 0;
605
+ const deletions = parseInt(parts[1]) || 0;
606
+ const filePath = parts[2];
607
+ currentEntry.filesChanged.push({
608
+ path: filePath,
609
+ status: additions === 0 && deletions === 0 ? 'modified' : (additions > 0 ? 'added' : 'deleted'),
610
+ additions,
611
+ deletions
612
+ });
613
+ currentEntry.additions += additions;
614
+ currentEntry.deletions += deletions;
615
+ }
616
+ }
617
+ }
618
+ // Add the last entry
619
+ if (currentEntry) {
620
+ await this.addHistoryEntry(currentEntry);
621
+ }
622
+ // Update last commit hash
623
+ const latestHash = await this.getLatestCommitHash();
624
+ if (latestHash) {
625
+ config.lastCommitHash = latestHash;
626
+ config.lastSyncTimestamp = new Date().toISOString();
627
+ await this.saveConfig(config);
628
+ }
629
+ }
630
+ catch (error) {
631
+ console.warn('Failed to sync recent commits:', error);
632
+ }
633
+ }
634
+ /**
635
+ * Get current branch
636
+ */
637
+ async getCurrentBranch(projectPath) {
638
+ try {
639
+ const result = await this.gitExecutor.executeGitCommand('branch', ['--show-current'], projectPath);
640
+ return result.success ? result.stdout.trim() : null;
641
+ }
642
+ catch {
643
+ return null;
644
+ }
645
+ }
646
+ /**
647
+ * Get git user name
648
+ */
649
+ async getGitUserName() {
650
+ try {
651
+ const result = await this.gitExecutor.executeGitCommand('config', ['user.name']);
652
+ return result.success ? result.stdout.trim() : null;
653
+ }
654
+ catch {
655
+ return null;
656
+ }
657
+ }
658
+ /**
659
+ * Get git user email
660
+ */
661
+ async getGitUserEmail() {
662
+ try {
663
+ const result = await this.gitExecutor.executeGitCommand('config', ['user.email']);
664
+ return result.success ? result.stdout.trim() : null;
665
+ }
666
+ catch {
667
+ return null;
668
+ }
669
+ }
670
+ /**
671
+ * Get latest commit hash
672
+ */
673
+ async getLatestCommitHash() {
674
+ try {
675
+ const result = await this.gitExecutor.executeGitCommand('rev-parse', ['HEAD']);
676
+ return result.success ? result.stdout.trim() : null;
677
+ }
678
+ catch {
679
+ return null;
680
+ }
681
+ }
682
+ /**
683
+ * Get repository name from git config
684
+ */
685
+ async getRepoName() {
686
+ try {
687
+ const result = await this.gitExecutor.executeGitCommand('config', ['--get', 'remote.origin.url']);
688
+ if (result.success) {
689
+ const url = result.stdout.trim();
690
+ // Extract repo name from URL (supports GitHub and Gitea formats)
691
+ const match = url.match(/\/([^\/]+?)(?:\.git)?$/);
692
+ return match ? match[1] : null;
693
+ }
694
+ }
695
+ catch {
696
+ // Ignore errors
697
+ }
698
+ return null;
699
+ }
700
+ /**
701
+ * Ensure history directory exists
702
+ */
703
+ ensureHistoryDirectory() {
704
+ try {
705
+ const dir = path.dirname(this.historyPath);
706
+ if (!fs.existsSync(dir)) {
707
+ fs.mkdirSync(dir, { recursive: true });
708
+ }
709
+ }
710
+ catch (error) {
711
+ console.warn('Failed to create history directory:', error);
712
+ }
713
+ }
714
+ /**
715
+ * Get tool schema for MCP registration
716
+ */
717
+ static getToolSchema() {
718
+ return {
719
+ name: 'git-history',
720
+ description: 'Comprehensive change tracking tool that records all repository modifications with detailed timestamps, storing history locally in JSON and syncing to remote providers.',
721
+ inputSchema: {
722
+ type: 'object',
723
+ properties: {
724
+ action: {
725
+ type: 'string',
726
+ enum: ['log', 'track', 'sync', 'export', 'auto'],
727
+ description: 'The git-history operation to perform'
728
+ },
729
+ projectPath: {
730
+ type: 'string',
731
+ description: 'Absolute path to the project directory'
732
+ },
733
+ limit: {
734
+ type: 'number',
735
+ description: 'Number of entries to show (default: 50)',
736
+ minimum: 1,
737
+ maximum: 1000
738
+ },
739
+ since: {
740
+ type: 'string',
741
+ description: 'Start date filter (ISO 8601 format)'
742
+ },
743
+ until: {
744
+ type: 'string',
745
+ description: 'End date filter (ISO 8601 format)'
746
+ },
747
+ author: {
748
+ type: 'string',
749
+ description: 'Filter by author name or email'
750
+ },
751
+ filePath: {
752
+ type: 'string',
753
+ description: 'Filter by file path'
754
+ },
755
+ branch: {
756
+ type: 'string',
757
+ description: 'Filter by branch name'
758
+ },
759
+ format: {
760
+ type: 'string',
761
+ enum: ['json', 'markdown'],
762
+ description: 'Output format for log operation'
763
+ },
764
+ message: {
765
+ type: 'string',
766
+ description: 'Change description (required for track operation)'
767
+ },
768
+ timestamp: {
769
+ type: 'string',
770
+ description: 'Custom timestamp for track operation (ISO 8601 format)'
771
+ },
772
+ files: {
773
+ type: 'array',
774
+ items: { type: 'string' },
775
+ description: 'Files affected by the change (for track operation)'
776
+ },
777
+ additions: {
778
+ type: 'number',
779
+ description: 'Lines added (for track operation)',
780
+ minimum: 0
781
+ },
782
+ deletions: {
783
+ type: 'number',
784
+ description: 'Lines deleted (for track operation)',
785
+ minimum: 0
786
+ },
787
+ provider: {
788
+ type: 'string',
789
+ enum: ['github', 'gitea', 'both'],
790
+ description: 'Provider for remote sync operations'
791
+ },
792
+ syncMethod: {
793
+ type: 'string',
794
+ enum: ['file', 'api'],
795
+ description: 'Sync method: file (commit to repo) or api (use provider API)'
796
+ },
797
+ repo: {
798
+ type: 'string',
799
+ description: 'Repository name for remote sync'
800
+ },
801
+ outputPath: {
802
+ type: 'string',
803
+ description: 'Export file path (absolute path required)'
804
+ },
805
+ includeDiffs: {
806
+ type: 'boolean',
807
+ description: 'Include full diffs in export (default: false)'
808
+ },
809
+ enabled: {
810
+ type: 'boolean',
811
+ description: 'Enable/disable auto-tracking (for auto operation)'
812
+ }
813
+ },
814
+ required: ['action', 'projectPath']
815
+ }
816
+ };
817
+ }
818
+ }
819
+ exports.GitHistoryTool = GitHistoryTool;
820
+ //# sourceMappingURL=git-history.js.map