@entro314labs/ai-changelog-generator 3.7.1 → 3.8.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.
@@ -9,7 +9,11 @@ import { CLIController } from '../src/infrastructure/cli/cli.controller.js'
9
9
 
10
10
  // Run the CLI with new refactored architecture using proper CLI controller
11
11
  const cliController = new CLIController()
12
- cliController.runCLI().catch((error) => {
12
+ cliController.runCLI().then(() => {
13
+ if (process.exitCode !== undefined) {
14
+ process.exit(process.exitCode)
15
+ }
16
+ }).catch((error) => {
13
17
  console.error('❌ CLI Error:', error.message)
14
18
  if (process.env.DEBUG) {
15
19
  console.error(error.stack)
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@entro314labs/ai-changelog-generator",
3
3
  "displayName": "AI Changelog Generator",
4
- "version": "3.7.1",
4
+ "version": "3.8.1",
5
5
  "type": "module",
6
6
  "description": "AI-powered changelog generator with MCP server support - works with most providers, online and local models",
7
7
  "main": "src/ai-changelog-generator.js",
8
8
  "types": "types/index.d.ts",
9
+ "workspaces": [
10
+ "vscode-extension"
11
+ ],
9
12
  "bin": {
10
13
  "ai-changelog": "./bin/ai-changelog.js",
11
14
  "ai-changelog-mcp": "./bin/ai-changelog-mcp.js"
@@ -23,15 +26,15 @@
23
26
  ],
24
27
  "dependencies": {
25
28
  "@anthropic-ai/sdk": "^0.71.2",
26
- "@aws-sdk/client-bedrock-runtime": "^3.958.0",
29
+ "@aws-sdk/client-bedrock-runtime": "^3.966.0",
27
30
  "@azure/identity": "^4.13.0",
28
31
  "@clack/prompts": "^0.11.0",
29
- "@google/genai": "^1.34.0",
32
+ "@google/genai": "^1.35.0",
30
33
  "@google/generative-ai": "^0.24.1",
31
34
  "@huggingface/hub": "^2.7.1",
32
35
  "@huggingface/inference": "^4.13.5",
33
36
  "@lmstudio/sdk": "^1.5.0",
34
- "@modelcontextprotocol/sdk": "^1.25.1",
37
+ "@modelcontextprotocol/sdk": "^1.25.2",
35
38
  "@modelcontextprotocol/server-filesystem": "2025.12.18",
36
39
  "boxen": "^8.0.1",
37
40
  "chalk": "^5.6.2",
@@ -42,13 +45,13 @@
42
45
  "gradient-string": "^3.0.0",
43
46
  "js-yaml": "^4.1.1",
44
47
  "ollama": "^0.6.3",
45
- "openai": "^6.15.0",
48
+ "openai": "^6.16.0",
46
49
  "ora": "^9.0.0",
47
50
  "yargs": "^18.0.0"
48
51
  },
49
52
  "devDependencies": {
50
53
  "@anthropic-ai/mcpb": "^2.1.2",
51
- "@types/node": "25.0.3",
54
+ "@types/node": "25.0.5",
52
55
  "@vitest/browser": "latest",
53
56
  "@vitest/coverage-v8": "latest",
54
57
  "@vitest/ui": "latest",
@@ -96,7 +99,7 @@
96
99
  },
97
100
  "engines": {
98
101
  "node": ">=22.21.1",
99
- "pnpm": ">=10.23.0"
102
+ "pnpm": ">=10.27.0"
100
103
  },
101
104
  "funding": {
102
105
  "type": "github",
@@ -108,8 +111,8 @@
108
111
  }
109
112
  },
110
113
  "volta": {
111
- "node": "22.22.1",
112
- "pnpm": "10.23.0"
114
+ "node": "22.21.1",
115
+ "pnpm": "10.27.0"
113
116
  },
114
117
  "scripts": {
115
118
  "start": "node bin/ai-changelog.js",
@@ -61,7 +61,12 @@ export class ChangelogOrchestrator {
61
61
  this.aiProvider = this.providerManager.getActiveProvider()
62
62
 
63
63
  // Create proper implementations using the new classes
64
- this.gitManager = new GitManager()
64
+ // Pass cwd/repositoryPath to GitManager so it runs commands in correct directory
65
+ const gitOptions = {}
66
+ if (this.options.cwd || this.options.repositoryPath) {
67
+ gitOptions.cwd = this.options.cwd || this.options.repositoryPath
68
+ }
69
+ this.gitManager = new GitManager(gitOptions)
65
70
  this.tagger = new CommitTagger()
66
71
  this.promptEngine = await this.createPromptEngine()
67
72
 
@@ -178,7 +183,11 @@ export class ChangelogOrchestrator {
178
183
  if (toTag) {
179
184
  effectiveUntil = toTag
180
185
  }
181
- console.log(colors.infoMessage(`📦 Generating changelog between tags: ${fromTag} → ${toTag || 'HEAD'}`))
186
+ console.log(
187
+ colors.infoMessage(
188
+ `📦 Generating changelog between tags: ${fromTag} → ${toTag || 'HEAD'}`
189
+ )
190
+ )
182
191
  }
183
192
 
184
193
  // Display author filter if specified
@@ -194,7 +203,11 @@ export class ChangelogOrchestrator {
194
203
  }
195
204
 
196
205
  // Generate changelog using the service
197
- const result = await this.changelogService.generateChangelog(version, effectiveSince, mergedOptions)
206
+ const result = await this.changelogService.generateChangelog(
207
+ version,
208
+ effectiveSince,
209
+ mergedOptions
210
+ )
198
211
 
199
212
  if (!result) {
200
213
  console.log(colors.warningMessage('No changelog generated'))
@@ -883,13 +896,6 @@ export class ChangelogOrchestrator {
883
896
  filesStaged: true,
884
897
  }
885
898
  }
886
-
887
- return {
888
- success: true,
889
- commitMessage,
890
- stagedFiles: finalStatus.staged.length,
891
- phase: 'staging-complete',
892
- }
893
899
  } catch (error) {
894
900
  console.error(colors.errorMessage(`Commit workflow error: ${error.message}`))
895
901
  throw error
@@ -7,49 +7,65 @@ import { ChangelogOrchestrator } from '../orchestrators/changelog.orchestrator.j
7
7
  export class ApplicationService {
8
8
  constructor(options = {}) {
9
9
  this.options = options
10
- this.configManager = new ConfigurationManager()
11
- this.orchestrator = new ChangelogOrchestrator(this.configManager, options)
10
+
11
+ // Normalize repositoryPath option (can be cwd or repositoryPath)
12
+ const repositoryPath = options.repositoryPath || options.cwd
13
+ if (repositoryPath) {
14
+ this.options.repositoryPath = repositoryPath
15
+ this.options.cwd = repositoryPath
16
+ }
17
+
18
+ this.configManager = new ConfigurationManager(options.configPath)
19
+ this.orchestrator = new ChangelogOrchestrator(this.configManager, this.options)
12
20
  this.initialized = false
21
+ this.initializationError = null
13
22
 
14
23
  // Apply options
15
24
  if (options.noColor || process.env.NO_COLOR) {
16
25
  colors.disable()
17
26
  }
18
27
 
19
- // Wait for orchestrator to initialize
20
- this.initializeAsync()
28
+ // Start initialization (don't await here to keep constructor sync)
29
+ this.initializationPromise = this.initializeAsync()
21
30
  }
22
31
 
23
32
  async initializeAsync() {
24
33
  try {
25
- // Wait for orchestrator services to be ready
26
- await this.orchestrator.ensureInitialized()
34
+ // Wait for orchestrator services to be ready with timeout
35
+ const timeoutPromise = new Promise((_, reject) =>
36
+ setTimeout(() => reject(new Error('Initialization timeout after 10 seconds')), 10000)
37
+ )
38
+
39
+ await Promise.race([
40
+ this.orchestrator.ensureInitialized(),
41
+ timeoutPromise
42
+ ])
43
+
27
44
  this.initialized = true
28
45
  } catch (error) {
46
+ this.initializationError = error
29
47
  console.error(
30
48
  colors.errorMessage('Application service initialization failed:'),
31
49
  error.message
32
50
  )
51
+ // Don't throw - allow graceful degradation
33
52
  }
34
53
  }
35
54
 
36
55
  async ensureInitialized() {
37
- if (!this.initialized) {
38
- await this.initializeAsync()
56
+ if (this.initialized) {
57
+ return
39
58
  }
40
- }
41
59
 
42
- async generateChangelog(options = {}) {
43
- try {
44
- const { version, since, author, tagRange, format, output, dryRun } = options
45
- return await this.orchestrator.generateChangelog(version, since, {
46
- author,
47
- tagRange,
48
- format,
49
- output,
50
- dryRun,
51
- })
52
- } catch (error) {
60
+ if (this.initializationError) {
61
+ throw new Error(`Service not initialized: ${this.initializationError.message}`)
62
+ }
63
+
64
+ // Wait for initialization to complete
65
+ await this.initializationPromise
66
+
67
+ if (this.initializationError) {
68
+ throw new Error(`Service initialization failed: ${this.initializationError.message}`)
53
69
  console.error(colors.errorMessage('Application service error:'), error.message)
54
70
  throw error
55
71
  }
@@ -13,6 +13,7 @@ import {
13
13
  getWorkingDirectoryChanges,
14
14
  performSemanticAnalysis,
15
15
  } from '../../shared/utils/utils.js'
16
+ import { EnhancedConsole } from '../../shared/utils/cli-ui.js'
16
17
 
17
18
  export class AnalysisEngine {
18
19
  constructor(gitService, aiAnalysisService, gitManager = null) {
@@ -51,16 +52,17 @@ export class AnalysisEngine {
51
52
  let enhancedChanges = []
52
53
 
53
54
  try {
54
- const changes = getWorkingDirectoryChanges()
55
+ // Get cwd from git manager if available
56
+ const cwd = this.gitService?.gitManager?.options?.cwd
57
+ console.log(`[AnalysisEngine] analyzeCurrentChanges called with cwd: ${cwd || 'undefined'}`)
58
+ const changes = getWorkingDirectoryChanges(cwd)
55
59
 
56
60
  if (changes.length === 0) {
57
- console.log(colors.infoMessage('No changes detected in working directory.'))
61
+ EnhancedConsole.info('No changes detected in working directory.')
58
62
  return { changes: [], analysis: null }
59
63
  }
60
64
 
61
- console.log(
62
- colors.processingMessage(`Analyzing ${changes.length} working directory changes...`)
63
- )
65
+ EnhancedConsole.processing(`Analyzing ${changes.length} working directory changes...`)
64
66
 
65
67
  // Enhanced file analysis with actual diff content
66
68
  enhancedChanges = []
@@ -102,9 +104,10 @@ export class AnalysisEngine {
102
104
  semanticChanges: { changeType: 'unknown', patterns: [], frameworks: [] },
103
105
  functionalImpact: { scope: 'local', severity: 'low' },
104
106
  })
107
+
105
108
  }
106
109
  } catch (error) {
107
- console.error(`Error processing change ${i}:`, change, error.message)
110
+ EnhancedConsole.error(`Error processing change ${i}:`, change, error.message)
108
111
  enhancedChanges.push({
109
112
  ...change,
110
113
  category: 'other',
@@ -130,7 +133,7 @@ export class AnalysisEngine {
130
133
  'working-directory'
131
134
  )
132
135
  } catch (error) {
133
- console.error('Error in AI analysis:', error.message)
136
+ EnhancedConsole.error('Error in AI analysis:', error.message)
134
137
  aiAnalysis = null
135
138
  }
136
139
  }
@@ -140,7 +143,7 @@ export class AnalysisEngine {
140
143
  try {
141
144
  ruleBasedAnalysis = this.analyzeChangesRuleBased(enhancedChanges)
142
145
  } catch (error) {
143
- console.error('Error in rule-based analysis:', error.message)
146
+ EnhancedConsole.error('Error in rule-based analysis:', error.message)
144
147
  ruleBasedAnalysis = {
145
148
  summary: 'Analysis failed',
146
149
  category: 'other',
@@ -154,7 +157,7 @@ export class AnalysisEngine {
154
157
  try {
155
158
  finalAnalysis = this.combineAnalyses(aiAnalysis, ruleBasedAnalysis)
156
159
  } catch (error) {
157
- console.error('Error combining analyses:', error.message)
160
+ EnhancedConsole.error('Error combining analyses:', error.message)
158
161
  finalAnalysis = {
159
162
  summary: `${enhancedChanges.length} working directory changes detected`,
160
163
  category: 'working-directory',
@@ -169,7 +172,7 @@ export class AnalysisEngine {
169
172
  summary: generateAnalysisSummary(enhancedChanges, finalAnalysis),
170
173
  }
171
174
  } catch (error) {
172
- console.error(colors.errorMessage('Error analyzing current changes:'), error.message)
175
+ EnhancedConsole.error('Error analyzing current changes:', error.message)
173
176
  // Return enhanced changes if they were created before the error
174
177
  return {
175
178
  changes: enhancedChanges,
@@ -189,13 +192,13 @@ export class AnalysisEngine {
189
192
 
190
193
  async analyzeRecentCommits(limit = 10, _config = {}) {
191
194
  try {
192
- console.log(colors.processingMessage(`Analyzing recent ${limit} commits...`))
195
+ EnhancedConsole.processing(`Analyzing recent ${limit} commits...`)
193
196
 
194
197
  const commits = await this.gitService.getCommitsSince(null)
195
198
  const recentCommits = commits.slice(0, limit)
196
199
 
197
200
  if (recentCommits.length === 0) {
198
- console.log(colors.infoMessage('No recent commits found.'))
201
+ EnhancedConsole.info('No recent commits found.')
199
202
  return { commits: [], analysis: null }
200
203
  }
201
204
 
@@ -219,7 +222,7 @@ export class AnalysisEngine {
219
222
  summary: this.generateCommitsSummary(analyzedCommits, overallAnalysis),
220
223
  }
221
224
  } catch (error) {
222
- console.error(colors.errorMessage('Error analyzing recent commits:'), error.message)
225
+ EnhancedConsole.error('Error analyzing recent commits:', error.message)
223
226
  return { commits: [], analysis: null, error: error.message }
224
227
  }
225
228
  }
@@ -259,11 +262,9 @@ export class AnalysisEngine {
259
262
  },
260
263
  }
261
264
  } catch (error) {
262
- console.warn(
263
- colors.warningMessage(
264
- `Failed to enhance analysis for commit ${commitAnalysis.hash}:`,
265
- error.message
266
- )
265
+ EnhancedConsole.warn(
266
+ `Failed to enhance analysis for commit ${commitAnalysis.hash}:`,
267
+ error.message
267
268
  )
268
269
  return commitAnalysis
269
270
  }
@@ -552,8 +553,9 @@ export class AnalysisEngine {
552
553
  totalCommits: commits.length,
553
554
  suggestions,
554
555
  }
556
+
555
557
  } catch (error) {
556
- console.warn(`Commit pattern analysis failed: ${error.message}`)
558
+ EnhancedConsole.warn(`Commit pattern analysis failed: ${error.message}`)
557
559
  return {
558
560
  patterns: [],
559
561
  compliance: 0,
@@ -621,8 +623,9 @@ export class AnalysisEngine {
621
623
  totalChanges: changes.length,
622
624
  details: `Detected ${detectedTypes.size} change types from ${changes.length} changes`,
623
625
  }
626
+
624
627
  } catch (error) {
625
- console.warn(`Change type detection failed: ${error.message}`)
628
+ EnhancedConsole.warn(`Change type detection failed: ${error.message}`)
626
629
  return {
627
630
  types: [],
628
631
  confidence: 'low',
@@ -706,8 +709,9 @@ export class AnalysisEngine {
706
709
  recommendations,
707
710
  filesAnalyzed: files.length,
708
711
  }
712
+
709
713
  } catch (error) {
710
- console.warn(`Code quality assessment failed: ${error.message}`)
714
+ EnhancedConsole.warn(`Code quality assessment failed: ${error.message}`)
711
715
  return {
712
716
  score: 0,
713
717
  issues: [`Assessment failed: ${error.message}`],
@@ -775,7 +779,7 @@ export class AnalysisEngine {
775
779
 
776
780
  return dependencies
777
781
  } catch (error) {
778
- console.warn(`Dependency analysis failed: ${error.message}`)
782
+ EnhancedConsole.warn(`Dependency analysis failed: ${error.message}`)
779
783
  return {
780
784
  added: [],
781
785
  removed: [],
@@ -1805,11 +1805,12 @@ export class ChangelogService {
1805
1805
  ]
1806
1806
 
1807
1807
  patterns.forEach((pattern) => {
1808
- let match
1809
- while ((match = pattern.exec(content)) !== null) {
1808
+ let match = pattern.exec(content)
1809
+ while (match !== null) {
1810
1810
  if (match[1] && !functions.includes(match[1])) {
1811
1811
  functions.push(match[1])
1812
1812
  }
1813
+ match = pattern.exec(content)
1813
1814
  }
1814
1815
  })
1815
1816
 
@@ -2186,7 +2187,7 @@ export class ChangelogService {
2186
2187
 
2187
2188
  // Add context information for detailed modes
2188
2189
  if (options.analysisMode === 'detailed' || options.analysisMode === 'enterprise') {
2189
- changelog += this.generateContextSection(result.context)
2190
+ changelog += this.generateContextSection(result.summary)
2190
2191
  }
2191
2192
 
2192
2193
  return {
@@ -2209,6 +2210,8 @@ export class ChangelogService {
2209
2210
  // Enhanced analysis of changes with diff content for AI analysis
2210
2211
  const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges)
2211
2212
  const changesSummary = summarizeFileChanges(enhancedChanges)
2213
+
2214
+ console.log('DEBUG generateComprehensiveWorkspaceChangelog changesSummary:', JSON.stringify(changesSummary))
2212
2215
 
2213
2216
  // Use DiffProcessor for intelligent processing
2214
2217
  const analysisMode = options.analysisMode || 'standard'
@@ -2244,6 +2247,9 @@ export class ChangelogService {
2244
2247
  }
2245
2248
 
2246
2249
  async generateCommitStyleWorkingDirectoryEntries(options = {}) {
2250
+ console.log('DEBUG: generateCommitStyleWorkingDirectoryEntries called')
2251
+ console.log('DEBUG: summarizeFileChanges type:', typeof summarizeFileChanges)
2252
+
2247
2253
  // Use provided working directory analysis or get current changes
2248
2254
  let rawChanges
2249
2255
  if (options.workingDirAnalysis?.changes) {
@@ -2260,6 +2266,9 @@ export class ChangelogService {
2260
2266
  // Enhanced analysis of changes with diff content for AI analysis
2261
2267
  const enhancedChanges = await this.enhanceChangesWithDiff(rawChanges)
2262
2268
  const changesSummary = summarizeFileChanges(enhancedChanges)
2269
+
2270
+ console.log('DEBUG: enhancedChanges length:', enhancedChanges.length)
2271
+ console.log('DEBUG: changesSummary:', changesSummary)
2263
2272
 
2264
2273
  // Use DiffProcessor for intelligent diff processing
2265
2274
  const analysisMode =
@@ -2272,6 +2281,10 @@ export class ChangelogService {
2272
2281
 
2273
2282
  const processedResult = diffProcessor.processFiles(enhancedChanges)
2274
2283
  const { processedFiles, patterns } = processedResult
2284
+
2285
+ console.log('DEBUG: changesSummary type:', typeof changesSummary)
2286
+ console.log('DEBUG: changesSummary:', JSON.stringify(changesSummary))
2287
+ console.log('DEBUG: processedResult:', JSON.stringify(processedResult))
2275
2288
 
2276
2289
  // Build pattern summary if patterns were detected
2277
2290
  const patternSummary =
@@ -2507,6 +2520,8 @@ Generate one entry per file or logical change group. Only describe what you can
2507
2520
  }
2508
2521
 
2509
2522
  async generateAIChangelogContentFromChanges(changes, changesSummary, analysisMode = 'standard') {
2523
+ console.log('DEBUG generateAIChangelogContentFromChanges changesSummary:', JSON.stringify(changesSummary))
2524
+
2510
2525
  if (!this.aiAnalysisService.hasAI) {
2511
2526
  console.log(colors.infoMessage('AI not available, using rule-based analysis...'))
2512
2527
  return this.generateBasicChangelogContentFromChanges(changes, changesSummary)
@@ -2548,6 +2563,7 @@ Generate one entry per file or logical change group. Only describe what you can
2548
2563
  .join('\n')
2549
2564
 
2550
2565
  // Build comprehensive prompt with processed changes
2566
+ console.log('DEBUG before prompt, changesSummary:', changesSummary ? 'defined' : 'undefined', 'totalFiles:', changesSummary?.totalFiles)
2551
2567
  const prompt = `Generate a comprehensive AI changelog for the following working directory changes:
2552
2568
 
2553
2569
  **Analysis Mode**: ${analysisMode}
@@ -2727,13 +2743,55 @@ ONLY describe what you can literally see in the diff content. Do not invent conn
2727
2743
  }
2728
2744
 
2729
2745
  generateContextSection(context) {
2746
+ if (!context) {
2747
+ return ''
2748
+ }
2749
+
2730
2750
  let section = '## Context Analysis\n\n'
2731
- section += `- **Total Files:** ${context.totalFiles}\n`
2732
- section += `- **Primary Category:** ${context.primaryCategory}\n`
2733
- section += `- **Risk Level:** ${context.riskLevel}\n`
2734
- section += `- **Complexity:** ${context.complexity}\n\n`
2751
+
2752
+ // Handle totalFiles from either structure
2753
+ if (context.totalFiles !== undefined) {
2754
+ section += `- **Total Files:** ${context.totalFiles}\n`
2755
+ }
2756
+
2757
+ // Handle categories from summarizeFileChanges structure
2758
+ if (context.categories && typeof context.categories === 'object') {
2759
+ const categoryNames = Object.keys(context.categories)
2760
+ if (categoryNames.length > 0) {
2761
+ section += `- **Affected Areas:** ${categoryNames.join(', ')}\n`
2762
+ }
2763
+ }
2764
+
2765
+ // Handle stats if available
2766
+ if (context.stats) {
2767
+ const statParts = []
2768
+ if (context.stats.added > 0) statParts.push(`${context.stats.added} added`)
2769
+ if (context.stats.modified > 0) statParts.push(`${context.stats.modified} modified`)
2770
+ if (context.stats.deleted > 0) statParts.push(`${context.stats.deleted} deleted`)
2771
+ if (statParts.length > 0) {
2772
+ section += `- **Changes:** ${statParts.join(', ')}\n`
2773
+ }
2774
+ }
2775
+
2776
+ // Handle languages if available
2777
+ if (context.languages && Array.isArray(context.languages) && context.languages.length > 0) {
2778
+ section += `- **Languages:** ${context.languages.join(', ')}\n`
2779
+ }
2780
+
2781
+ // Legacy properties for backward compatibility
2782
+ if (context.primaryCategory) {
2783
+ section += `- **Primary Category:** ${context.primaryCategory}\n`
2784
+ }
2785
+ if (context.riskLevel) {
2786
+ section += `- **Risk Level:** ${context.riskLevel}\n`
2787
+ }
2788
+ if (context.complexity) {
2789
+ section += `- **Complexity:** ${context.complexity}\n`
2790
+ }
2791
+
2792
+ section += '\n'
2735
2793
 
2736
- if (context.recommendations.length > 0) {
2794
+ if (context.recommendations && Array.isArray(context.recommendations) && context.recommendations.length > 0) {
2737
2795
  section += '### Recommendations\n\n'
2738
2796
  context.recommendations.forEach((rec) => {
2739
2797
  section += `- ${rec}\n`
@@ -14,6 +14,7 @@ export class GitManager {
14
14
  encoding: 'utf8',
15
15
  timeout: 30000,
16
16
  stdio: 'pipe',
17
+ cwd: undefined, // Will use process.cwd() if not specified
17
18
  ...options,
18
19
  }
19
20
 
@@ -56,11 +57,18 @@ export class GitManager {
56
57
  */
57
58
  execGit(command) {
58
59
  try {
59
- return execSync(command, {
60
+ const execOptions = {
60
61
  encoding: this.options.encoding,
61
62
  stdio: this.options.stdio,
62
63
  timeout: this.options.timeout,
63
- })
64
+ }
65
+
66
+ // Add cwd if specified
67
+ if (this.options.cwd) {
68
+ execOptions.cwd = this.options.cwd
69
+ }
70
+
71
+ return execSync(command, execOptions)
64
72
  } catch (error) {
65
73
  throw this._createGitError(command, error)
66
74
  }
@@ -239,9 +247,7 @@ export class GitManager {
239
247
  */
240
248
  getCommitsByAuthor(author, limit = 50) {
241
249
  try {
242
- const output = this.execGitSafe(
243
- `git log --author="${author}" --oneline -n ${limit}`
244
- )
250
+ const output = this.execGitSafe(`git log --author="${author}" --oneline -n ${limit}`)
245
251
  return output
246
252
  .split('\n')
247
253
  .filter((line) => line.trim())
@@ -341,7 +347,9 @@ export class GitManager {
341
347
  .filter(Boolean)
342
348
 
343
349
  // Get summary stats
344
- const summaryMatch = statOutput.match(/(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/)
350
+ const summaryMatch = statOutput.match(
351
+ /(\d+) files? changed(?:, (\d+) insertions?)?(?:, (\d+) deletions?)?/
352
+ )
345
353
 
346
354
  return {
347
355
  ref: stashRef,
@@ -351,7 +359,7 @@ export class GitManager {
351
359
  stats: {
352
360
  filesChanged: summaryMatch ? parseInt(summaryMatch[1], 10) : files.length,
353
361
  insertions: summaryMatch && summaryMatch[2] ? parseInt(summaryMatch[2], 10) : 0,
354
- deletions: summaryMatch && summaryMatch[3] ? parseInt(summaryMatch[3], 10) : 0,
362
+ deletions: summaryMatch?.[3] ? parseInt(summaryMatch[3], 10) : 0,
355
363
  },
356
364
  }
357
365
  } catch {