@entro314labs/ai-changelog-generator 3.1.1 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +383 -877
  2. package/README.md +8 -3
  3. package/ai-changelog-mcp.sh +0 -0
  4. package/ai-changelog.sh +0 -0
  5. package/bin/ai-changelog-dxt.js +9 -9
  6. package/bin/ai-changelog-mcp.js +19 -17
  7. package/bin/ai-changelog.js +6 -6
  8. package/package.json +80 -48
  9. package/src/ai-changelog-generator.js +83 -81
  10. package/src/application/orchestrators/changelog.orchestrator.js +791 -516
  11. package/src/application/services/application.service.js +137 -128
  12. package/src/cli.js +76 -57
  13. package/src/domains/ai/ai-analysis.service.js +289 -209
  14. package/src/domains/analysis/analysis.engine.js +253 -193
  15. package/src/domains/changelog/changelog.service.js +1062 -784
  16. package/src/domains/changelog/workspace-changelog.service.js +420 -249
  17. package/src/domains/git/git-repository.analyzer.js +348 -258
  18. package/src/domains/git/git.service.js +132 -112
  19. package/src/infrastructure/cli/cli.controller.js +390 -274
  20. package/src/infrastructure/config/configuration.manager.js +220 -190
  21. package/src/infrastructure/interactive/interactive-staging.service.js +154 -135
  22. package/src/infrastructure/interactive/interactive-workflow.service.js +200 -159
  23. package/src/infrastructure/mcp/mcp-server.service.js +208 -207
  24. package/src/infrastructure/metrics/metrics.collector.js +140 -123
  25. package/src/infrastructure/providers/core/base-provider.js +87 -40
  26. package/src/infrastructure/providers/implementations/anthropic.js +101 -99
  27. package/src/infrastructure/providers/implementations/azure.js +124 -101
  28. package/src/infrastructure/providers/implementations/bedrock.js +136 -126
  29. package/src/infrastructure/providers/implementations/dummy.js +23 -23
  30. package/src/infrastructure/providers/implementations/google.js +123 -114
  31. package/src/infrastructure/providers/implementations/huggingface.js +94 -87
  32. package/src/infrastructure/providers/implementations/lmstudio.js +75 -60
  33. package/src/infrastructure/providers/implementations/mock.js +69 -73
  34. package/src/infrastructure/providers/implementations/ollama.js +89 -66
  35. package/src/infrastructure/providers/implementations/openai.js +88 -89
  36. package/src/infrastructure/providers/implementations/vertex.js +227 -197
  37. package/src/infrastructure/providers/provider-management.service.js +245 -207
  38. package/src/infrastructure/providers/provider-manager.service.js +145 -125
  39. package/src/infrastructure/providers/utils/base-provider-helpers.js +308 -302
  40. package/src/infrastructure/providers/utils/model-config.js +220 -195
  41. package/src/infrastructure/providers/utils/provider-utils.js +105 -100
  42. package/src/infrastructure/validation/commit-message-validation.service.js +259 -161
  43. package/src/shared/constants/colors.js +453 -180
  44. package/src/shared/utils/cli-demo.js +285 -0
  45. package/src/shared/utils/cli-entry-utils.js +257 -249
  46. package/src/shared/utils/cli-ui.js +447 -0
  47. package/src/shared/utils/diff-processor.js +513 -0
  48. package/src/shared/utils/error-classes.js +125 -156
  49. package/src/shared/utils/json-utils.js +93 -89
  50. package/src/shared/utils/utils.js +1117 -945
  51. package/types/index.d.ts +353 -344
@@ -12,11 +12,12 @@
12
12
  * For specialized error handling, use error classes from './error-classes.js'
13
13
  */
14
14
 
15
- import fs from 'fs';
16
- import { execSync } from 'child_process';
17
- import colors from '../constants/colors.js';
18
- import JsonUtils from './json-utils.js';
19
- import { GitError, ValidationError, ConfigError } from './error-classes.js';
15
+ import { execSync } from 'node:child_process'
16
+ import fs from 'node:fs'
17
+
18
+ import colors from '../constants/colors.js'
19
+ import { DiffProcessor } from './diff-processor.js'
20
+ import JsonUtils from './json-utils.js'
20
21
 
21
22
  // ========================================
22
23
  // ERROR HANDLING UTILITIES
@@ -27,12 +28,12 @@ import { GitError, ValidationError, ConfigError } from './error-classes.js';
27
28
  */
28
29
  export class AIChangelogError extends Error {
29
30
  constructor(message, type, context = {}, originalError = null) {
30
- super(message);
31
- this.name = this.constructor.name;
32
- this.type = type;
33
- this.context = context;
34
- this.originalError = originalError;
35
- this.timestamp = new Date().toISOString();
31
+ super(message)
32
+ this.name = this.constructor.name
33
+ this.type = type
34
+ this.context = context
35
+ this.originalError = originalError
36
+ this.timestamp = new Date().toISOString()
36
37
  }
37
38
 
38
39
  toJSON() {
@@ -42,8 +43,8 @@ export class AIChangelogError extends Error {
42
43
  type: this.type,
43
44
  context: this.context,
44
45
  timestamp: this.timestamp,
45
- stack: this.stack
46
- };
46
+ stack: this.stack,
47
+ }
47
48
  }
48
49
  }
49
50
 
@@ -52,9 +53,9 @@ export class AIChangelogError extends Error {
52
53
  */
53
54
  export class AbstractMethodError extends AIChangelogError {
54
55
  constructor(message, className, methodName, context = {}) {
55
- super(message, 'abstract', { className, methodName, ...context });
56
- this.className = className;
57
- this.methodName = methodName;
56
+ super(message, 'abstract', { className, methodName, ...context })
57
+ this.className = className
58
+ this.methodName = methodName
58
59
  }
59
60
  }
60
61
 
@@ -63,8 +64,8 @@ export class AbstractMethodError extends AIChangelogError {
63
64
  */
64
65
  export class ProviderError extends AIChangelogError {
65
66
  constructor(message, providerName, context = {}, originalError = null) {
66
- super(message, 'provider', { providerName, ...context }, originalError);
67
- this.providerName = providerName;
67
+ super(message, 'provider', { providerName, ...context }, originalError)
68
+ this.providerName = providerName
68
69
  }
69
70
  }
70
71
 
@@ -77,22 +78,22 @@ export class ProviderError extends AIChangelogError {
77
78
  */
78
79
  export function convertSetsToArrays(obj) {
79
80
  if (obj === null || typeof obj !== 'object') {
80
- return obj;
81
+ return obj
81
82
  }
82
83
 
83
84
  if (obj instanceof Set) {
84
- return Array.from(obj);
85
+ return Array.from(obj)
85
86
  }
86
87
 
87
88
  if (Array.isArray(obj)) {
88
- return obj.map(convertSetsToArrays);
89
+ return obj.map(convertSetsToArrays)
89
90
  }
90
91
 
91
- const result = {};
92
+ const result = {}
92
93
  for (const [key, value] of Object.entries(obj)) {
93
- result[key] = convertSetsToArrays(value);
94
+ result[key] = convertSetsToArrays(value)
94
95
  }
95
- return result;
96
+ return result
96
97
  }
97
98
 
98
99
  /**
@@ -101,18 +102,20 @@ export function convertSetsToArrays(obj) {
101
102
  */
102
103
  export function extractCommitScope(message) {
103
104
  // Enhanced regex to match conventional commits format: type(scope)!: description
104
- const conventionalMatch = message.match(/^(?<type>\w+)(?:\((?<scope>[^()]+)\))?(?<breaking>!)?:\s*(?<description>.+)/);
105
+ const conventionalMatch = message.match(
106
+ /^(?<type>\w+)(?:\((?<scope>[^()]+)\))?(?<breaking>!)?:\s*(?<description>.+)/
107
+ )
105
108
 
106
109
  if (conventionalMatch) {
107
- const { type, scope, breaking, description } = conventionalMatch.groups;
110
+ const { type, scope, breaking, description } = conventionalMatch.groups
108
111
 
109
112
  return {
110
- type: type,
113
+ type,
111
114
  scope: scope || null,
112
115
  description: description.trim(),
113
116
  breaking: !!breaking,
114
- isConventional: true
115
- };
117
+ isConventional: true,
118
+ }
116
119
  }
117
120
 
118
121
  // Fallback for non-conventional commits
@@ -121,49 +124,53 @@ export function extractCommitScope(message) {
121
124
  scope: null,
122
125
  description: message.trim(),
123
126
  breaking: false,
124
- isConventional: false
125
- };
127
+ isConventional: false,
128
+ }
126
129
  }
127
130
 
128
131
  /**
129
132
  * Parse conventional commit message with full body analysis
130
133
  */
131
134
  export function parseConventionalCommit(subject, body = '') {
132
- const parsed = extractCommitScope(subject);
133
- const fullMessage = `${subject}\n\n${body}`.trim();
135
+ const parsed = extractCommitScope(subject)
136
+ const fullMessage = `${subject}\n\n${body}`.trim()
134
137
 
135
138
  // Enhanced breaking change detection from body
136
- const breakingChanges = [];
139
+ const breakingChanges = []
137
140
 
138
141
  // Check for BREAKING CHANGE: footer
139
- const breakingMatch = fullMessage.match(/BREAKING CHANGE:\s*(.*?)(?:\n\n|\n[A-Z]|\n*$)/s);
142
+ const breakingMatch = fullMessage.match(/BREAKING CHANGE:\s*(.*?)(?:\n\n|\n[A-Z]|\n*$)/s)
140
143
  if (breakingMatch) {
141
- breakingChanges.push(breakingMatch[1].trim());
142
- parsed.breaking = true;
144
+ breakingChanges.push(breakingMatch[1].trim())
145
+ parsed.breaking = true
143
146
  }
144
147
 
145
148
  // Check for BREAKING-CHANGE: footer (alternative format)
146
- const breakingAltMatch = fullMessage.match(/BREAKING-CHANGE:\s*(.*?)(?:\n\n|\n[A-Z]|\n*$)/s);
149
+ const breakingAltMatch = fullMessage.match(/BREAKING-CHANGE:\s*(.*?)(?:\n\n|\n[A-Z]|\n*$)/s)
147
150
  if (breakingAltMatch) {
148
- breakingChanges.push(breakingAltMatch[1].trim());
149
- parsed.breaking = true;
151
+ breakingChanges.push(breakingAltMatch[1].trim())
152
+ parsed.breaking = true
150
153
  }
151
154
 
152
155
  // Extract issue references (GitHub/GitLab format)
153
- const issueRefs = [];
154
- const issueMatches = fullMessage.match(/#[0-9]+/g);
156
+ const issueRefs = []
157
+ const issueMatches = fullMessage.match(/#[0-9]+/g)
155
158
  if (issueMatches) {
156
- issueRefs.push(...issueMatches);
159
+ issueRefs.push(...issueMatches)
157
160
  }
158
161
 
159
162
  // Extract closes references
160
- const closesMatches = fullMessage.match(/(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?):?\s*#?([0-9]+)/gi);
161
- const closesRefs = [];
163
+ const closesMatches = fullMessage.match(
164
+ /(?:close[sd]?|fix(?:e[sd])?|resolve[sd]?):?\s*#?([0-9]+)/gi
165
+ )
166
+ const closesRefs = []
162
167
  if (closesMatches) {
163
- closesMatches.forEach(match => {
164
- const num = match.match(/([0-9]+)/);
165
- if (num) closesRefs.push(`#${num[1]}`);
166
- });
168
+ closesMatches.forEach((match) => {
169
+ const num = match.match(/([0-9]+)/)
170
+ if (num) {
171
+ closesRefs.push(`#${num[1]}`)
172
+ }
173
+ })
167
174
  }
168
175
 
169
176
  return {
@@ -172,8 +179,8 @@ export function parseConventionalCommit(subject, body = '') {
172
179
  issueReferences: [...new Set(issueRefs)],
173
180
  closesReferences: [...new Set(closesRefs)],
174
181
  body: body.trim(),
175
- revert: subject.toLowerCase().startsWith('revert')
176
- };
182
+ revert: subject.toLowerCase().startsWith('revert'),
183
+ }
177
184
  }
178
185
 
179
186
  /**
@@ -181,12 +188,12 @@ export function parseConventionalCommit(subject, body = '') {
181
188
  */
182
189
  export function markdownCommitLink(commitHash, commitUrl, shortHash = true) {
183
190
  if (!commitUrl) {
184
- return shortHash ? commitHash.substring(0, 7) : commitHash;
191
+ return shortHash ? commitHash.substring(0, 7) : commitHash
185
192
  }
186
193
 
187
- const url = commitUrl.replace('%commit%', commitHash);
188
- const displayHash = shortHash ? commitHash.substring(0, 7) : commitHash;
189
- return `[${displayHash}](${url})`;
194
+ const url = commitUrl.replace('%commit%', commitHash)
195
+ const displayHash = shortHash ? commitHash.substring(0, 7) : commitHash
196
+ return `[${displayHash}](${url})`
190
197
  }
191
198
 
192
199
  /**
@@ -194,13 +201,11 @@ export function markdownCommitLink(commitHash, commitUrl, shortHash = true) {
194
201
  */
195
202
  export function markdownCommitRangeLink(fromCommit, toCommit, commitRangeUrl) {
196
203
  if (!commitRangeUrl) {
197
- return `${fromCommit.substring(0, 7)}...${toCommit.substring(0, 7)}`;
204
+ return `${fromCommit.substring(0, 7)}...${toCommit.substring(0, 7)}`
198
205
  }
199
206
 
200
- const url = commitRangeUrl
201
- .replace('%from%', fromCommit)
202
- .replace('%to%', toCommit);
203
- return `[${fromCommit.substring(0, 7)}...${toCommit.substring(0, 7)}](${url})`;
207
+ const url = commitRangeUrl.replace('%from%', fromCommit).replace('%to%', toCommit)
208
+ return `[${fromCommit.substring(0, 7)}...${toCommit.substring(0, 7)}](${url})`
204
209
  }
205
210
 
206
211
  /**
@@ -208,40 +213,42 @@ export function markdownCommitRangeLink(fromCommit, toCommit, commitRangeUrl) {
208
213
  */
209
214
  export function markdownIssueLink(issueId, issueUrl) {
210
215
  if (!issueUrl) {
211
- return issueId;
216
+ return issueId
212
217
  }
213
218
 
214
- const cleanIssueId = issueId.replace('#', '');
215
- const url = issueUrl.replace('%issue%', cleanIssueId);
216
- return `[${issueId}](${url})`;
219
+ const cleanIssueId = issueId.replace('#', '')
220
+ const url = issueUrl.replace('%issue%', cleanIssueId)
221
+ return `[${issueId}](${url})`
217
222
  }
218
223
 
219
224
  /**
220
225
  * Process issue references in text and convert to markdown links
221
226
  */
222
227
  export function processIssueReferences(text, issueUrl, issueRegex) {
223
- if (!issueUrl || !text) return text;
228
+ if (!(issueUrl && text)) {
229
+ return text
230
+ }
224
231
 
225
232
  return text.replace(issueRegex, (match) => {
226
- return markdownIssueLink(match, issueUrl);
227
- });
233
+ return markdownIssueLink(match, issueUrl)
234
+ })
228
235
  }
229
236
 
230
237
  /**
231
238
  * Deep merge objects
232
239
  */
233
240
  export function deepMerge(target, source) {
234
- const result = { ...target };
241
+ const result = { ...target }
235
242
 
236
243
  for (const key in source) {
237
244
  if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
238
- result[key] = deepMerge(result[key] || {}, source[key]);
245
+ result[key] = deepMerge(result[key] || {}, source[key])
239
246
  } else {
240
- result[key] = source[key];
247
+ result[key] = source[key]
241
248
  }
242
249
  }
243
250
 
244
- return result;
251
+ return result
245
252
  }
246
253
 
247
254
  // ========================================
@@ -252,18 +259,24 @@ export function deepMerge(target, source) {
252
259
  * Format duration in human-readable format
253
260
  */
254
261
  export function formatDuration(ms) {
255
- if (ms < 1000) return `${ms}ms`;
256
- if (ms < 60000) return `${(ms / 1000).toFixed(1)}s`;
257
- if (ms < 3600000) return `${(ms / 60000).toFixed(1)}m`;
258
- return `${(ms / 3600000).toFixed(1)}h`;
262
+ if (ms < 1000) {
263
+ return `${ms}ms`
264
+ }
265
+ if (ms < 60000) {
266
+ return `${(ms / 1000).toFixed(1)}s`
267
+ }
268
+ if (ms < 3600000) {
269
+ return `${(ms / 60000).toFixed(1)}m`
270
+ }
271
+ return `${(ms / 3600000).toFixed(1)}h`
259
272
  }
260
273
 
261
274
  /**
262
275
  * Interactive configuration prompt (simplified)
263
276
  */
264
277
  export function promptForConfig(message = 'Configure settings', defaultValue = '') {
265
- console.log(colors.infoMessage(message));
266
- return Promise.resolve(defaultValue);
278
+ console.log(colors.infoMessage(message))
279
+ return Promise.resolve(defaultValue)
267
280
  }
268
281
 
269
282
  /**
@@ -271,32 +284,36 @@ export function promptForConfig(message = 'Configure settings', defaultValue = '
271
284
  */
272
285
  export function getHealthColor(status) {
273
286
  const colorMap = {
274
- 'excellent': colors.successMessage,
275
- 'good': colors.successMessage,
276
- 'fair': colors.warningMessage,
277
- 'poor': colors.errorMessage,
278
- 'critical': colors.errorMessage
279
- };
280
- return colorMap[status] || colors.infoMessage;
287
+ excellent: colors.successMessage,
288
+ good: colors.successMessage,
289
+ fair: colors.warningMessage,
290
+ poor: colors.errorMessage,
291
+ critical: colors.errorMessage,
292
+ }
293
+ return colorMap[status] || colors.infoMessage
281
294
  }
282
295
 
283
296
  /**
284
297
  * Format file size
285
298
  */
286
299
  export function formatFileSize(bytes) {
287
- if (bytes === 0) return '0 B';
288
- const k = 1024;
289
- const sizes = ['B', 'KB', 'MB', 'GB'];
290
- const i = Math.floor(Math.log(bytes) / Math.log(k));
291
- return `${parseFloat((bytes / Math.pow(k, i)).toFixed(2))} ${sizes[i]}`;
300
+ if (bytes === 0) {
301
+ return '0 B'
302
+ }
303
+ const k = 1024
304
+ const sizes = ['B', 'KB', 'MB', 'GB']
305
+ const i = Math.floor(Math.log(bytes) / Math.log(k))
306
+ return `${Number.parseFloat((bytes / k ** i).toFixed(2))} ${sizes[i]}`
292
307
  }
293
308
 
294
309
  /**
295
310
  * Format percentage
296
311
  */
297
312
  export function formatPercentage(value, total) {
298
- if (total === 0) return '0%';
299
- return `${((value / total) * 100).toFixed(1)}%`;
313
+ if (total === 0) {
314
+ return '0%'
315
+ }
316
+ return `${((value / total) * 100).toFixed(1)}%`
300
317
  }
301
318
 
302
319
  // ========================================
@@ -308,63 +325,88 @@ export function formatPercentage(value, total) {
308
325
  */
309
326
  export function categorizeFile(filePath) {
310
327
  if (!filePath || typeof filePath !== 'string') {
311
- return 'other';
328
+ return 'other'
312
329
  }
313
- const path = filePath.toLowerCase();
314
- const ext = path.split('.').pop();
330
+ const path = filePath.toLowerCase()
331
+ const ext = path.split('.').pop()
315
332
 
316
333
  // Configuration files
317
- if (path.includes('package.json') || path.includes('yarn.lock') ||
318
- path.includes('pnpm-lock') || path.includes('.gitignore') ||
319
- ext === 'toml' || ext === 'yaml' || ext === 'yml' ||
320
- path.includes('dockerfile') || path.includes('.env')) {
321
- return 'configuration';
334
+ if (
335
+ path.includes('package.json') ||
336
+ path.includes('yarn.lock') ||
337
+ path.includes('pnpm-lock') ||
338
+ path.includes('.gitignore') ||
339
+ ext === 'toml' ||
340
+ ext === 'yaml' ||
341
+ ext === 'yml' ||
342
+ path.includes('dockerfile') ||
343
+ path.includes('.env')
344
+ ) {
345
+ return 'configuration'
322
346
  }
323
347
 
324
348
  // Documentation
325
- if (ext === 'md' || ext === 'txt' || ext === 'rst' ||
326
- path.includes('readme') || path.includes('changelog') ||
327
- path.includes('/docs/') || path.includes('/doc/')) {
328
- return 'documentation';
349
+ if (
350
+ ext === 'md' ||
351
+ ext === 'txt' ||
352
+ ext === 'rst' ||
353
+ path.includes('readme') ||
354
+ path.includes('changelog') ||
355
+ path.includes('/docs/') ||
356
+ path.includes('/doc/')
357
+ ) {
358
+ return 'documentation'
329
359
  }
330
360
 
331
361
  // Test files
332
- if (path.includes('/test/') || path.includes('/tests/') ||
333
- path.includes('__tests__') || path.includes('.test.') ||
334
- path.includes('.spec.') || ext === 'test' || ext === 'spec') {
335
- return 'tests';
362
+ if (
363
+ path.includes('/test/') ||
364
+ path.includes('/tests/') ||
365
+ path.includes('__tests__') ||
366
+ path.includes('.test.') ||
367
+ path.includes('.spec.') ||
368
+ ext === 'test' ||
369
+ ext === 'spec'
370
+ ) {
371
+ return 'tests'
336
372
  }
337
373
 
338
374
  // Source code
339
- const sourceExts = ['js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'c', 'cs', 'go', 'rs', 'php'];
375
+ const sourceExts = ['js', 'ts', 'jsx', 'tsx', 'py', 'java', 'cpp', 'c', 'cs', 'go', 'rs', 'php']
340
376
  if (sourceExts.includes(ext)) {
341
377
  if (path.includes('/src/') || path.includes('/lib/')) {
342
- return 'source';
378
+ return 'source'
343
379
  }
344
- return 'source';
380
+ return 'source'
345
381
  }
346
382
 
347
383
  // Frontend/UI
348
- const frontendExts = ['html', 'css', 'scss', 'sass', 'less', 'vue', 'svelte'];
384
+ const frontendExts = ['html', 'css', 'scss', 'sass', 'less', 'vue', 'svelte']
349
385
  if (frontendExts.includes(ext)) {
350
- return 'frontend';
386
+ return 'frontend'
351
387
  }
352
388
 
353
389
  // Assets
354
- const assetExts = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'webp'];
390
+ const assetExts = ['png', 'jpg', 'jpeg', 'gif', 'svg', 'ico', 'webp']
355
391
  if (assetExts.includes(ext)) {
356
- return 'assets';
392
+ return 'assets'
357
393
  }
358
394
 
359
395
  // Build/tooling
360
- if (path.includes('webpack') || path.includes('rollup') ||
361
- path.includes('vite') || path.includes('babel') ||
362
- path.includes('eslint') || path.includes('prettier') ||
363
- path.includes('/build/') || path.includes('/dist/')) {
364
- return 'build';
365
- }
366
-
367
- return 'other';
396
+ if (
397
+ path.includes('webpack') ||
398
+ path.includes('rollup') ||
399
+ path.includes('vite') ||
400
+ path.includes('babel') ||
401
+ path.includes('eslint') ||
402
+ path.includes('prettier') ||
403
+ path.includes('/build/') ||
404
+ path.includes('/dist/')
405
+ ) {
406
+ return 'build'
407
+ }
408
+
409
+ return 'other'
368
410
  }
369
411
 
370
412
  /**
@@ -372,9 +414,9 @@ export function categorizeFile(filePath) {
372
414
  */
373
415
  export function detectLanguage(filePath) {
374
416
  if (!filePath || typeof filePath !== 'string') {
375
- return 'Unknown';
417
+ return 'Unknown'
376
418
  }
377
- const ext = filePath.split('.').pop()?.toLowerCase();
419
+ const ext = filePath.split('.').pop()?.toLowerCase()
378
420
 
379
421
  const langMap = {
380
422
  js: 'JavaScript',
@@ -405,10 +447,10 @@ export function detectLanguage(filePath) {
405
447
  yml: 'YAML',
406
448
  toml: 'TOML',
407
449
  md: 'Markdown',
408
- sql: 'SQL'
409
- };
450
+ sql: 'SQL',
451
+ }
410
452
 
411
- return langMap[ext] || 'Unknown';
453
+ return langMap[ext] || 'Unknown'
412
454
  }
413
455
 
414
456
  /**
@@ -416,47 +458,56 @@ export function detectLanguage(filePath) {
416
458
  */
417
459
  export function assessFileImportance(filePath, status) {
418
460
  if (!filePath || typeof filePath !== 'string') {
419
- return 'medium';
461
+ return 'medium'
420
462
  }
421
- const path = filePath.toLowerCase();
463
+ const path = filePath.toLowerCase()
422
464
 
423
465
  // Critical files
424
- if (path.includes('package.json') || path.includes('pom.xml') ||
425
- path.includes('cargo.toml') || path.includes('requirements.txt') ||
426
- path.includes('dockerfile') || path.includes('docker-compose')) {
427
- return 'critical';
466
+ if (
467
+ path.includes('package.json') ||
468
+ path.includes('pom.xml') ||
469
+ path.includes('cargo.toml') ||
470
+ path.includes('requirements.txt') ||
471
+ path.includes('dockerfile') ||
472
+ path.includes('docker-compose')
473
+ ) {
474
+ return 'critical'
428
475
  }
429
476
 
430
477
  // Core source files
431
478
  if (path.includes('/src/') || path.includes('/lib/')) {
432
- if (path.includes('index.') || path.includes('main.') ||
433
- path.includes('app.') || path.includes('server.')) {
434
- return 'critical';
479
+ if (
480
+ path.includes('index.') ||
481
+ path.includes('main.') ||
482
+ path.includes('app.') ||
483
+ path.includes('server.')
484
+ ) {
485
+ return 'critical'
435
486
  }
436
- return 'high';
487
+ return 'high'
437
488
  }
438
489
 
439
490
  // Configuration
440
491
  if (categorizeFile(filePath) === 'configuration') {
441
- return 'high';
492
+ return 'high'
442
493
  }
443
494
 
444
495
  // Tests
445
496
  if (categorizeFile(filePath) === 'tests') {
446
- return 'medium';
497
+ return 'medium'
447
498
  }
448
499
 
449
500
  // Documentation
450
501
  if (categorizeFile(filePath) === 'documentation') {
451
- return 'low';
502
+ return 'low'
452
503
  }
453
504
 
454
505
  // File deletion is always important
455
506
  if (status === 'D') {
456
- return 'high';
507
+ return 'high'
457
508
  }
458
509
 
459
- return 'medium';
510
+ return 'medium'
460
511
  }
461
512
 
462
513
  // ========================================
@@ -467,18 +518,18 @@ export function assessFileImportance(filePath, status) {
467
518
  * Assess overall complexity of changes
468
519
  */
469
520
  export function assessOverallComplexity(diffContent, fileCount) {
470
- const lines = diffContent.split('\n');
471
- const addedLines = lines.filter(line => line.startsWith('+')).length;
472
- const deletedLines = lines.filter(line => line.startsWith('-')).length;
473
- const totalChanges = addedLines + deletedLines;
521
+ const lines = diffContent.split('\n')
522
+ const addedLines = lines.filter((line) => line.startsWith('+')).length
523
+ const deletedLines = lines.filter((line) => line.startsWith('-')).length
524
+ const totalChanges = addedLines + deletedLines
474
525
 
475
526
  // Complexity factors
476
- let complexity = 'low';
527
+ let complexity = 'low'
477
528
 
478
529
  if (fileCount > 20 || totalChanges > 500) {
479
- complexity = 'high';
530
+ complexity = 'high'
480
531
  } else if (fileCount > 5 || totalChanges > 100) {
481
- complexity = 'medium';
532
+ complexity = 'medium'
482
533
  }
483
534
 
484
535
  // Check for complex patterns
@@ -489,39 +540,39 @@ export function assessOverallComplexity(diffContent, fileCount) {
489
540
  /try\s*{/g,
490
541
  /catch\s*\(/g,
491
542
  /interface\s+\w+/g,
492
- /type\s+\w+/g
493
- ];
543
+ /type\s+\w+/g,
544
+ ]
494
545
 
495
546
  const patternMatches = complexPatterns.reduce((count, pattern) => {
496
- return count + (diffContent.match(pattern) || []).length;
497
- }, 0);
547
+ return count + (diffContent.match(pattern) || []).length
548
+ }, 0)
498
549
 
499
550
  if (patternMatches > 10) {
500
- complexity = complexity === 'low' ? 'medium' : 'high';
551
+ complexity = complexity === 'low' ? 'medium' : 'high'
501
552
  }
502
553
 
503
- return complexity;
554
+ return complexity
504
555
  }
505
556
 
506
557
  /**
507
558
  * Assess risk level of changes
508
559
  */
509
560
  export function assessRisk(diffContent, fileCount, commitMessage) {
510
- let risk = 'low';
561
+ let risk = 'low'
511
562
 
512
563
  // High-risk keywords in commit message
513
- const highRiskKeywords = ['breaking', 'remove', 'delete', 'deprecated', 'migration'];
514
- const mediumRiskKeywords = ['refactor', 'restructure', 'change', 'modify', 'update'];
564
+ const highRiskKeywords = ['breaking', 'remove', 'delete', 'deprecated', 'migration']
565
+ const mediumRiskKeywords = ['refactor', 'restructure', 'change', 'modify', 'update']
515
566
 
516
567
  if (!commitMessage || typeof commitMessage !== 'string') {
517
- return risk; // Return early with default risk level
568
+ return risk // Return early with default risk level
518
569
  }
519
- const message = commitMessage.toLowerCase();
570
+ const message = commitMessage.toLowerCase()
520
571
 
521
- if (highRiskKeywords.some(keyword => message.includes(keyword))) {
522
- risk = 'high';
523
- } else if (mediumRiskKeywords.some(keyword => message.includes(keyword))) {
524
- risk = 'medium';
572
+ if (highRiskKeywords.some((keyword) => message.includes(keyword))) {
573
+ risk = 'high'
574
+ } else if (mediumRiskKeywords.some((keyword) => message.includes(keyword))) {
575
+ risk = 'medium'
525
576
  }
526
577
 
527
578
  // High-risk file patterns
@@ -534,24 +585,24 @@ export function assessRisk(diffContent, fileCount, commitMessage) {
534
585
  /database/,
535
586
  /migration/,
536
587
  /config/,
537
- /env/
538
- ];
588
+ /env/,
589
+ ]
539
590
 
540
- const diffLines = diffContent.split('\n');
541
- const hasHighRiskFiles = diffLines.some(line =>
542
- highRiskPatterns.some(pattern => pattern.test(line))
543
- );
591
+ const diffLines = diffContent.split('\n')
592
+ const hasHighRiskFiles = diffLines.some((line) =>
593
+ highRiskPatterns.some((pattern) => pattern.test(line))
594
+ )
544
595
 
545
596
  if (hasHighRiskFiles) {
546
- risk = risk === 'low' ? 'medium' : 'high';
597
+ risk = risk === 'low' ? 'medium' : 'high'
547
598
  }
548
599
 
549
600
  // Large changes are risky
550
601
  if (fileCount > 15 || diffContent.length > 10000) {
551
- risk = risk === 'low' ? 'medium' : 'high';
602
+ risk = risk === 'low' ? 'medium' : 'high'
552
603
  }
553
604
 
554
- return risk;
605
+ return risk
555
606
  }
556
607
 
557
608
  /**
@@ -559,13 +610,13 @@ export function assessRisk(diffContent, fileCount, commitMessage) {
559
610
  */
560
611
  export function isBreakingChange(commitMessage, diffContent) {
561
612
  if (!commitMessage || typeof commitMessage !== 'string') {
562
- return false;
613
+ return false
563
614
  }
564
- const message = commitMessage.toLowerCase();
615
+ const message = commitMessage.toLowerCase()
565
616
 
566
617
  // Check commit message for breaking indicators
567
618
  if (message.includes('breaking') || message.includes('!:')) {
568
- return true;
619
+ return true
569
620
  }
570
621
 
571
622
  // Check diff for breaking patterns
@@ -573,10 +624,10 @@ export function isBreakingChange(commitMessage, diffContent) {
573
624
  /function\s+\w+\s*\([^)]*\)\s*{[\s\S]*?}[\s\S]*?-/g, // Function signature changes
574
625
  /class\s+\w+[\s\S]*?-/g, // Class changes
575
626
  /interface\s+\w+[\s\S]*?-/g, // Interface changes
576
- /export\s+[\s\S]*?-/g // Export changes
577
- ];
627
+ /export\s+[\s\S]*?-/g, // Export changes
628
+ ]
578
629
 
579
- return breakingPatterns.some(pattern => pattern.test(diffContent));
630
+ return breakingPatterns.some((pattern) => pattern.test(diffContent))
580
631
  }
581
632
 
582
633
  /**
@@ -584,33 +635,54 @@ export function isBreakingChange(commitMessage, diffContent) {
584
635
  */
585
636
  export function assessBusinessRelevance(commitMessage, filePaths) {
586
637
  if (!commitMessage || typeof commitMessage !== 'string') {
587
- return 'low';
638
+ return 'low'
588
639
  }
589
- const message = commitMessage.toLowerCase();
640
+ const message = commitMessage.toLowerCase()
590
641
 
591
642
  // Business-relevant keywords
592
643
  const businessKeywords = [
593
- 'feature', 'user', 'customer', 'client', 'business', 'revenue',
594
- 'payment', 'billing', 'subscription', 'auth', 'login', 'security'
595
- ];
596
-
597
- const hasBusinessKeywords = businessKeywords.some(keyword =>
598
- message.includes(keyword)
599
- );
644
+ 'feature',
645
+ 'user',
646
+ 'customer',
647
+ 'client',
648
+ 'business',
649
+ 'revenue',
650
+ 'payment',
651
+ 'billing',
652
+ 'subscription',
653
+ 'auth',
654
+ 'login',
655
+ 'security',
656
+ ]
657
+
658
+ const hasBusinessKeywords = businessKeywords.some((keyword) => message.includes(keyword))
600
659
 
601
660
  // Business-relevant file paths
602
661
  const businessPaths = [
603
- '/api/', '/service/', '/controller/', '/model/',
604
- '/auth/', '/payment/', '/billing/', '/user/'
605
- ];
606
-
607
- const hasBusinessFiles = filePaths.some(path =>
608
- path && typeof path === 'string' && businessPaths.some(businessPath => path.includes(businessPath))
609
- );
610
-
611
- if (hasBusinessKeywords && hasBusinessFiles) return 'high';
612
- if (hasBusinessKeywords || hasBusinessFiles) return 'medium';
613
- return 'low';
662
+ '/api/',
663
+ '/service/',
664
+ '/controller/',
665
+ '/model/',
666
+ '/auth/',
667
+ '/payment/',
668
+ '/billing/',
669
+ '/user/',
670
+ ]
671
+
672
+ const hasBusinessFiles = filePaths.some(
673
+ (path) =>
674
+ path &&
675
+ typeof path === 'string' &&
676
+ businessPaths.some((businessPath) => path.includes(businessPath))
677
+ )
678
+
679
+ if (hasBusinessKeywords && hasBusinessFiles) {
680
+ return 'high'
681
+ }
682
+ if (hasBusinessKeywords || hasBusinessFiles) {
683
+ return 'medium'
684
+ }
685
+ return 'low'
614
686
  }
615
687
 
616
688
  // ========================================
@@ -622,7 +694,7 @@ export function assessBusinessRelevance(commitMessage, filePaths) {
622
694
  */
623
695
  export function safeJsonParse(jsonString, fallback = null) {
624
696
  // Use the advanced JsonUtils for better error handling
625
- return JsonUtils.safeParse(jsonString, fallback);
697
+ return JsonUtils.safeParse(jsonString, fallback)
626
698
  }
627
699
 
628
700
  /**
@@ -630,10 +702,10 @@ export function safeJsonParse(jsonString, fallback = null) {
630
702
  */
631
703
  export function safeJsonStringify(obj, indent = 2) {
632
704
  try {
633
- return JSON.stringify(convertSetsToArrays(obj), null, indent);
705
+ return JSON.stringify(convertSetsToArrays(obj), null, indent)
634
706
  } catch (error) {
635
- console.warn(colors.warningMessage(`JSON stringify error: ${error.message}`));
636
- return '{}';
707
+ console.warn(colors.warningMessage(`JSON stringify error: ${error.message}`))
708
+ return '{}'
637
709
  }
638
710
  }
639
711
 
@@ -645,57 +717,57 @@ export function safeJsonStringify(obj, indent = 2) {
645
717
  * Sleep utility for rate limiting
646
718
  */
647
719
  export function sleep(ms) {
648
- return new Promise(resolve => setTimeout(resolve, ms));
720
+ return new Promise((resolve) => setTimeout(resolve, ms))
649
721
  }
650
722
 
651
723
  /**
652
724
  * Retry utility with exponential backoff
653
725
  */
654
726
  export async function retry(fn, maxRetries = 3, baseDelay = 1000) {
655
- let lastError;
727
+ let lastError
656
728
 
657
729
  for (let i = 0; i < maxRetries; i++) {
658
730
  try {
659
- return await fn();
731
+ return await fn()
660
732
  } catch (error) {
661
- lastError = error;
733
+ lastError = error
662
734
  if (i < maxRetries - 1) {
663
- const delay = baseDelay * Math.pow(2, i);
664
- await sleep(delay);
735
+ const delay = baseDelay * 2 ** i
736
+ await sleep(delay)
665
737
  }
666
738
  }
667
739
  }
668
740
 
669
- throw lastError;
741
+ throw lastError
670
742
  }
671
743
 
672
744
  /**
673
745
  * Debounce utility
674
746
  */
675
747
  export function debounce(func, wait) {
676
- let timeout;
748
+ let timeout
677
749
  return function executedFunction(...args) {
678
750
  const later = () => {
679
- clearTimeout(timeout);
680
- func(...args);
681
- };
682
- clearTimeout(timeout);
683
- timeout = setTimeout(later, wait);
684
- };
751
+ clearTimeout(timeout)
752
+ func(...args)
753
+ }
754
+ clearTimeout(timeout)
755
+ timeout = setTimeout(later, wait)
756
+ }
685
757
  }
686
758
 
687
759
  /**
688
760
  * Throttle utility
689
761
  */
690
762
  export function throttle(func, limit) {
691
- let inThrottle;
763
+ let inThrottle
692
764
  return function executedFunction(...args) {
693
765
  if (!inThrottle) {
694
- func.apply(this, args);
695
- inThrottle = true;
696
- setTimeout(() => inThrottle = false, limit);
766
+ func.apply(this, args)
767
+ inThrottle = true
768
+ setTimeout(() => (inThrottle = false), limit)
697
769
  }
698
- };
770
+ }
699
771
  }
700
772
 
701
773
  // ========================================
@@ -713,90 +785,106 @@ export function analyzeSemanticChanges(diff, filePath) {
713
785
  keywords: new Set(),
714
786
  codeElements: new Set(),
715
787
  apiChanges: [],
716
- dataChanges: []
717
- };
788
+ dataChanges: [],
789
+ }
718
790
 
719
791
  if (!diff || diff === 'Binary file or diff unavailable') {
720
- return convertSetsToArrays(analysis);
792
+ return convertSetsToArrays(analysis)
721
793
  }
722
794
 
723
- const addedLines = diff.split('\n').filter(line => line.startsWith('+') && !line.startsWith('+++'));
724
- const removedLines = diff.split('\n').filter(line => line.startsWith('-') && !line.startsWith('---'));
795
+ const _addedLines = diff
796
+ .split('\n')
797
+ .filter((line) => line.startsWith('+') && !line.startsWith('+++'))
798
+ const _removedLines = diff
799
+ .split('\n')
800
+ .filter((line) => line.startsWith('-') && !line.startsWith('---'))
725
801
 
726
802
  // Framework detection (only if filePath is valid)
727
- if (filePath && typeof filePath === 'string' && (filePath.includes('database/') || filePath.includes('sql/') || filePath.includes('migrations/'))) {
728
- analysis.frameworks.add('Database');
803
+ if (
804
+ filePath &&
805
+ typeof filePath === 'string' &&
806
+ (filePath.includes('database/') ||
807
+ filePath.includes('sql/') ||
808
+ filePath.includes('migrations/'))
809
+ ) {
810
+ analysis.frameworks.add('Database')
729
811
  if (diff.includes('CREATE TABLE') || diff.includes('ALTER TABLE')) {
730
- analysis.changeType = 'schema_change';
731
- analysis.patterns.add('database_schema');
812
+ analysis.changeType = 'schema_change'
813
+ analysis.patterns.add('database_schema')
732
814
  }
733
815
  if (diff.includes('CREATE POLICY') || diff.includes('ALTER POLICY')) {
734
- analysis.patterns.add('security_policy');
816
+ analysis.patterns.add('security_policy')
735
817
  }
736
818
  }
737
819
 
738
820
  if (filePath.endsWith('.tsx') || filePath.endsWith('.jsx')) {
739
- analysis.frameworks.add('React');
821
+ analysis.frameworks.add('React')
740
822
  if (diff.includes('useState') || diff.includes('useEffect')) {
741
- analysis.patterns.add('react_hooks');
823
+ analysis.patterns.add('react_hooks')
742
824
  }
743
825
  if (diff.includes('useCallback') || diff.includes('useMemo')) {
744
- analysis.patterns.add('performance_optimization');
826
+ analysis.patterns.add('performance_optimization')
745
827
  }
746
828
  }
747
829
 
748
- if (filePath && typeof filePath === 'string' && (filePath.includes('/api/') || filePath.includes('route.'))) {
749
- analysis.frameworks.add('API');
750
- analysis.changeType = 'api_change';
751
-
752
- ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].forEach(method => {
753
- if (diff.includes(`export async function ${method}`) ||
754
- diff.includes(`app.${method.toLowerCase()}`)) {
755
- analysis.apiChanges.push(`${method} endpoint`);
756
- analysis.patterns.add('api_endpoint');
830
+ if (
831
+ filePath &&
832
+ typeof filePath === 'string' &&
833
+ (filePath.includes('/api/') || filePath.includes('route.'))
834
+ ) {
835
+ analysis.frameworks.add('API')
836
+ analysis.changeType = 'api_change'
837
+
838
+ ;['GET', 'POST', 'PUT', 'DELETE', 'PATCH'].forEach((method) => {
839
+ if (
840
+ diff.includes(`export async function ${method}`) ||
841
+ diff.includes(`app.${method.toLowerCase()}`)
842
+ ) {
843
+ analysis.apiChanges.push(`${method} endpoint`)
844
+ analysis.patterns.add('api_endpoint')
757
845
  }
758
- });
846
+ })
759
847
  }
760
848
 
761
849
  // Code element detection
762
850
  const codePatterns = {
763
- 'function_definition': /(?:export\s+)?(?:async\s+)?function\s+(\w+)/g,
764
- 'component_definition': /(?:export\s+)?(?:const|function)\s+(\w+Component|\w+Page|\w+Layout)/g,
765
- 'hook_definition': /(?:export\s+)?(?:const|function)\s+(use\w+)/g,
766
- 'type_definition': /(?:export\s+)?(?:type|interface)\s+(\w+)/g,
767
- 'constant_definition': /(?:export\s+)?const\s+(\w+)/g
768
- };
851
+ function_definition: /(?:export\s+)?(?:async\s+)?function\s+(\w+)/g,
852
+ component_definition: /(?:export\s+)?(?:const|function)\s+(\w+Component|\w+Page|\w+Layout)/g,
853
+ hook_definition: /(?:export\s+)?(?:const|function)\s+(use\w+)/g,
854
+ type_definition: /(?:export\s+)?(?:type|interface)\s+(\w+)/g,
855
+ constant_definition: /(?:export\s+)?const\s+(\w+)/g,
856
+ }
769
857
 
770
858
  Object.entries(codePatterns).forEach(([pattern, regex]) => {
771
- const matches = [...diff.matchAll(regex)];
859
+ const matches = [...diff.matchAll(regex)]
772
860
  if (matches.length > 0) {
773
- analysis.patterns.add(pattern);
774
- matches.forEach(match => analysis.codeElements.add(match[1]));
861
+ analysis.patterns.add(pattern)
862
+ matches.forEach((match) => analysis.codeElements.add(match[1]))
775
863
  }
776
- });
864
+ })
777
865
 
778
866
  // Advanced pattern detection
779
867
  const advancedPatterns = {
780
- 'error_handling': [/try\s*{/, /catch\s*\(/, /throw\s+/, /Error\(/],
781
- 'async_operations': [/async\s+/, /await\s+/, /Promise\./, /\.then\(/],
782
- 'data_validation': [/validate/, /schema/, /validation/, /validator/i],
783
- 'authentication': [/auth/, /login/, /logout/, /token/, /jwt/, /session/i],
784
- 'authorization': [/permission/, /role/, /access/, /policy/, /guard/i],
785
- 'caching': [/cache/, /memo/, /useMemo/, /useCallback/i],
786
- 'testing': [/test/, /spec/, /mock/, /describe/, /it\(/],
787
- 'styling': [/className/, /css/, /styled/, /style/],
788
- 'state_management': [/useState/, /useReducer/, /store/, /state/i],
789
- 'routing': [/router/, /navigate/, /redirect/, /route/, /Link/],
790
- 'data_fetching': [/fetch/, /axios/, /useQuery/, /useMutation/, /api/i]
791
- };
868
+ error_handling: [/try\s*{/, /catch\s*\(/, /throw\s+/, /Error\(/],
869
+ async_operations: [/async\s+/, /await\s+/, /Promise\./, /\.then\(/],
870
+ data_validation: [/validate/, /schema/, /validation/, /validator/i],
871
+ authentication: [/auth/, /login/, /logout/, /token/, /jwt/, /session/i],
872
+ authorization: [/permission/, /role/, /access/, /policy/, /guard/i],
873
+ caching: [/cache/, /memo/, /useMemo/, /useCallback/i],
874
+ testing: [/test/, /spec/, /mock/, /describe/, /it\(/],
875
+ styling: [/className/, /css/, /styled/, /style/],
876
+ state_management: [/useState/, /useReducer/, /store/, /state/i],
877
+ routing: [/router/, /navigate/, /redirect/, /route/, /Link/],
878
+ data_fetching: [/fetch/, /axios/, /useQuery/, /useMutation/, /api/i],
879
+ }
792
880
 
793
881
  Object.entries(advancedPatterns).forEach(([pattern, regexes]) => {
794
- if (regexes.some(regex => regex.test(diff))) {
795
- analysis.patterns.add(pattern);
882
+ if (regexes.some((regex) => regex.test(diff))) {
883
+ analysis.patterns.add(pattern)
796
884
  }
797
- });
885
+ })
798
886
 
799
- return convertSetsToArrays(analysis);
887
+ return convertSetsToArrays(analysis)
800
888
  }
801
889
 
802
890
  /**
@@ -810,49 +898,57 @@ export function analyzeFunctionalImpact(diff, filePath, status) {
810
898
  businessImpact: 'minimal',
811
899
  technicalDebt: 'none',
812
900
  migrationRequired: false,
813
- backwardCompatible: true
814
- };
901
+ backwardCompatible: true,
902
+ }
815
903
 
816
904
  if (!diff || diff === 'Binary file or diff unavailable') {
817
- return convertSetsToArrays(impact);
905
+ return convertSetsToArrays(impact)
818
906
  }
819
907
 
820
908
  // File deletion has high impact
821
909
  if (status === 'D') {
822
- impact.severity = 'high';
823
- impact.scope = 'global';
824
- impact.backwardCompatible = false;
825
- return convertSetsToArrays(impact);
910
+ impact.severity = 'high'
911
+ impact.scope = 'global'
912
+ impact.backwardCompatible = false
913
+ return convertSetsToArrays(impact)
826
914
  }
827
915
 
828
- const addedLines = diff.split('\n').filter(line => line.startsWith('+') && !line.startsWith('+++'));
829
- const removedLines = diff.split('\n').filter(line => line.startsWith('-') && !line.startsWith('---'));
916
+ const _addedLines = diff
917
+ .split('\n')
918
+ .filter((line) => line.startsWith('+') && !line.startsWith('+++'))
919
+ const _removedLines = diff
920
+ .split('\n')
921
+ .filter((line) => line.startsWith('-') && !line.startsWith('---'))
830
922
 
831
923
  // Assess scope based on file type and changes (only if filePath is valid)
832
924
  if (filePath && typeof filePath === 'string') {
833
- if (filePath.includes('/api/') || filePath.includes('/server/') || filePath.includes('/backend/')) {
834
- impact.scope = 'system';
835
- impact.affectedSystems.add('backend');
925
+ if (
926
+ filePath.includes('/api/') ||
927
+ filePath.includes('/server/') ||
928
+ filePath.includes('/backend/')
929
+ ) {
930
+ impact.scope = 'system'
931
+ impact.affectedSystems.add('backend')
836
932
 
837
933
  if (diff.includes('export') || diff.includes('endpoint') || diff.includes('route')) {
838
- impact.scope = 'global';
839
- impact.severity = 'medium';
934
+ impact.scope = 'global'
935
+ impact.severity = 'medium'
840
936
  }
841
937
  }
842
938
 
843
939
  if (filePath.includes('package.json') || filePath.includes('package-lock.json')) {
844
- impact.scope = 'global';
845
- impact.severity = 'high';
846
- impact.affectedSystems.add('dependencies');
847
- impact.migrationRequired = true;
940
+ impact.scope = 'global'
941
+ impact.severity = 'high'
942
+ impact.affectedSystems.add('dependencies')
943
+ impact.migrationRequired = true
848
944
  }
849
945
 
850
946
  if (filePath.includes('database/') || filePath.includes('migrations/')) {
851
- impact.scope = 'global';
852
- impact.severity = 'high';
853
- impact.affectedSystems.add('database');
854
- impact.migrationRequired = true;
855
- impact.backwardCompatible = false;
947
+ impact.scope = 'global'
948
+ impact.severity = 'high'
949
+ impact.affectedSystems.add('database')
950
+ impact.migrationRequired = true
951
+ impact.backwardCompatible = false
856
952
  }
857
953
  }
858
954
 
@@ -860,28 +956,28 @@ export function analyzeFunctionalImpact(diff, filePath, status) {
860
956
  const breakingPatterns = [
861
957
  /export\s+(?:async\s+)?function\s+\w+\s*\([^)]*\)\s*{[\s\S]*?}-/g,
862
958
  /export\s+(?:const|let|var)\s+\w+\s*=[\s\S]*?-/g,
863
- /export\s+(?:class|interface|type)\s+\w+[\s\S]*?-/g
864
- ];
959
+ /export\s+(?:class|interface|type)\s+\w+[\s\S]*?-/g,
960
+ ]
865
961
 
866
- if (breakingPatterns.some(pattern => pattern.test(diff))) {
867
- impact.backwardCompatible = false;
868
- impact.severity = 'high';
869
- impact.businessImpact = 'high';
962
+ if (breakingPatterns.some((pattern) => pattern.test(diff))) {
963
+ impact.backwardCompatible = false
964
+ impact.severity = 'high'
965
+ impact.businessImpact = 'high'
870
966
  }
871
967
 
872
968
  // Performance impact
873
969
  if (diff.includes('async') || diff.includes('await') || diff.includes('Promise')) {
874
- impact.affectedSystems.add('performance');
970
+ impact.affectedSystems.add('performance')
875
971
  }
876
972
 
877
973
  // Security impact
878
974
  if (diff.includes('auth') || diff.includes('permission') || diff.includes('security')) {
879
- impact.affectedSystems.add('security');
880
- impact.severity = 'high';
881
- impact.businessImpact = 'high';
975
+ impact.affectedSystems.add('security')
976
+ impact.severity = 'high'
977
+ impact.businessImpact = 'high'
882
978
  }
883
979
 
884
- return convertSetsToArrays(impact);
980
+ return convertSetsToArrays(impact)
885
981
  }
886
982
 
887
983
  /**
@@ -896,56 +992,56 @@ export function generateAnalysisSummary(semanticAnalysis, functionalImpact) {
896
992
  frameworks: Array.from(semanticAnalysis.frameworks || []),
897
993
  patterns: Array.from(semanticAnalysis.patterns || []),
898
994
  recommendations: [],
899
- riskFactors: []
900
- };
995
+ riskFactors: [],
996
+ }
901
997
 
902
998
  // Generate primary changes based on patterns
903
- if (semanticAnalysis.patterns && semanticAnalysis.patterns.includes('api_endpoint')) {
904
- summary.primaryChanges.push('API endpoint modifications');
999
+ if (semanticAnalysis.patterns?.includes('api_endpoint')) {
1000
+ summary.primaryChanges.push('API endpoint modifications')
905
1001
  }
906
- if (semanticAnalysis.patterns && semanticAnalysis.patterns.includes('database_schema')) {
907
- summary.primaryChanges.push('Database schema changes');
1002
+ if (semanticAnalysis.patterns?.includes('database_schema')) {
1003
+ summary.primaryChanges.push('Database schema changes')
908
1004
  }
909
- if (semanticAnalysis.patterns && semanticAnalysis.patterns.includes('react_hooks')) {
910
- summary.primaryChanges.push('React component updates');
1005
+ if (semanticAnalysis.patterns?.includes('react_hooks')) {
1006
+ summary.primaryChanges.push('React component updates')
911
1007
  }
912
- if (semanticAnalysis.patterns && semanticAnalysis.patterns.includes('function_definition')) {
913
- summary.primaryChanges.push('Function implementations');
1008
+ if (semanticAnalysis.patterns?.includes('function_definition')) {
1009
+ summary.primaryChanges.push('Function implementations')
914
1010
  }
915
1011
 
916
1012
  // Generate recommendations
917
1013
  if (functionalImpact.migrationRequired) {
918
- summary.recommendations.push('Migration script required');
1014
+ summary.recommendations.push('Migration script required')
919
1015
  }
920
1016
  if (!functionalImpact.backwardCompatible) {
921
- summary.recommendations.push('Breaking change - update dependent code');
1017
+ summary.recommendations.push('Breaking change - update dependent code')
922
1018
  }
923
- if (functionalImpact.affectedSystems && functionalImpact.affectedSystems.includes('security')) {
924
- summary.recommendations.push('Security review recommended');
1019
+ if (functionalImpact.affectedSystems?.includes('security')) {
1020
+ summary.recommendations.push('Security review recommended')
925
1021
  }
926
- if (functionalImpact.affectedSystems && functionalImpact.affectedSystems.includes('performance')) {
927
- summary.recommendations.push('Performance testing advised');
1022
+ if (functionalImpact.affectedSystems?.includes('performance')) {
1023
+ summary.recommendations.push('Performance testing advised')
928
1024
  }
929
1025
 
930
1026
  // Identify risk factors
931
1027
  if (functionalImpact.severity === 'high') {
932
- summary.riskFactors.push('High impact changes');
1028
+ summary.riskFactors.push('High impact changes')
933
1029
  }
934
1030
  if (functionalImpact.scope === 'global') {
935
- summary.riskFactors.push('System-wide effects');
1031
+ summary.riskFactors.push('System-wide effects')
936
1032
  }
937
1033
  if (semanticAnalysis.frameworks && semanticAnalysis.frameworks.length > 2) {
938
- summary.riskFactors.push('Multiple framework dependencies');
1034
+ summary.riskFactors.push('Multiple framework dependencies')
939
1035
  }
940
1036
 
941
- return summary;
1037
+ return summary
942
1038
  }
943
1039
 
944
1040
  /**
945
1041
  * Perform semantic analysis on files and commit message
946
1042
  */
947
1043
  export function performSemanticAnalysis(files, subject, body) {
948
- const conventionalCommit = parseConventionalCommit(subject, body);
1044
+ const conventionalCommit = parseConventionalCommit(subject, body)
949
1045
  const analysis = {
950
1046
  commitType: conventionalCommit.type || 'unknown',
951
1047
  scope: conventionalCommit.scope,
@@ -958,43 +1054,43 @@ export function performSemanticAnalysis(files, subject, body) {
958
1054
  affectedDomains: new Set(),
959
1055
  technicalElements: new Set(),
960
1056
  businessElements: new Set(),
961
- complexity: 'low'
962
- };
1057
+ complexity: 'low',
1058
+ }
963
1059
 
964
1060
  // Analyze files
965
- files.forEach(file => {
966
- const filePath = file.filePath || file.path || '';
967
- const category = categorizeFile(filePath);
968
- const language = detectLanguage(filePath);
1061
+ files.forEach((file) => {
1062
+ const filePath = file.filePath || file.path || ''
1063
+ const category = categorizeFile(filePath)
1064
+ const language = detectLanguage(filePath)
969
1065
 
970
- analysis.affectedDomains.add(category);
971
- analysis.technicalElements.add(language);
1066
+ analysis.affectedDomains.add(category)
1067
+ analysis.technicalElements.add(language)
972
1068
 
973
1069
  // Business domain detection (only if filePath exists)
974
1070
  if (filePath && typeof filePath === 'string') {
975
1071
  if (filePath.includes('/user/') || filePath.includes('/customer/')) {
976
- analysis.businessElements.add('user_management');
1072
+ analysis.businessElements.add('user_management')
977
1073
  }
978
1074
  if (filePath.includes('/payment/') || filePath.includes('/billing/')) {
979
- analysis.businessElements.add('financial_operations');
1075
+ analysis.businessElements.add('financial_operations')
980
1076
  }
981
1077
  if (filePath.includes('/auth/') || filePath.includes('/security/')) {
982
- analysis.businessElements.add('security_access');
1078
+ analysis.businessElements.add('security_access')
983
1079
  }
984
1080
  if (filePath.includes('/analytics/') || filePath.includes('/metrics/')) {
985
- analysis.businessElements.add('business_intelligence');
1081
+ analysis.businessElements.add('business_intelligence')
986
1082
  }
987
1083
  }
988
- });
1084
+ })
989
1085
 
990
1086
  // Complexity assessment
991
1087
  if (files.length > 10 || analysis.affectedDomains.size > 3) {
992
- analysis.complexity = 'high';
1088
+ analysis.complexity = 'high'
993
1089
  } else if (files.length > 3 || analysis.affectedDomains.size > 1) {
994
- analysis.complexity = 'medium';
1090
+ analysis.complexity = 'medium'
995
1091
  }
996
1092
 
997
- return convertSetsToArrays(analysis);
1093
+ return convertSetsToArrays(analysis)
998
1094
  }
999
1095
 
1000
1096
  // ========================================
@@ -1005,102 +1101,57 @@ export function performSemanticAnalysis(files, subject, body) {
1005
1101
  * Build enhanced prompt for AI analysis
1006
1102
  */
1007
1103
  export function buildEnhancedPrompt(commitAnalysis, analysisMode = 'standard') {
1008
- const { subject, files = [], semanticAnalysis = {}, diffStats = {}, complexity = {}, riskAssessment = {} } = commitAnalysis;
1009
-
1010
- // Build comprehensive context - adjust based on commit size
1011
- const fileCount = files.length;
1012
- let fileLimit, diffLimit;
1013
-
1014
- if (fileCount <= 10) {
1015
- // Small commits: show all files with full diffs
1016
- fileLimit = fileCount;
1017
- diffLimit = 2000;
1018
- } else if (fileCount <= 30) {
1019
- // Medium commits: show most files with substantial diffs
1020
- fileLimit = Math.min(20, fileCount);
1021
- diffLimit = 1500;
1022
- } else {
1023
- // Large commits: prioritize modified files with meaningful diffs
1024
- fileLimit = 20;
1025
- diffLimit = 800; // Increased from 300 to actually see code changes
1026
- }
1027
-
1028
- // Sort files to prioritize those with actual code changes
1029
- const sortedFiles = [...files].sort((a, b) => {
1030
- // Prioritize modified files over added files
1031
- const statusPriority = { 'M': 0, 'R': 1, 'A': 2, 'D': 3 };
1032
- const aScore = statusPriority[a.status] || 4;
1033
- const bScore = statusPriority[b.status] || 4;
1034
- if (aScore !== bScore) return aScore - bScore;
1035
-
1036
- // Secondary: prioritize source code files
1037
- const isSourceFile = (path) => path.includes('/src/') || path.endsWith('.js') || path.endsWith('.ts');
1038
- const aIsSource = isSourceFile(a.filePath) ? 0 : 1;
1039
- const bIsSource = isSourceFile(b.filePath) ? 0 : 1;
1040
- return aIsSource - bIsSource;
1041
- });
1042
- const filesContext = sortedFiles.slice(0, fileLimit).map(file => ({
1043
- path: file.filePath,
1044
- status: file.status,
1045
- category: file.category || categorizeFile(file.filePath),
1046
- language: file.language || detectLanguage(file.filePath),
1047
- complexity: file.complexity,
1048
- semanticChanges: file.semanticChanges,
1049
- functionalImpact: file.functionalImpact,
1050
- businessRelevance: file.businessRelevance,
1051
- diff: file.diff || 'No diff available' // Include actual diff content so AI can see what changed
1052
- })).slice(0, fileLimit);
1053
-
1054
- // Enhanced analysis requirements based on mode
1055
- const getAnalysisRequirements = () => {
1056
- const baseRequirements = [
1057
- "**Primary Impact**: What does this change do for end users?",
1058
- "**Technical Scope**: How does this affect the codebase architecture?",
1059
- "**Business Value**: What problem does this solve or feature does this enable?",
1060
- "**Risk Assessment**: What are the potential impacts of this change?"
1061
- ];
1062
-
1063
- if (analysisMode === 'detailed' || analysisMode === 'enterprise') {
1064
- baseRequirements.push(
1065
- "**Migration Needs**: Are there any breaking changes or upgrade requirements?",
1066
- "**Performance Impact**: How might this affect system performance?",
1067
- "**Security Implications**: Are there any security considerations?",
1068
- "**Testing Strategy**: What areas should be prioritized for testing?",
1069
- "**Rollback Plan**: What would be needed if this change needs to be reverted?"
1070
- );
1071
- }
1072
-
1073
- if (analysisMode === 'enterprise') {
1074
- baseRequirements.push(
1075
- "**Operational Impact**: How does this affect deployment, monitoring, or operations?",
1076
- "**Compliance**: Are there regulatory or compliance considerations?",
1077
- "**Cross-team Impact**: What teams or systems might be affected?",
1078
- "**Timeline Implications**: Are there dependencies or staging requirements?"
1079
- );
1080
- }
1081
-
1082
- return baseRequirements;
1083
- };
1084
-
1085
- // Enhanced task description based on analysis mode
1086
- const getTaskDescription = () => {
1087
- switch (analysisMode) {
1088
- case 'enterprise':
1089
- return `Perform comprehensive enterprise-grade analysis of this git commit for changelog generation. Provide deep technical insights, thorough risk assessment, and detailed operational considerations suitable for enterprise deployment and stakeholder communication.`;
1090
- case 'detailed':
1091
- return `Perform detailed technical analysis of this git commit for changelog generation. Provide comprehensive technical insights, thorough impact assessment, and detailed migration considerations for development teams.`;
1092
- default:
1093
- return `Analyze this git commit for changelog generation using your reasoning capabilities.`;
1094
- }
1095
- };
1104
+ const {
1105
+ subject,
1106
+ files = [],
1107
+ semanticAnalysis = {},
1108
+ diffStats = {},
1109
+ complexity = {},
1110
+ riskAssessment = {},
1111
+ } = commitAnalysis
1112
+
1113
+ // Initialize DiffProcessor with analysis mode
1114
+ const diffProcessor = new DiffProcessor({
1115
+ analysisMode,
1116
+ enableFiltering: true,
1117
+ enablePatternDetection: true,
1118
+ })
1119
+
1120
+ // Process all files with intelligent diff compression
1121
+ const processedResult = diffProcessor.processFiles(files)
1122
+ const { processedFiles, patterns, filesProcessed, filesSkipped } = processedResult
1123
+
1124
+ const insertions = diffStats.insertions || 0
1125
+ const deletions = diffStats.deletions || 0
1126
+
1127
+ // Build pattern summary if patterns were detected
1128
+ const patternSummary =
1129
+ Object.keys(patterns).length > 0
1130
+ ? `\n**BULK PATTERNS DETECTED:**\n${Object.values(patterns)
1131
+ .map((p) => `- ${p.description}`)
1132
+ .join('\n')}\n`
1133
+ : ''
1134
+
1135
+ // Build files section with processed diffs
1136
+ const filesSection = processedFiles
1137
+ .map((file) => {
1138
+ if (file.isSummary) {
1139
+ return `\n**[REMAINING FILES SUMMARY]:**\n${file.diff}\n`
1140
+ }
1141
+
1142
+ const compressionInfo = file.compressionApplied
1143
+ ? ` [compressed from ${file.originalSize || 'unknown'} chars]`
1144
+ : ''
1145
+ const patternInfo = file.bulkPattern ? ` [${file.bulkPattern}]` : ''
1096
1146
 
1097
- const insertions = diffStats.insertions || 0;
1098
- const deletions = diffStats.deletions || 0;
1147
+ return `\n**${file.filePath || file.path}** (${file.status})${compressionInfo}${patternInfo}:\n${file.diff}\n`
1148
+ })
1149
+ .join('')
1099
1150
 
1100
1151
  const prompt = `Analyze this git commit for changelog generation.
1101
1152
 
1102
1153
  **COMMIT:** ${subject}
1103
- **FILES:** ${files.length} files, ${insertions + deletions} lines changed
1154
+ **FILES:** ${files.length} files (${filesProcessed} analyzed, ${filesSkipped} summarized), ${insertions + deletions} lines changed${patternSummary}
1104
1155
 
1105
1156
  **TARGET AUDIENCE:** End users and project stakeholders who need to understand what changed and why it matters.
1106
1157
 
@@ -1111,14 +1162,7 @@ export function buildEnhancedPrompt(commitAnalysis, analysisMode = 'standard') {
1111
1162
  2. **Then, focus on user impact** - what can they do now that they couldn't before?
1112
1163
  3. **Keep technical details minimal** - only what's necessary for understanding
1113
1164
 
1114
- **ACTUAL DIFFS** (top ${fileLimit} files):
1115
- ${filesContext.map(file => {
1116
- const diffPreview = file.diff ? file.diff.substring(0, diffLimit) + (file.diff.length > diffLimit ? '...' : '') : 'No diff available';
1117
- return `
1118
- **${file.path}** (${file.status}):
1119
- ${diffPreview}
1120
- `;
1121
- }).join('')}
1165
+ **PROCESSED DIFFS:**${filesSection}
1122
1166
 
1123
1167
  **CATEGORIZATION RULES (STRICTLY ENFORCED):**
1124
1168
  - **fix**: ONLY actual bug fixes - broken functionality now works correctly
@@ -1145,102 +1189,116 @@ Provide a JSON response with ONLY these fields:
1145
1189
  "recommendations": ["minimal", "list"],
1146
1190
  "breakingChanges": false,
1147
1191
  "migrationRequired": false
1148
- }`;
1192
+ }`
1149
1193
 
1150
- return prompt;
1194
+ return prompt
1151
1195
  }
1152
1196
 
1153
1197
  /**
1154
1198
  * Validate and correct AI categorization and impact assessment based on commit characteristics
1155
1199
  */
1156
1200
  export function validateCommitCategory(category, commitAnalysis) {
1157
- const { files = [], diffStats = {}, subject = '' } = commitAnalysis;
1158
- const fileCount = files.length;
1159
- const addedFiles = files.filter(f => f.status === 'A' || f.status === '??').length;
1160
- const { insertions = 0, deletions = 0 } = diffStats;
1201
+ const { files = [], diffStats = {}, subject = '' } = commitAnalysis
1202
+ const fileCount = files.length
1203
+ const addedFiles = files.filter((f) => f.status === 'A' || f.status === '??').length
1204
+ const { insertions = 0, deletions = 0 } = diffStats
1161
1205
 
1162
1206
  // Rule 1: Large additions cannot be bug fixes
1163
1207
  if (category === 'fix' && (fileCount > 10 || addedFiles > 5 || insertions > 1000)) {
1164
1208
  // Check if it's likely a refactor vs new feature
1165
1209
  if (deletions > insertions * 0.5) {
1166
- return 'refactor'; // Significant deletions suggest refactoring
1210
+ return 'refactor' // Significant deletions suggest refactoring
1167
1211
  }
1168
- return 'feature'; // More additions suggest new functionality
1212
+ return 'feature' // More additions suggest new functionality
1169
1213
  }
1170
1214
 
1171
1215
  // Rule 2: New module/class additions are features
1172
1216
  if (category === 'fix' && addedFiles > 0) {
1173
- const hasNewModules = files.some(f =>
1174
- (f.status === 'A' || f.status === '??') &&
1175
- (f.filePath.includes('.js') || f.filePath.includes('.ts') || f.filePath.includes('.py'))
1176
- );
1217
+ const hasNewModules = files.some(
1218
+ (f) =>
1219
+ (f.status === 'A' || f.status === '??') &&
1220
+ (f.filePath.includes('.js') || f.filePath.includes('.ts') || f.filePath.includes('.py'))
1221
+ )
1177
1222
  if (hasNewModules) {
1178
- return 'feature';
1223
+ return 'feature'
1179
1224
  }
1180
1225
  }
1181
1226
 
1182
1227
  // Rule 3: Documentation-only changes
1183
- if (category !== 'docs' && files.every(f =>
1184
- f.filePath.endsWith('.md') ||
1185
- f.filePath.endsWith('.txt') ||
1186
- f.filePath.includes('README') ||
1187
- f.filePath.includes('CHANGELOG')
1188
- )) {
1189
- return 'docs';
1228
+ if (
1229
+ category !== 'docs' &&
1230
+ files.every(
1231
+ (f) =>
1232
+ f.filePath.endsWith('.md') ||
1233
+ f.filePath.endsWith('.txt') ||
1234
+ f.filePath.includes('README') ||
1235
+ f.filePath.includes('CHANGELOG')
1236
+ )
1237
+ ) {
1238
+ return 'docs'
1190
1239
  }
1191
1240
 
1192
1241
  // Rule 4: Test-only changes
1193
- if (category !== 'test' && files.every(f =>
1194
- f.filePath.includes('test') ||
1195
- f.filePath.includes('spec') ||
1196
- f.filePath.endsWith('.test.js') ||
1197
- f.filePath.endsWith('.spec.js')
1198
- )) {
1199
- return 'test';
1200
- }
1201
-
1202
- return category; // No correction needed
1242
+ if (
1243
+ category !== 'test' &&
1244
+ files.every(
1245
+ (f) =>
1246
+ f.filePath.includes('test') ||
1247
+ f.filePath.includes('spec') ||
1248
+ f.filePath.endsWith('.test.js') ||
1249
+ f.filePath.endsWith('.spec.js')
1250
+ )
1251
+ ) {
1252
+ return 'test'
1253
+ }
1254
+
1255
+ return category // No correction needed
1203
1256
  }
1204
1257
 
1205
1258
  /**
1206
1259
  * Validate and correct impact assessment based on actual change magnitude
1207
1260
  */
1208
1261
  export function validateImpactAssessment(impact, commitAnalysis) {
1209
- const { files = [], diffStats = {}, subject = '' } = commitAnalysis;
1210
- const fileCount = files.length;
1211
- const addedFiles = files.filter(f => f.status === 'A' || f.status === '??').length;
1212
- const { insertions = 0, deletions = 0 } = diffStats;
1213
- const totalChanges = insertions + deletions;
1262
+ const { files = [], diffStats = {}, subject = '' } = commitAnalysis
1263
+ const fileCount = files.length
1264
+ const addedFiles = files.filter((f) => f.status === 'A' || f.status === '??').length
1265
+ const { insertions = 0, deletions = 0 } = diffStats
1266
+ const totalChanges = insertions + deletions
1214
1267
 
1215
1268
  // Rule 1: Large architectural changes cannot be "minimal" or "low"
1216
1269
  if ((impact === 'minimal' || impact === 'low') && (fileCount > 50 || totalChanges > 5000)) {
1217
- return 'high';
1270
+ return 'high'
1218
1271
  }
1219
1272
 
1220
1273
  // Rule 2: Major refactors or large features should be at least "medium"
1221
1274
  if (impact === 'minimal' && (fileCount > 20 || totalChanges > 2000 || addedFiles > 10)) {
1222
- return 'medium';
1275
+ return 'medium'
1223
1276
  }
1224
1277
 
1225
1278
  // Rule 3: Small changes cannot be "critical" or "high" unless breaking
1226
1279
  if ((impact === 'critical' || impact === 'high') && fileCount <= 3 && totalChanges <= 100) {
1227
- const hasBreakingIndicators = subject.includes('!') || subject.toLowerCase().includes('breaking');
1280
+ const hasBreakingIndicators =
1281
+ subject.includes('!') || subject.toLowerCase().includes('breaking')
1228
1282
  if (!hasBreakingIndicators) {
1229
- return 'medium';
1283
+ return 'medium'
1230
1284
  }
1231
1285
  }
1232
1286
 
1233
1287
  // Rule 4: Documentation-only changes should be low impact
1234
- if (files.every(f =>
1235
- f.filePath.endsWith('.md') ||
1236
- f.filePath.endsWith('.txt') ||
1237
- f.filePath.includes('README') ||
1238
- f.filePath.includes('CHANGELOG')
1239
- ) && (impact === 'critical' || impact === 'high')) {
1240
- return 'low';
1241
- }
1242
-
1243
- return impact; // No correction needed
1288
+ if (
1289
+ files.every(
1290
+ (f) =>
1291
+ f.filePath.endsWith('.md') ||
1292
+ f.filePath.endsWith('.txt') ||
1293
+ f.filePath.includes('README') ||
1294
+ f.filePath.includes('CHANGELOG')
1295
+ ) &&
1296
+ (impact === 'critical' || impact === 'high')
1297
+ ) {
1298
+ return 'low'
1299
+ }
1300
+
1301
+ return impact // No correction needed
1244
1302
  }
1245
1303
 
1246
1304
  /**
@@ -1258,19 +1316,19 @@ export function parseAIResponse(content, originalCommit = {}) {
1258
1316
  riskFactors: [],
1259
1317
  recommendations: ['Consider configuring AI provider for detailed analysis'],
1260
1318
  breakingChanges: false,
1261
- migrationRequired: false
1262
- };
1319
+ migrationRequired: false,
1320
+ }
1263
1321
  }
1264
1322
 
1265
1323
  try {
1266
1324
  // Try to extract JSON from the response
1267
- const jsonMatch = content.match(/\{[\s\S]*\}/);
1325
+ const jsonMatch = content.match(/\{[\s\S]*\}/)
1268
1326
  if (jsonMatch) {
1269
- const parsed = safeJsonParse(jsonMatch[0]);
1327
+ const parsed = safeJsonParse(jsonMatch[0])
1270
1328
  if (parsed) {
1271
1329
  // Validate and correct the category and impact
1272
- const validatedCategory = validateCommitCategory(parsed.category || 'chore', originalCommit);
1273
- const validatedImpact = validateImpactAssessment(parsed.impact || 'low', originalCommit);
1330
+ const validatedCategory = validateCommitCategory(parsed.category || 'chore', originalCommit)
1331
+ const validatedImpact = validateImpactAssessment(parsed.impact || 'low', originalCommit)
1274
1332
 
1275
1333
  return {
1276
1334
  summary: parsed.summary || originalCommit.subject || 'Unknown change',
@@ -1282,20 +1340,24 @@ export function parseAIResponse(content, originalCommit = {}) {
1282
1340
  riskFactors: Array.isArray(parsed.riskFactors) ? parsed.riskFactors : [],
1283
1341
  recommendations: Array.isArray(parsed.recommendations) ? parsed.recommendations : [],
1284
1342
  breakingChanges: Boolean(parsed.breakingChanges),
1285
- migrationRequired: Boolean(parsed.migrationRequired)
1286
- };
1343
+ migrationRequired: Boolean(parsed.migrationRequired),
1344
+ }
1287
1345
  }
1288
1346
  }
1289
1347
 
1290
1348
  // Fallback to text parsing if JSON extraction fails
1291
- const safeContent = content && typeof content === 'string' ? content : '';
1292
- const lowerContent = safeContent.toLowerCase();
1349
+ const safeContent = content && typeof content === 'string' ? content : ''
1350
+ const lowerContent = safeContent.toLowerCase()
1293
1351
 
1294
1352
  return {
1295
1353
  summary: originalCommit.subject || 'Unknown change',
1296
- impact: lowerContent.includes('critical') ? 'critical' :
1297
- lowerContent.includes('high') ? 'high' :
1298
- lowerContent.includes('medium') ? 'medium' : 'low',
1354
+ impact: lowerContent.includes('critical')
1355
+ ? 'critical'
1356
+ : lowerContent.includes('high')
1357
+ ? 'high'
1358
+ : lowerContent.includes('medium')
1359
+ ? 'medium'
1360
+ : 'low',
1299
1361
  category: extractCategoryFromText(safeContent),
1300
1362
  description: safeContent.substring(0, 200) + (safeContent.length > 200 ? '...' : ''),
1301
1363
  technicalDetails: safeContent,
@@ -1303,10 +1365,10 @@ export function parseAIResponse(content, originalCommit = {}) {
1303
1365
  riskFactors: [],
1304
1366
  recommendations: [],
1305
1367
  breakingChanges: lowerContent.includes('breaking'),
1306
- migrationRequired: lowerContent.includes('migration')
1307
- };
1368
+ migrationRequired: lowerContent.includes('migration'),
1369
+ }
1308
1370
  } catch (error) {
1309
- console.warn(colors.warningMessage(`AI response parsing error: ${error.message}`));
1371
+ console.warn(colors.warningMessage(`AI response parsing error: ${error.message}`))
1310
1372
  return {
1311
1373
  summary: originalCommit.subject || 'Unknown change',
1312
1374
  impact: 'low',
@@ -1317,8 +1379,8 @@ export function parseAIResponse(content, originalCommit = {}) {
1317
1379
  riskFactors: [],
1318
1380
  recommendations: [],
1319
1381
  breakingChanges: false,
1320
- migrationRequired: false
1321
- };
1382
+ migrationRequired: false,
1383
+ }
1322
1384
  }
1323
1385
  }
1324
1386
 
@@ -1328,21 +1390,39 @@ export function parseAIResponse(content, originalCommit = {}) {
1328
1390
  */
1329
1391
  function extractCategoryFromText(content) {
1330
1392
  if (!content || typeof content !== 'string') {
1331
- return 'chore';
1393
+ return 'chore'
1332
1394
  }
1333
- const text = content.toLowerCase();
1395
+ const text = content.toLowerCase()
1334
1396
 
1335
- if (text.includes('feature') || text.includes('feat')) return 'feature';
1336
- if (text.includes('fix') || text.includes('bug')) return 'fix';
1337
- if (text.includes('security')) return 'security';
1338
- if (text.includes('breaking')) return 'breaking';
1339
- if (text.includes('doc')) return 'docs';
1340
- if (text.includes('style')) return 'style';
1341
- if (text.includes('refactor')) return 'refactor';
1342
- if (text.includes('perf') || text.includes('performance')) return 'perf';
1343
- if (text.includes('test')) return 'test';
1397
+ if (text.includes('feature') || text.includes('feat')) {
1398
+ return 'feature'
1399
+ }
1400
+ if (text.includes('fix') || text.includes('bug')) {
1401
+ return 'fix'
1402
+ }
1403
+ if (text.includes('security')) {
1404
+ return 'security'
1405
+ }
1406
+ if (text.includes('breaking')) {
1407
+ return 'breaking'
1408
+ }
1409
+ if (text.includes('doc')) {
1410
+ return 'docs'
1411
+ }
1412
+ if (text.includes('style')) {
1413
+ return 'style'
1414
+ }
1415
+ if (text.includes('refactor')) {
1416
+ return 'refactor'
1417
+ }
1418
+ if (text.includes('perf') || text.includes('performance')) {
1419
+ return 'perf'
1420
+ }
1421
+ if (text.includes('test')) {
1422
+ return 'test'
1423
+ }
1344
1424
 
1345
- return 'chore';
1425
+ return 'chore'
1346
1426
  }
1347
1427
 
1348
1428
  // ========================================
@@ -1357,43 +1437,43 @@ function extractCategoryFromText(content) {
1357
1437
  */
1358
1438
  export function getWorkingDirectoryChanges() {
1359
1439
  try {
1360
- const result = execSync('git status --porcelain', { encoding: 'utf8' });
1440
+ const result = execSync('git status --porcelain', { encoding: 'utf8' })
1361
1441
 
1362
1442
  if (!result.trim()) {
1363
- return [];
1443
+ return []
1364
1444
  }
1365
1445
 
1366
- const lines = result.split('\n').filter(line => line.trim());
1446
+ const lines = result.split('\n').filter((line) => line.trim())
1367
1447
 
1368
- const changes = lines.map(line => {
1369
- const status = line.substring(0, 2).trim() || '??';
1370
- const filePath = line.substring(3);
1448
+ const changes = lines.map((line) => {
1449
+ const status = line.substring(0, 2).trim() || '??'
1450
+ const filePath = line.substring(3)
1371
1451
 
1372
1452
  return {
1373
- status: status,
1374
- filePath: filePath,
1453
+ status,
1454
+ filePath,
1375
1455
  path: filePath, // alias for compatibility
1376
1456
  diff: '', // Will be populated later if needed
1377
1457
  additions: 0,
1378
- deletions: 0
1379
- };
1380
- });
1458
+ deletions: 0,
1459
+ }
1460
+ })
1381
1461
 
1382
- return changes;
1462
+ return changes
1383
1463
  } catch (error) {
1384
- console.warn('Could not get git status:', error.message);
1385
- return [];
1464
+ console.warn('Could not get git status:', error.message)
1465
+ return []
1386
1466
  }
1387
1467
  }
1388
1468
 
1389
- export function processWorkingDirectoryChanges(workingDirChanges, gitManager = null) {
1469
+ export function processWorkingDirectoryChanges(workingDirChanges, _gitManager = null) {
1390
1470
  // If no changes provided, get them from git status
1391
1471
  if (!workingDirChanges) {
1392
- workingDirChanges = getWorkingDirectoryChanges();
1472
+ workingDirChanges = getWorkingDirectoryChanges()
1393
1473
  }
1394
1474
 
1395
1475
  if (!workingDirChanges || workingDirChanges.length === 0) {
1396
- return [];
1476
+ return []
1397
1477
  }
1398
1478
 
1399
1479
  const categorizedChanges = {
@@ -1401,15 +1481,15 @@ export function processWorkingDirectoryChanges(workingDirChanges, gitManager = n
1401
1481
  modified: [],
1402
1482
  deleted: [],
1403
1483
  renamed: [],
1404
- unknown: []
1405
- };
1484
+ unknown: [],
1485
+ }
1406
1486
 
1407
- let totalFiles = 0;
1487
+ let totalFiles = 0
1408
1488
 
1409
- workingDirChanges.forEach(change => {
1410
- const category = categorizeFile(change.filePath);
1411
- const language = detectLanguage(change.filePath);
1412
- const importance = assessFileImportance(change.filePath, change.status);
1489
+ workingDirChanges.forEach((change) => {
1490
+ const category = categorizeFile(change.filePath)
1491
+ const language = detectLanguage(change.filePath)
1492
+ const importance = assessFileImportance(change.filePath, change.status)
1413
1493
 
1414
1494
  const processedChange = {
1415
1495
  filePath: change.filePath,
@@ -1419,41 +1499,41 @@ export function processWorkingDirectoryChanges(workingDirChanges, gitManager = n
1419
1499
  importance,
1420
1500
  diff: change.diff || '',
1421
1501
  additions: change.additions || 0,
1422
- deletions: change.deletions || 0
1423
- };
1502
+ deletions: change.deletions || 0,
1503
+ }
1424
1504
 
1425
1505
  switch (change.status) {
1426
1506
  case 'A':
1427
- categorizedChanges.added.push(processedChange);
1428
- break;
1507
+ categorizedChanges.added.push(processedChange)
1508
+ break
1429
1509
  case 'M':
1430
- categorizedChanges.modified.push(processedChange);
1431
- break;
1510
+ categorizedChanges.modified.push(processedChange)
1511
+ break
1432
1512
  case 'D':
1433
- categorizedChanges.deleted.push(processedChange);
1434
- break;
1513
+ categorizedChanges.deleted.push(processedChange)
1514
+ break
1435
1515
  case 'R':
1436
- categorizedChanges.renamed.push(processedChange);
1437
- break;
1516
+ categorizedChanges.renamed.push(processedChange)
1517
+ break
1438
1518
  default:
1439
- categorizedChanges.unknown.push(processedChange);
1519
+ categorizedChanges.unknown.push(processedChange)
1440
1520
  }
1441
1521
 
1442
- totalFiles++;
1443
- });
1522
+ totalFiles++
1523
+ })
1444
1524
 
1445
1525
  // Generate summary
1446
- const summary = generateWorkspaceChangesSummary(categorizedChanges);
1526
+ const summary = generateWorkspaceChangesSummary(categorizedChanges)
1447
1527
 
1448
1528
  // Assess complexity
1449
- const complexity = assessWorkspaceComplexity(categorizedChanges, totalFiles);
1529
+ const complexity = assessWorkspaceComplexity(categorizedChanges, totalFiles)
1450
1530
 
1451
1531
  return {
1452
1532
  categorizedChanges,
1453
1533
  summary,
1454
1534
  totalFiles,
1455
- complexity
1456
- };
1535
+ complexity,
1536
+ }
1457
1537
  }
1458
1538
 
1459
1539
  /**
@@ -1465,195 +1545,232 @@ export function summarizeFileChanges(changes) {
1465
1545
  summary: 'No file changes detected',
1466
1546
  categories: {},
1467
1547
  stats: { added: 0, modified: 0, deleted: 0, renamed: 0 },
1468
- languages: {}
1469
- };
1548
+ languages: {},
1549
+ }
1470
1550
  }
1471
1551
 
1472
1552
  const stats = {
1473
1553
  added: 0,
1474
1554
  modified: 0,
1475
1555
  deleted: 0,
1476
- renamed: 0
1477
- };
1556
+ renamed: 0,
1557
+ }
1478
1558
 
1479
- const categories = {};
1480
- const languages = new Set();
1559
+ const categories = {}
1560
+ const languages = new Set()
1481
1561
 
1482
- changes.forEach(change => {
1562
+ changes.forEach((change) => {
1483
1563
  // Handle status mapping for git status format
1484
- let status = change.status;
1485
- if (status === '??') status = 'A'; // Untracked files are "added"
1486
- if (status.length > 1) status = status[0]; // Take first character for multiple status codes
1564
+ let status = change.status
1565
+ if (status === '??') {
1566
+ status = 'A' // Untracked files are "added"
1567
+ }
1568
+ if (status.length > 1) {
1569
+ status = status[0] // Take first character for multiple status codes
1570
+ }
1487
1571
 
1488
1572
  switch (status) {
1489
- case 'A': stats.added++; break;
1490
- case 'M': stats.modified++; break;
1491
- case 'D': stats.deleted++; break;
1492
- case 'R': stats.renamed++; break;
1573
+ case 'A':
1574
+ stats.added++
1575
+ break
1576
+ case 'M':
1577
+ stats.modified++
1578
+ break
1579
+ case 'D':
1580
+ stats.deleted++
1581
+ break
1582
+ case 'R':
1583
+ stats.renamed++
1584
+ break
1493
1585
  }
1494
1586
 
1495
- const filePath = change.filePath || change.path;
1496
- const category = categorizeFile(filePath);
1497
- const language = detectLanguage(filePath);
1587
+ const filePath = change.filePath || change.path
1588
+ const category = categorizeFile(filePath)
1589
+ const language = detectLanguage(filePath)
1498
1590
 
1499
1591
  // Group files by category
1500
1592
  if (!categories[category]) {
1501
- categories[category] = [];
1593
+ categories[category] = []
1502
1594
  }
1503
1595
  categories[category].push({
1504
- status: status,
1596
+ status,
1505
1597
  path: filePath,
1506
- language: language
1507
- });
1598
+ language,
1599
+ })
1508
1600
 
1509
- languages.add(language);
1510
- });
1601
+ languages.add(language)
1602
+ })
1511
1603
 
1512
- const parts = [];
1513
- if (stats.added > 0) parts.push(`${stats.added} added`);
1514
- if (stats.modified > 0) parts.push(`${stats.modified} modified`);
1515
- if (stats.deleted > 0) parts.push(`${stats.deleted} deleted`);
1516
- if (stats.renamed > 0) parts.push(`${stats.renamed} renamed`);
1604
+ const parts = []
1605
+ if (stats.added > 0) {
1606
+ parts.push(`${stats.added} added`)
1607
+ }
1608
+ if (stats.modified > 0) {
1609
+ parts.push(`${stats.modified} modified`)
1610
+ }
1611
+ if (stats.deleted > 0) {
1612
+ parts.push(`${stats.deleted} deleted`)
1613
+ }
1614
+ if (stats.renamed > 0) {
1615
+ parts.push(`${stats.renamed} renamed`)
1616
+ }
1517
1617
 
1518
- let summary = `${changes.length} files changed: ${parts.join(', ')}`;
1618
+ let summary = `${changes.length} files changed: ${parts.join(', ')}`
1519
1619
 
1520
1620
  if (Object.keys(categories).length > 0) {
1521
- summary += `. Affected areas: ${Object.keys(categories).join(', ')}`;
1621
+ summary += `. Affected areas: ${Object.keys(categories).join(', ')}`
1522
1622
  }
1523
1623
 
1524
1624
  return {
1525
1625
  summary,
1526
1626
  categories,
1527
1627
  stats,
1528
- languages: Array.from(languages)
1529
- };
1628
+ languages: Array.from(languages),
1629
+ }
1530
1630
  }
1531
1631
 
1532
1632
  /**
1533
1633
  * Build commit changelog from analyzed commits
1534
1634
  */
1535
1635
  export function buildCommitChangelog(analyzedCommits, releaseInsights, version, options = {}) {
1536
- const { metrics, includeAttribution = true } = options;
1537
- const currentDate = new Date().toISOString().split('T')[0];
1538
- const versionHeader = version || 'Unreleased';
1636
+ const { metrics, includeAttribution = true } = options
1637
+ const currentDate = new Date().toISOString().split('T')[0]
1638
+ const versionHeader = version || 'Unreleased'
1539
1639
 
1540
- let changelog = `# Changelog\n\n## [${versionHeader}] - ${currentDate}\n\n`;
1640
+ let changelog = `# Changelog\n\n## [${versionHeader}] - ${currentDate}\n\n`
1541
1641
 
1542
1642
  // Add release summary with business impact
1543
- if (releaseInsights && releaseInsights.summary) {
1544
- changelog += `### 📋 Release Summary\n${releaseInsights.summary}\n\n`;
1545
- changelog += `**Business Impact**: ${releaseInsights.businessImpact}\n`;
1546
- changelog += `**Complexity**: ${releaseInsights.complexity}\n`;
1547
- if (releaseInsights.deploymentRequirements && releaseInsights.deploymentRequirements.length > 0) {
1548
- changelog += `**Deployment Requirements**: ${releaseInsights.deploymentRequirements.join(', ')}\n`;
1643
+ if (releaseInsights?.summary) {
1644
+ changelog += `### 📋 Release Summary\n${releaseInsights.summary}\n\n`
1645
+ changelog += `**Business Impact**: ${releaseInsights.businessImpact}\n`
1646
+ changelog += `**Complexity**: ${releaseInsights.complexity}\n`
1647
+ if (
1648
+ releaseInsights.deploymentRequirements &&
1649
+ releaseInsights.deploymentRequirements.length > 0
1650
+ ) {
1651
+ changelog += `**Deployment Requirements**: ${releaseInsights.deploymentRequirements.join(', ')}\n`
1549
1652
  }
1550
- changelog += '\n';
1653
+ changelog += '\n'
1551
1654
  }
1552
1655
 
1553
1656
  // Use unified format with impact-based ordering instead of categorized sections
1554
- changelog += `## Changes\n\n`;
1657
+ changelog += '## Changes\n\n'
1555
1658
 
1556
1659
  // Sort commits by business impact (breaking/critical first, then by impact level)
1557
1660
  const sortedCommits = analyzedCommits.sort((a, b) => {
1558
1661
  // Breaking changes always come first
1559
- if (a.breaking && !b.breaking) return -1;
1560
- if (!a.breaking && b.breaking) return 1;
1662
+ if (a.breaking && !b.breaking) {
1663
+ return -1
1664
+ }
1665
+ if (!a.breaking && b.breaking) {
1666
+ return 1
1667
+ }
1561
1668
 
1562
1669
  // Then sort by impact level
1563
- const impactOrder = { critical: 0, high: 1, medium: 2, low: 3, minimal: 4 };
1564
- const aImpact = impactOrder[a.aiSummary?.impact] ?? 5;
1565
- const bImpact = impactOrder[b.aiSummary?.impact] ?? 5;
1670
+ const impactOrder = { critical: 0, high: 1, medium: 2, low: 3, minimal: 4 }
1671
+ const aImpact = impactOrder[a.aiSummary?.impact] ?? 5
1672
+ const bImpact = impactOrder[b.aiSummary?.impact] ?? 5
1566
1673
 
1567
- return aImpact - bImpact;
1568
- });
1674
+ return aImpact - bImpact
1675
+ })
1569
1676
 
1570
- sortedCommits.forEach(commit => {
1571
- const type = commit.breaking ? 'breaking' : (commit.type || 'chore');
1572
- const summary = commit.aiSummary?.summary || commit.subject;
1573
- const confidence = commit.aiSummary?.confidence ? `${Math.round(commit.aiSummary.confidence * 100)}%` : '85%';
1677
+ sortedCommits.forEach((commit) => {
1678
+ const type = commit.breaking ? 'breaking' : commit.type || 'chore'
1679
+ const summary = commit.aiSummary?.summary || commit.subject
1680
+ const confidence = commit.aiSummary?.confidence
1681
+ ? `${Math.round(commit.aiSummary.confidence * 100)}%`
1682
+ : '85%'
1574
1683
 
1575
1684
  // Format: - (tag) Brief description - Detailed explanation (hash) (confidence%)
1576
- let line = `- (${type}) ${summary}`;
1685
+ let line = `- (${type}) ${summary}`
1577
1686
 
1578
1687
  // Add breaking change indicator
1579
1688
  if (commit.breaking || commit.aiSummary?.breaking) {
1580
- line += ' ⚠️ BREAKING CHANGE';
1689
+ line += ' ⚠️ BREAKING CHANGE'
1581
1690
  }
1582
1691
 
1583
1692
  // Add high/critical impact indicator
1584
1693
  if (commit.aiSummary?.impact === 'critical' || commit.aiSummary?.impact === 'high') {
1585
- line += ' 🔥';
1694
+ line += ' 🔥'
1586
1695
  }
1587
1696
 
1588
1697
  // Add detailed technical explanation
1589
1698
  if (commit.aiSummary?.technicalDetails) {
1590
- line += ` - ${commit.aiSummary.technicalDetails}`;
1591
- } else if (commit.aiSummary?.description && commit.aiSummary.description !== commit.aiSummary.summary) {
1592
- line += ` - ${commit.aiSummary.description}`;
1699
+ line += ` - ${commit.aiSummary.technicalDetails}`
1700
+ } else if (
1701
+ commit.aiSummary?.description &&
1702
+ commit.aiSummary.description !== commit.aiSummary.summary
1703
+ ) {
1704
+ line += ` - ${commit.aiSummary.description}`
1593
1705
  }
1594
1706
 
1595
1707
  // Add commit hash and confidence
1596
- line += ` (${commit.hash}) (${confidence})`;
1708
+ line += ` (${commit.hash}) (${confidence})`
1597
1709
 
1598
- changelog += `${line}\n`;
1710
+ changelog += `${line}\n`
1599
1711
 
1600
1712
  // Add sub-bullets for additional details
1601
1713
  if (commit.aiSummary?.highlights?.length > 1) {
1602
- commit.aiSummary.highlights.slice(1).forEach(highlight => {
1603
- changelog += ` - ${highlight}\n`;
1604
- });
1714
+ commit.aiSummary.highlights.slice(1).forEach((highlight) => {
1715
+ changelog += ` - ${highlight}\n`
1716
+ })
1605
1717
  }
1606
1718
 
1607
1719
  // Add migration notes
1608
1720
  if (commit.aiSummary?.migrationNotes) {
1609
- changelog += ` - **Migration**: ${commit.aiSummary.migrationNotes}\n`;
1721
+ changelog += ` - **Migration**: ${commit.aiSummary.migrationNotes}\n`
1610
1722
  }
1611
1723
 
1612
- changelog += '\n';
1613
- });
1724
+ changelog += '\n'
1725
+ })
1614
1726
 
1615
1727
  // Add detailed risk assessment
1616
1728
  if (releaseInsights && (releaseInsights.riskLevel !== 'low' || releaseInsights.breaking)) {
1617
- changelog += `### ⚠️ Risk Assessment\n`;
1618
- changelog += `**Risk Level:** ${releaseInsights.riskLevel.toUpperCase()}\n\n`;
1729
+ changelog += '### ⚠️ Risk Assessment\n'
1730
+ changelog += `**Risk Level:** ${releaseInsights.riskLevel.toUpperCase()}\n\n`
1619
1731
 
1620
1732
  if (releaseInsights.breaking) {
1621
- changelog += `🚨 **Breaking Changes**: This release contains breaking changes. Please review migration notes above.\n\n`;
1733
+ changelog +=
1734
+ '🚨 **Breaking Changes**: This release contains breaking changes. Please review migration notes above.\n\n'
1622
1735
  }
1623
1736
 
1624
- if (releaseInsights.deploymentRequirements && releaseInsights.deploymentRequirements.length > 0) {
1625
- changelog += `📋 **Deployment Requirements**:\n`;
1626
- releaseInsights.deploymentRequirements.forEach(req => {
1627
- changelog += `- ${req}\n`;
1628
- });
1629
- changelog += '\n';
1737
+ if (
1738
+ releaseInsights.deploymentRequirements &&
1739
+ releaseInsights.deploymentRequirements.length > 0
1740
+ ) {
1741
+ changelog += '📋 **Deployment Requirements**:\n'
1742
+ releaseInsights.deploymentRequirements.forEach((req) => {
1743
+ changelog += `- ${req}\n`
1744
+ })
1745
+ changelog += '\n'
1630
1746
  }
1631
1747
  }
1632
1748
 
1633
1749
  // Add performance metrics
1634
1750
  if (metrics) {
1635
- changelog += `### 📊 Generation Metrics\n`;
1636
- changelog += `- **Total Commits**: ${analyzedCommits.length}\n`;
1751
+ changelog += '### 📊 Generation Metrics\n'
1752
+ changelog += `- **Total Commits**: ${analyzedCommits.length}\n`
1637
1753
  if (metrics.startTime) {
1638
- changelog += `- **Processing Time**: ${formatDuration(Date.now() - metrics.startTime)}\n`;
1754
+ changelog += `- **Processing Time**: ${formatDuration(Date.now() - metrics.startTime)}\n`
1639
1755
  }
1640
- changelog += `- **AI Calls**: ${metrics.apiCalls || 0}\n`;
1756
+ changelog += `- **AI Calls**: ${metrics.apiCalls || 0}\n`
1641
1757
  if (metrics.totalTokens > 0) {
1642
- changelog += `- **Tokens Used**: ${metrics.totalTokens.toLocaleString()}\n`;
1758
+ changelog += `- **Tokens Used**: ${metrics.totalTokens.toLocaleString()}\n`
1643
1759
  }
1644
- changelog += `- **Batches Processed**: ${metrics.batchesProcessed || 0}\n`;
1760
+ changelog += `- **Batches Processed**: ${metrics.batchesProcessed || 0}\n`
1645
1761
  if (metrics.errors > 0) {
1646
- changelog += `- **Errors**: ${metrics.errors}\n`;
1762
+ changelog += `- **Errors**: ${metrics.errors}\n`
1647
1763
  }
1648
- changelog += '\n';
1764
+ changelog += '\n'
1649
1765
  }
1650
1766
 
1651
1767
  // Add attribution footer
1652
1768
  if (includeAttribution !== false) {
1653
- changelog += `---\n\n*Generated using [ai-changelog-generator](https://github.com/entro314-labs/AI-changelog-generator) - AI-powered changelog generation for Git repositories*\n`;
1769
+ changelog +=
1770
+ '---\n\n*Generated using [ai-changelog-generator](https://github.com/entro314-labs/AI-changelog-generator) - AI-powered changelog generation for Git repositories*\n'
1654
1771
  }
1655
1772
 
1656
- return changelog;
1773
+ return changelog
1657
1774
  }
1658
1775
 
1659
1776
  /**
@@ -1661,22 +1778,22 @@ export function buildCommitChangelog(analyzedCommits, releaseInsights, version,
1661
1778
  * Helper for processWorkingDirectoryChanges
1662
1779
  */
1663
1780
  function generateWorkspaceChangesSummary(categorizedChanges) {
1664
- const parts = [];
1781
+ const parts = []
1665
1782
 
1666
1783
  if (categorizedChanges.added.length > 0) {
1667
- parts.push(`${categorizedChanges.added.length} files added`);
1784
+ parts.push(`${categorizedChanges.added.length} files added`)
1668
1785
  }
1669
1786
  if (categorizedChanges.modified.length > 0) {
1670
- parts.push(`${categorizedChanges.modified.length} files modified`);
1787
+ parts.push(`${categorizedChanges.modified.length} files modified`)
1671
1788
  }
1672
1789
  if (categorizedChanges.deleted.length > 0) {
1673
- parts.push(`${categorizedChanges.deleted.length} files deleted`);
1790
+ parts.push(`${categorizedChanges.deleted.length} files deleted`)
1674
1791
  }
1675
1792
  if (categorizedChanges.renamed.length > 0) {
1676
- parts.push(`${categorizedChanges.renamed.length} files renamed`);
1793
+ parts.push(`${categorizedChanges.renamed.length} files renamed`)
1677
1794
  }
1678
1795
 
1679
- return parts.length > 0 ? parts.join(', ') : 'No changes detected';
1796
+ return parts.length > 0 ? parts.join(', ') : 'No changes detected'
1680
1797
  }
1681
1798
 
1682
1799
  /**
@@ -1684,10 +1801,16 @@ function generateWorkspaceChangesSummary(categorizedChanges) {
1684
1801
  * Helper for processWorkingDirectoryChanges
1685
1802
  */
1686
1803
  function assessWorkspaceComplexity(categorizedChanges, totalFiles) {
1687
- if (totalFiles > 20) return 'high';
1688
- if (totalFiles > 5) return 'medium';
1689
- if (categorizedChanges.deleted.length > 0) return 'medium';
1690
- return 'low';
1804
+ if (totalFiles > 20) {
1805
+ return 'high'
1806
+ }
1807
+ if (totalFiles > 5) {
1808
+ return 'medium'
1809
+ }
1810
+ if (categorizedChanges.deleted.length > 0) {
1811
+ return 'medium'
1812
+ }
1813
+ return 'low'
1691
1814
  }
1692
1815
 
1693
1816
  // ========================================
@@ -1698,48 +1821,48 @@ function assessWorkspaceComplexity(categorizedChanges, totalFiles) {
1698
1821
  * Handle unified output for analysis commands
1699
1822
  */
1700
1823
  export function handleUnifiedOutput(data, config) {
1701
- const { format = 'markdown', outputFile, silent } = config;
1824
+ const { format = 'markdown', outputFile, silent } = config
1702
1825
  if (format === 'json') {
1703
- const jsonOutput = safeJsonStringify(data);
1826
+ const jsonOutput = safeJsonStringify(data)
1704
1827
 
1705
1828
  if (outputFile) {
1706
1829
  // Write to file
1707
1830
  try {
1708
- fs.writeFileSync(outputFile, jsonOutput, 'utf8');
1831
+ fs.writeFileSync(outputFile, jsonOutput, 'utf8')
1709
1832
  if (!silent) {
1710
- console.log(colors.success(`📄 Changelog saved to: ${colors.file(outputFile)}`));
1833
+ console.log(colors.success(`📄 Changelog saved to: ${colors.file(outputFile)}`))
1711
1834
  }
1712
1835
  } catch (error) {
1713
- console.error(`❌ Error writing to file: ${error.message}`);
1836
+ console.error(`❌ Error writing to file: ${error.message}`)
1714
1837
  if (!silent) {
1715
- console.log(jsonOutput);
1838
+ console.log(jsonOutput)
1716
1839
  }
1717
1840
  }
1718
1841
  } else if (!silent) {
1719
- console.log(jsonOutput);
1842
+ console.log(jsonOutput)
1720
1843
  }
1721
1844
  } else {
1722
1845
  // Markdown format (default)
1723
- const markdownOutput = typeof data === 'string' ? data : safeJsonStringify(data);
1846
+ const markdownOutput = typeof data === 'string' ? data : safeJsonStringify(data)
1724
1847
 
1725
1848
  if (outputFile) {
1726
1849
  try {
1727
- fs.writeFileSync(outputFile, markdownOutput, 'utf8');
1850
+ fs.writeFileSync(outputFile, markdownOutput, 'utf8')
1728
1851
  if (!silent) {
1729
- console.log(colors.success(`📄 Changelog saved to: ${colors.file(outputFile)}`));
1852
+ console.log(colors.success(`📄 Changelog saved to: ${colors.file(outputFile)}`))
1730
1853
  }
1731
1854
  } catch (error) {
1732
- console.error(`❌ Error writing to file: ${error.message}`);
1855
+ console.error(`❌ Error writing to file: ${error.message}`)
1733
1856
  if (!silent) {
1734
- console.log(markdownOutput);
1857
+ console.log(markdownOutput)
1735
1858
  }
1736
1859
  }
1737
1860
  } else if (!silent) {
1738
- console.log(markdownOutput);
1861
+ console.log(markdownOutput)
1739
1862
  }
1740
1863
  }
1741
1864
 
1742
- return data;
1865
+ return data
1743
1866
  }
1744
1867
 
1745
1868
  /**
@@ -1747,7 +1870,7 @@ export function handleUnifiedOutput(data, config) {
1747
1870
  * Wrapper around handleUnifiedOutput for backwards compatibility
1748
1871
  */
1749
1872
  export function outputData(data, format = 'markdown') {
1750
- return handleUnifiedOutput(data, { format, silent: false });
1873
+ return handleUnifiedOutput(data, { format, silent: false })
1751
1874
  }
1752
1875
 
1753
1876
  // ========================================
@@ -1758,9 +1881,9 @@ export function outputData(data, format = 'markdown') {
1758
1881
  * Run interactive mode with full menu system
1759
1882
  */
1760
1883
  export async function runInteractiveMode() {
1761
- const { select, intro } = await import('@clack/prompts');
1884
+ const { select, intro } = await import('@clack/prompts')
1762
1885
 
1763
- intro(colors.header('🎮 AI Changelog Generator - Interactive Mode'));
1886
+ intro(colors.header('🎮 AI Changelog Generator - Interactive Mode'))
1764
1887
 
1765
1888
  const choices = [
1766
1889
  { value: 'changelog-recent', label: '📝 Generate changelog from recent commits' },
@@ -1770,114 +1893,131 @@ export async function runInteractiveMode() {
1770
1893
  { value: 'commit-message', label: '💬 Generate commit message for current changes' },
1771
1894
  { value: 'configure-providers', label: '⚙️ Configure AI providers' },
1772
1895
  { value: 'validate-config', label: '🔍 Validate configuration' },
1773
- { value: 'exit', label: '❌ Exit' }
1774
- ];
1896
+ { value: 'exit', label: '❌ Exit' },
1897
+ ]
1775
1898
 
1776
1899
  const action = await select({
1777
1900
  message: 'What would you like to do?',
1778
- options: choices
1779
- });
1901
+ options: choices,
1902
+ })
1780
1903
 
1781
- return { action, timestamp: new Date().toISOString() };
1904
+ return { action, timestamp: new Date().toISOString() }
1782
1905
  }
1783
1906
 
1784
1907
  /**
1785
1908
  * Analyze changes for commit message with detailed analysis
1786
1909
  */
1787
1910
  export function analyzeChangesForCommitMessage(changes, includeScope = false) {
1788
- if (!changes || !Array.isArray(changes) || changes.length === 0) {
1911
+ if (!(changes && Array.isArray(changes)) || changes.length === 0) {
1789
1912
  return {
1790
1913
  summary: 'No changes detected',
1791
1914
  scope: null,
1792
1915
  changes: 0,
1793
1916
  recommendations: ['Make some changes first'],
1794
- type: 'chore'
1795
- };
1917
+ type: 'chore',
1918
+ }
1796
1919
  }
1797
1920
 
1798
1921
  // Categorize changes
1799
- const categories = {};
1800
- const scopes = new Set();
1801
- let hasTests = false;
1802
- let hasDocs = false;
1803
- let hasConfig = false;
1804
- let hasSource = false;
1922
+ const categories = {}
1923
+ const scopes = new Set()
1924
+ let hasTests = false
1925
+ let hasDocs = false
1926
+ let hasConfig = false
1927
+ let hasSource = false
1805
1928
 
1806
- changes.forEach(change => {
1807
- const filePath = change.path || change.filePath || '';
1808
- const category = categorizeFile(filePath);
1929
+ changes.forEach((change) => {
1930
+ const filePath = change.path || change.filePath || ''
1931
+ const category = categorizeFile(filePath)
1809
1932
 
1810
- if (!categories[category]) categories[category] = [];
1811
- categories[category].push(change);
1933
+ if (!categories[category]) {
1934
+ categories[category] = []
1935
+ }
1936
+ categories[category].push(change)
1812
1937
 
1813
1938
  // Extract scope from path
1814
1939
  if (includeScope) {
1815
- const pathParts = filePath.split('/');
1940
+ const pathParts = filePath.split('/')
1816
1941
  if (pathParts.length > 1) {
1817
- const scope = pathParts[pathParts.length - 2];
1942
+ const scope = pathParts.at(-2)
1818
1943
  if (scope && scope !== '.' && scope !== '..') {
1819
- scopes.add(scope);
1944
+ scopes.add(scope)
1820
1945
  }
1821
1946
  }
1822
1947
  }
1823
1948
 
1824
1949
  // Track content types
1825
- if (category === 'tests') hasTests = true;
1826
- if (category === 'documentation') hasDocs = true;
1827
- if (category === 'configuration') hasConfig = true;
1828
- if (category === 'source') hasSource = true;
1829
- });
1950
+ if (category === 'tests') {
1951
+ hasTests = true
1952
+ }
1953
+ if (category === 'documentation') {
1954
+ hasDocs = true
1955
+ }
1956
+ if (category === 'configuration') {
1957
+ hasConfig = true
1958
+ }
1959
+ if (category === 'source') {
1960
+ hasSource = true
1961
+ }
1962
+ })
1830
1963
 
1831
1964
  // Determine primary category and type
1832
- const primaryCategory = Object.keys(categories)
1833
- .sort((a, b) => categories[b].length - categories[a].length)[0];
1965
+ const primaryCategory = Object.keys(categories).sort(
1966
+ (a, b) => categories[b].length - categories[a].length
1967
+ )[0]
1834
1968
 
1835
1969
  const typeMapping = {
1836
- 'source': 'feat',
1837
- 'tests': 'test',
1838
- 'documentation': 'docs',
1839
- 'configuration': 'chore',
1840
- 'build': 'build',
1841
- 'frontend': 'feat',
1842
- 'assets': 'chore'
1843
- };
1970
+ source: 'feat',
1971
+ tests: 'test',
1972
+ documentation: 'docs',
1973
+ configuration: 'chore',
1974
+ build: 'build',
1975
+ frontend: 'feat',
1976
+ assets: 'chore',
1977
+ }
1844
1978
 
1845
- const commitType = typeMapping[primaryCategory] || 'chore';
1979
+ const commitType = typeMapping[primaryCategory] || 'chore'
1846
1980
 
1847
1981
  // Generate summary
1848
- const fileCount = changes.length;
1849
- const addedFiles = changes.filter(c => c.status === 'A').length;
1850
- const modifiedFiles = changes.filter(c => c.status === 'M').length;
1851
- const deletedFiles = changes.filter(c => c.status === 'D').length;
1982
+ const fileCount = changes.length
1983
+ const addedFiles = changes.filter((c) => c.status === 'A').length
1984
+ const modifiedFiles = changes.filter((c) => c.status === 'M').length
1985
+ const deletedFiles = changes.filter((c) => c.status === 'D').length
1852
1986
 
1853
- let summary = `${fileCount} file${fileCount === 1 ? '' : 's'} changed`;
1987
+ let summary = `${fileCount} file${fileCount === 1 ? '' : 's'} changed`
1854
1988
 
1855
- const changeDetails = [];
1856
- if (addedFiles > 0) changeDetails.push(`${addedFiles} added`);
1857
- if (modifiedFiles > 0) changeDetails.push(`${modifiedFiles} modified`);
1858
- if (deletedFiles > 0) changeDetails.push(`${deletedFiles} deleted`);
1989
+ const changeDetails = []
1990
+ if (addedFiles > 0) {
1991
+ changeDetails.push(`${addedFiles} added`)
1992
+ }
1993
+ if (modifiedFiles > 0) {
1994
+ changeDetails.push(`${modifiedFiles} modified`)
1995
+ }
1996
+ if (deletedFiles > 0) {
1997
+ changeDetails.push(`${deletedFiles} deleted`)
1998
+ }
1859
1999
 
1860
2000
  if (changeDetails.length > 0) {
1861
- summary += ` (${changeDetails.join(', ')})`;
2001
+ summary += ` (${changeDetails.join(', ')})`
1862
2002
  }
1863
2003
 
1864
2004
  // Generate recommendations
1865
- const recommendations = [];
2005
+ const recommendations = []
1866
2006
 
1867
2007
  if (hasSource && !hasTests) {
1868
- recommendations.push('Consider adding tests for source changes');
2008
+ recommendations.push('Consider adding tests for source changes')
1869
2009
  }
1870
2010
 
1871
2011
  if (hasSource && !hasDocs) {
1872
- recommendations.push('Consider updating documentation');
2012
+ recommendations.push('Consider updating documentation')
1873
2013
  }
1874
2014
 
1875
2015
  if (fileCount > 10) {
1876
- recommendations.push('Consider breaking this into smaller commits');
2016
+ recommendations.push('Consider breaking this into smaller commits')
1877
2017
  }
1878
2018
 
1879
2019
  if (hasConfig) {
1880
- recommendations.push('Review configuration changes carefully');
2020
+ recommendations.push('Review configuration changes carefully')
1881
2021
  }
1882
2022
 
1883
2023
  return {
@@ -1895,68 +2035,70 @@ export function analyzeChangesForCommitMessage(changes, includeScope = false) {
1895
2035
  hasTests,
1896
2036
  hasDocs,
1897
2037
  hasConfig,
1898
- hasSource
1899
- }
1900
- };
2038
+ hasSource,
2039
+ },
2040
+ }
1901
2041
  }
1902
2042
 
1903
2043
  /**
1904
2044
  * Select specific commits with interactive interface
1905
2045
  */
1906
2046
  export async function selectSpecificCommits(maxCommits = 20) {
1907
- const { multiselect, note } = await import('@clack/prompts');
1908
- const { execSync } = await import('child_process');
2047
+ const { multiselect, note } = await import('@clack/prompts')
2048
+ const { execSync } = await import('node:child_process')
1909
2049
 
1910
2050
  try {
1911
- note('🔍 Fetching recent commits...', 'Please wait');
2051
+ note('🔍 Fetching recent commits...', 'Please wait')
1912
2052
 
1913
2053
  // Get recent commits with formatted output
1914
- const gitCommand = `git log --oneline --no-merges -${maxCommits} --pretty=format:"%h|%s|%an|%ar"`;
1915
- const output = execSync(gitCommand, { encoding: 'utf8' });
2054
+ const gitCommand = `git log --oneline --no-merges -${maxCommits} --pretty=format:"%h|%s|%an|%ar"`
2055
+ const output = execSync(gitCommand, { encoding: 'utf8' })
1916
2056
 
1917
2057
  if (!output.trim()) {
1918
- note('No commits found', 'Warning');
1919
- return [];
2058
+ note('No commits found', 'Warning')
2059
+ return []
1920
2060
  }
1921
2061
 
1922
2062
  // Parse commits
1923
- const commits = output.trim().split('\n').map(line => {
1924
- const [hash, subject, author, date] = line.split('|');
1925
- return {
1926
- hash,
1927
- subject: subject || 'No subject',
1928
- author: author || 'Unknown',
1929
- date: date || 'Unknown',
1930
- display: `${hash} ${subject} (${author}, ${date})`
1931
- };
1932
- });
2063
+ const commits = output
2064
+ .trim()
2065
+ .split('\n')
2066
+ .map((line) => {
2067
+ const [hash, subject, author, date] = line.split('|')
2068
+ return {
2069
+ hash,
2070
+ subject: subject || 'No subject',
2071
+ author: author || 'Unknown',
2072
+ date: date || 'Unknown',
2073
+ display: `${hash} ${subject} (${author}, ${date})`,
2074
+ }
2075
+ })
1933
2076
 
1934
2077
  if (commits.length === 0) {
1935
- note('No commits available', 'Warning');
1936
- return [];
2078
+ note('No commits available', 'Warning')
2079
+ return []
1937
2080
  }
1938
2081
 
1939
- note(`Found ${commits.length} recent commits`, 'Info');
2082
+ note(`Found ${commits.length} recent commits`, 'Info')
1940
2083
 
1941
2084
  // Create choices for selection
1942
- const choices = commits.map(commit => ({
2085
+ const choices = commits.map((commit) => ({
1943
2086
  value: commit.hash,
1944
2087
  label: commit.display,
1945
- hint: commit.hash
1946
- }));
2088
+ hint: commit.hash,
2089
+ }))
1947
2090
 
1948
2091
  const selectedCommits = await multiselect({
1949
2092
  message: 'Select commits to include in changelog:',
1950
2093
  options: choices,
1951
- required: true
1952
- });
1953
-
1954
- note(`✅ Selected ${selectedCommits.length} commits`, 'Success');
1955
- return selectedCommits;
2094
+ required: true,
2095
+ })
1956
2096
 
2097
+ note(`✅ Selected ${selectedCommits.length} commits`, 'Success')
2098
+ return selectedCommits
1957
2099
  } catch (error) {
1958
- note(`Error fetching commits: ${error.message}`, 'Error');
1959
- return [];
2100
+ note(`Error fetching commits: ${error.message}`, 'Error')
2101
+ return []
1960
2102
  }
1961
2103
  }
1962
2104
 
@@ -1968,26 +2110,33 @@ export async function selectSpecificCommits(maxCommits = 20) {
1968
2110
  * Assess change complexity based on diff output
1969
2111
  */
1970
2112
  export function assessChangeComplexity(diff) {
1971
- if (!diff || diff === 'Binary file or diff unavailable') return { score: 1 };
2113
+ if (!diff || diff === 'Binary file or diff unavailable') {
2114
+ return { score: 1 }
2115
+ }
1972
2116
 
1973
- const lines = diff.split('\n');
1974
- const additions = lines.filter(line => line.startsWith('+')).length;
1975
- const deletions = lines.filter(line => line.startsWith('-')).length;
1976
- const total = additions + deletions;
2117
+ const lines = diff.split('\n')
2118
+ const additions = lines.filter((line) => line.startsWith('+')).length
2119
+ const deletions = lines.filter((line) => line.startsWith('-')).length
2120
+ const total = additions + deletions
1977
2121
 
1978
- let score = 1;
1979
- if (total >= 200) score = 5;
1980
- else if (total >= 100) score = 4;
1981
- else if (total >= 50) score = 3;
1982
- else if (total >= 10) score = 2;
2122
+ let score = 1
2123
+ if (total >= 200) {
2124
+ score = 5
2125
+ } else if (total >= 100) {
2126
+ score = 4
2127
+ } else if (total >= 50) {
2128
+ score = 3
2129
+ } else if (total >= 10) {
2130
+ score = 2
2131
+ }
1983
2132
 
1984
2133
  return {
1985
2134
  score,
1986
2135
  additions,
1987
2136
  deletions,
1988
2137
  total,
1989
- level: score <= 2 ? 'low' : score <= 3 ? 'medium' : 'high'
1990
- };
2138
+ level: score <= 2 ? 'low' : score <= 3 ? 'medium' : 'high',
2139
+ }
1991
2140
  }
1992
2141
 
1993
2142
  // ========================================
@@ -1999,11 +2148,11 @@ export function assessChangeComplexity(diff) {
1999
2148
  */
2000
2149
  export function getCurrentBranch() {
2001
2150
  try {
2002
- const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim();
2003
- return branch !== 'HEAD' ? branch : null;
2004
- } catch (error) {
2005
- console.warn(colors.warning('Could not determine current branch'));
2006
- return null;
2151
+ const branch = execSync('git rev-parse --abbrev-ref HEAD', { encoding: 'utf8' }).trim()
2152
+ return branch !== 'HEAD' ? branch : null
2153
+ } catch (_error) {
2154
+ console.warn(colors.warning('Could not determine current branch'))
2155
+ return null
2007
2156
  }
2008
2157
  }
2009
2158
 
@@ -2012,8 +2161,8 @@ export function getCurrentBranch() {
2012
2161
  * Based on better-commits patterns: type/ticket-description, feat/ABC-123-add-feature
2013
2162
  */
2014
2163
  export function analyzeBranchIntelligence(branchName = null) {
2015
- const branch = branchName || getCurrentBranch();
2016
-
2164
+ const branch = branchName || getCurrentBranch()
2165
+
2017
2166
  if (!branch) {
2018
2167
  return {
2019
2168
  branch: null,
@@ -2021,8 +2170,8 @@ export function analyzeBranchIntelligence(branchName = null) {
2021
2170
  ticket: null,
2022
2171
  description: null,
2023
2172
  confidence: 0,
2024
- patterns: []
2025
- };
2173
+ patterns: [],
2174
+ }
2026
2175
  }
2027
2176
 
2028
2177
  const analysis = {
@@ -2031,193 +2180,214 @@ export function analyzeBranchIntelligence(branchName = null) {
2031
2180
  ticket: null,
2032
2181
  description: null,
2033
2182
  confidence: 0,
2034
- patterns: []
2035
- };
2183
+ patterns: [],
2184
+ }
2036
2185
 
2037
2186
  // Pattern 1: type/ticket-description (e.g., feat/ABC-123-add-feature)
2038
- const typeTicketDescPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/([A-Z]{2,}-\d+)-(.+)$/i;
2039
- let match = branch.match(typeTicketDescPattern);
2187
+ const typeTicketDescPattern =
2188
+ /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/([A-Z]{2,}-\d+)-(.+)$/i
2189
+ let match = branch.match(typeTicketDescPattern)
2040
2190
  if (match) {
2041
- analysis.type = match[1].toLowerCase();
2042
- analysis.ticket = match[2].toUpperCase();
2043
- analysis.description = match[3].replace(/[-_]/g, ' ');
2044
- analysis.confidence = 95;
2045
- analysis.patterns.push('type/ticket-description');
2046
- return analysis;
2191
+ analysis.type = match[1].toLowerCase()
2192
+ analysis.ticket = match[2].toUpperCase()
2193
+ analysis.description = match[3].replace(/[-_]/g, ' ')
2194
+ analysis.confidence = 95
2195
+ analysis.patterns.push('type/ticket-description')
2196
+ return analysis
2047
2197
  }
2048
2198
 
2049
2199
  // Pattern 2: type/description (e.g., feat/add-new-feature)
2050
- const typeDescPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/(.+)$/i;
2051
- match = branch.match(typeDescPattern);
2200
+ const typeDescPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)\/(.+)$/i
2201
+ match = branch.match(typeDescPattern)
2052
2202
  if (match) {
2053
- analysis.type = match[1].toLowerCase();
2054
- analysis.description = match[2].replace(/[-_]/g, ' ');
2055
- analysis.confidence = 85;
2056
- analysis.patterns.push('type/description');
2057
-
2203
+ analysis.type = match[1].toLowerCase()
2204
+ analysis.description = match[2].replace(/[-_]/g, ' ')
2205
+ analysis.confidence = 85
2206
+ analysis.patterns.push('type/description')
2207
+
2058
2208
  // Look for ticket in description
2059
- const ticketInDesc = match[2].match(/([A-Z]{2,}-\d+)/);
2209
+ const ticketInDesc = match[2].match(/([A-Z]{2,}-\d+)/)
2060
2210
  if (ticketInDesc) {
2061
- analysis.ticket = ticketInDesc[1];
2062
- analysis.confidence = 90;
2063
- analysis.patterns.push('ticket-in-description');
2211
+ analysis.ticket = ticketInDesc[1]
2212
+ analysis.confidence = 90
2213
+ analysis.patterns.push('ticket-in-description')
2064
2214
  }
2065
- return analysis;
2215
+ return analysis
2066
2216
  }
2067
2217
 
2068
2218
  // Pattern 3: ticket-description (e.g., ABC-123-fix-bug)
2069
- const ticketDescPattern = /^([A-Z]{2,}-\d+)-(.+)$/;
2070
- match = branch.match(ticketDescPattern);
2219
+ const ticketDescPattern = /^([A-Z]{2,}-\d+)-(.+)$/
2220
+ match = branch.match(ticketDescPattern)
2071
2221
  if (match) {
2072
- analysis.ticket = match[1];
2073
- analysis.description = match[2].replace(/[-_]/g, ' ');
2074
- analysis.confidence = 70;
2075
- analysis.patterns.push('ticket-description');
2076
-
2222
+ analysis.ticket = match[1]
2223
+ analysis.description = match[2].replace(/[-_]/g, ' ')
2224
+ analysis.confidence = 70
2225
+ analysis.patterns.push('ticket-description')
2226
+
2077
2227
  // Infer type from description
2078
- const inferredType = inferTypeFromDescription(match[2]);
2228
+ const inferredType = inferTypeFromDescription(match[2])
2079
2229
  if (inferredType) {
2080
- analysis.type = inferredType;
2081
- analysis.confidence = 75;
2082
- analysis.patterns.push('inferred-type');
2230
+ analysis.type = inferredType
2231
+ analysis.confidence = 75
2232
+ analysis.patterns.push('inferred-type')
2083
2233
  }
2084
- return analysis;
2234
+ return analysis
2085
2235
  }
2086
2236
 
2087
2237
  // Pattern 4: just type at start (e.g., feat-add-feature)
2088
- const typeStartPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)[-_](.+)$/i;
2089
- match = branch.match(typeStartPattern);
2238
+ const typeStartPattern = /^(feat|fix|docs|style|refactor|perf|test|build|ci|chore)[-_](.+)$/i
2239
+ match = branch.match(typeStartPattern)
2090
2240
  if (match) {
2091
- analysis.type = match[1].toLowerCase();
2092
- analysis.description = match[2].replace(/[-_]/g, ' ');
2093
- analysis.confidence = 60;
2094
- analysis.patterns.push('type-start');
2095
- return analysis;
2241
+ analysis.type = match[1].toLowerCase()
2242
+ analysis.description = match[2].replace(/[-_]/g, ' ')
2243
+ analysis.confidence = 60
2244
+ analysis.patterns.push('type-start')
2245
+ return analysis
2096
2246
  }
2097
2247
 
2098
2248
  // Pattern 5: ticket at start (e.g., ABC-123_add_feature)
2099
- const ticketStartPattern = /^([A-Z]{2,}-\d+)[-_](.+)$/;
2100
- match = branch.match(ticketStartPattern);
2249
+ const ticketStartPattern = /^([A-Z]{2,}-\d+)[-_](.+)$/
2250
+ match = branch.match(ticketStartPattern)
2101
2251
  if (match) {
2102
- analysis.ticket = match[1];
2103
- analysis.description = match[2].replace(/[-_]/g, ' ');
2104
- analysis.confidence = 60;
2105
- analysis.patterns.push('ticket-start');
2106
-
2252
+ analysis.ticket = match[1]
2253
+ analysis.description = match[2].replace(/[-_]/g, ' ')
2254
+ analysis.confidence = 60
2255
+ analysis.patterns.push('ticket-start')
2256
+
2107
2257
  // Infer type from description
2108
- const inferredType = inferTypeFromDescription(match[2]);
2258
+ const inferredType = inferTypeFromDescription(match[2])
2109
2259
  if (inferredType) {
2110
- analysis.type = inferredType;
2111
- analysis.confidence = 65;
2112
- analysis.patterns.push('inferred-type');
2260
+ analysis.type = inferredType
2261
+ analysis.confidence = 65
2262
+ analysis.patterns.push('inferred-type')
2113
2263
  }
2114
- return analysis;
2264
+ return analysis
2115
2265
  }
2116
2266
 
2117
2267
  // Pattern 6: just ticket anywhere (e.g., add-feature-ABC-123)
2118
- const ticketAnywherePattern = /([A-Z]{2,}-\d+)/;
2119
- match = branch.match(ticketAnywherePattern);
2268
+ const ticketAnywherePattern = /([A-Z]{2,}-\d+)/
2269
+ match = branch.match(ticketAnywherePattern)
2120
2270
  if (match) {
2121
- analysis.ticket = match[1];
2122
- analysis.confidence = 40;
2123
- analysis.patterns.push('ticket-anywhere');
2124
-
2271
+ analysis.ticket = match[1]
2272
+ analysis.confidence = 40
2273
+ analysis.patterns.push('ticket-anywhere')
2274
+
2125
2275
  // Use the whole branch as description, removing ticket
2126
- analysis.description = branch.replace(match[1], '').replace(/[-_]/g, ' ').trim();
2127
- return analysis;
2276
+ analysis.description = branch.replace(match[1], '').replace(/[-_]/g, ' ').trim()
2277
+ return analysis
2128
2278
  }
2129
2279
 
2130
2280
  // Pattern 7: conventional type anywhere in branch
2131
- const typeAnywherePattern = /(feat|fix|docs|style|refactor|perf|test|build|ci|chore)/i;
2132
- match = branch.match(typeAnywherePattern);
2281
+ const typeAnywherePattern = /(feat|fix|docs|style|refactor|perf|test|build|ci|chore)/i
2282
+ match = branch.match(typeAnywherePattern)
2133
2283
  if (match) {
2134
- analysis.type = match[1].toLowerCase();
2135
- analysis.confidence = 30;
2136
- analysis.patterns.push('type-anywhere');
2137
- analysis.description = branch.replace(/[-_]/g, ' ');
2138
- return analysis;
2284
+ analysis.type = match[1].toLowerCase()
2285
+ analysis.confidence = 30
2286
+ analysis.patterns.push('type-anywhere')
2287
+ analysis.description = branch.replace(/[-_]/g, ' ')
2288
+ return analysis
2139
2289
  }
2140
2290
 
2141
2291
  // Fallback: use branch as description and try to infer type
2142
- analysis.description = branch.replace(/[-_]/g, ' ');
2143
- const inferredType = inferTypeFromDescription(branch);
2292
+ analysis.description = branch.replace(/[-_]/g, ' ')
2293
+ const inferredType = inferTypeFromDescription(branch)
2144
2294
  if (inferredType) {
2145
- analysis.type = inferredType;
2146
- analysis.confidence = 25;
2147
- analysis.patterns.push('inferred-type');
2295
+ analysis.type = inferredType
2296
+ analysis.confidence = 25
2297
+ analysis.patterns.push('inferred-type')
2148
2298
  } else {
2149
- analysis.confidence = 10;
2150
- analysis.patterns.push('no-pattern');
2299
+ analysis.confidence = 10
2300
+ analysis.patterns.push('no-pattern')
2151
2301
  }
2152
2302
 
2153
- return analysis;
2303
+ return analysis
2154
2304
  }
2155
2305
 
2156
2306
  /**
2157
2307
  * Infer commit type from description text
2158
2308
  */
2159
2309
  function inferTypeFromDescription(description) {
2160
- const text = description.toLowerCase();
2161
-
2310
+ const text = description.toLowerCase()
2311
+
2162
2312
  // Feature indicators
2163
- if (text.match(/add|new|create|implement|introduce|feature/)) return 'feat';
2164
-
2313
+ if (text.match(/add|new|create|implement|introduce|feature/)) {
2314
+ return 'feat'
2315
+ }
2316
+
2165
2317
  // Fix indicators
2166
- if (text.match(/fix|bug|error|issue|resolve|correct|patch/)) return 'fix';
2167
-
2318
+ if (text.match(/fix|bug|error|issue|resolve|correct|patch/)) {
2319
+ return 'fix'
2320
+ }
2321
+
2168
2322
  // Documentation indicators
2169
- if (text.match(/doc|readme|comment|guide|tutorial/)) return 'docs';
2170
-
2323
+ if (text.match(/doc|readme|comment|guide|tutorial/)) {
2324
+ return 'docs'
2325
+ }
2326
+
2171
2327
  // Style indicators
2172
- if (text.match(/style|format|lint|prettier|eslint/)) return 'style';
2173
-
2328
+ if (text.match(/style|format|lint|prettier|eslint/)) {
2329
+ return 'style'
2330
+ }
2331
+
2174
2332
  // Refactor indicators
2175
- if (text.match(/refactor|restructure|reorganize|cleanup|clean/)) return 'refactor';
2176
-
2333
+ if (text.match(/refactor|restructure|reorganize|cleanup|clean/)) {
2334
+ return 'refactor'
2335
+ }
2336
+
2177
2337
  // Performance indicators
2178
- if (text.match(/perf|performance|optimize|speed|fast/)) return 'perf';
2179
-
2338
+ if (text.match(/perf|performance|optimize|speed|fast/)) {
2339
+ return 'perf'
2340
+ }
2341
+
2180
2342
  // Test indicators
2181
- if (text.match(/test|spec|unit|integration/)) return 'test';
2182
-
2343
+ if (text.match(/test|spec|unit|integration/)) {
2344
+ return 'test'
2345
+ }
2346
+
2183
2347
  // Build indicators
2184
- if (text.match(/build|deploy|package|bundle|webpack|vite/)) return 'build';
2185
-
2348
+ if (text.match(/build|deploy|package|bundle|webpack|vite/)) {
2349
+ return 'build'
2350
+ }
2351
+
2186
2352
  // CI indicators
2187
- if (text.match(/ci|pipeline|action|workflow|github/)) return 'ci';
2188
-
2353
+ if (text.match(/ci|pipeline|action|workflow|github/)) {
2354
+ return 'ci'
2355
+ }
2356
+
2189
2357
  // Chore indicators
2190
- if (text.match(/chore|update|upgrade|dependency|deps|config/)) return 'chore';
2191
-
2192
- return null;
2358
+ if (text.match(/chore|update|upgrade|dependency|deps|config/)) {
2359
+ return 'chore'
2360
+ }
2361
+
2362
+ return null
2193
2363
  }
2194
2364
 
2195
2365
  /**
2196
2366
  * Generate enhanced commit message context from branch intelligence
2197
2367
  */
2198
- export function generateCommitContextFromBranch(branchAnalysis, changes = []) {
2368
+ export function generateCommitContextFromBranch(branchAnalysis, _changes = []) {
2199
2369
  if (!branchAnalysis || branchAnalysis.confidence < 20) {
2200
- return '';
2370
+ return ''
2201
2371
  }
2202
2372
 
2203
- let context = '\n**Branch Context:**';
2204
-
2373
+ let context = '\n**Branch Context:**'
2374
+
2205
2375
  if (branchAnalysis.type) {
2206
- context += `\n- Inferred type: ${branchAnalysis.type}`;
2376
+ context += `\n- Inferred type: ${branchAnalysis.type}`
2207
2377
  }
2208
-
2378
+
2209
2379
  if (branchAnalysis.ticket) {
2210
- context += `\n- Related ticket: ${branchAnalysis.ticket}`;
2380
+ context += `\n- Related ticket: ${branchAnalysis.ticket}`
2211
2381
  }
2212
-
2382
+
2213
2383
  if (branchAnalysis.description) {
2214
- context += `\n- Branch description: ${branchAnalysis.description}`;
2384
+ context += `\n- Branch description: ${branchAnalysis.description}`
2215
2385
  }
2216
-
2217
- context += `\n- Confidence: ${branchAnalysis.confidence}%`;
2218
- context += `\n- Detection patterns: ${branchAnalysis.patterns.join(', ')}`;
2219
-
2220
- return context;
2386
+
2387
+ context += `\n- Confidence: ${branchAnalysis.confidence}%`
2388
+ context += `\n- Detection patterns: ${branchAnalysis.patterns.join(', ')}`
2389
+
2390
+ return context
2221
2391
  }
2222
2392
 
2223
2393
  /**
@@ -2225,48 +2395,50 @@ export function generateCommitContextFromBranch(branchAnalysis, changes = []) {
2225
2395
  */
2226
2396
  export function getSuggestedCommitType(branchAnalysis, changes = []) {
2227
2397
  // High confidence branch type takes precedence
2228
- if (branchAnalysis && branchAnalysis.type && branchAnalysis.confidence >= 70) {
2398
+ if (branchAnalysis?.type && branchAnalysis.confidence >= 70) {
2229
2399
  return {
2230
2400
  type: branchAnalysis.type,
2231
2401
  source: 'branch',
2232
- confidence: branchAnalysis.confidence
2233
- };
2402
+ confidence: branchAnalysis.confidence,
2403
+ }
2234
2404
  }
2235
2405
 
2236
2406
  // Analyze file changes to infer type
2237
- const changeAnalysis = analyzeChangesForType(changes);
2238
-
2407
+ const changeAnalysis = analyzeChangesForType(changes)
2408
+
2239
2409
  // Medium confidence branch type combined with file analysis
2240
- if (branchAnalysis && branchAnalysis.type && branchAnalysis.confidence >= 40) {
2241
- if (changeAnalysis.type === branchAnalysis.type) {
2242
- return {
2243
- type: branchAnalysis.type,
2244
- source: 'branch+files',
2245
- confidence: Math.min(95, branchAnalysis.confidence + 20)
2246
- };
2410
+ if (
2411
+ branchAnalysis?.type &&
2412
+ branchAnalysis.confidence >= 40 &&
2413
+ changeAnalysis.type === branchAnalysis.type
2414
+ ) {
2415
+ return {
2416
+ type: branchAnalysis.type,
2417
+ source: 'branch+files',
2418
+ confidence: Math.min(95, branchAnalysis.confidence + 20),
2247
2419
  }
2248
2420
  }
2249
2421
 
2250
2422
  // Use file-based analysis
2251
2423
  if (changeAnalysis.confidence >= 60) {
2252
- return changeAnalysis;
2424
+ return changeAnalysis
2253
2425
  }
2254
2426
 
2255
2427
  // Low confidence branch type as fallback
2256
- if (branchAnalysis && branchAnalysis.type) {
2428
+ if (branchAnalysis?.type) {
2257
2429
  return {
2258
2430
  type: branchAnalysis.type,
2259
2431
  source: 'branch-fallback',
2260
- confidence: branchAnalysis.confidence
2261
- };
2432
+ confidence: branchAnalysis.confidence,
2433
+ }
2262
2434
  }
2263
2435
 
2264
2436
  // Default fallback
2265
2437
  return {
2266
2438
  type: 'feat',
2267
2439
  source: 'default',
2268
- confidence: 20
2269
- };
2440
+ confidence: 20,
2441
+ }
2270
2442
  }
2271
2443
 
2272
2444
  /**
@@ -2274,7 +2446,7 @@ export function getSuggestedCommitType(branchAnalysis, changes = []) {
2274
2446
  */
2275
2447
  function analyzeChangesForType(changes) {
2276
2448
  if (!changes || changes.length === 0) {
2277
- return { type: 'feat', source: 'default', confidence: 20 };
2449
+ return { type: 'feat', source: 'default', confidence: 20 }
2278
2450
  }
2279
2451
 
2280
2452
  const categories = {
@@ -2282,68 +2454,68 @@ function analyzeChangesForType(changes) {
2282
2454
  test: 0,
2283
2455
  config: 0,
2284
2456
  source: 0,
2285
- style: 0
2286
- };
2457
+ style: 0,
2458
+ }
2287
2459
 
2288
2460
  // Categorize files
2289
- changes.forEach(change => {
2290
- const path = change.path || change.filePath || '';
2291
- const ext = path.split('.').pop()?.toLowerCase() || '';
2292
-
2461
+ changes.forEach((change) => {
2462
+ const path = change.path || change.filePath || ''
2463
+ const ext = path.split('.').pop()?.toLowerCase() || ''
2464
+
2293
2465
  if (path.match(/readme|doc|\.md$|guide|tutorial/i)) {
2294
- categories.docs++;
2466
+ categories.docs++
2295
2467
  } else if (path.match(/test|spec|\.test\.|\.spec\./i)) {
2296
- categories.test++;
2468
+ categories.test++
2297
2469
  } else if (path.match(/config|\.json$|\.yaml$|\.yml$|\.toml$|\.ini$/i)) {
2298
- categories.config++;
2470
+ categories.config++
2299
2471
  } else if (path.match(/\.css$|\.scss$|\.less$|\.styl$/i)) {
2300
- categories.style++;
2472
+ categories.style++
2301
2473
  } else if (['js', 'ts', 'jsx', 'tsx', 'py', 'go', 'rs', 'java', 'c', 'cpp'].includes(ext)) {
2302
- categories.source++;
2474
+ categories.source++
2303
2475
  }
2304
- });
2476
+ })
2477
+
2478
+ const total = Object.values(categories).reduce((a, b) => a + b, 0)
2305
2479
 
2306
- const total = Object.values(categories).reduce((a, b) => a + b, 0);
2307
-
2308
2480
  if (total === 0) {
2309
- return { type: 'feat', source: 'files-default', confidence: 30 };
2481
+ return { type: 'feat', source: 'files-default', confidence: 30 }
2310
2482
  }
2311
2483
 
2312
2484
  // Determine primary type
2313
2485
  if (categories.docs / total > 0.6) {
2314
- return { type: 'docs', source: 'files', confidence: 80 };
2486
+ return { type: 'docs', source: 'files', confidence: 80 }
2315
2487
  }
2316
-
2488
+
2317
2489
  if (categories.test / total > 0.6) {
2318
- return { type: 'test', source: 'files', confidence: 80 };
2490
+ return { type: 'test', source: 'files', confidence: 80 }
2319
2491
  }
2320
-
2492
+
2321
2493
  if (categories.config / total > 0.6) {
2322
- return { type: 'chore', source: 'files', confidence: 70 };
2494
+ return { type: 'chore', source: 'files', confidence: 70 }
2323
2495
  }
2324
-
2496
+
2325
2497
  if (categories.style / total > 0.6) {
2326
- return { type: 'style', source: 'files', confidence: 75 };
2498
+ return { type: 'style', source: 'files', confidence: 75 }
2327
2499
  }
2328
2500
 
2329
2501
  // Mixed or source-heavy changes - look at modification patterns
2330
- const hasNewFiles = changes.some(c => c.status === 'A' || c.status === '??');
2331
- const hasDeletedFiles = changes.some(c => c.status === 'D');
2332
-
2502
+ const hasNewFiles = changes.some((c) => c.status === 'A' || c.status === '??')
2503
+ const hasDeletedFiles = changes.some((c) => c.status === 'D')
2504
+
2333
2505
  if (hasNewFiles && !hasDeletedFiles) {
2334
- return { type: 'feat', source: 'files-new', confidence: 65 };
2506
+ return { type: 'feat', source: 'files-new', confidence: 65 }
2335
2507
  }
2336
-
2508
+
2337
2509
  if (hasDeletedFiles) {
2338
- return { type: 'refactor', source: 'files-deleted', confidence: 60 };
2510
+ return { type: 'refactor', source: 'files-deleted', confidence: 60 }
2339
2511
  }
2340
2512
 
2341
2513
  // Default to feat for source changes
2342
- return { type: 'feat', source: 'files-mixed', confidence: 50 };
2514
+ return { type: 'feat', source: 'files-mixed', confidence: 50 }
2343
2515
  }
2344
2516
 
2345
2517
  // ========================================
2346
2518
  // CHANGELOG UTILITIES (EXTENDED)
2347
2519
  // ========================================
2348
2520
 
2349
- // Additional changelog utility functions
2521
+ // Additional changelog utility functions