commit-analyzer 1.1.4 → 1.1.6

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 (55) hide show
  1. package/README.md +164 -82
  2. package/dist/main.ts +0 -0
  3. package/package.json +2 -1
  4. package/.claude/settings.local.json +0 -23
  5. package/commits.csv +0 -2
  6. package/csv-to-report-prompt.md +0 -97
  7. package/eslint.config.mts +0 -45
  8. package/prompt.md +0 -69
  9. package/src/1.domain/analysis.ts +0 -93
  10. package/src/1.domain/analyzed-commit.ts +0 -97
  11. package/src/1.domain/application-error.ts +0 -32
  12. package/src/1.domain/category.ts +0 -52
  13. package/src/1.domain/commit-analysis-service.ts +0 -92
  14. package/src/1.domain/commit-hash.ts +0 -40
  15. package/src/1.domain/commit.ts +0 -99
  16. package/src/1.domain/date-formatting-service.ts +0 -81
  17. package/src/1.domain/date-range.ts +0 -76
  18. package/src/1.domain/report-generation-service.ts +0 -443
  19. package/src/2.application/analyze-commits.usecase.ts +0 -307
  20. package/src/2.application/generate-report.usecase.ts +0 -209
  21. package/src/2.application/llm-service.ts +0 -54
  22. package/src/2.application/resume-analysis.usecase.ts +0 -123
  23. package/src/3.presentation/analysis-repository.interface.ts +0 -27
  24. package/src/3.presentation/analyze-command.ts +0 -128
  25. package/src/3.presentation/cli-application.ts +0 -278
  26. package/src/3.presentation/command-handler.interface.ts +0 -4
  27. package/src/3.presentation/commit-analysis-controller.ts +0 -101
  28. package/src/3.presentation/commit-repository.interface.ts +0 -47
  29. package/src/3.presentation/console-formatter.ts +0 -129
  30. package/src/3.presentation/progress-repository.interface.ts +0 -49
  31. package/src/3.presentation/report-command.ts +0 -50
  32. package/src/3.presentation/resume-command.ts +0 -59
  33. package/src/3.presentation/storage-repository.interface.ts +0 -33
  34. package/src/3.presentation/storage-service.interface.ts +0 -32
  35. package/src/3.presentation/version-control-service.interface.ts +0 -46
  36. package/src/4.infrastructure/cache-service.ts +0 -271
  37. package/src/4.infrastructure/cached-analysis-repository.ts +0 -46
  38. package/src/4.infrastructure/claude-llm-adapter.ts +0 -124
  39. package/src/4.infrastructure/csv-service.ts +0 -252
  40. package/src/4.infrastructure/file-storage-repository.ts +0 -108
  41. package/src/4.infrastructure/file-system-storage-adapter.ts +0 -87
  42. package/src/4.infrastructure/gemini-llm-adapter.ts +0 -46
  43. package/src/4.infrastructure/git-adapter.ts +0 -143
  44. package/src/4.infrastructure/git-commit-repository.ts +0 -85
  45. package/src/4.infrastructure/json-progress-tracker.ts +0 -182
  46. package/src/4.infrastructure/llm-adapter-factory.ts +0 -26
  47. package/src/4.infrastructure/llm-adapter.ts +0 -485
  48. package/src/4.infrastructure/llm-analysis-repository.ts +0 -38
  49. package/src/4.infrastructure/openai-llm-adapter.ts +0 -57
  50. package/src/di.ts +0 -109
  51. package/src/main.ts +0 -63
  52. package/src/utils/app-paths.ts +0 -36
  53. package/src/utils/concurrency.ts +0 -81
  54. package/src/utils.ts +0 -77
  55. package/tsconfig.json +0 -25
@@ -1,443 +0,0 @@
1
- import { AnalyzedCommit } from "./analyzed-commit"
2
- import { Category, CategoryType } from "./category"
3
- import { DateRange } from "./date-range"
4
-
5
- export type TimePeriod =
6
- | "hourly"
7
- | "daily"
8
- | "weekly"
9
- | "monthly"
10
- | "quarterly"
11
- | "yearly"
12
-
13
- /**
14
- * Statistics for analyzed commits
15
- */
16
- export interface CommitStatistics {
17
- totalCommits: number
18
- yearRange: {
19
- min: number
20
- max: number
21
- }
22
- categoryBreakdown: Record<CategoryType, number>
23
- yearlyBreakdown: Record<number, number>
24
- largeChanges: number
25
- }
26
-
27
- /**
28
- * Domain service for report generation operations
29
- */
30
- export class ReportGenerationService {
31
- /**
32
- * Generates statistics from analyzed commits
33
- */
34
- generateStatistics(commits: AnalyzedCommit[]): CommitStatistics {
35
- if (commits.length === 0) {
36
- throw new Error("Cannot generate statistics from empty commit list")
37
- }
38
-
39
- const years = commits.map((c) => c.getYear())
40
-
41
- const categoryBreakdown: Record<CategoryType, number> = {
42
- tweak: 0,
43
- feature: 0,
44
- process: 0,
45
- }
46
-
47
- const yearlyBreakdown: Record<number, number> = {}
48
- let largeChanges = 0
49
-
50
- for (const commit of commits) {
51
- // Category breakdown
52
- const category = commit.getAnalysis().getCategory().getValue()
53
- categoryBreakdown[category]++
54
-
55
- // Yearly breakdown
56
- const year = commit.getYear()
57
- yearlyBreakdown[year] = (yearlyBreakdown[year] || 0) + 1
58
-
59
- // Large changes count
60
- if (commit.isLargeChange()) {
61
- largeChanges++
62
- }
63
- }
64
-
65
- return {
66
- totalCommits: commits.length,
67
- yearRange: {
68
- min: Math.min(...years),
69
- max: Math.max(...years),
70
- },
71
- categoryBreakdown,
72
- yearlyBreakdown,
73
- largeChanges,
74
- }
75
- }
76
-
77
- /**
78
- * Filters commits by date range
79
- */
80
- filterByDateRange(
81
- commits: AnalyzedCommit[],
82
- dateRange: DateRange,
83
- ): AnalyzedCommit[] {
84
- return commits.filter((commit) => dateRange.contains(commit.getDate()))
85
- }
86
-
87
- /**
88
- * Filters commits by category
89
- */
90
- filterByCategory(
91
- commits: AnalyzedCommit[],
92
- category: Category,
93
- ): AnalyzedCommit[] {
94
- return commits.filter((commit) =>
95
- commit.getAnalysis().getCategory().equals(category),
96
- )
97
- }
98
-
99
- /**
100
- * Groups commits by year
101
- */
102
- groupByYear(commits: AnalyzedCommit[]): Map<number, AnalyzedCommit[]> {
103
- const grouped = new Map<number, AnalyzedCommit[]>()
104
-
105
- for (const commit of commits) {
106
- const year = commit.getYear()
107
- if (!grouped.has(year)) {
108
- grouped.set(year, [])
109
- }
110
- grouped.get(year)!.push(commit)
111
- }
112
-
113
- return grouped
114
- }
115
-
116
- /**
117
- * Groups commits by category
118
- */
119
- groupByCategory(
120
- commits: AnalyzedCommit[],
121
- ): Map<CategoryType, AnalyzedCommit[]> {
122
- const grouped = new Map<CategoryType, AnalyzedCommit[]>()
123
-
124
- for (const commit of commits) {
125
- const category = commit.getAnalysis().getCategory().getValue()
126
- if (!grouped.has(category)) {
127
- grouped.set(category, [])
128
- }
129
- grouped.get(category)!.push(commit)
130
- }
131
-
132
- return grouped
133
- }
134
-
135
- /**
136
- * Sorts commits by date (newest first by default)
137
- */
138
- sortByDate(
139
- commits: AnalyzedCommit[],
140
- ascending: boolean = false,
141
- ): AnalyzedCommit[] {
142
- return commits.slice().sort((a, b) => {
143
- const aDate = a.getDate().getTime()
144
- const bDate = b.getDate().getTime()
145
- return ascending ? aDate - bDate : bDate - aDate
146
- })
147
- }
148
-
149
- /**
150
- * Gets the most significant commits (features and large changes)
151
- */
152
- getSignificantCommits(commits: AnalyzedCommit[]): AnalyzedCommit[] {
153
- return commits.filter(
154
- (commit) =>
155
- commit.getAnalysis().isFeatureAnalysis() || commit.isLargeChange(),
156
- )
157
- }
158
-
159
- /**
160
- * Determines the appropriate time period for summaries based on date range
161
- */
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())))
168
-
169
- const diffInMilliseconds = maxDate.getTime() - minDate.getTime()
170
- const diffInDays = diffInMilliseconds / (1000 * 60 * 60 * 24)
171
-
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"
178
- }
179
-
180
- /**
181
- * Groups commits by the appropriate time period
182
- */
183
- groupByTimePeriod(
184
- commits: AnalyzedCommit[],
185
- period: TimePeriod,
186
- ): Map<string, AnalyzedCommit[]> {
187
- const grouped = new Map<string, AnalyzedCommit[]>()
188
-
189
- for (const commit of commits) {
190
- const date = commit.getDate()
191
- let key: string
192
-
193
- switch (period) {
194
- case "hourly":
195
- key = this.formatHourlyKey(date)
196
- break
197
- case "daily":
198
- key = this.formatDailyKey(date)
199
- break
200
- case "weekly":
201
- key = this.formatWeeklyKey(date)
202
- break
203
- case "monthly":
204
- key = this.formatMonthlyKey(date)
205
- break
206
- case "quarterly":
207
- key = this.formatQuarterlyKey(date)
208
- break
209
- case "yearly":
210
- default:
211
- key = date.getFullYear().toString()
212
- break
213
- }
214
-
215
- if (!grouped.has(key)) {
216
- grouped.set(key, [])
217
- }
218
- grouped.get(key)!.push(commit)
219
- }
220
-
221
- return grouped
222
- }
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
-
231
- private formatDailyKey(date: Date): string {
232
- const year = date.getFullYear()
233
- const month = date.getMonth() + 1
234
- const day = date.getDate()
235
- const hour = date.getHours()
236
-
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`
242
- }
243
-
244
- private formatWeeklyKey(date: Date): string {
245
- const startOfWeek = new Date(date)
246
- startOfWeek.setDate(date.getDate() - date.getDay())
247
- const endOfWeek = new Date(startOfWeek)
248
- endOfWeek.setDate(startOfWeek.getDate() + 6)
249
-
250
- const formatDate = (d: Date) =>
251
- `${d.getFullYear()}-${(d.getMonth() + 1).toString().padStart(2, "0")}-${d.getDate().toString().padStart(2, "0")}`
252
-
253
- return `Week of ${formatDate(startOfWeek)} to ${formatDate(endOfWeek)}`
254
- }
255
-
256
- private formatMonthlyKey(date: Date): string {
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
- ]
271
- return `${months[date.getMonth()]} ${date.getFullYear()}`
272
- }
273
-
274
- private formatQuarterlyKey(date: Date): string {
275
- const quarter = Math.floor(date.getMonth() / 3) + 1
276
- return `Q${quarter} ${date.getFullYear()}`
277
- }
278
-
279
- /**
280
- * Converts analyzed commits to CSV string format for LLM consumption with enhanced context
281
- */
282
- convertToCSVString(commits: AnalyzedCommit[]): string {
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
-
288
- const rows = commits.map((commit) => {
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
-
305
- return [
306
- commit.getYear().toString(),
307
- this.escapeCsvField(analysis.getCategory().getValue()),
308
- this.escapeCsvField(analysis.getSummary()),
309
- this.escapeCsvField(analysis.getDescription()),
310
- context.count.toString(),
311
- this.escapeCsvField(dateRange),
312
- ].join(",")
313
- })
314
-
315
- return [header, ...rows].join("\n")
316
- }
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
-
323
- /**
324
- * Converts grouped commits to CSV with time period information and enhanced context
325
- */
326
- convertGroupedToCSV(
327
- groupedCommits: Map<string, AnalyzedCommit[]>,
328
- period: string,
329
- ): string {
330
- const header = `${period},category,summary,description,commit_count,similar_commits`
331
- const rows: string[] = []
332
-
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
346
- for (const commit of commits) {
347
- const analysis = commit.getAnalysis()
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
- )
370
- }
371
- }
372
-
373
- return [header, ...rows].join("\n")
374
- }
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
-
434
- /**
435
- * Escape CSV fields that contain commas, quotes, or newlines
436
- */
437
- private escapeCsvField(field: string): string {
438
- if (field.includes(",") || field.includes('"') || field.includes("\n")) {
439
- return `"${field.replace(/"/g, '""')}"`
440
- }
441
- return field
442
- }
443
- }