git-creeper 1.0.1

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/src/index.js ADDED
@@ -0,0 +1,1235 @@
1
+ const simpleGit = require('simple-git');
2
+ const chalk = require('chalk');
3
+ const { t } = require('./i18n');
4
+
5
+ const git = simpleGit();
6
+
7
+ async function timeline(options) {
8
+ const days = parseInt(options.days);
9
+ const since = new Date(Date.now() - days * 24 * 60 * 60 * 1000);
10
+ const title = `${t('timeline.title')} (${t('timeline.last')} ${days} ${t('stats.days')})`;
11
+ const titlePadding = Math.floor((60 - title.length) / 2);
12
+
13
+ console.log(chalk.bold.cyan('\n╔════════════════════════════════════════════════════════════╗'));
14
+ console.log(chalk.bold.cyan(`║${' '.repeat(titlePadding)}${title}${' '.repeat(60 - title.length - titlePadding)}║`));
15
+ console.log(chalk.bold.cyan('╚════════════════════════════════════════════════════════════╝\n'));
16
+
17
+ const log = await git.log({
18
+ '--since': since.toISOString()
19
+ });
20
+
21
+ const commits = log.all;
22
+
23
+ if (commits.length === 0) {
24
+ console.log(chalk.gray('No commits found in this time range.'));
25
+ return;
26
+ }
27
+
28
+ commits.forEach((commit, index) => {
29
+ const date = new Date(commit.date);
30
+ const timeAgo = getTimeAgo(date);
31
+ const type = commit.message.startsWith('feat') ? 'FEAT' :
32
+ commit.message.startsWith('fix') ? 'FIX' :
33
+ commit.message.startsWith('docs') ? 'DOCS' : 'OTHER';
34
+
35
+ const typeColor = type === 'FEAT' ? chalk.green :
36
+ type === 'FIX' ? chalk.red :
37
+ type === 'DOCS' ? chalk.blue : chalk.gray;
38
+
39
+ console.log(`${typeColor('[' + type.padEnd(5) + ']')} ${chalk.bold(commit.message)}`);
40
+ console.log(` ${chalk.gray('Hash:')} ${chalk.white(commit.hash.substring(0, 7))}`);
41
+ console.log(` ${chalk.gray('Author:')} ${chalk.cyan(commit.author_name)}`);
42
+ console.log(` ${chalk.gray('Time:')} ${chalk.yellow(timeAgo)}`);
43
+
44
+ if (index < commits.length - 1) {
45
+ console.log(chalk.gray(' │'));
46
+ } else {
47
+ console.log();
48
+ }
49
+ });
50
+ }
51
+
52
+ function getTimeAgo(date) {
53
+ const seconds = Math.floor((new Date() - date) / 1000);
54
+ const intervals = {
55
+ year: 31536000,
56
+ month: 2592000,
57
+ week: 604800,
58
+ day: 86400,
59
+ hour: 3600,
60
+ minute: 60
61
+ };
62
+
63
+ for (const [unit, secondsInUnit] of Object.entries(intervals)) {
64
+ const interval = Math.floor(seconds / secondsInUnit);
65
+ if (interval >= 1) {
66
+ return `${interval} ${unit}${interval > 1 ? 's' : ''} ago`;
67
+ }
68
+ }
69
+ return 'just now';
70
+ }
71
+
72
+ async function hotspots(options) {
73
+ const limit = parseInt(options.limit);
74
+ const title = `${t('hotspots.title')} (${t('hotspots.top')} ${limit})`;
75
+ const titlePadding = Math.floor((60 - title.length) / 2);
76
+
77
+ console.log(chalk.bold.red('\n╔════════════════════════════════════════════════════════════╗'));
78
+ console.log(chalk.bold.red(`║${' '.repeat(titlePadding)}${title}${' '.repeat(60 - title.length - titlePadding)}║`));
79
+ console.log(chalk.bold.red('╚════════════════════════════════════════════════════════════╝\n'));
80
+
81
+ const log = await git.raw(['log', '--name-only', '--pretty=format:']);
82
+ const files = log.split('\n').filter(f => f.trim());
83
+
84
+ const frequency = {};
85
+ files.forEach(file => {
86
+ frequency[file] = (frequency[file] || 0) + 1;
87
+ });
88
+
89
+ const sorted = Object.entries(frequency)
90
+ .sort((a, b) => b[1] - a[1])
91
+ .slice(0, limit);
92
+
93
+ const maxCount = sorted[0]?.[1] || 1;
94
+
95
+ console.log(chalk.bold('RANK FILE CHANGES IMPACT'));
96
+ console.log(chalk.gray('─'.repeat(60)));
97
+
98
+ sorted.forEach(([file, count], i) => {
99
+ const barLength = Math.floor((count / maxCount) * 30);
100
+ const bar = '█'.repeat(barLength);
101
+ const percentage = ((count / maxCount) * 100).toFixed(0);
102
+ const rank = (i + 1).toString().padStart(4);
103
+ const fileShort = file.length > 35 ? '...' + file.slice(-32) : file.padEnd(35);
104
+ const countStr = count.toString().padStart(7);
105
+
106
+ console.log(`${chalk.yellow(rank)} ${chalk.cyan(fileShort)} ${chalk.white(countStr)} ${chalk.red(bar)} ${chalk.gray(percentage + '%')}`);
107
+ });
108
+ console.log();
109
+ }
110
+
111
+ async function changelog(options) {
112
+ console.log(chalk.bold.green('\n╔════════════════════════════════════════════════════════════╗'));
113
+ console.log(chalk.bold.green('║ 📝 Smart Changelog ║'));
114
+ console.log(chalk.bold.green('╚════════════════════════════════════════════════════════════╝\n'));
115
+
116
+ const range = options.from ? `${options.from}..${options.to}` : options.to;
117
+ const log = await git.log([range]);
118
+
119
+ const categories = {
120
+ feat: [],
121
+ fix: [],
122
+ docs: [],
123
+ other: []
124
+ };
125
+
126
+ log.all.forEach(commit => {
127
+ const msg = commit.message;
128
+ if (msg.startsWith('feat')) categories.feat.push(msg);
129
+ else if (msg.startsWith('fix')) categories.fix.push(msg);
130
+ else if (msg.startsWith('docs')) categories.docs.push(msg);
131
+ else categories.other.push(msg);
132
+ });
133
+
134
+ if (categories.feat.length) {
135
+ console.log(chalk.bold.green('┌─ ✨ Features'));
136
+ categories.feat.forEach((m, i) => {
137
+ const prefix = i === categories.feat.length - 1 ? '└─' : '├─';
138
+ console.log(chalk.green(`${prefix} ${m.replace('feat: ', '').replace('feat:', '')}`));
139
+ });
140
+ console.log();
141
+ }
142
+
143
+ if (categories.fix.length) {
144
+ console.log(chalk.bold.red('┌─ 🐛 Bug Fixes'));
145
+ categories.fix.forEach((m, i) => {
146
+ const prefix = i === categories.fix.length - 1 ? '└─' : '├─';
147
+ console.log(chalk.red(`${prefix} ${m.replace('fix: ', '').replace('fix:', '')}`));
148
+ });
149
+ console.log();
150
+ }
151
+
152
+ if (categories.docs.length) {
153
+ console.log(chalk.bold.blue('┌─ 📚 Documentation'));
154
+ categories.docs.forEach((m, i) => {
155
+ const prefix = i === categories.docs.length - 1 ? '└─' : '├─';
156
+ console.log(chalk.blue(`${prefix} ${m.replace('docs: ', '').replace('docs:', '')}`));
157
+ });
158
+ console.log();
159
+ }
160
+
161
+ if (categories.other.length) {
162
+ console.log(chalk.bold.gray('┌─ 🔧 Other Changes'));
163
+ categories.other.forEach((m, i) => {
164
+ const prefix = i === categories.other.length - 1 ? '└─' : '├─';
165
+ console.log(chalk.gray(`${prefix} ${m}`));
166
+ });
167
+ console.log();
168
+ }
169
+ }
170
+
171
+ async function blameAnalysis(file) {
172
+ console.log(chalk.bold.magenta(`\n🔍 Blame Analysis: ${file}\n`));
173
+
174
+ try {
175
+ const blame = await git.raw(['blame', '--line-porcelain', file]);
176
+ const lines = blame.split('\n');
177
+
178
+ const authors = {};
179
+ let currentAuthor = null;
180
+
181
+ lines.forEach(line => {
182
+ if (line.startsWith('author ')) {
183
+ currentAuthor = line.substring(7);
184
+ authors[currentAuthor] = (authors[currentAuthor] || 0) + 1;
185
+ }
186
+ });
187
+
188
+ const sorted = Object.entries(authors).sort((a, b) => b[1] - a[1]);
189
+ const total = sorted.reduce((sum, [, count]) => sum + count, 0);
190
+
191
+ console.log(chalk.bold('Contributors:\n'));
192
+ sorted.forEach(([author, lines]) => {
193
+ const percentage = ((lines / total) * 100).toFixed(1);
194
+ const bar = '█'.repeat(Math.floor(percentage / 2));
195
+ console.log(`${chalk.cyan(author)}`);
196
+ console.log(` ${chalk.green(bar)} ${lines} lines (${percentage}%)\n`);
197
+ });
198
+ } catch (error) {
199
+ console.error(chalk.red(`Error: ${error.message}`));
200
+ }
201
+ }
202
+
203
+
204
+
205
+ function showHelp() {
206
+ const chalk = require('chalk');
207
+
208
+ console.log(chalk.bold.magenta('\n╔════════════════════════════════════════════════════════════╗'));
209
+ console.log(chalk.bold.magenta('║ �️ GIT CREEPER v1.0.0 ║'));
210
+ console.log(chalk.bold.magenta('║ Creep on your Git History with Smart Insights ║'));
211
+ console.log(chalk.bold.magenta('╚════════════════════════════════════════════════════════════╝\n'));
212
+
213
+ console.log(chalk.bold('🚀 CORE COMMANDS:\n'));
214
+
215
+ console.log(chalk.cyan(' timeline') + chalk.gray(' View project evolution over time'));
216
+ console.log(chalk.gray(' git-creeper timeline --days 60\n'));
217
+
218
+ console.log(chalk.cyan(' stats') + chalk.gray(' Comprehensive repository statistics'));
219
+ console.log(chalk.gray(' git-creeper stats\n'));
220
+
221
+ console.log(chalk.cyan(' hotspots') + chalk.gray(' Find frequently changed files'));
222
+ console.log(chalk.gray(' git-creeper hotspots --limit 20\n'));
223
+
224
+ console.log(chalk.cyan(' contributors') + chalk.gray(' Analyze team member contributions'));
225
+ console.log(chalk.gray(' git-creeper contributors\n'));
226
+
227
+ console.log(chalk.bold('🔍 ANALYSIS TOOLS:\n'));
228
+
229
+ console.log(chalk.cyan(' changelog') + chalk.gray(' Generate organized changelog'));
230
+ console.log(chalk.gray(' git-creeper changelog --from v1.0.0 --to v2.0.0\n'));
231
+
232
+ console.log(chalk.cyan(' compare') + chalk.gray(' Compare branches or commits'));
233
+ console.log(chalk.gray(' git-creeper compare --from main --to develop\n'));
234
+
235
+ console.log(chalk.cyan(' search') + chalk.gray(' Search commits with filters'));
236
+ console.log(chalk.gray(' git-creeper search "bug" --author "John"\n'));
237
+
238
+ console.log(chalk.cyan(' blame-smart') + chalk.gray(' Analyze file contributors'));
239
+ console.log(chalk.gray(' git-creeper blame-smart src/index.js\n'));
240
+
241
+ console.log(chalk.bold('🤖 SMART FEATURES:\n'));
242
+
243
+ console.log(chalk.cyan(' smart-commit') + chalk.gray(' Get smart commit message suggestions'));
244
+ console.log(chalk.gray(' git-creeper smart-commit\n'));
245
+
246
+ console.log(chalk.cyan(' review') + chalk.gray(' Pattern-based code review'));
247
+ console.log(chalk.gray(' git-creeper review\n'));
248
+
249
+ console.log(chalk.cyan(' bug-risk') + chalk.gray(' Predict files with high bug risk'));
250
+ console.log(chalk.gray(' git-creeper bug-risk\n'));
251
+
252
+ console.log(chalk.cyan(' refactor') + chalk.gray(' Get code quality suggestions'));
253
+ console.log(chalk.gray(' git-creeper refactor\n'));
254
+
255
+ console.log(chalk.bold('📊 INSIGHTS & REPORTS:\n'));
256
+
257
+ console.log(chalk.cyan(' insights') + chalk.gray(' Get smart recommendations'));
258
+ console.log(chalk.gray(' git-creeper insights\n'));
259
+
260
+ console.log(chalk.cyan(' visualize') + chalk.gray(' Show ASCII visualizations'));
261
+ console.log(chalk.gray(' git-creeper visualize --type heatmap\n'));
262
+
263
+ console.log(chalk.cyan(' export') + chalk.gray(' Export data to file'));
264
+ console.log(chalk.gray(' git-creeper export --format markdown\n'));
265
+
266
+ console.log(chalk.cyan(' lang') + chalk.gray(' Set default language (en, tr)'));
267
+ console.log(chalk.gray(' git-creeper lang tr\n'));
268
+
269
+ console.log(chalk.bold('⚙️ OPTIONS:\n'));
270
+ console.log(chalk.gray(' -v, --version Display version number'));
271
+ console.log(chalk.gray(' -h, --help Show command help\n'));
272
+
273
+ console.log(chalk.bold('💡 TIPS:\n'));
274
+ console.log(chalk.gray(' • Use conventional commits (feat:, fix:, docs:) for better results'));
275
+ console.log(chalk.gray(' • Run "git-creeper insights" regularly for code quality recommendations'));
276
+ console.log(chalk.gray(' • Try smart features: smart-commit, review, bug-risk, refactor'));
277
+ console.log(chalk.gray(' • Export reports for team meetings and stakeholder updates'));
278
+ console.log(chalk.gray(' • Set your preferred language once with "git-creeper lang tr"\n'));
279
+
280
+ console.log(chalk.dim('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━'));
281
+ console.log(chalk.dim('Made with ❤️ by @levantedev | MIT License'));
282
+ console.log(chalk.dim('npm: npmjs.com/package/git-creeper'));
283
+ console.log(chalk.dim('GitHub: github.com/levantedev/git-creeper\n'));
284
+ }
285
+
286
+
287
+
288
+ module.exports = { timeline, hotspots, changelog, blameAnalysis, showHelp };
289
+
290
+ async function stats() {
291
+ const title = t('stats.title');
292
+ const titlePadding = Math.floor((60 - title.length) / 2);
293
+
294
+ console.log(chalk.bold.blue('\n╔════════════════════════════════════════════════════════════╗'));
295
+ console.log(chalk.bold.blue(`║${' '.repeat(titlePadding)}${title}${' '.repeat(60 - title.length - titlePadding)}║`));
296
+ console.log(chalk.bold.blue('╚════════════════════════════════════════════════════════════╝\n'));
297
+
298
+ const log = await git.log();
299
+ const commits = log.all;
300
+
301
+ if (commits.length === 0) {
302
+ console.log(chalk.gray(t('timeline.noCommits')));
303
+ return;
304
+ }
305
+
306
+ // Date range
307
+ const firstCommit = new Date(commits[commits.length - 1].date);
308
+ const lastCommit = new Date(commits[0].date);
309
+ const daysDiff = Math.floor((lastCommit - firstCommit) / (1000 * 60 * 60 * 24));
310
+
311
+ // Average message length
312
+ const avgMessageLength = commits.reduce((sum, c) => sum + c.message.length, 0) / commits.length;
313
+
314
+ // Contributors
315
+ const contributors = {};
316
+ commits.forEach(c => {
317
+ contributors[c.author_name] = (contributors[c.author_name] || 0) + 1;
318
+ });
319
+
320
+ // Commit types
321
+ const types = { feat: 0, fix: 0, docs: 0, other: 0 };
322
+ commits.forEach(c => {
323
+ if (c.message.startsWith('feat')) types.feat++;
324
+ else if (c.message.startsWith('fix')) types.fix++;
325
+ else if (c.message.startsWith('docs')) types.docs++;
326
+ else types.other++;
327
+ });
328
+
329
+ // Activity by day and hour
330
+ const days = {};
331
+ const hours = {};
332
+ const dayNames = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];
333
+
334
+ commits.forEach(c => {
335
+ const date = new Date(c.date);
336
+ const day = dayNames[date.getDay()];
337
+ const hour = date.getHours();
338
+ days[day] = (days[day] || 0) + 1;
339
+ hours[hour] = (hours[hour] || 0) + 1;
340
+ });
341
+
342
+ const mostActiveDay = Object.entries(days).sort((a, b) => b[1] - a[1])[0];
343
+ const peakHour = Object.entries(hours).sort((a, b) => b[1] - a[1])[0];
344
+ const topContributor = Object.entries(contributors).sort((a, b) => b[1] - a[1])[0];
345
+
346
+ // Display in clean table format
347
+ console.log(chalk.bold(t('stats.overview')));
348
+ console.log(chalk.gray('─'.repeat(60)));
349
+ console.log(` ${t('stats.totalCommits').padEnd(25)} ${chalk.cyan(commits.length.toString().padStart(10))}`);
350
+ console.log(` ${t('stats.repoAge').padEnd(25)} ${chalk.cyan((daysDiff + ' ' + t('stats.days')).padStart(10))}`);
351
+ console.log(` ${t('stats.commitsPerDay').padEnd(25)} ${chalk.cyan((commits.length / Math.max(daysDiff, 1)).toFixed(2).padStart(10))}`);
352
+ console.log(` ${t('stats.avgMessageLength').padEnd(25)} ${chalk.cyan((avgMessageLength.toFixed(0) + ' ' + t('stats.chars')).padStart(10))}`);
353
+ console.log();
354
+
355
+ console.log(chalk.bold(t('stats.contributors')));
356
+ console.log(chalk.gray('─'.repeat(60)));
357
+ console.log(` ${t('stats.totalContributors').padEnd(25)} ${chalk.cyan(Object.keys(contributors).length.toString().padStart(10))}`);
358
+ console.log(` ${t('stats.mostProductive').padEnd(25)} ${chalk.cyan(topContributor[0].padStart(10))}`);
359
+ console.log(` ${t('stats.theirCommits').padEnd(25)} ${chalk.cyan(topContributor[1].toString().padStart(10))}`);
360
+ console.log();
361
+
362
+ console.log(chalk.bold(t('stats.commitTypes')));
363
+ console.log(chalk.gray('─'.repeat(60)));
364
+ console.log(` ${t('stats.features').padEnd(25)} ${chalk.green(types.feat.toString().padStart(10))}`);
365
+ console.log(` ${t('stats.bugFixes').padEnd(25)} ${chalk.red(types.fix.toString().padStart(10))}`);
366
+ console.log(` ${t('stats.documentation').padEnd(25)} ${chalk.blue(types.docs.toString().padStart(10))}`);
367
+ console.log(` ${t('stats.other').padEnd(25)} ${chalk.gray(types.other.toString().padStart(10))}`);
368
+ console.log();
369
+
370
+ console.log(chalk.bold(t('stats.activityPatterns')));
371
+ console.log(chalk.gray('─'.repeat(60)));
372
+ console.log(` ${t('stats.mostActiveDay').padEnd(25)} ${chalk.yellow(mostActiveDay[0].padStart(10))} (${mostActiveDay[1]} ${t('stats.commits')})`);
373
+ console.log(` ${t('stats.peakHour').padEnd(25)} ${chalk.yellow((peakHour[0] + ':00').padStart(10))} (${peakHour[1]} ${t('stats.commits')})`);
374
+ console.log();
375
+ }
376
+
377
+ async function contributors() {
378
+ const title = t('contributors.title');
379
+ const titlePadding = Math.floor((60 - title.length) / 2);
380
+
381
+ console.log(chalk.bold.magenta('\n╔════════════════════════════════════════════════════════════╗'));
382
+ console.log(chalk.bold.magenta(`║${' '.repeat(titlePadding)}${title}${' '.repeat(60 - title.length - titlePadding)}║`));
383
+ console.log(chalk.bold.magenta('╚════════════════════════════════════════════════════════════╝\n'));
384
+
385
+ const log = await git.log();
386
+ const commits = log.all;
387
+
388
+ const contributorData = {};
389
+
390
+ // Get commit data
391
+ commits.forEach(c => {
392
+ if (!contributorData[c.author_name]) {
393
+ contributorData[c.author_name] = {
394
+ commits: 0,
395
+ firstCommit: c.date,
396
+ lastCommit: c.date,
397
+ insertions: 0,
398
+ deletions: 0
399
+ };
400
+ }
401
+ contributorData[c.author_name].commits++;
402
+ contributorData[c.author_name].lastCommit = c.date;
403
+ });
404
+
405
+ const sorted = Object.entries(contributorData).sort((a, b) => b[1].commits - a[1].commits);
406
+ const totalCommits = commits.length;
407
+
408
+ console.log(chalk.bold('RANK CONTRIBUTOR COMMITS SHARE LAST ACTIVE'));
409
+ console.log(chalk.gray('─'.repeat(60)));
410
+
411
+ sorted.forEach(([name, data], i) => {
412
+ const percentage = ((data.commits / totalCommits) * 100).toFixed(1);
413
+ const barLength = Math.floor((data.commits / sorted[0][1].commits) * 20);
414
+ const bar = '█'.repeat(barLength);
415
+ const rank = (i + 1).toString().padStart(4);
416
+ const nameShort = name.length > 20 ? name.substring(0, 17) + '...' : name.padEnd(20);
417
+ const commitStr = data.commits.toString().padStart(7);
418
+ const shareStr = (percentage + '%').padStart(7);
419
+ const lastActive = getTimeAgo(new Date(data.lastCommit));
420
+
421
+ console.log(`${chalk.yellow(rank)} ${chalk.cyan(nameShort)} ${chalk.white(commitStr)} ${chalk.magenta(bar)}${shareStr.padStart(10 - barLength)} ${chalk.gray(lastActive)}`);
422
+ });
423
+ console.log();
424
+ }
425
+
426
+ async function compare(options) {
427
+ const from = options.from || 'HEAD~10';
428
+ const to = options.to || 'HEAD';
429
+
430
+ console.log(chalk.bold.yellow('\n╔════════════════════════════════════════════════════════════╗'));
431
+ console.log(chalk.bold.yellow(`║ 🔄 Compare: ${from} → ${to}`.padEnd(61) + '║'));
432
+ console.log(chalk.bold.yellow('╚════════════════════════════════════════════════════════════╝\n'));
433
+
434
+ try {
435
+ const diff = await git.diffSummary([from, to]);
436
+ const log = await git.log([`${from}..${to}`]);
437
+
438
+ console.log(chalk.bold('📊 Summary:'));
439
+ console.log(` Files Changed: ${chalk.cyan(diff.files.length)}`);
440
+ console.log(` Insertions: ${chalk.green('+' + diff.insertions)}`);
441
+ console.log(` Deletions: ${chalk.red('-' + diff.deletions)}`);
442
+ console.log(` Commits: ${chalk.yellow(log.all.length)}\n`);
443
+
444
+ // Who did what
445
+ console.log(chalk.bold('👥 Contributors:\n'));
446
+ const authorChanges = {};
447
+ log.all.forEach(commit => {
448
+ if (!authorChanges[commit.author_name]) {
449
+ authorChanges[commit.author_name] = 0;
450
+ }
451
+ authorChanges[commit.author_name]++;
452
+ });
453
+
454
+ Object.entries(authorChanges).sort((a, b) => b[1] - a[1]).forEach(([author, count]) => {
455
+ console.log(` ${chalk.cyan(author)}: ${chalk.yellow(count + ' commits')}`);
456
+ });
457
+ console.log();
458
+
459
+ console.log(chalk.bold('📁 Changed Files:\n'));
460
+
461
+ diff.files.slice(0, 15).forEach(file => {
462
+ const changes = file.insertions + file.deletions;
463
+ const barLength = Math.min(Math.floor(changes / 2), 30);
464
+ const bar = '█'.repeat(barLength);
465
+
466
+ console.log(chalk.cyan(file.file));
467
+ console.log(` ${chalk.green('+' + file.insertions)} ${chalk.red('-' + file.deletions)} ${chalk.gray(bar)}\n`);
468
+ });
469
+
470
+ if (diff.files.length > 15) {
471
+ console.log(chalk.gray(` ... and ${diff.files.length - 15} more files\n`));
472
+ }
473
+ } catch (error) {
474
+ console.error(chalk.red(`Error: ${error.message}`));
475
+ }
476
+ }
477
+
478
+ async function search(query, options) {
479
+ console.log(chalk.bold.green('\n╔════════════════════════════════════════════════════════════╗'));
480
+ console.log(chalk.bold.green(`║ 🔍 Search: "${query}"`.padEnd(61) + '║'));
481
+ console.log(chalk.bold.green('╚════════════════════════════════════════════════════════════╝\n'));
482
+
483
+ const logOptions = ['--all', '--grep=' + query, '-i'];
484
+ if (options.author) logOptions.push('--author=' + options.author);
485
+ if (options.since) logOptions.push('--since=' + options.since);
486
+
487
+ const log = await git.log(logOptions);
488
+ const commits = log.all;
489
+
490
+ if (commits.length === 0) {
491
+ console.log(chalk.gray('No commits found matching your search.\n'));
492
+ return;
493
+ }
494
+
495
+ console.log(chalk.bold(`Found ${chalk.cyan(commits.length)} commits:\n`));
496
+
497
+ commits.slice(0, 20).forEach(commit => {
498
+ const icon = commit.message.startsWith('feat') ? '✨' :
499
+ commit.message.startsWith('fix') ? '🐛' :
500
+ commit.message.startsWith('docs') ? '📚' : '🔧';
501
+
502
+ console.log(`${icon} ${chalk.bold(commit.message)}`);
503
+ console.log(` ${chalk.gray(commit.hash.substring(0, 7))} by ${chalk.cyan(commit.author_name)} - ${chalk.yellow(getTimeAgo(new Date(commit.date)))}`);
504
+
505
+ // Show files changed in this commit
506
+ console.log(chalk.gray(` Files: ${commit.diff?.files?.length || 'N/A'}\n`));
507
+ });
508
+
509
+ if (commits.length > 20) {
510
+ console.log(chalk.gray(`... and ${commits.length - 20} more results\n`));
511
+ }
512
+
513
+ // Search in specific file if needed
514
+ console.log(chalk.dim('💡 Tip: Use --author "name" or --since "2 weeks ago" to filter results\n'));
515
+ }
516
+
517
+ async function insights() {
518
+ console.log(chalk.bold.magenta('\n╔════════════════════════════════════════════════════════════╗'));
519
+ console.log(chalk.bold.magenta('║ 💡 Smart Insights ║'));
520
+ console.log(chalk.bold.magenta('╚════════════════════════════════════════════════════════════╝\n'));
521
+
522
+ const log = await git.log();
523
+ const commits = log.all;
524
+
525
+ const insights = [];
526
+
527
+ // Check recent activity
528
+ const lastCommit = new Date(commits[0].date);
529
+ const daysSinceLastCommit = Math.floor((new Date() - lastCommit) / (1000 * 60 * 60 * 24));
530
+
531
+ if (daysSinceLastCommit > 7) {
532
+ insights.push({
533
+ type: 'warning',
534
+ icon: '⚠️',
535
+ message: `No commits in the last ${daysSinceLastCommit} days. Project might be inactive.`
536
+ });
537
+ } else if (daysSinceLastCommit === 0) {
538
+ insights.push({
539
+ type: 'success',
540
+ icon: '✅',
541
+ message: 'Active development! Commits made today.'
542
+ });
543
+ }
544
+
545
+ // Check hotspots
546
+ const fileLog = await git.raw(['log', '--name-only', '--pretty=format:']);
547
+ const files = fileLog.split('\n').filter(f => f.trim());
548
+ const frequency = {};
549
+ files.forEach(file => {
550
+ frequency[file] = (frequency[file] || 0) + 1;
551
+ });
552
+
553
+ const hotFiles = Object.entries(frequency).filter(([, count]) => count > 10);
554
+ if (hotFiles.length > 0) {
555
+ insights.push({
556
+ type: 'info',
557
+ icon: '🔥',
558
+ message: `${hotFiles.length} files changed more than 10 times. Consider refactoring.`
559
+ });
560
+ }
561
+
562
+ // Check commit message quality
563
+ const shortMessages = commits.filter(c => c.message.length < 10).length;
564
+ if (shortMessages > commits.length * 0.3) {
565
+ insights.push({
566
+ type: 'warning',
567
+ icon: '📝',
568
+ message: `${((shortMessages / commits.length) * 100).toFixed(0)}% of commits have short messages. Use descriptive commit messages.`
569
+ });
570
+ }
571
+
572
+ // Check conventional commits
573
+ const conventionalCommits = commits.filter(c =>
574
+ c.message.startsWith('feat') ||
575
+ c.message.startsWith('fix') ||
576
+ c.message.startsWith('docs')
577
+ ).length;
578
+
579
+ if (conventionalCommits < commits.length * 0.5) {
580
+ insights.push({
581
+ type: 'info',
582
+ icon: '💡',
583
+ message: 'Consider using conventional commits (feat:, fix:, docs:) for better changelogs.'
584
+ });
585
+ } else {
586
+ insights.push({
587
+ type: 'success',
588
+ icon: '🎉',
589
+ message: `${((conventionalCommits / commits.length) * 100).toFixed(0)}% of commits follow conventional format. Great job!`
590
+ });
591
+ }
592
+
593
+ // Display insights
594
+ insights.forEach(insight => {
595
+ const color = insight.type === 'warning' ? chalk.yellow :
596
+ insight.type === 'success' ? chalk.green :
597
+ chalk.blue;
598
+
599
+ console.log(`${insight.icon} ${color(insight.message)}\n`);
600
+ });
601
+
602
+ if (insights.length === 0) {
603
+ console.log(chalk.green('✨ Everything looks good! Keep up the great work.\n'));
604
+ }
605
+ }
606
+
607
+ module.exports = {
608
+ timeline,
609
+ hotspots,
610
+ changelog,
611
+ blameAnalysis,
612
+ showHelp,
613
+ stats,
614
+ contributors,
615
+ compare,
616
+ search,
617
+ insights,
618
+ exportData,
619
+ visualize,
620
+ setLang
621
+ };
622
+
623
+ async function setLang(locale) {
624
+ const chalk = require('chalk');
625
+ const { setLanguage } = require('./config');
626
+ const { setLocale } = require('./i18n');
627
+
628
+ const validLocales = ['en', 'tr', 'auto'];
629
+
630
+ if (!validLocales.includes(locale)) {
631
+ console.log(chalk.red(`\n✗ Invalid language: ${locale}`));
632
+ console.log(chalk.gray(` Valid options: ${validLocales.join(', ')}\n`));
633
+ return;
634
+ }
635
+
636
+ if (setLanguage(locale)) {
637
+ if (locale === 'auto') {
638
+ console.log(chalk.green('\n✓ Language set to auto-detect'));
639
+ } else {
640
+ setLocale(locale);
641
+ console.log(chalk.green(`\n✓ Language set to: ${locale}`));
642
+ }
643
+ console.log(chalk.gray(` Config saved to: ~/.git-creeper/config.json\n`));
644
+ } else {
645
+ console.log(chalk.red('\n✗ Failed to save language preference\n'));
646
+ }
647
+ }
648
+
649
+ async function exportData(options) {
650
+ const format = options.format || 'json';
651
+ const output = options.output || 'git-creeper-report';
652
+
653
+ console.log(chalk.bold.cyan(`\n📦 Exporting data to ${format.toUpperCase()} format...\n`));
654
+
655
+ const log = await git.log();
656
+ const commits = log.all;
657
+
658
+ // Gather all data
659
+ const data = {
660
+ summary: {
661
+ totalCommits: commits.length,
662
+ firstCommit: commits[commits.length - 1]?.date,
663
+ lastCommit: commits[0]?.date,
664
+ contributors: {}
665
+ },
666
+ commits: commits.slice(0, 100).map(c => ({
667
+ hash: c.hash,
668
+ message: c.message,
669
+ author: c.author_name,
670
+ date: c.date
671
+ }))
672
+ };
673
+
674
+ commits.forEach(c => {
675
+ data.summary.contributors[c.author_name] = (data.summary.contributors[c.author_name] || 0) + 1;
676
+ });
677
+
678
+ const fs = require('fs');
679
+
680
+ if (format === 'json') {
681
+ const filename = `${output}.json`;
682
+ fs.writeFileSync(filename, JSON.stringify(data, null, 2));
683
+ console.log(chalk.green(`✅ Exported to ${filename}\n`));
684
+ } else if (format === 'markdown') {
685
+ const filename = `${output}.md`;
686
+ let md = `# Git-Creeper Report\n\n`;
687
+ md += `## Summary\n\n`;
688
+ md += `- Total Commits: ${data.summary.totalCommits}\n`;
689
+ md += `- Contributors: ${Object.keys(data.summary.contributors).length}\n\n`;
690
+ md += `## Top Contributors\n\n`;
691
+ Object.entries(data.summary.contributors)
692
+ .sort((a, b) => b[1] - a[1])
693
+ .slice(0, 10)
694
+ .forEach(([name, count]) => {
695
+ md += `- ${name}: ${count} commits\n`;
696
+ });
697
+ md += `\n## Recent Commits\n\n`;
698
+ data.commits.slice(0, 20).forEach(c => {
699
+ md += `- **${c.message}** (${c.hash.substring(0, 7)}) by ${c.author}\n`;
700
+ });
701
+ fs.writeFileSync(filename, md);
702
+ console.log(chalk.green(`✅ Exported to ${filename}\n`));
703
+ } else if (format === 'html') {
704
+ const filename = `${output}.html`;
705
+ let html = `<!DOCTYPE html>
706
+ <html>
707
+ <head>
708
+ <title>Git Story Report</title>
709
+ <style>
710
+ body { font-family: Arial, sans-serif; max-width: 1200px; margin: 0 auto; padding: 20px; background: #f5f5f5; }
711
+ h1 { color: #333; }
712
+ .summary { background: white; padding: 20px; border-radius: 8px; margin-bottom: 20px; }
713
+ .commit { background: white; padding: 15px; border-radius: 8px; margin-bottom: 10px; }
714
+ .hash { color: #666; font-family: monospace; }
715
+ .author { color: #0066cc; }
716
+ </style>
717
+ </head>
718
+ <body>
719
+ <h1>📖 Git Story Report</h1>
720
+ <div class="summary">
721
+ <h2>Summary</h2>
722
+ <p>Total Commits: <strong>${data.summary.totalCommits}</strong></p>
723
+ <p>Contributors: <strong>${Object.keys(data.summary.contributors).length}</strong></p>
724
+ </div>
725
+ <h2>Recent Commits</h2>`;
726
+ data.commits.slice(0, 20).forEach(c => {
727
+ html += `
728
+ <div class="commit">
729
+ <strong>${c.message}</strong><br>
730
+ <span class="hash">${c.hash.substring(0, 7)}</span> by <span class="author">${c.author}</span>
731
+ </div>`;
732
+ });
733
+ html += `
734
+ </body>
735
+ </html>`;
736
+ fs.writeFileSync(filename, html);
737
+ console.log(chalk.green(`✅ Exported to ${filename}\n`));
738
+ }
739
+ }
740
+
741
+ async function visualize(options) {
742
+ const type = options.type || 'frequency';
743
+
744
+ console.log(chalk.bold.magenta('\n╔════════════════════════════════════════════════════════════╗'));
745
+ console.log(chalk.bold.magenta(`║ 📊 Visualization: ${type}`.padEnd(61) + '║'));
746
+ console.log(chalk.bold.magenta('╚════════════════════════════════════════════════════════════╝\n'));
747
+
748
+ const log = await git.log();
749
+ const commits = log.all;
750
+
751
+ if (type === 'frequency') {
752
+ // Commit frequency over last 30 days
753
+ console.log(chalk.bold('📈 Commit Frequency (Last 30 Days)\n'));
754
+
755
+ const days = 30;
756
+ const frequency = Array(days).fill(0);
757
+ const now = new Date();
758
+
759
+ commits.forEach(c => {
760
+ const commitDate = new Date(c.date);
761
+ const daysAgo = Math.floor((now - commitDate) / (1000 * 60 * 60 * 24));
762
+ if (daysAgo < days) {
763
+ frequency[days - 1 - daysAgo]++;
764
+ }
765
+ });
766
+
767
+ const maxFreq = Math.max(...frequency, 1);
768
+
769
+ frequency.forEach((count, i) => {
770
+ const daysAgo = days - i;
771
+ const barLength = Math.floor((count / maxFreq) * 40);
772
+ const bar = '█'.repeat(barLength);
773
+ const color = count === 0 ? chalk.gray : count > maxFreq * 0.7 ? chalk.green : chalk.yellow;
774
+ console.log(`${String(daysAgo).padStart(2)}d ago: ${color(bar)} ${count}`);
775
+ });
776
+ console.log();
777
+
778
+ } else if (type === 'heatmap') {
779
+ // Contributor activity heatmap
780
+ console.log(chalk.bold('🔥 Contributor Activity Heatmap (Last 7 Days)\n'));
781
+
782
+ const contributors = {};
783
+ const days = 7;
784
+ const now = new Date();
785
+
786
+ commits.forEach(c => {
787
+ const commitDate = new Date(c.date);
788
+ const daysAgo = Math.floor((now - commitDate) / (1000 * 60 * 60 * 24));
789
+ if (daysAgo < days) {
790
+ if (!contributors[c.author_name]) {
791
+ contributors[c.author_name] = Array(days).fill(0);
792
+ }
793
+ contributors[c.author_name][days - 1 - daysAgo]++;
794
+ }
795
+ });
796
+
797
+ const dayLabels = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'];
798
+ console.log(' ' + dayLabels.map(d => d.padEnd(4)).join(''));
799
+
800
+ Object.entries(contributors).forEach(([name, activity]) => {
801
+ const nameShort = name.substring(0, 8).padEnd(8);
802
+ const heatmap = activity.map(count => {
803
+ if (count === 0) return chalk.gray('░░░░');
804
+ if (count <= 2) return chalk.yellow('▒▒▒▒');
805
+ if (count <= 5) return chalk.yellow('▓▓▓▓');
806
+ return chalk.red('████');
807
+ }).join('');
808
+ console.log(`${nameShort} ${heatmap}`);
809
+ });
810
+ console.log();
811
+
812
+ } else if (type === 'trends') {
813
+ // File change trends
814
+ console.log(chalk.bold('📁 File Change Trends (Top 10 Files)\n'));
815
+
816
+ const fileLog = await git.raw(['log', '--name-only', '--pretty=format:']);
817
+ const files = fileLog.split('\n').filter(f => f.trim());
818
+ const frequency = {};
819
+
820
+ files.forEach(file => {
821
+ frequency[file] = (frequency[file] || 0) + 1;
822
+ });
823
+
824
+ const sorted = Object.entries(frequency).sort((a, b) => b[1] - a[1]).slice(0, 10);
825
+ const maxCount = sorted[0]?.[1] || 1;
826
+
827
+ sorted.forEach(([file, count]) => {
828
+ const barLength = Math.floor((count / maxCount) * 40);
829
+ const bar = '█'.repeat(barLength);
830
+ const fileShort = file.length > 30 ? '...' + file.slice(-27) : file.padEnd(30);
831
+ console.log(`${chalk.cyan(fileShort)} ${chalk.red(bar)} ${count}`);
832
+ });
833
+ console.log();
834
+ }
835
+
836
+ console.log(chalk.dim('💡 Try: --type frequency, --type heatmap, or --type trends\n'));
837
+ }
838
+
839
+ // AI-Powered Features
840
+ async function smartCommitMessage() {
841
+ const { t } = require('./i18n');
842
+
843
+ try {
844
+ const git = simpleGit();
845
+ const status = await git.status();
846
+
847
+ if (status.files.length === 0) {
848
+ console.log(chalk.yellow(t('ai.noChanges')));
849
+ return;
850
+ }
851
+
852
+ console.log(chalk.blue.bold(t('ai.smartCommitTitle')));
853
+ console.log('─'.repeat(60));
854
+
855
+ // Analyze changed files
856
+ const changedFiles = status.files;
857
+ const suggestions = await analyzeChangesForCommit(changedFiles);
858
+
859
+ console.log(chalk.green('📝 ' + t('ai.suggestedMessages') + ':'));
860
+ suggestions.forEach((msg, index) => {
861
+ console.log(chalk.cyan(`${index + 1}. ${msg}`));
862
+ });
863
+
864
+ console.log('\n' + chalk.gray(t('ai.commitTip')));
865
+
866
+ } catch (error) {
867
+ console.error(chalk.red(t('error') + ':'), error.message);
868
+ }
869
+ }
870
+
871
+
872
+ async function analyzeChangesForCommit(files) {
873
+ const suggestions = [];
874
+
875
+ // Analyze file patterns
876
+ const hasNewFiles = files.some(f => f.index === 'A');
877
+ const hasDeletedFiles = files.some(f => f.working_dir === 'D');
878
+ const hasModifiedFiles = files.some(f => f.working_dir === 'M');
879
+
880
+ // Check file types
881
+ const jsFiles = files.filter(f => f.path.endsWith('.js') || f.path.endsWith('.ts'));
882
+ const cssFiles = files.filter(f => f.path.endsWith('.css') || f.path.endsWith('.scss'));
883
+ const configFiles = files.filter(f => f.path.includes('config') || f.path.includes('package.json'));
884
+ const testFiles = files.filter(f => f.path.includes('test') || f.path.includes('spec'));
885
+
886
+ // Generate smart suggestions
887
+ if (testFiles.length > 0) {
888
+ suggestions.push('test: add unit tests for new features');
889
+ suggestions.push('test: update test cases');
890
+ }
891
+
892
+ if (configFiles.length > 0) {
893
+ suggestions.push('config: update project configuration');
894
+ suggestions.push('chore: update dependencies');
895
+ }
896
+
897
+ if (hasNewFiles && jsFiles.length > 0) {
898
+ suggestions.push('feat: add new component/module');
899
+ suggestions.push('feat: implement new functionality');
900
+ }
901
+
902
+ if (hasModifiedFiles && jsFiles.length > 0) {
903
+ suggestions.push('fix: resolve bug in core functionality');
904
+ suggestions.push('refactor: improve code structure');
905
+ suggestions.push('perf: optimize performance');
906
+ }
907
+
908
+ if (cssFiles.length > 0) {
909
+ suggestions.push('style: update UI components');
910
+ suggestions.push('style: improve responsive design');
911
+ }
912
+
913
+ if (hasDeletedFiles) {
914
+ suggestions.push('refactor: remove unused code');
915
+ suggestions.push('cleanup: remove deprecated files');
916
+ }
917
+
918
+ // Default suggestions
919
+ if (suggestions.length === 0) {
920
+ suggestions.push('feat: add new feature');
921
+ suggestions.push('fix: resolve issue');
922
+ suggestions.push('docs: update documentation');
923
+ suggestions.push('refactor: improve code quality');
924
+ }
925
+
926
+ return suggestions.slice(0, 5); // Return top 5 suggestions
927
+ }
928
+
929
+ async function codeReviewAssistant() {
930
+ const { t } = require('./i18n');
931
+
932
+ try {
933
+ const git = simpleGit();
934
+ const diff = await git.diff(['--cached']);
935
+
936
+ if (!diff) {
937
+ console.log(chalk.yellow(t('noStagedChanges')));
938
+ return;
939
+ }
940
+
941
+ console.log(chalk.blue.bold(t('codeReviewTitle')));
942
+ console.log('─'.repeat(60));
943
+
944
+ const issues = await analyzeCodeForReview(diff);
945
+
946
+ if (issues.length === 0) {
947
+ console.log(chalk.green('✅ ' + t('noIssuesFound')));
948
+ return;
949
+ }
950
+
951
+ console.log(chalk.yellow('⚠️ ' + t('foundIssues') + ':'));
952
+ issues.forEach((issue, index) => {
953
+ console.log(chalk.red(`${index + 1}. ${issue.type}: ${issue.message}`));
954
+ if (issue.suggestion) {
955
+ console.log(chalk.gray(` 💡 ${issue.suggestion}`));
956
+ }
957
+ });
958
+
959
+ } catch (error) {
960
+ console.error(chalk.red(t('error') + ':'), error.message);
961
+ }
962
+ }
963
+
964
+ async function analyzeCodeForReview(diff) {
965
+ const issues = [];
966
+ const lines = diff.split('\n');
967
+
968
+ lines.forEach((line, index) => {
969
+ if (line.startsWith('+')) {
970
+ const code = line.substring(1).trim();
971
+
972
+ // Check for common issues
973
+ if (code.includes('console.log')) {
974
+ issues.push({
975
+ type: 'Debug Code',
976
+ message: 'Console.log statement found',
977
+ suggestion: 'Remove debug statements before commit'
978
+ });
979
+ }
980
+
981
+ if (code.includes('TODO') || code.includes('FIXME')) {
982
+ issues.push({
983
+ type: 'TODO/FIXME',
984
+ message: 'Unresolved TODO or FIXME comment',
985
+ suggestion: 'Complete the task or create an issue'
986
+ });
987
+ }
988
+
989
+ if (code.length > 120) {
990
+ issues.push({
991
+ type: 'Long Line',
992
+ message: 'Line exceeds 120 characters',
993
+ suggestion: 'Break long lines for better readability'
994
+ });
995
+ }
996
+
997
+ if (code.includes('var ')) {
998
+ issues.push({
999
+ type: 'Deprecated Syntax',
1000
+ message: 'Use of var instead of let/const',
1001
+ suggestion: 'Use let or const for better scoping'
1002
+ });
1003
+ }
1004
+
1005
+ if (code.includes('==') && !code.includes('===')) {
1006
+ issues.push({
1007
+ type: 'Loose Equality',
1008
+ message: 'Use of == instead of ===',
1009
+ suggestion: 'Use strict equality (===) for type safety'
1010
+ });
1011
+ }
1012
+ }
1013
+ });
1014
+
1015
+ return issues;
1016
+ }
1017
+
1018
+ async function bugPrediction() {
1019
+ const { t } = require('./i18n');
1020
+
1021
+ try {
1022
+ const git = simpleGit();
1023
+ const logs = await git.log(['--since=3 months ago', '--pretty=format:%H|%s|%an|%ad']);
1024
+
1025
+ console.log(chalk.blue.bold(t('bugPredictionTitle')));
1026
+ console.log('─'.repeat(60));
1027
+
1028
+ const riskAnalysis = await analyzeBugRisk(logs.all);
1029
+
1030
+ console.log(chalk.red('🚨 ' + t('highRiskFiles') + ':'));
1031
+ riskAnalysis.highRisk.forEach(file => {
1032
+ console.log(chalk.red(` • ${file.path} (Risk: ${file.score}%)`));
1033
+ console.log(chalk.gray(` ${file.reason}`));
1034
+ });
1035
+
1036
+ console.log('\n' + chalk.yellow('⚠️ ' + t('mediumRiskFiles') + ':'));
1037
+ riskAnalysis.mediumRisk.forEach(file => {
1038
+ console.log(chalk.yellow(` • ${file.path} (Risk: ${file.score}%)`));
1039
+ console.log(chalk.gray(` ${file.reason}`));
1040
+ });
1041
+
1042
+ } catch (error) {
1043
+ console.error(chalk.red(t('error') + ':'), error.message);
1044
+ }
1045
+ }
1046
+
1047
+ async function analyzeBugRisk(commits) {
1048
+ const fileStats = {};
1049
+ const bugKeywords = ['fix', 'bug', 'error', 'issue', 'problem', 'crash'];
1050
+
1051
+ // Analyze commit patterns
1052
+ for (const commit of commits) {
1053
+ const message = commit.message.toLowerCase();
1054
+ const isBugFix = bugKeywords.some(keyword => message.includes(keyword));
1055
+
1056
+ if (isBugFix) {
1057
+ try {
1058
+ const git = simpleGit();
1059
+ const files = await git.show(['--name-only', commit.hash]);
1060
+ const changedFiles = files.split('\n').filter(f => f.trim());
1061
+
1062
+ changedFiles.forEach(file => {
1063
+ if (!fileStats[file]) {
1064
+ fileStats[file] = { bugFixes: 0, totalChanges: 0 };
1065
+ }
1066
+ fileStats[file].bugFixes++;
1067
+ fileStats[file].totalChanges++;
1068
+ });
1069
+ } catch (error) {
1070
+ // Skip if commit not accessible
1071
+ }
1072
+ }
1073
+ }
1074
+
1075
+ // Calculate risk scores
1076
+ const riskFiles = Object.entries(fileStats)
1077
+ .map(([path, stats]) => ({
1078
+ path,
1079
+ score: Math.round((stats.bugFixes / stats.totalChanges) * 100),
1080
+ reason: `${stats.bugFixes} bug fixes out of ${stats.totalChanges} changes`
1081
+ }))
1082
+ .sort((a, b) => b.score - a.score);
1083
+
1084
+ return {
1085
+ highRisk: riskFiles.filter(f => f.score >= 50),
1086
+ mediumRisk: riskFiles.filter(f => f.score >= 25 && f.score < 50)
1087
+ };
1088
+ }
1089
+
1090
+ async function refactorSuggestions() {
1091
+ const { t } = require('./i18n');
1092
+
1093
+ try {
1094
+ console.log(chalk.blue.bold(t('refactorTitle')));
1095
+ console.log('─'.repeat(60));
1096
+
1097
+ const suggestions = await analyzeCodeQuality();
1098
+
1099
+ if (suggestions.length === 0) {
1100
+ console.log(chalk.green('✅ ' + t('codeQualityGood')));
1101
+ return;
1102
+ }
1103
+
1104
+ console.log(chalk.yellow('💡 ' + t('refactorSuggestions') + ':'));
1105
+ suggestions.forEach((suggestion, index) => {
1106
+ console.log(chalk.cyan(`${index + 1}. ${suggestion.title}`));
1107
+ console.log(chalk.gray(` 📁 ${suggestion.file}`));
1108
+ console.log(chalk.gray(` 📝 ${suggestion.description}`));
1109
+ console.log(chalk.gray(` 🎯 ${suggestion.benefit}`));
1110
+ console.log('');
1111
+ });
1112
+
1113
+ } catch (error) {
1114
+ console.error(chalk.red(t('error') + ':'), error.message);
1115
+ }
1116
+ }
1117
+
1118
+ async function analyzeCodeQuality() {
1119
+ const suggestions = [];
1120
+ const fs = require('fs');
1121
+ const path = require('path');
1122
+
1123
+ // Find JavaScript/TypeScript files
1124
+ const findFiles = (dir, fileList = []) => {
1125
+ const files = fs.readdirSync(dir);
1126
+
1127
+ files.forEach(file => {
1128
+ const filePath = path.join(dir, file);
1129
+ const stat = fs.statSync(filePath);
1130
+
1131
+ if (stat.isDirectory() && !file.startsWith('.') && file !== 'node_modules') {
1132
+ findFiles(filePath, fileList);
1133
+ } else if (file.endsWith('.js') || file.endsWith('.ts')) {
1134
+ fileList.push(filePath);
1135
+ }
1136
+ });
1137
+
1138
+ return fileList;
1139
+ };
1140
+
1141
+ try {
1142
+ const files = findFiles('.');
1143
+
1144
+ for (const file of files) {
1145
+ const content = fs.readFileSync(file, 'utf8');
1146
+ const lines = content.split('\n');
1147
+
1148
+ // Check file size
1149
+ if (lines.length > 300) {
1150
+ suggestions.push({
1151
+ title: 'Large File Detected',
1152
+ file: file,
1153
+ description: `File has ${lines.length} lines, consider splitting into smaller modules`,
1154
+ benefit: 'Improves maintainability and readability'
1155
+ });
1156
+ }
1157
+
1158
+ // Check for long functions
1159
+ let functionLines = 0;
1160
+ let inFunction = false;
1161
+
1162
+ lines.forEach(line => {
1163
+ if (line.includes('function') || line.includes('=>')) {
1164
+ inFunction = true;
1165
+ functionLines = 0;
1166
+ }
1167
+ if (inFunction) functionLines++;
1168
+ if (line.includes('}') && inFunction && functionLines > 50) {
1169
+ suggestions.push({
1170
+ title: 'Long Function Detected',
1171
+ file: file,
1172
+ description: `Function has ${functionLines} lines, consider breaking it down`,
1173
+ benefit: 'Improves testability and code reuse'
1174
+ });
1175
+ inFunction = false;
1176
+ }
1177
+ });
1178
+
1179
+ // Check for code duplication patterns
1180
+ const duplicateLines = findDuplicateLines(lines);
1181
+ if (duplicateLines.length > 0) {
1182
+ suggestions.push({
1183
+ title: 'Code Duplication Found',
1184
+ file: file,
1185
+ description: `${duplicateLines.length} duplicate code blocks detected`,
1186
+ benefit: 'Reduces maintenance overhead and bugs'
1187
+ });
1188
+ }
1189
+ }
1190
+ } catch (error) {
1191
+ // Skip files that can't be read
1192
+ }
1193
+
1194
+ return suggestions.slice(0, 10); // Return top 10 suggestions
1195
+ }
1196
+
1197
+ function findDuplicateLines(lines) {
1198
+ const lineCount = {};
1199
+ const duplicates = [];
1200
+
1201
+ lines.forEach(line => {
1202
+ const trimmed = line.trim();
1203
+ if (trimmed.length > 10 && !trimmed.startsWith('//') && !trimmed.startsWith('*')) {
1204
+ lineCount[trimmed] = (lineCount[trimmed] || 0) + 1;
1205
+ }
1206
+ });
1207
+
1208
+ Object.entries(lineCount).forEach(([line, count]) => {
1209
+ if (count > 1) {
1210
+ duplicates.push(line);
1211
+ }
1212
+ });
1213
+
1214
+ return duplicates;
1215
+ }
1216
+
1217
+ module.exports = {
1218
+ timeline,
1219
+ hotspots,
1220
+ changelog,
1221
+ blameAnalysis,
1222
+ showHelp,
1223
+ stats,
1224
+ contributors,
1225
+ compare,
1226
+ search,
1227
+ insights,
1228
+ exportData,
1229
+ visualize,
1230
+ setLang,
1231
+ smartCommitMessage,
1232
+ codeReviewAssistant,
1233
+ bugPrediction,
1234
+ refactorSuggestions
1235
+ };