@entro314labs/ai-changelog-generator 3.0.5 → 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.
- package/CHANGELOG.md +383 -785
- package/README.md +30 -3
- package/ai-changelog-mcp.sh +0 -0
- package/ai-changelog.sh +0 -0
- package/bin/ai-changelog-dxt.js +9 -9
- package/bin/ai-changelog-mcp.js +19 -17
- package/bin/ai-changelog.js +6 -6
- package/package.json +84 -52
- package/src/ai-changelog-generator.js +83 -81
- package/src/application/orchestrators/changelog.orchestrator.js +1040 -296
- package/src/application/services/application.service.js +145 -123
- package/src/cli.js +76 -57
- package/src/domains/ai/ai-analysis.service.js +289 -209
- package/src/domains/analysis/analysis.engine.js +253 -193
- package/src/domains/changelog/changelog.service.js +1062 -784
- package/src/domains/changelog/workspace-changelog.service.js +420 -249
- package/src/domains/git/git-repository.analyzer.js +348 -258
- package/src/domains/git/git.service.js +132 -112
- package/src/infrastructure/cli/cli.controller.js +415 -247
- package/src/infrastructure/config/configuration.manager.js +220 -190
- package/src/infrastructure/interactive/interactive-staging.service.js +332 -0
- package/src/infrastructure/interactive/interactive-workflow.service.js +200 -159
- package/src/infrastructure/mcp/mcp-server.service.js +208 -207
- package/src/infrastructure/metrics/metrics.collector.js +140 -123
- package/src/infrastructure/providers/core/base-provider.js +87 -40
- package/src/infrastructure/providers/implementations/anthropic.js +101 -99
- package/src/infrastructure/providers/implementations/azure.js +124 -101
- package/src/infrastructure/providers/implementations/bedrock.js +136 -126
- package/src/infrastructure/providers/implementations/dummy.js +23 -23
- package/src/infrastructure/providers/implementations/google.js +123 -114
- package/src/infrastructure/providers/implementations/huggingface.js +94 -87
- package/src/infrastructure/providers/implementations/lmstudio.js +75 -60
- package/src/infrastructure/providers/implementations/mock.js +69 -73
- package/src/infrastructure/providers/implementations/ollama.js +89 -66
- package/src/infrastructure/providers/implementations/openai.js +88 -89
- package/src/infrastructure/providers/implementations/vertex.js +227 -197
- package/src/infrastructure/providers/provider-management.service.js +245 -207
- package/src/infrastructure/providers/provider-manager.service.js +145 -125
- package/src/infrastructure/providers/utils/base-provider-helpers.js +308 -302
- package/src/infrastructure/providers/utils/model-config.js +220 -195
- package/src/infrastructure/providers/utils/provider-utils.js +105 -100
- package/src/infrastructure/validation/commit-message-validation.service.js +556 -0
- package/src/shared/constants/colors.js +467 -172
- package/src/shared/utils/cli-demo.js +285 -0
- package/src/shared/utils/cli-entry-utils.js +257 -249
- package/src/shared/utils/cli-ui.js +447 -0
- package/src/shared/utils/diff-processor.js +513 -0
- package/src/shared/utils/error-classes.js +125 -156
- package/src/shared/utils/json-utils.js +93 -89
- package/src/shared/utils/utils.js +1299 -775
- package/types/index.d.ts +353 -344
|
@@ -0,0 +1,513 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiffProcessor - Central utility for intelligent diff compression and processing
|
|
3
|
+
*
|
|
4
|
+
* Handles large diffs by applying smart reduction strategies while preserving
|
|
5
|
+
* semantic meaning for AI analysis.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export class DiffProcessor {
|
|
9
|
+
constructor(options = {}) {
|
|
10
|
+
this.analysisMode = options.analysisMode || 'standard'
|
|
11
|
+
this.maxTotalSize = options.maxTotalSize || this.getDefaultMaxSize()
|
|
12
|
+
this.priorityFiles = options.priorityFiles || this.getDefaultPriorityFiles()
|
|
13
|
+
this.enableFiltering = options.enableFiltering !== false
|
|
14
|
+
this.enablePatternDetection = options.enablePatternDetection !== false
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
getDefaultMaxSize() {
|
|
18
|
+
const sizes = {
|
|
19
|
+
standard: 12000,
|
|
20
|
+
detailed: 20000,
|
|
21
|
+
enterprise: 30000,
|
|
22
|
+
}
|
|
23
|
+
return sizes[this.analysisMode] || sizes.standard
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
getDefaultPriorityFiles() {
|
|
27
|
+
const counts = {
|
|
28
|
+
standard: 15,
|
|
29
|
+
detailed: 25,
|
|
30
|
+
enterprise: 40,
|
|
31
|
+
}
|
|
32
|
+
return counts[this.analysisMode] || counts.standard
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Process a collection of files with diffs, applying intelligent compression
|
|
37
|
+
*/
|
|
38
|
+
processFiles(files, _options = {}) {
|
|
39
|
+
if (!(files && Array.isArray(files)) || files.length === 0) {
|
|
40
|
+
return {
|
|
41
|
+
processedFiles: [],
|
|
42
|
+
totalSize: 0,
|
|
43
|
+
patterns: {},
|
|
44
|
+
filesProcessed: 0,
|
|
45
|
+
filesSkipped: 0,
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Step 1: Prioritize files by importance
|
|
50
|
+
const prioritizedFiles = this.prioritizeFiles(files)
|
|
51
|
+
|
|
52
|
+
// Step 2: Detect bulk change patterns
|
|
53
|
+
const patterns = this.enablePatternDetection ? this.detectChangePatterns(files) : {}
|
|
54
|
+
|
|
55
|
+
// Step 3: Process individual files
|
|
56
|
+
const processedFiles = []
|
|
57
|
+
let totalSize = 0
|
|
58
|
+
let remainingBudget = this.maxTotalSize
|
|
59
|
+
|
|
60
|
+
for (let i = 0; i < Math.min(prioritizedFiles.length, this.priorityFiles); i++) {
|
|
61
|
+
const file = prioritizedFiles[i]
|
|
62
|
+
|
|
63
|
+
// Calculate budget for this file (remaining budget distributed across remaining files)
|
|
64
|
+
const remainingFiles = Math.min(prioritizedFiles.length - i, this.priorityFiles - i)
|
|
65
|
+
const fileBudget = Math.floor(remainingBudget / remainingFiles)
|
|
66
|
+
|
|
67
|
+
const processedFile = this.processFileDiff(file, {
|
|
68
|
+
budget: fileBudget,
|
|
69
|
+
patterns,
|
|
70
|
+
isHighPriority: i < 5, // First 5 files get high priority
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
processedFiles.push(processedFile)
|
|
74
|
+
|
|
75
|
+
const fileSize = this.estimateSize(processedFile.diff)
|
|
76
|
+
totalSize += fileSize
|
|
77
|
+
remainingBudget -= fileSize
|
|
78
|
+
|
|
79
|
+
if (remainingBudget <= 0) {
|
|
80
|
+
break
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Step 4: Add summary for remaining files
|
|
85
|
+
if (prioritizedFiles.length > processedFiles.length) {
|
|
86
|
+
const remainingFiles = prioritizedFiles.slice(processedFiles.length)
|
|
87
|
+
const summary = this.createRemainingFilesSummary(remainingFiles, patterns)
|
|
88
|
+
processedFiles.push(summary)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
processedFiles,
|
|
93
|
+
totalSize,
|
|
94
|
+
patterns,
|
|
95
|
+
filesProcessed: processedFiles.length,
|
|
96
|
+
filesSkipped: Math.max(0, files.length - processedFiles.length),
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Process a single file's diff with intelligent compression
|
|
102
|
+
*/
|
|
103
|
+
processFileDiff(file, options = {}) {
|
|
104
|
+
const { budget = 1500, patterns = {}, isHighPriority = false } = options
|
|
105
|
+
|
|
106
|
+
if (!file.diff || file.diff === 'No diff available') {
|
|
107
|
+
return {
|
|
108
|
+
...file,
|
|
109
|
+
diff: this.generateFallbackDescription(file),
|
|
110
|
+
processed: true,
|
|
111
|
+
compressionApplied: false,
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
let processedDiff = file.diff
|
|
116
|
+
|
|
117
|
+
// Apply filtering pipeline if enabled
|
|
118
|
+
if (this.enableFiltering) {
|
|
119
|
+
processedDiff = this.applyDiffFiltering(processedDiff, file)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Check if file matches bulk patterns
|
|
123
|
+
const patternMatch = this.matchesBulkPattern(file, patterns)
|
|
124
|
+
if (patternMatch) {
|
|
125
|
+
return {
|
|
126
|
+
...file,
|
|
127
|
+
diff: `[Bulk ${patternMatch.type}]: ${patternMatch.description}`,
|
|
128
|
+
processed: true,
|
|
129
|
+
compressionApplied: true,
|
|
130
|
+
bulkPattern: patternMatch.type,
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Apply intelligent truncation if still too large
|
|
135
|
+
if (processedDiff.length > budget) {
|
|
136
|
+
processedDiff = this.intelligentTruncation(processedDiff, budget, {
|
|
137
|
+
preserveStructure: isHighPriority,
|
|
138
|
+
filePath: file.filePath || file.path,
|
|
139
|
+
})
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
...file,
|
|
144
|
+
diff: processedDiff,
|
|
145
|
+
processed: true,
|
|
146
|
+
compressionApplied: processedDiff.length < file.diff.length,
|
|
147
|
+
originalSize: file.diff.length,
|
|
148
|
+
compressedSize: processedDiff.length,
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Prioritize files by importance for analysis
|
|
154
|
+
*/
|
|
155
|
+
prioritizeFiles(files) {
|
|
156
|
+
return [...files].sort((a, b) => {
|
|
157
|
+
// Primary: Status priority (Modified > Added > Deleted)
|
|
158
|
+
const statusPriority = { M: 0, R: 1, A: 2, D: 3 }
|
|
159
|
+
const aStatus = statusPriority[a.status] ?? 4
|
|
160
|
+
const bStatus = statusPriority[b.status] ?? 4
|
|
161
|
+
if (aStatus !== bStatus) {
|
|
162
|
+
return aStatus - bStatus
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Secondary: File importance
|
|
166
|
+
const aImportance = this.calculateFileImportance(a)
|
|
167
|
+
const bImportance = this.calculateFileImportance(b)
|
|
168
|
+
if (aImportance !== bImportance) {
|
|
169
|
+
return bImportance - aImportance
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Tertiary: Diff size (larger changes first)
|
|
173
|
+
const aDiffSize = (a.diff || '').length
|
|
174
|
+
const bDiffSize = (b.diff || '').length
|
|
175
|
+
return bDiffSize - aDiffSize
|
|
176
|
+
})
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Calculate file importance score
|
|
181
|
+
*/
|
|
182
|
+
calculateFileImportance(file) {
|
|
183
|
+
const filePath = file.filePath || file.path || ''
|
|
184
|
+
let score = 0
|
|
185
|
+
|
|
186
|
+
// Core source files
|
|
187
|
+
if (filePath.includes('/src/') && (filePath.endsWith('.js') || filePath.endsWith('.ts'))) {
|
|
188
|
+
score += 100
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// Important directories
|
|
192
|
+
if (filePath.includes('/domains/') || filePath.includes('/services/')) {
|
|
193
|
+
score += 50
|
|
194
|
+
}
|
|
195
|
+
if (filePath.includes('/utils/') || filePath.includes('/shared/')) {
|
|
196
|
+
score += 30
|
|
197
|
+
}
|
|
198
|
+
if (filePath.includes('/components/')) {
|
|
199
|
+
score += 40
|
|
200
|
+
}
|
|
201
|
+
if (filePath.includes('/api/') || filePath.includes('/routes/')) {
|
|
202
|
+
score += 60
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// Configuration files
|
|
206
|
+
if (filePath.includes('package.json') || filePath.includes('config')) {
|
|
207
|
+
score += 20
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Tests (lower priority)
|
|
211
|
+
if (filePath.includes('/test/') || filePath.includes('.test.') || filePath.includes('.spec.')) {
|
|
212
|
+
score -= 20
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Documentation (lowest priority for code analysis)
|
|
216
|
+
if (filePath.endsWith('.md') || filePath.includes('/docs/')) {
|
|
217
|
+
score -= 30
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// Generated/build files (very low priority)
|
|
221
|
+
if (
|
|
222
|
+
filePath.includes('/node_modules/') ||
|
|
223
|
+
filePath.includes('/dist/') ||
|
|
224
|
+
filePath.includes('/build/') ||
|
|
225
|
+
filePath.includes('lock')
|
|
226
|
+
) {
|
|
227
|
+
score -= 100
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return score
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Detect bulk change patterns across files
|
|
235
|
+
*/
|
|
236
|
+
detectChangePatterns(files) {
|
|
237
|
+
const patterns = {}
|
|
238
|
+
|
|
239
|
+
// Detect mass renames
|
|
240
|
+
const renames = files.filter((f) => f.status === 'R')
|
|
241
|
+
if (renames.length >= 3) {
|
|
242
|
+
patterns.massRename = {
|
|
243
|
+
type: 'massRename',
|
|
244
|
+
count: renames.length,
|
|
245
|
+
description: `${renames.length} files renamed`,
|
|
246
|
+
files: renames.map((f) => ({ from: f.oldPath, to: f.filePath || f.path })),
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
// Detect formatting changes
|
|
251
|
+
const formattingFiles = files.filter((f) => this.isLikelyFormattingChange(f))
|
|
252
|
+
if (formattingFiles.length >= 5) {
|
|
253
|
+
patterns.formatting = {
|
|
254
|
+
type: 'formatting',
|
|
255
|
+
count: formattingFiles.length,
|
|
256
|
+
description: `Formatting/linting applied to ${formattingFiles.length} files`,
|
|
257
|
+
files: formattingFiles.map((f) => f.filePath || f.path),
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Detect dependency updates
|
|
262
|
+
const packageFiles = files.filter(
|
|
263
|
+
(f) => (f.filePath || f.path).includes('package') || (f.filePath || f.path).includes('lock')
|
|
264
|
+
)
|
|
265
|
+
if (packageFiles.length > 0) {
|
|
266
|
+
patterns.dependencies = {
|
|
267
|
+
type: 'dependencies',
|
|
268
|
+
count: packageFiles.length,
|
|
269
|
+
description: `Package/dependency updates in ${packageFiles.length} files`,
|
|
270
|
+
files: packageFiles.map((f) => f.filePath || f.path),
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
return patterns
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Check if a file matches a bulk pattern
|
|
279
|
+
*/
|
|
280
|
+
matchesBulkPattern(file, patterns) {
|
|
281
|
+
const filePath = file.filePath || file.path || ''
|
|
282
|
+
|
|
283
|
+
// Check for formatting pattern
|
|
284
|
+
if (patterns.formatting?.files.includes(filePath)) {
|
|
285
|
+
return patterns.formatting
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
// Check for rename pattern
|
|
289
|
+
if (patterns.massRename?.files.some((r) => r.to === filePath)) {
|
|
290
|
+
return patterns.massRename
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Check for dependency pattern
|
|
294
|
+
if (patterns.dependencies?.files.includes(filePath)) {
|
|
295
|
+
return patterns.dependencies
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
return null
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Apply diff filtering to remove noise
|
|
303
|
+
*/
|
|
304
|
+
applyDiffFiltering(diff, _file) {
|
|
305
|
+
let filteredDiff = diff
|
|
306
|
+
|
|
307
|
+
// Remove whitespace-only changes
|
|
308
|
+
filteredDiff = filteredDiff.replace(/^[+-]\s*$/gm, '')
|
|
309
|
+
|
|
310
|
+
// Remove pure import/require shuffling (common in large refactors)
|
|
311
|
+
const importLines = filteredDiff.split('\n').filter((line) => {
|
|
312
|
+
const trimmed = line.replace(/^[+-]\s*/, '')
|
|
313
|
+
return trimmed.match(/^(import|require|from\s+['"])/)
|
|
314
|
+
})
|
|
315
|
+
|
|
316
|
+
if (importLines.length > 10) {
|
|
317
|
+
// If many import changes, summarize them
|
|
318
|
+
filteredDiff = filteredDiff.replace(/^[+-]\s*(import|require|from\s+['"]).*$/gm, '')
|
|
319
|
+
filteredDiff = `[${importLines.length} import/require changes summarized]\n${filteredDiff}`
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// Remove console.log additions/removals (common in development)
|
|
323
|
+
filteredDiff = filteredDiff.replace(/^[+-]\s*console\.(log|debug|info).*$/gm, '')
|
|
324
|
+
|
|
325
|
+
// Clean up multiple empty lines
|
|
326
|
+
filteredDiff = filteredDiff.replace(/\n\s*\n\s*\n/g, '\n\n')
|
|
327
|
+
|
|
328
|
+
return filteredDiff.trim()
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Check if a file appears to be mostly formatting changes
|
|
333
|
+
*/
|
|
334
|
+
isLikelyFormattingChange(file) {
|
|
335
|
+
if (!file.diff) {
|
|
336
|
+
return false
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const lines = file.diff.split('\n')
|
|
340
|
+
const changeLines = lines.filter((line) => line.startsWith('+') || line.startsWith('-'))
|
|
341
|
+
|
|
342
|
+
if (changeLines.length === 0) {
|
|
343
|
+
return false
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// Count lines that are likely formatting (whitespace, semicolons, brackets)
|
|
347
|
+
const formattingLines = changeLines.filter((line) => {
|
|
348
|
+
const content = line.substring(1).trim()
|
|
349
|
+
return (
|
|
350
|
+
content === '' || // Empty lines
|
|
351
|
+
content.match(/^[\s{}[\];,]+$/) || // Brackets, semicolons, commas
|
|
352
|
+
content.match(/^\/\/\s*eslint/) || // ESLint comments
|
|
353
|
+
(content.match(/^\s*(import|from)/) && // Import reordering
|
|
354
|
+
changeLines.filter((l) =>
|
|
355
|
+
l
|
|
356
|
+
.substring(1)
|
|
357
|
+
.trim()
|
|
358
|
+
.includes(content.replace(/^[\s+-]*/, ''))
|
|
359
|
+
).length > 1)
|
|
360
|
+
)
|
|
361
|
+
})
|
|
362
|
+
|
|
363
|
+
// If more than 80% of changes are formatting-related
|
|
364
|
+
return formattingLines.length / changeLines.length > 0.8
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
/**
|
|
368
|
+
* Apply intelligent truncation that preserves structure
|
|
369
|
+
*/
|
|
370
|
+
intelligentTruncation(diff, budget, options = {}) {
|
|
371
|
+
const { preserveStructure = false, filePath = '' } = options
|
|
372
|
+
|
|
373
|
+
if (diff.length <= budget) {
|
|
374
|
+
return diff
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const lines = diff.split('\n')
|
|
378
|
+
|
|
379
|
+
if (preserveStructure && lines.length > 20) {
|
|
380
|
+
// For high-priority files, show context at beginning and end
|
|
381
|
+
const headerLines = Math.floor((budget * 0.4) / 80) // Estimate chars per line
|
|
382
|
+
const footerLines = Math.floor((budget * 0.3) / 80)
|
|
383
|
+
const middleLines = Math.floor((budget * 0.2) / 80)
|
|
384
|
+
|
|
385
|
+
const header = lines.slice(0, headerLines).join('\n')
|
|
386
|
+
const footer = lines.slice(-footerLines).join('\n')
|
|
387
|
+
|
|
388
|
+
// Find important middle sections (function definitions, class declarations)
|
|
389
|
+
const importantMiddle = lines
|
|
390
|
+
.slice(headerLines, -footerLines)
|
|
391
|
+
.filter((line) => line.match(/^[+-]\s*(function|class|const|let|var|export|async)/))
|
|
392
|
+
.slice(0, middleLines)
|
|
393
|
+
|
|
394
|
+
const middle =
|
|
395
|
+
importantMiddle.length > 0
|
|
396
|
+
? `\n... [${lines.length - headerLines - footerLines - importantMiddle.length} lines omitted] ...\n` +
|
|
397
|
+
importantMiddle.join('\n')
|
|
398
|
+
: `\n... [${lines.length - headerLines - footerLines} lines omitted] ...`
|
|
399
|
+
|
|
400
|
+
return `${header}${middle}\n${footer}`.substring(0, budget)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// Simple truncation with context preservation
|
|
404
|
+
const truncated = diff.substring(0, budget - 50)
|
|
405
|
+
const lastNewline = truncated.lastIndexOf('\n')
|
|
406
|
+
|
|
407
|
+
return (
|
|
408
|
+
(lastNewline > budget * 0.8 ? truncated.substring(0, lastNewline) : truncated) +
|
|
409
|
+
'\n... [truncated]'
|
|
410
|
+
)
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Generate fallback description for files without diffs
|
|
415
|
+
*/
|
|
416
|
+
generateFallbackDescription(file) {
|
|
417
|
+
const filePath = file.filePath || file.path || 'unknown file'
|
|
418
|
+
const status = file.status || 'modified'
|
|
419
|
+
|
|
420
|
+
const statusDescriptions = {
|
|
421
|
+
M: 'Modified',
|
|
422
|
+
A: 'Added',
|
|
423
|
+
D: 'Deleted',
|
|
424
|
+
R: 'Renamed',
|
|
425
|
+
'??': 'Untracked',
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const description = statusDescriptions[status] || 'Changed'
|
|
429
|
+
const fileType = this.getFileTypeDescription(filePath)
|
|
430
|
+
|
|
431
|
+
return `${description} ${fileType}: ${filePath}`
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Get file type description
|
|
436
|
+
*/
|
|
437
|
+
getFileTypeDescription(filePath) {
|
|
438
|
+
if (filePath.endsWith('.js') || filePath.endsWith('.ts')) {
|
|
439
|
+
return 'JavaScript/TypeScript file'
|
|
440
|
+
}
|
|
441
|
+
if (filePath.endsWith('.json')) {
|
|
442
|
+
return 'JSON configuration'
|
|
443
|
+
}
|
|
444
|
+
if (filePath.endsWith('.md')) {
|
|
445
|
+
return 'documentation file'
|
|
446
|
+
}
|
|
447
|
+
if (filePath.includes('package')) {
|
|
448
|
+
return 'package configuration'
|
|
449
|
+
}
|
|
450
|
+
if (filePath.includes('test') || filePath.includes('spec')) {
|
|
451
|
+
return 'test file'
|
|
452
|
+
}
|
|
453
|
+
return 'file'
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Create summary for remaining files that weren't processed
|
|
458
|
+
*/
|
|
459
|
+
createRemainingFilesSummary(remainingFiles, _patterns) {
|
|
460
|
+
const categories = {}
|
|
461
|
+
|
|
462
|
+
remainingFiles.forEach((file) => {
|
|
463
|
+
const type = this.categorizeFile(file)
|
|
464
|
+
if (!categories[type]) {
|
|
465
|
+
categories[type] = []
|
|
466
|
+
}
|
|
467
|
+
categories[type].push(file)
|
|
468
|
+
})
|
|
469
|
+
|
|
470
|
+
const summaryParts = Object.entries(categories).map(([type, files]) => {
|
|
471
|
+
return `${files.length} ${type} files`
|
|
472
|
+
})
|
|
473
|
+
|
|
474
|
+
return {
|
|
475
|
+
path: '[SUMMARY]',
|
|
476
|
+
status: 'SUMMARY',
|
|
477
|
+
diff: `Additional ${remainingFiles.length} files not analyzed in detail: ${summaryParts.join(', ')}`,
|
|
478
|
+
processed: true,
|
|
479
|
+
compressionApplied: true,
|
|
480
|
+
isSummary: true,
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Categorize a file for summary purposes
|
|
486
|
+
*/
|
|
487
|
+
categorizeFile(file) {
|
|
488
|
+
const filePath = file.filePath || file.path || ''
|
|
489
|
+
|
|
490
|
+
if (filePath.includes('/test/') || filePath.includes('.test.') || filePath.includes('.spec.')) {
|
|
491
|
+
return 'test'
|
|
492
|
+
}
|
|
493
|
+
if (filePath.endsWith('.md') || filePath.includes('/docs/')) {
|
|
494
|
+
return 'documentation'
|
|
495
|
+
}
|
|
496
|
+
if (filePath.includes('package') || filePath.includes('lock') || filePath.includes('config')) {
|
|
497
|
+
return 'configuration'
|
|
498
|
+
}
|
|
499
|
+
if (filePath.includes('/build/') || filePath.includes('/dist/')) {
|
|
500
|
+
return 'build'
|
|
501
|
+
}
|
|
502
|
+
return 'other'
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Estimate size of processed content
|
|
507
|
+
*/
|
|
508
|
+
estimateSize(content) {
|
|
509
|
+
return (content || '').length
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
export default DiffProcessor
|