@entro314labs/ai-changelog-generator 3.6.1 → 3.7.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.
@@ -1,566 +0,0 @@
1
- import colors from '../../shared/constants/colors.js'
2
- import { getWorkingDirectoryChanges } from '../../shared/utils/utils.js'
3
- import { ChangelogService } from './changelog.service.js'
4
-
5
- /**
6
- * WorkspaceChangelogService extends ChangelogService with workspace-specific functionality
7
- * for analyzing uncommitted changes and workspace state
8
- *
9
- * @deprecated This class is being phased out. The workspace changelog logic has been
10
- * consolidated into ChangelogService. This class is maintained for backward compatibility
11
- * with existing tests but should not be used in new code.
12
- */
13
- export class WorkspaceChangelogService extends ChangelogService {
14
- constructor(gitService, aiAnalysisService, analysisEngine = null, configManager = null) {
15
- super(gitService, aiAnalysisService, analysisEngine, configManager)
16
- this.workspaceMetrics = {
17
- unstagedFiles: 0,
18
- stagedFiles: 0,
19
- untrackedFiles: 0,
20
- modifiedLines: 0,
21
- }
22
- }
23
-
24
- /**
25
- * Analyze workspace changes without committing
26
- * @returns {Promise<Object>} Workspace analysis results
27
- */
28
- async analyzeWorkspaceChanges() {
29
- console.log(colors.processingMessage('🔍 Analyzing workspace changes...'))
30
-
31
- try {
32
- // Get git status information using utility function
33
- const changes = getWorkingDirectoryChanges()
34
-
35
- // Categorize changes
36
- const status = {
37
- staged: [],
38
- unstaged: [],
39
- untracked: [],
40
- }
41
-
42
- changes.forEach((change) => {
43
- const statusCode = change.status || '??'
44
- if (statusCode.startsWith('??')) {
45
- status.untracked.push(change.filePath)
46
- } else if (statusCode[0] !== ' ' && statusCode[0] !== '?') {
47
- status.staged.push(change.filePath)
48
- } else {
49
- status.unstaged.push(change.filePath)
50
- }
51
- })
52
-
53
- // Update workspace metrics
54
- this.workspaceMetrics.unstagedFiles = status.unstaged?.length || 0
55
- this.workspaceMetrics.stagedFiles = status.staged?.length || 0
56
- this.workspaceMetrics.untrackedFiles = status.untracked?.length || 0
57
-
58
- // Get detailed diff for staged/unstaged changes (empty for now)
59
- const diff = ''
60
-
61
- // Use analysis engine if available
62
- let analysis = null
63
- if (this.analysisEngine) {
64
- analysis = await this.analysisEngine.analyzeCurrentChanges()
65
- }
66
-
67
- return {
68
- status,
69
- diff,
70
- analysis,
71
- metrics: this.workspaceMetrics,
72
- }
73
- } catch (error) {
74
- console.error(colors.errorMessage(`Failed to analyze workspace: ${error.message}`))
75
- throw error
76
- }
77
- }
78
-
79
- /**
80
- * Generate changelog preview for workspace changes
81
- * @returns {Promise<string>} Preview changelog content
82
- */
83
- async generateWorkspacePreview() {
84
- console.log(colors.processingMessage('📝 Generating workspace preview...'))
85
-
86
- const workspaceData = await this.analyzeWorkspaceChanges()
87
-
88
- if (!workspaceData.analysis || workspaceData.analysis.changes.length === 0) {
89
- return colors.infoMessage('No significant workspace changes detected.')
90
- }
91
-
92
- // Generate preview using parent class methods
93
- const previewContent = await this.generateChangelogFromAnalysis(workspaceData.analysis)
94
-
95
- return previewContent
96
- }
97
-
98
- /**
99
- * Get workspace statistics
100
- * @returns {Object} Workspace statistics
101
- */
102
- getWorkspaceStats() {
103
- return {
104
- ...this.workspaceMetrics,
105
- hasChanges: this.hasWorkspaceChanges(),
106
- summary: this.getWorkspaceSummary(),
107
- }
108
- }
109
-
110
- /**
111
- * Check if workspace has any changes
112
- * @returns {boolean} True if workspace has changes
113
- */
114
- hasWorkspaceChanges() {
115
- return (
116
- this.workspaceMetrics.unstagedFiles > 0 ||
117
- this.workspaceMetrics.stagedFiles > 0 ||
118
- this.workspaceMetrics.untrackedFiles > 0
119
- )
120
- }
121
-
122
- /**
123
- * Get workspace summary string
124
- * @returns {string} Human-readable workspace summary
125
- */
126
- getWorkspaceSummary() {
127
- const { unstagedFiles, stagedFiles, untrackedFiles } = this.workspaceMetrics
128
- const parts = []
129
-
130
- if (stagedFiles > 0) {
131
- parts.push(`${stagedFiles} staged`)
132
- }
133
- if (unstagedFiles > 0) {
134
- parts.push(`${unstagedFiles} unstaged`)
135
- }
136
- if (untrackedFiles > 0) {
137
- parts.push(`${untrackedFiles} untracked`)
138
- }
139
-
140
- return parts.length > 0 ? parts.join(', ') : 'No changes'
141
- }
142
-
143
- /**
144
- * Validate workspace state before operations
145
- * @returns {Promise<boolean>} True if workspace is valid
146
- */
147
- async validateWorkspace() {
148
- try {
149
- // Check if we're in a git repository
150
- let isGitRepo = false
151
- try {
152
- const { execSync } = await import('node:child_process')
153
- execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' })
154
- isGitRepo = true
155
- } catch {
156
- isGitRepo = false
157
- }
158
-
159
- if (!isGitRepo) {
160
- console.error(colors.errorMessage('Not in a git repository'))
161
- return false
162
- }
163
-
164
- const changes = getWorkingDirectoryChanges()
165
-
166
- // Check if workspace has changes
167
- if (!this.hasWorkspaceChanges() && changes.length === 0) {
168
- console.log(colors.infoMessage('No workspace changes detected'))
169
- return false
170
- }
171
-
172
- return true
173
- } catch (error) {
174
- console.error(colors.errorMessage(`Workspace validation failed: ${error.message}`))
175
- return false
176
- }
177
- }
178
-
179
- /**
180
- * Generate comprehensive workspace changelog
181
- * @returns {Promise<string>} Comprehensive changelog content
182
- */
183
- async generateComprehensiveWorkspaceChangelog() {
184
- console.log(colors.processingMessage('📋 Generating comprehensive workspace changelog...'))
185
-
186
- try {
187
- const workspaceData = await this.analyzeWorkspaceChanges()
188
-
189
- if (!workspaceData.analysis) {
190
- return this.generateBasicWorkspaceChangelog(workspaceData)
191
- }
192
-
193
- return await this.generateAIChangelogContentFromChanges(workspaceData.analysis.changes)
194
- } catch (error) {
195
- console.error(
196
- colors.errorMessage(`Failed to generate comprehensive changelog: ${error.message}`)
197
- )
198
- throw error
199
- }
200
- }
201
-
202
- /**
203
- * Generate AI-powered changelog content from changes
204
- * @param {Array} changes - Array of change objects
205
- * @returns {Promise<string>} Generated changelog content
206
- */
207
- async generateAIChangelogContentFromChanges(changes) {
208
- if (!changes || changes.length === 0) {
209
- return colors.infoMessage('No changes to process for changelog.')
210
- }
211
-
212
- try {
213
- // Use AI analysis service if available
214
- if (this.aiAnalysisService && this.aiAnalysisService.hasAI) {
215
- const enhancedChanges = await this.enhanceChangesWithDiff(changes)
216
- return await this.generateChangelogContent(enhancedChanges)
217
- }
218
-
219
- // Fallback to rule-based generation
220
- return this.generateRuleBasedChangelog(changes)
221
- } catch (error) {
222
- console.error(colors.errorMessage(`AI changelog generation failed: ${error.message}`))
223
- return this.generateRuleBasedChangelog(changes)
224
- }
225
- }
226
-
227
- /**
228
- * Enhance changes with diff information
229
- * @param {Array} changes - Array of change objects
230
- * @returns {Promise<Array>} Enhanced changes with diff data
231
- */
232
- async enhanceChangesWithDiff(changes) {
233
- const enhancedChanges = []
234
-
235
- for (const change of changes) {
236
- try {
237
- // For workspace changelog service, we don't have detailed diffs
238
- // Just add basic enhancement
239
- enhancedChanges.push({
240
- ...change,
241
- diff: '',
242
- complexity: this.assessChangeComplexity(''),
243
- impact: this.assessChangeImpact(change.file, ''),
244
- })
245
- } catch (error) {
246
- console.warn(
247
- colors.warningMessage(`Failed to enhance change for ${change.file}: ${error.message}`)
248
- )
249
- enhancedChanges.push(change)
250
- }
251
- }
252
-
253
- return enhancedChanges
254
- }
255
-
256
- /**
257
- * Generate changelog content from enhanced changes
258
- * @param {Array} enhancedChanges - Enhanced change objects
259
- * @returns {Promise<string>} Generated changelog content
260
- */
261
- async generateChangelogContent(enhancedChanges) {
262
- const sections = {
263
- features: [],
264
- fixes: [],
265
- improvements: [],
266
- docs: [],
267
- tests: [],
268
- chores: [],
269
- }
270
-
271
- // Categorize changes
272
- for (const change of enhancedChanges) {
273
- const category = this.categorizeChange(change)
274
- if (sections[category]) {
275
- sections[category].push(change)
276
- }
277
- }
278
-
279
- // Generate markdown content
280
- let content = '# Workspace Changes\n\n'
281
-
282
- for (const [section, changes] of Object.entries(sections)) {
283
- if (changes.length > 0) {
284
- content += `## ${this.formatSectionTitle(section)}\n\n`
285
-
286
- for (const change of changes) {
287
- content += `- ${this.formatChangeEntry(change)}\n`
288
- }
289
-
290
- content += '\n'
291
- }
292
- }
293
-
294
- return content
295
- }
296
-
297
- /**
298
- * Generate commit-style working directory entries
299
- * @returns {Promise<Array>} Array of commit-style entries
300
- */
301
- async generateCommitStyleWorkingDirectoryEntries() {
302
- const workspaceData = await this.analyzeWorkspaceChanges()
303
- const entries = []
304
-
305
- if (workspaceData.status) {
306
- // Process staged files
307
- if (workspaceData.status.staged) {
308
- for (const file of workspaceData.status.staged) {
309
- entries.push({
310
- type: 'staged',
311
- file,
312
- message: `Add: ${file}`,
313
- timestamp: new Date().toISOString(),
314
- })
315
- }
316
- }
317
-
318
- // Process unstaged files
319
- if (workspaceData.status.unstaged) {
320
- for (const file of workspaceData.status.unstaged) {
321
- entries.push({
322
- type: 'unstaged',
323
- file,
324
- message: `Modify: ${file}`,
325
- timestamp: new Date().toISOString(),
326
- })
327
- }
328
- }
329
-
330
- // Process untracked files
331
- if (workspaceData.status.untracked) {
332
- for (const file of workspaceData.status.untracked) {
333
- entries.push({
334
- type: 'untracked',
335
- file,
336
- message: `Create: ${file}`,
337
- timestamp: new Date().toISOString(),
338
- })
339
- }
340
- }
341
- }
342
-
343
- return entries
344
- }
345
-
346
- /**
347
- * Generate workspace changelog
348
- * @returns {Promise<string>} Workspace changelog content
349
- */
350
- async generateWorkspaceChangelog() {
351
- console.log(colors.processingMessage('📝 Generating workspace changelog...'))
352
-
353
- try {
354
- const entries = await this.generateCommitStyleWorkingDirectoryEntries()
355
-
356
- if (entries.length === 0) {
357
- return colors.infoMessage('No workspace changes to include in changelog.')
358
- }
359
-
360
- let content = '# Workspace Changes\n\n'
361
- content += `Generated on: ${new Date().toLocaleString()}\n\n`
362
-
363
- const groupedEntries = this.groupEntriesByType(entries)
364
-
365
- for (const [type, typeEntries] of Object.entries(groupedEntries)) {
366
- if (typeEntries.length > 0) {
367
- content += `## ${this.formatEntryType(type)} (${typeEntries.length})\n\n`
368
-
369
- for (const entry of typeEntries) {
370
- content += `- ${entry.message}\n`
371
- }
372
-
373
- content += '\n'
374
- }
375
- }
376
-
377
- return content
378
- } catch (error) {
379
- console.error(colors.errorMessage(`Failed to generate workspace changelog: ${error.message}`))
380
- throw error
381
- }
382
- }
383
-
384
- // Helper methods
385
-
386
- /**
387
- * Generate basic workspace changelog without AI
388
- */
389
- generateBasicWorkspaceChangelog(workspaceData) {
390
- let content = '# Workspace Changes\n\n'
391
-
392
- if (workspaceData.metrics) {
393
- content += '## Summary\n\n'
394
- content += `- Staged files: ${workspaceData.metrics.stagedFiles}\n`
395
- content += `- Unstaged files: ${workspaceData.metrics.unstagedFiles}\n`
396
- content += `- Untracked files: ${workspaceData.metrics.untrackedFiles}\n\n`
397
- }
398
-
399
- return content
400
- }
401
-
402
- /**
403
- * Generate rule-based changelog fallback
404
- */
405
- generateRuleBasedChangelog(changes) {
406
- let content = '# Changes Summary\n\n'
407
-
408
- for (const change of changes) {
409
- content += `- ${change.type || 'Modified'}: ${change.file}\n`
410
- }
411
-
412
- return content
413
- }
414
-
415
- /**
416
- * Assess change complexity
417
- */
418
- assessChangeComplexity(diff) {
419
- if (!diff) return 'low'
420
-
421
- const lines = diff.split('\n').length
422
- if (lines > 100) return 'high'
423
- if (lines > 20) return 'medium'
424
- return 'low'
425
- }
426
-
427
- /**
428
- * Assess change impact
429
- */
430
- assessChangeImpact(file, diff) {
431
- const criticalFiles = ['package.json', 'README.md', 'Dockerfile', '.env']
432
- const isCritical = criticalFiles.some((critical) => file.includes(critical))
433
-
434
- if (isCritical) return 'high'
435
- if (file.includes('test') || file.includes('spec')) return 'low'
436
- return 'medium'
437
- }
438
-
439
- /**
440
- * Categorize a change
441
- */
442
- categorizeChange(change) {
443
- const file = change.file.toLowerCase()
444
-
445
- if (file.includes('test') || file.includes('spec')) return 'tests'
446
- if (file.includes('readme') || file.includes('doc')) return 'docs'
447
- if (file.includes('fix') || change.type === 'fix') return 'fixes'
448
- if (file.includes('feat') || change.type === 'feature') return 'features'
449
- if (file.includes('config') || file.includes('package')) return 'chores'
450
-
451
- return 'improvements'
452
- }
453
-
454
- /**
455
- * Format section title
456
- */
457
- formatSectionTitle(section) {
458
- const titles = {
459
- features: 'Features',
460
- fixes: 'Bug Fixes',
461
- improvements: 'Improvements',
462
- docs: 'Documentation',
463
- tests: 'Tests',
464
- chores: 'Maintenance',
465
- }
466
- return titles[section] || section
467
- }
468
-
469
- /**
470
- * Format change entry
471
- */
472
- formatChangeEntry(change) {
473
- const impact = change.impact ? `[${change.impact}]` : ''
474
- const complexity = change.complexity ? `{${change.complexity}}` : ''
475
- return `${change.file} ${impact} ${complexity}`.trim()
476
- }
477
-
478
- /**
479
- * Group entries by type
480
- */
481
- groupEntriesByType(entries) {
482
- return entries.reduce((groups, entry) => {
483
- const type = entry.type || 'unknown'
484
- if (!groups[type]) groups[type] = []
485
- groups[type].push(entry)
486
- return groups
487
- }, {})
488
- }
489
-
490
- /**
491
- * Format entry type for display
492
- */
493
- formatEntryType(type) {
494
- const types = {
495
- staged: 'Staged Changes',
496
- unstaged: 'Unstaged Changes',
497
- untracked: 'New Files',
498
- }
499
- return types[type] || type
500
- }
501
-
502
- /**
503
- * Initialize workspace for analysis
504
- * @returns {Promise<boolean>} True if initialization successful
505
- */
506
- async initializeWorkspace() {
507
- try {
508
- console.log(colors.processingMessage('🔧 Initializing workspace...'))
509
-
510
- // Reset metrics
511
- this.cleanup()
512
-
513
- // Validate git repository
514
- const isValid = await this.validateWorkspace()
515
- if (!isValid) {
516
- return false
517
- }
518
-
519
- // Perform initial analysis
520
- await this.analyzeWorkspaceChanges()
521
-
522
- console.log(colors.successMessage('✅ Workspace initialized successfully'))
523
- return true
524
- } catch (error) {
525
- console.error(colors.errorMessage(`Failed to initialize workspace: ${error.message}`))
526
- return false
527
- }
528
- }
529
-
530
- /**
531
- * Validate workspace structure
532
- * @returns {boolean} True if workspace structure is valid
533
- */
534
- validateWorkspaceStructure() {
535
- try {
536
- // Check if we have the required services
537
- if (!this.gitService) {
538
- console.error(colors.errorMessage('Git service not available'))
539
- return false
540
- }
541
-
542
- // Check if workspace has any changes to analyze
543
- if (!this.hasWorkspaceChanges()) {
544
- console.log(colors.infoMessage('No workspace changes detected'))
545
- return true // Still valid, just nothing to do
546
- }
547
-
548
- return true
549
- } catch (error) {
550
- console.error(colors.errorMessage(`Workspace structure validation failed: ${error.message}`))
551
- return false
552
- }
553
- }
554
-
555
- /**
556
- * Clean up workspace analysis resources
557
- */
558
- cleanup() {
559
- this.workspaceMetrics = {
560
- unstagedFiles: 0,
561
- stagedFiles: 0,
562
- untrackedFiles: 0,
563
- modifiedLines: 0,
564
- }
565
- }
566
- }