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,129 +0,0 @@
1
- /**
2
- * Console formatter for consistent output styling
3
- */
4
- export class ConsoleFormatter {
5
- /**
6
- * Log a success message with checkmark
7
- */
8
- static logSuccess(message: string): void {
9
- console.log(`✓ ${message}`)
10
- }
11
-
12
- /**
13
- * Log an error message with X mark
14
- */
15
- static logError(message: string): void {
16
- console.error(`❌ ${message}`)
17
- }
18
-
19
- /**
20
- * Log a warning message with warning icon
21
- */
22
- static logWarning(message: string): void {
23
- console.log(`⚠️ ${message}`)
24
- }
25
-
26
- /**
27
- * Log a debug message for development purposes
28
- */
29
- static logDebug(message: string): void {
30
- console.log(`🐛 ${message}`)
31
- }
32
-
33
- /**
34
- * Log an info message with bullet point
35
- */
36
- static logInfo(message: string): void {
37
- console.log(` - ${message}`)
38
- }
39
-
40
- /**
41
- * Log a progress message with arrow
42
- */
43
- static logProgress(message: string): void {
44
- console.log(`▶️ ${message}`)
45
- }
46
-
47
- /**
48
- * Log a completion message with celebration icon
49
- */
50
- static logComplete(message: string): void {
51
- console.log(`🎉 ${message}`)
52
- }
53
-
54
- /**
55
- * Log a file operation with folder icon
56
- */
57
- static logFile(message: string): void {
58
- console.log(`📁 ${message}`)
59
- }
60
-
61
- /**
62
- * Log a save operation with disk icon
63
- */
64
- static logSave(message: string): void {
65
- console.log(`💾 ${message}`)
66
- }
67
-
68
- /**
69
- * Log a report generation with chart icon
70
- */
71
- static logReport(message: string): void {
72
- console.log(`📊 ${message}`)
73
- }
74
-
75
- /**
76
- * Log with indentation for nested information
77
- */
78
- static logIndented(message: string, level: number = 1): void {
79
- const indent = ' '.repeat(level)
80
- console.log(`${indent}${message}`)
81
- }
82
-
83
- /**
84
- * Log a section header with newlines for spacing
85
- */
86
- static logSection(title: string): void {
87
- console.log(`\n${title}`)
88
- }
89
-
90
- /**
91
- * Log with custom emoji/icon
92
- */
93
- static logWithIcon(icon: string, message: string): void {
94
- console.log(`${icon} ${message}`)
95
- }
96
-
97
- /**
98
- * Display analysis summary in a formatted way
99
- */
100
- static displayAnalysisSummary(summary: Record<string, number>): void {
101
- this.logSection("Summary by category:")
102
- Object.entries(summary).forEach(([category, count]) => {
103
- this.logInfo(`${category}: ${count} commits`)
104
- })
105
- }
106
-
107
- /**
108
- * Update progress bar inline
109
- */
110
- static updateProgress(current: number, total: number, description?: string): void {
111
- const percentage = Math.round((current / total) * 100)
112
- const completed = Math.round((current / total) * 20) // 20 characters for progress bar
113
- const remaining = 20 - completed
114
-
115
- const progressBar = '█'.repeat(completed) + '░'.repeat(remaining)
116
- const desc = description ? ` ${description}` : ''
117
-
118
- // Use \r to overwrite the line
119
- process.stdout.write(`\r▶️ [${progressBar}] ${percentage}% (${current}/${total})${desc}`)
120
- }
121
-
122
- /**
123
- * Complete progress and clear the line
124
- */
125
- static completeProgress(): void {
126
- // Clear the current line and move to beginning
127
- process.stdout.write('\r\x1b[K')
128
- }
129
- }
@@ -1,49 +0,0 @@
1
- import { AnalyzedCommit } from "@domain/analyzed-commit"
2
- import { CommitHash } from "@domain/commit-hash"
3
-
4
- /**
5
- * Progress state for tracking analysis progress
6
- */
7
- export interface ProgressState {
8
- totalCommits: CommitHash[]
9
- processedCommits: CommitHash[]
10
- analyzedCommits: AnalyzedCommit[]
11
- lastProcessedIndex: number
12
- startTime: Date
13
- outputFile: string
14
- }
15
-
16
- /**
17
- * Repository interface for progress tracking operations
18
- */
19
- export interface IProgressRepository {
20
- /**
21
- * Saves the current progress state
22
- */
23
- saveProgress(state: ProgressState): Promise<void>
24
-
25
- /**
26
- * Loads the saved progress state
27
- */
28
- loadProgress(): Promise<ProgressState | null>
29
-
30
- /**
31
- * Checks if there is saved progress
32
- */
33
- hasProgress(): Promise<boolean>
34
-
35
- /**
36
- * Clears any saved progress
37
- */
38
- clearProgress(): Promise<void>
39
-
40
- /**
41
- * Gets the remaining commits from a progress state
42
- */
43
- getRemainingCommits(state: ProgressState): CommitHash[]
44
-
45
- /**
46
- * Formats a progress summary for display
47
- */
48
- formatProgressSummary(state: ProgressState): string
49
- }
@@ -1,50 +0,0 @@
1
- import { GenerateReportUseCase } from "@app/generate-report.usecase"
2
-
3
- import { ConsoleFormatter } from "./console-formatter"
4
-
5
- export interface ReportCommandOptions {
6
- inputCsv?: string
7
- output: string
8
- includeStatistics?: boolean
9
- sourceInfo?: {
10
- type: "author" | "commits" | "csv"
11
- value: string
12
- }
13
- }
14
-
15
- export class ReportCommand {
16
- constructor(private readonly generateReportUseCase: GenerateReportUseCase) {}
17
-
18
- async execute(options: ReportCommandOptions): Promise<void> {
19
- try {
20
- console.log("\nGenerating report from existing CSV...")
21
-
22
- const result = await this.generateReportUseCase.handle({
23
- inputCsvPath: options.inputCsv,
24
- outputPath: options.output,
25
- includeStatistics: options.includeStatistics ?? true,
26
- sourceInfo:
27
- options.sourceInfo ||
28
- (options.inputCsv
29
- ? { type: "csv", value: options.inputCsv }
30
- : undefined),
31
- })
32
-
33
- ConsoleFormatter.logReport(`Report generated: ${result.reportPath}`)
34
-
35
- const stats = result.statistics
36
- ConsoleFormatter.logSuccess(
37
- `Processed ${result.commitsProcessed} commits`,
38
- )
39
-
40
- ConsoleFormatter.logInfo(
41
- `Categories: ${stats.categoryBreakdown.feature} features, ${stats.categoryBreakdown.process} process, ${stats.categoryBreakdown.tweak} tweaks`,
42
- )
43
- } catch (error) {
44
- ConsoleFormatter.logError(
45
- error instanceof Error ? error.message : "Unknown error occurred",
46
- )
47
- throw error
48
- }
49
- }
50
- }
@@ -1,59 +0,0 @@
1
- import { ResumeAnalysisUseCase } from "@app/resume-analysis.usecase"
2
-
3
- import { ConsoleFormatter } from "./console-formatter"
4
-
5
- export interface ResumeCommandOptions {
6
- verbose?: boolean
7
- }
8
-
9
- export class ResumeCommand {
10
- constructor(private readonly resumeAnalysisUseCase: ResumeAnalysisUseCase) {}
11
-
12
- async execute(options: ResumeCommandOptions): Promise<boolean> {
13
- try {
14
- const result = await this.resumeAnalysisUseCase.handle({
15
- verbose: options.verbose,
16
- })
17
-
18
- if (!result) {
19
- ConsoleFormatter.logInfo(
20
- "No previous checkpoint found or user chose to start fresh",
21
- )
22
- return false
23
- }
24
-
25
- // Display results
26
- ConsoleFormatter.logSuccess(
27
- "Analysis resumed and completed successfully!",
28
- )
29
- ConsoleFormatter.logInfo(
30
- `Total analyzed: ${result.analyzedCommits.length} commits`,
31
- )
32
-
33
- if (result.failedCommits > 0) {
34
- ConsoleFormatter.logWarning(
35
- `Failed commits during resume: ${result.failedCommits}`,
36
- )
37
- }
38
-
39
- // Display category summary
40
- const summary = result.analyzedCommits.reduce(
41
- (acc, commit) => {
42
- const category = commit.getAnalysis().getCategory().getValue()
43
- acc[category] = (acc[category] || 0) + 1
44
- return acc
45
- },
46
- {} as Record<string, number>,
47
- )
48
-
49
- ConsoleFormatter.displayAnalysisSummary(summary)
50
-
51
- return true
52
- } catch (error) {
53
- ConsoleFormatter.logError(
54
- error instanceof Error ? error.message : "Unknown error occurred",
55
- )
56
- throw error
57
- }
58
- }
59
- }
@@ -1,33 +0,0 @@
1
- import { AnalyzedCommit } from "@domain/analyzed-commit"
2
-
3
- export interface IStorageRepository {
4
- /**
5
- * Exports analyzed commits to CSV format
6
- */
7
- exportToCSV(commits: AnalyzedCommit[], filePath: string): Promise<void>
8
-
9
- /**
10
- * Imports commits from CSV format
11
- */
12
- importFromCSV(filePath: string): Promise<AnalyzedCommit[]>
13
-
14
- /**
15
- * Generates a markdown report from analyzed commits
16
- */
17
- generateReport(commits: AnalyzedCommit[], outputPath: string): Promise<void>
18
-
19
- /**
20
- * Reads commits from a file (one hash per line)
21
- */
22
- readCommitHashesFromFile(filePath: string): Promise<string[]>
23
-
24
- /**
25
- * Ensures a directory exists
26
- */
27
- ensureDirectoryExists(directoryPath: string): Promise<void>
28
-
29
- /**
30
- * Writes content to a file
31
- */
32
- writeFile(filePath: string, content: string): Promise<void>
33
- }
@@ -1,32 +0,0 @@
1
- export interface IStorageService {
2
- /**
3
- * Writes content to a file
4
- */
5
- writeFile(filePath: string, content: string): Promise<void>
6
-
7
- /**
8
- * Reads content from a file
9
- */
10
- readFile(filePath: string): Promise<string>
11
-
12
- /**
13
- * Checks if a file exists
14
- */
15
- fileExists(filePath: string): Promise<boolean>
16
-
17
- /**
18
- * Creates a directory if it doesn't exist
19
- */
20
- ensureDirectory(directoryPath: string): Promise<void>
21
-
22
- /**
23
- * Deletes a file
24
- */
25
- deleteFile(filePath: string): Promise<void>
26
-
27
- /**
28
- * Reads lines from a file
29
- */
30
- readLines(filePath: string): Promise<string[]>
31
- }
32
-
@@ -1,46 +0,0 @@
1
- export interface IVersionControlService {
2
- /**
3
- * Gets detailed commit information by hash
4
- */
5
- getCommitInfo(hash: string): Promise<{
6
- hash: string
7
- message: string
8
- date: Date
9
- diff: string
10
- }>
11
-
12
- /**
13
- * Validates if a commit hash exists
14
- */
15
- validateCommitHash(hash: string): Promise<boolean>
16
-
17
- /**
18
- * Checks if current directory is a valid repository
19
- */
20
- isValidRepository(): Promise<boolean>
21
-
22
- /**
23
- * Gets current user's email from repository config
24
- */
25
- getCurrentUserEmail(): Promise<string>
26
-
27
- /**
28
- * Gets current user's name from repository config
29
- */
30
- getCurrentUserName(): Promise<string>
31
-
32
- /**
33
- * Gets the repository/project name
34
- */
35
- getRepositoryName(): Promise<string>
36
-
37
- /**
38
- * Gets commits authored by a specific user
39
- */
40
- getUserAuthoredCommits(params: {
41
- authorEmail: string
42
- limit?: number
43
- since?: string
44
- until?: string
45
- }): Promise<string[]>
46
- }
@@ -1,271 +0,0 @@
1
- import { promises as fs } from "fs"
2
- import path from "path"
3
-
4
- import { Analysis } from "@domain/analysis"
5
- import { Category, CategoryType } from "@domain/category"
6
-
7
- import { AppPaths } from "../utils/app-paths"
8
-
9
- /**
10
- * Cache entry for storing analyzed commit results
11
- */
12
- interface CacheEntry {
13
- hash: string
14
- timestamp: number
15
- analysis: {
16
- category: string
17
- summary: string
18
- description: string
19
- }
20
- }
21
-
22
- /**
23
- * Service for caching analyzed commit results
24
- */
25
- export class CacheService {
26
- private static readonly DEFAULT_TTL_DAYS = 30
27
- private static readonly CACHE_FILE_PREFIX = "commit-"
28
-
29
- private readonly cacheDir: string
30
- private readonly ttlMs: number
31
- private cacheEnabled: boolean = true
32
-
33
- constructor(
34
- baseDir: string = process.cwd(),
35
- ttlDays: number = CacheService.DEFAULT_TTL_DAYS
36
- ) {
37
- this.cacheDir = AppPaths.getCacheDir(baseDir)
38
- this.ttlMs = ttlDays * 24 * 60 * 60 * 1000
39
- }
40
-
41
- /**
42
- * Enable or disable caching
43
- */
44
- setCacheEnabled(enabled: boolean): void {
45
- this.cacheEnabled = enabled
46
- }
47
-
48
- /**
49
- * Initialize cache directory
50
- */
51
- async initialize(): Promise<void> {
52
- if (!this.cacheEnabled) {
53
- return
54
- }
55
-
56
- try {
57
- await fs.mkdir(this.cacheDir, { recursive: true })
58
- } catch (error) {
59
- console.warn("Failed to initialize cache directory:", error)
60
- this.cacheEnabled = false
61
- }
62
- }
63
-
64
- /**
65
- * Get cached analysis result for a commit hash
66
- */
67
- async get(commitHash: string): Promise<Analysis | null> {
68
- if (!this.cacheEnabled) {
69
- return null
70
- }
71
-
72
- try {
73
- const cacheFilePath = this.getCacheFilePath(commitHash)
74
-
75
- // Check if cache file exists
76
- const stat = await fs.stat(cacheFilePath)
77
- const now = Date.now()
78
-
79
- // Check if cache entry is expired
80
- if (now - stat.mtimeMs > this.ttlMs) {
81
- await this.delete(commitHash)
82
- return null
83
- }
84
-
85
- // Read and parse cache entry
86
- const cacheData = await fs.readFile(cacheFilePath, "utf-8")
87
- const entry: CacheEntry = JSON.parse(cacheData)
88
-
89
- // Verify hash matches
90
- if (entry.hash !== commitHash) {
91
- await this.delete(commitHash)
92
- return null
93
- }
94
-
95
- // Reconstruct Analysis object
96
- const category = Category.create(entry.analysis.category as CategoryType)
97
- return new Analysis({
98
- category,
99
- summary: entry.analysis.summary,
100
- description: entry.analysis.description,
101
- })
102
- } catch {
103
- // Cache miss or error - return null
104
- return null
105
- }
106
- }
107
-
108
- /**
109
- * Store analysis result in cache
110
- */
111
- async set(commitHash: string, analysis: Analysis): Promise<void> {
112
- if (!this.cacheEnabled) {
113
- return
114
- }
115
-
116
- try {
117
- await this.initialize()
118
-
119
- const entry: CacheEntry = {
120
- hash: commitHash,
121
- timestamp: Date.now(),
122
- analysis: {
123
- category: analysis.getCategory().getValue(),
124
- summary: analysis.getSummary(),
125
- description: analysis.getDescription(),
126
- },
127
- }
128
-
129
- const cacheFilePath = this.getCacheFilePath(commitHash)
130
- await fs.writeFile(cacheFilePath, JSON.stringify(entry, null, 2))
131
- } catch (error) {
132
- // Silent fail for cache writes
133
- console.warn(`Failed to cache analysis for ${commitHash}:`, error)
134
- }
135
- }
136
-
137
- /**
138
- * Delete cached entry for a commit hash
139
- */
140
- async delete(commitHash: string): Promise<void> {
141
- if (!this.cacheEnabled) {
142
- return
143
- }
144
-
145
- try {
146
- const cacheFilePath = this.getCacheFilePath(commitHash)
147
- await fs.unlink(cacheFilePath)
148
- } catch {
149
- // Silent fail for cache deletes
150
- }
151
- }
152
-
153
- /**
154
- * Clear all cache entries
155
- */
156
- async clear(): Promise<void> {
157
- if (!this.cacheEnabled) {
158
- return
159
- }
160
-
161
- try {
162
- const files = await fs.readdir(this.cacheDir)
163
- const cacheFiles = files.filter(file =>
164
- file.startsWith(CacheService.CACHE_FILE_PREFIX)
165
- )
166
-
167
- await Promise.all(
168
- cacheFiles.map(file =>
169
- fs.unlink(path.join(this.cacheDir, file)).catch(() => {})
170
- )
171
- )
172
- } catch {
173
- // Silent fail for cache clear
174
- }
175
- }
176
-
177
- /**
178
- * Get cache statistics
179
- */
180
- async getStats(): Promise<{
181
- totalEntries: number
182
- totalSize: number
183
- oldestEntry: Date | null
184
- newestEntry: Date | null
185
- }> {
186
- if (!this.cacheEnabled) {
187
- return {
188
- totalEntries: 0,
189
- totalSize: 0,
190
- oldestEntry: null,
191
- newestEntry: null,
192
- }
193
- }
194
-
195
- try {
196
- const files = await fs.readdir(this.cacheDir)
197
- const cacheFiles = files.filter(file =>
198
- file.startsWith(CacheService.CACHE_FILE_PREFIX)
199
- )
200
-
201
- let totalSize = 0
202
- let oldestTime = Number.MAX_SAFE_INTEGER
203
- let newestTime = 0
204
-
205
- for (const file of cacheFiles) {
206
- const filePath = path.join(this.cacheDir, file)
207
- const stat = await fs.stat(filePath)
208
- totalSize += stat.size
209
- oldestTime = Math.min(oldestTime, stat.mtimeMs)
210
- newestTime = Math.max(newestTime, stat.mtimeMs)
211
- }
212
-
213
- return {
214
- totalEntries: cacheFiles.length,
215
- totalSize,
216
- oldestEntry: cacheFiles.length > 0 ? new Date(oldestTime) : null,
217
- newestEntry: cacheFiles.length > 0 ? new Date(newestTime) : null,
218
- }
219
- } catch {
220
- return {
221
- totalEntries: 0,
222
- totalSize: 0,
223
- oldestEntry: null,
224
- newestEntry: null,
225
- }
226
- }
227
- }
228
-
229
- /**
230
- * Clean expired cache entries
231
- */
232
- async cleanExpired(): Promise<number> {
233
- if (!this.cacheEnabled) {
234
- return 0
235
- }
236
-
237
- try {
238
- const files = await fs.readdir(this.cacheDir)
239
- const cacheFiles = files.filter(file =>
240
- file.startsWith(CacheService.CACHE_FILE_PREFIX)
241
- )
242
-
243
- const now = Date.now()
244
- let cleanedCount = 0
245
-
246
- for (const file of cacheFiles) {
247
- const filePath = path.join(this.cacheDir, file)
248
- try {
249
- const stat = await fs.stat(filePath)
250
- if (now - stat.mtimeMs > this.ttlMs) {
251
- await fs.unlink(filePath)
252
- cleanedCount++
253
- }
254
- } catch {
255
- // Skip files that can't be processed
256
- }
257
- }
258
-
259
- return cleanedCount
260
- } catch {
261
- return 0
262
- }
263
- }
264
-
265
- private getCacheFilePath(commitHash: string): string {
266
- return path.join(
267
- this.cacheDir,
268
- `${CacheService.CACHE_FILE_PREFIX}${commitHash}.json`
269
- )
270
- }
271
- }