commit-analyzer 1.1.3 → 1.1.4
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commit-analyzer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.4",
|
|
4
4
|
"description": "Analyze git commits and generate categories, summaries, and descriptions for each commit. Optionally generate a yearly breakdown report of your commit history.",
|
|
5
5
|
"main": "dist/main.ts",
|
|
6
6
|
"bin": {
|
|
@@ -2,6 +2,14 @@ import { AnalyzedCommit } from "./analyzed-commit"
|
|
|
2
2
|
import { Category, CategoryType } from "./category"
|
|
3
3
|
import { DateRange } from "./date-range"
|
|
4
4
|
|
|
5
|
+
export type TimePeriod =
|
|
6
|
+
| "hourly"
|
|
7
|
+
| "daily"
|
|
8
|
+
| "weekly"
|
|
9
|
+
| "monthly"
|
|
10
|
+
| "quarterly"
|
|
11
|
+
| "yearly"
|
|
12
|
+
|
|
5
13
|
/**
|
|
6
14
|
* Statistics for analyzed commits
|
|
7
15
|
*/
|
|
@@ -151,27 +159,31 @@ export class ReportGenerationService {
|
|
|
151
159
|
/**
|
|
152
160
|
* Determines the appropriate time period for summaries based on date range
|
|
153
161
|
*/
|
|
154
|
-
determineTimePeriod(commits: AnalyzedCommit[]):
|
|
155
|
-
if (commits.length === 0) return
|
|
162
|
+
determineTimePeriod(commits: AnalyzedCommit[]): TimePeriod {
|
|
163
|
+
if (commits.length === 0) return "yearly"
|
|
164
|
+
|
|
165
|
+
const dates = commits.map((c) => c.getDate())
|
|
166
|
+
const minDate = new Date(Math.min(...dates.map((d) => d.getTime())))
|
|
167
|
+
const maxDate = new Date(Math.max(...dates.map((d) => d.getTime())))
|
|
156
168
|
|
|
157
|
-
const dates = commits.map(c => c.getDate())
|
|
158
|
-
const minDate = new Date(Math.min(...dates.map(d => d.getTime())))
|
|
159
|
-
const maxDate = new Date(Math.max(...dates.map(d => d.getTime())))
|
|
160
|
-
|
|
161
169
|
const diffInMilliseconds = maxDate.getTime() - minDate.getTime()
|
|
162
170
|
const diffInDays = diffInMilliseconds / (1000 * 60 * 60 * 24)
|
|
163
171
|
|
|
164
|
-
if (diffInDays <= 1) return
|
|
165
|
-
if (diffInDays <= 7) return
|
|
166
|
-
if (diffInDays <= 31) return
|
|
167
|
-
if (diffInDays <= 93) return
|
|
168
|
-
return
|
|
172
|
+
if (diffInDays <= 1) return "hourly"
|
|
173
|
+
if (diffInDays <= 7) return "daily"
|
|
174
|
+
if (diffInDays <= 31) return "weekly"
|
|
175
|
+
if (diffInDays <= 93) return "monthly" // ~3 months
|
|
176
|
+
if (diffInDays <= 365) return "quarterly" // ~3 months
|
|
177
|
+
return "yearly"
|
|
169
178
|
}
|
|
170
179
|
|
|
171
180
|
/**
|
|
172
181
|
* Groups commits by the appropriate time period
|
|
173
182
|
*/
|
|
174
|
-
groupByTimePeriod(
|
|
183
|
+
groupByTimePeriod(
|
|
184
|
+
commits: AnalyzedCommit[],
|
|
185
|
+
period: TimePeriod,
|
|
186
|
+
): Map<string, AnalyzedCommit[]> {
|
|
175
187
|
const grouped = new Map<string, AnalyzedCommit[]>()
|
|
176
188
|
|
|
177
189
|
for (const commit of commits) {
|
|
@@ -179,19 +191,22 @@ export class ReportGenerationService {
|
|
|
179
191
|
let key: string
|
|
180
192
|
|
|
181
193
|
switch (period) {
|
|
182
|
-
case
|
|
194
|
+
case "hourly":
|
|
195
|
+
key = this.formatHourlyKey(date)
|
|
196
|
+
break
|
|
197
|
+
case "daily":
|
|
183
198
|
key = this.formatDailyKey(date)
|
|
184
199
|
break
|
|
185
|
-
case
|
|
200
|
+
case "weekly":
|
|
186
201
|
key = this.formatWeeklyKey(date)
|
|
187
202
|
break
|
|
188
|
-
case
|
|
203
|
+
case "monthly":
|
|
189
204
|
key = this.formatMonthlyKey(date)
|
|
190
205
|
break
|
|
191
|
-
case
|
|
206
|
+
case "quarterly":
|
|
192
207
|
key = this.formatQuarterlyKey(date)
|
|
193
208
|
break
|
|
194
|
-
case
|
|
209
|
+
case "yearly":
|
|
195
210
|
default:
|
|
196
211
|
key = date.getFullYear().toString()
|
|
197
212
|
break
|
|
@@ -206,15 +221,24 @@ export class ReportGenerationService {
|
|
|
206
221
|
return grouped
|
|
207
222
|
}
|
|
208
223
|
|
|
224
|
+
private formatHourlyKey(date: Date): string {
|
|
225
|
+
const hour = date.getHours()
|
|
226
|
+
const displayHour = hour === 0 ? 12 : hour > 12 ? hour - 12 : hour
|
|
227
|
+
const ampm = hour < 12 ? "AM" : "PM"
|
|
228
|
+
return `${displayHour}:00 ${ampm}`
|
|
229
|
+
}
|
|
230
|
+
|
|
209
231
|
private formatDailyKey(date: Date): string {
|
|
210
232
|
const year = date.getFullYear()
|
|
211
233
|
const month = date.getMonth() + 1
|
|
212
234
|
const day = date.getDate()
|
|
213
235
|
const hour = date.getHours()
|
|
214
236
|
|
|
215
|
-
if (hour < 12)
|
|
216
|
-
|
|
217
|
-
|
|
237
|
+
if (hour < 12)
|
|
238
|
+
return `${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")} Morning`
|
|
239
|
+
if (hour < 17)
|
|
240
|
+
return `${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")} Afternoon`
|
|
241
|
+
return `${year}-${month.toString().padStart(2, "0")}-${day.toString().padStart(2, "0")} Evening`
|
|
218
242
|
}
|
|
219
243
|
|
|
220
244
|
private formatWeeklyKey(date: Date): string {
|
|
@@ -223,15 +247,27 @@ export class ReportGenerationService {
|
|
|
223
247
|
const endOfWeek = new Date(startOfWeek)
|
|
224
248
|
endOfWeek.setDate(startOfWeek.getDate() + 6)
|
|
225
249
|
|
|
226
|
-
const formatDate = (d: Date) =>
|
|
227
|
-
`${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2,
|
|
250
|
+
const formatDate = (d: Date) =>
|
|
251
|
+
`${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")}`
|
|
228
252
|
|
|
229
253
|
return `Week of ${formatDate(startOfWeek)} to ${formatDate(endOfWeek)}`
|
|
230
254
|
}
|
|
231
255
|
|
|
232
256
|
private formatMonthlyKey(date: Date): string {
|
|
233
|
-
const months = [
|
|
234
|
-
|
|
257
|
+
const months = [
|
|
258
|
+
"January",
|
|
259
|
+
"February",
|
|
260
|
+
"March",
|
|
261
|
+
"April",
|
|
262
|
+
"May",
|
|
263
|
+
"June",
|
|
264
|
+
"July",
|
|
265
|
+
"August",
|
|
266
|
+
"September",
|
|
267
|
+
"October",
|
|
268
|
+
"November",
|
|
269
|
+
"December",
|
|
270
|
+
]
|
|
235
271
|
return `${months[date.getMonth()]} ${date.getFullYear()}`
|
|
236
272
|
}
|
|
237
273
|
|
|
@@ -241,45 +277,160 @@ export class ReportGenerationService {
|
|
|
241
277
|
}
|
|
242
278
|
|
|
243
279
|
/**
|
|
244
|
-
* Converts analyzed commits to CSV string format for LLM consumption
|
|
280
|
+
* Converts analyzed commits to CSV string format for LLM consumption with enhanced context
|
|
245
281
|
*/
|
|
246
282
|
convertToCSVString(commits: AnalyzedCommit[]): string {
|
|
247
|
-
const header = "year,category,summary,description"
|
|
283
|
+
const header = "year,category,summary,description,commit_count,date_range"
|
|
284
|
+
|
|
285
|
+
// Group commits by year and category for context
|
|
286
|
+
const contextMap = new Map<string, { count: number; dates: Date[] }>()
|
|
287
|
+
|
|
248
288
|
const rows = commits.map((commit) => {
|
|
249
289
|
const analysis = commit.getAnalysis()
|
|
290
|
+
const key = `${commit.getYear()}-${analysis.getCategory().getValue()}`
|
|
291
|
+
|
|
292
|
+
if (!contextMap.has(key)) {
|
|
293
|
+
contextMap.set(key, { count: 0, dates: [] })
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const context = contextMap.get(key)!
|
|
297
|
+
context.count++
|
|
298
|
+
context.dates.push(commit.getDate())
|
|
299
|
+
|
|
300
|
+
const dateRange =
|
|
301
|
+
context.dates.length > 1
|
|
302
|
+
? `${this.formatDate(Math.min(...context.dates.map((d) => d.getTime())))} to ${this.formatDate(Math.max(...context.dates.map((d) => d.getTime())))}`
|
|
303
|
+
: this.formatDate(commit.getDate())
|
|
304
|
+
|
|
250
305
|
return [
|
|
251
306
|
commit.getYear().toString(),
|
|
252
307
|
this.escapeCsvField(analysis.getCategory().getValue()),
|
|
253
308
|
this.escapeCsvField(analysis.getSummary()),
|
|
254
309
|
this.escapeCsvField(analysis.getDescription()),
|
|
310
|
+
context.count.toString(),
|
|
311
|
+
this.escapeCsvField(dateRange),
|
|
255
312
|
].join(",")
|
|
256
313
|
})
|
|
257
314
|
|
|
258
315
|
return [header, ...rows].join("\n")
|
|
259
316
|
}
|
|
260
317
|
|
|
318
|
+
private formatDate(date: Date | number): string {
|
|
319
|
+
const d = new Date(date)
|
|
320
|
+
return `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")}`
|
|
321
|
+
}
|
|
322
|
+
|
|
261
323
|
/**
|
|
262
|
-
* Converts grouped commits to CSV with time period information
|
|
324
|
+
* Converts grouped commits to CSV with time period information and enhanced context
|
|
263
325
|
*/
|
|
264
|
-
convertGroupedToCSV(
|
|
265
|
-
|
|
326
|
+
convertGroupedToCSV(
|
|
327
|
+
groupedCommits: Map<string, AnalyzedCommit[]>,
|
|
328
|
+
period: string,
|
|
329
|
+
): string {
|
|
330
|
+
const header = `${period},category,summary,description,commit_count,similar_commits`
|
|
266
331
|
const rows: string[] = []
|
|
267
|
-
|
|
332
|
+
|
|
268
333
|
for (const [timePeriod, commits] of groupedCommits) {
|
|
334
|
+
// Group commits by category within the time period for context
|
|
335
|
+
const categoryGroups = new Map<string, AnalyzedCommit[]>()
|
|
336
|
+
|
|
337
|
+
for (const commit of commits) {
|
|
338
|
+
const category = commit.getAnalysis().getCategory().getValue()
|
|
339
|
+
if (!categoryGroups.has(category)) {
|
|
340
|
+
categoryGroups.set(category, [])
|
|
341
|
+
}
|
|
342
|
+
categoryGroups.get(category)!.push(commit)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Add context about similar commits in the same period and category
|
|
269
346
|
for (const commit of commits) {
|
|
270
347
|
const analysis = commit.getAnalysis()
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
348
|
+
const category = analysis.getCategory().getValue()
|
|
349
|
+
const similarCommits = categoryGroups.get(category)!
|
|
350
|
+
|
|
351
|
+
// Find similar summaries in the same category
|
|
352
|
+
const similarSummaries = similarCommits
|
|
353
|
+
.filter((c) => c !== commit)
|
|
354
|
+
.map((c) => c.getAnalysis().getSummary())
|
|
355
|
+
.filter((summary) =>
|
|
356
|
+
this.isSimilarSummary(analysis.getSummary(), summary),
|
|
357
|
+
)
|
|
358
|
+
.slice(0, 3) // Limit to 3 similar items
|
|
359
|
+
|
|
360
|
+
rows.push(
|
|
361
|
+
[
|
|
362
|
+
this.escapeCsvField(timePeriod),
|
|
363
|
+
this.escapeCsvField(category),
|
|
364
|
+
this.escapeCsvField(analysis.getSummary()),
|
|
365
|
+
this.escapeCsvField(analysis.getDescription()),
|
|
366
|
+
similarCommits.length.toString(),
|
|
367
|
+
this.escapeCsvField(similarSummaries.join("; ")),
|
|
368
|
+
].join(","),
|
|
369
|
+
)
|
|
277
370
|
}
|
|
278
371
|
}
|
|
279
372
|
|
|
280
373
|
return [header, ...rows].join("\n")
|
|
281
374
|
}
|
|
282
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Determines if two summaries are similar based on common keywords
|
|
378
|
+
*/
|
|
379
|
+
private isSimilarSummary(summary1: string, summary2: string): boolean {
|
|
380
|
+
const keywords1 = this.extractKeywords(summary1)
|
|
381
|
+
const keywords2 = this.extractKeywords(summary2)
|
|
382
|
+
|
|
383
|
+
// Check if they share significant keywords (at least 2 common words)
|
|
384
|
+
const commonKeywords = keywords1.filter((word) => keywords2.includes(word))
|
|
385
|
+
return commonKeywords.length >= 2
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Extracts meaningful keywords from a summary for similarity detection
|
|
390
|
+
*/
|
|
391
|
+
private extractKeywords(summary: string): string[] {
|
|
392
|
+
// Remove common stopwords and extract meaningful terms
|
|
393
|
+
const stopwords = new Set([
|
|
394
|
+
"the",
|
|
395
|
+
"a",
|
|
396
|
+
"an",
|
|
397
|
+
"and",
|
|
398
|
+
"or",
|
|
399
|
+
"but",
|
|
400
|
+
"in",
|
|
401
|
+
"on",
|
|
402
|
+
"at",
|
|
403
|
+
"to",
|
|
404
|
+
"for",
|
|
405
|
+
"of",
|
|
406
|
+
"with",
|
|
407
|
+
"by",
|
|
408
|
+
"is",
|
|
409
|
+
"are",
|
|
410
|
+
"was",
|
|
411
|
+
"were",
|
|
412
|
+
"be",
|
|
413
|
+
"been",
|
|
414
|
+
"have",
|
|
415
|
+
"has",
|
|
416
|
+
"had",
|
|
417
|
+
"do",
|
|
418
|
+
"does",
|
|
419
|
+
"did",
|
|
420
|
+
"will",
|
|
421
|
+
"would",
|
|
422
|
+
"could",
|
|
423
|
+
"should",
|
|
424
|
+
])
|
|
425
|
+
|
|
426
|
+
return summary
|
|
427
|
+
.toLowerCase()
|
|
428
|
+
.replace(/[^\w\s]/g, " ")
|
|
429
|
+
.split(/\s+/)
|
|
430
|
+
.filter((word) => word.length > 2 && !stopwords.has(word))
|
|
431
|
+
.slice(0, 5) // Take first 5 meaningful words
|
|
432
|
+
}
|
|
433
|
+
|
|
283
434
|
/**
|
|
284
435
|
* Escape CSV fields that contain commas, quotes, or newlines
|
|
285
436
|
*/
|
|
@@ -24,7 +24,7 @@ export interface CLIOptions {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
export class CLIApplication {
|
|
27
|
-
private static readonly VERSION = "1.1.
|
|
27
|
+
private static readonly VERSION = "1.1.4"
|
|
28
28
|
private static readonly DEFAULT_COMMITS_OUTPUT_FILE = "results/commits.csv"
|
|
29
29
|
private static readonly DEFAULT_REPORT_OUTPUT_FILE = "results/report.md"
|
|
30
30
|
|
|
@@ -243,9 +243,11 @@ ${csvContent}
|
|
|
243
243
|
INSTRUCTIONS:
|
|
244
244
|
1. Group the data by year (descending order, most recent first)
|
|
245
245
|
2. Within each year, group by category: Features, Process Improvements, and Tweaks & Bug Fixes
|
|
246
|
-
3.
|
|
247
|
-
4.
|
|
248
|
-
5.
|
|
246
|
+
3. Use the 'commit_count' and 'date_range' columns to understand the scope and timeline of work
|
|
247
|
+
4. Consolidate similar items within each category to create readable summaries
|
|
248
|
+
5. Focus on what was accomplished rather than individual commit details
|
|
249
|
+
6. Use clear, professional language appropriate for stakeholders
|
|
250
|
+
7. Pay attention to recurring themes and patterns across commits
|
|
249
251
|
|
|
250
252
|
CATEGORY MAPPING:
|
|
251
253
|
- "feature" → "Features" section
|
|
@@ -253,12 +255,15 @@ CATEGORY MAPPING:
|
|
|
253
255
|
- "tweak" → "Tweaks & Bug Fixes" section
|
|
254
256
|
|
|
255
257
|
CONSOLIDATION GUIDELINES:
|
|
256
|
-
-
|
|
257
|
-
-
|
|
258
|
-
-
|
|
259
|
-
-
|
|
258
|
+
- FIRST: Extract common themes and keywords from commit summaries within each category
|
|
259
|
+
- SECOND: Identify and merge duplicate or highly similar work items (e.g., multiple "fix auth bug" commits become "resolved authentication issues")
|
|
260
|
+
- Group similar features together by theme (e.g., "authentication system improvements", "payment processing enhancements")
|
|
261
|
+
- Combine related bug fixes by area/system (e.g., "resolved 8 authentication issues", "fixed 5 database connection problems")
|
|
262
|
+
- Summarize process changes by theme (e.g., "CI/CD pipeline enhancements", "testing infrastructure improvements")
|
|
263
|
+
- Use bullet points for individual consolidated items within categories
|
|
260
264
|
- Aim for 3-7 bullet points per category per year
|
|
261
265
|
- Include specific numbers when relevant (e.g., "15 bug fixes", "3 new features")
|
|
266
|
+
- Avoid listing near-identical items separately - consolidate them into meaningful groups
|
|
262
267
|
|
|
263
268
|
OUTPUT FORMAT:
|
|
264
269
|
Generate yearly summary sections with this exact structure (DO NOT include the main title or commit analysis section):
|
|
@@ -290,6 +295,16 @@ QUALITY REQUIREMENTS:
|
|
|
290
295
|
- Avoid technical jargon where possible
|
|
291
296
|
- Ensure each bullet point represents meaningful work
|
|
292
297
|
- Make the report valuable for both technical and non-technical readers
|
|
298
|
+
- Focus on business impact and user value rather than technical implementation details
|
|
299
|
+
- When consolidating, preserve the most important aspects from similar commits
|
|
300
|
+
- Use progressive disclosure: start with high-level themes, then add specific details
|
|
301
|
+
|
|
302
|
+
CONTEXT ANALYSIS:
|
|
303
|
+
Before consolidating, analyze the commit data for:
|
|
304
|
+
1. Common file patterns or system areas being modified
|
|
305
|
+
2. Recurring keywords in commit messages that indicate related work
|
|
306
|
+
3. Sequential commits that build upon each other
|
|
307
|
+
4. Bug fixes that address the same underlying issue
|
|
293
308
|
|
|
294
309
|
Generate the markdown report now:`
|
|
295
310
|
}
|
|
@@ -306,10 +321,12 @@ ${csvContent}
|
|
|
306
321
|
INSTRUCTIONS:
|
|
307
322
|
1. Group the data by ${periodDisplayName} (descending order, most recent first)
|
|
308
323
|
2. Within each ${periodDisplayName.toLowerCase()}, group by category: Features, Process Improvements, and Tweaks & Bug Fixes
|
|
309
|
-
3.
|
|
310
|
-
4.
|
|
311
|
-
5.
|
|
312
|
-
6.
|
|
324
|
+
3. Use the 'commit_count' and 'similar_commits' columns to understand related work and consolidation opportunities
|
|
325
|
+
4. Consolidate similar items within each category to create readable summaries
|
|
326
|
+
5. Focus on what was accomplished rather than individual commit details
|
|
327
|
+
6. Use clear, professional language appropriate for stakeholders
|
|
328
|
+
7. Only include sections for time periods that have commits
|
|
329
|
+
8. Pay attention to recurring themes and patterns across commits
|
|
313
330
|
|
|
314
331
|
CATEGORY MAPPING:
|
|
315
332
|
- "feature" → "Features" section
|
|
@@ -317,12 +334,15 @@ CATEGORY MAPPING:
|
|
|
317
334
|
- "tweak" → "Tweaks & Bug Fixes" section
|
|
318
335
|
|
|
319
336
|
CONSOLIDATION GUIDELINES:
|
|
320
|
-
-
|
|
321
|
-
-
|
|
322
|
-
-
|
|
323
|
-
-
|
|
337
|
+
- FIRST: Extract common themes and keywords from commit summaries within each category
|
|
338
|
+
- SECOND: Identify and merge duplicate or highly similar work items (e.g., multiple "fix auth bug" commits become "resolved authentication issues")
|
|
339
|
+
- Group similar features together by theme (e.g., "authentication system improvements", "payment processing enhancements")
|
|
340
|
+
- Combine related bug fixes by area/system (e.g., "resolved 8 authentication issues", "fixed 5 database connection problems")
|
|
341
|
+
- Summarize process changes by theme (e.g., "CI/CD pipeline enhancements", "testing infrastructure improvements")
|
|
342
|
+
- Use bullet points for individual consolidated items within categories
|
|
324
343
|
- Aim for 3-7 bullet points per category per ${periodDisplayName.toLowerCase()}
|
|
325
344
|
- Include specific numbers when relevant (e.g., "15 bug fixes", "3 new features")
|
|
345
|
+
- Avoid listing near-identical items separately - consolidate them into meaningful groups
|
|
326
346
|
|
|
327
347
|
OUTPUT FORMAT:
|
|
328
348
|
Generate ${periodDisplayName.toLowerCase()} summary sections with this exact structure (DO NOT include the main title or commit analysis section):
|
|
@@ -354,6 +374,16 @@ QUALITY REQUIREMENTS:
|
|
|
354
374
|
- Avoid technical jargon where possible
|
|
355
375
|
- Ensure each bullet point represents meaningful work
|
|
356
376
|
- Make the report valuable for both technical and non-technical readers
|
|
377
|
+
- Focus on business impact and user value rather than technical implementation details
|
|
378
|
+
- When consolidating, preserve the most important aspects from similar commits
|
|
379
|
+
- Use progressive disclosure: start with high-level themes, then add specific details
|
|
380
|
+
|
|
381
|
+
CONTEXT ANALYSIS:
|
|
382
|
+
Before consolidating, analyze the commit data for:
|
|
383
|
+
1. Common file patterns or system areas being modified
|
|
384
|
+
2. Recurring keywords in commit messages that indicate related work
|
|
385
|
+
3. Sequential commits that build upon each other
|
|
386
|
+
4. Bug fixes that address the same underlying issue
|
|
357
387
|
|
|
358
388
|
Generate the markdown report now:`
|
|
359
389
|
}
|