commit-analyzer 1.1.0 → 1.1.1
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 +3 -2
- package/src/2.application/generate-report.usecase.ts +6 -1
- package/src/3.presentation/cli-application.ts +1 -1
- package/src/3.presentation/version-control-service.interface.ts +5 -0
- package/src/4.infrastructure/csv-service.ts +50 -4
- package/src/4.infrastructure/git-adapter.ts +27 -0
- package/src/di.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "commit-analyzer",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.1",
|
|
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": {
|
|
@@ -16,7 +16,8 @@
|
|
|
16
16
|
"lint": "eslint src/**/*.ts",
|
|
17
17
|
"typecheck": "tsc --noEmit",
|
|
18
18
|
"link": "bun link",
|
|
19
|
-
"publish": "bun publish"
|
|
19
|
+
"publish": "bun publish",
|
|
20
|
+
"deploy": "bun run build && bun link && bun publish"
|
|
20
21
|
},
|
|
21
22
|
"keywords": [
|
|
22
23
|
"git",
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
|
|
8
8
|
import { ICommandHandler } from "@presentation/command-handler.interface"
|
|
9
9
|
import { IStorageRepository } from "@presentation/storage-repository.interface"
|
|
10
|
+
import { IVersionControlService } from "@presentation/version-control-service.interface"
|
|
10
11
|
|
|
11
12
|
import { ILLMService } from "./llm-service"
|
|
12
13
|
|
|
@@ -35,6 +36,7 @@ export class GenerateReportUseCase
|
|
|
35
36
|
private readonly storageRepository: IStorageRepository,
|
|
36
37
|
private readonly llmService: ILLMService,
|
|
37
38
|
private readonly dateFormattingService: DateFormattingService,
|
|
39
|
+
private readonly versionControlService: IVersionControlService,
|
|
38
40
|
) {}
|
|
39
41
|
|
|
40
42
|
async handle(command: GenerateReportCommand): Promise<GenerateReportResult> {
|
|
@@ -99,7 +101,10 @@ export class GenerateReportUseCase
|
|
|
99
101
|
sourceInfo?: { type: 'author' | 'commits' | 'csv'; value: string }
|
|
100
102
|
}): Promise<void> {
|
|
101
103
|
const { commits, statistics, outputPath, includeStatistics, sourceInfo } = params
|
|
102
|
-
|
|
104
|
+
|
|
105
|
+
// Get repository name for the report heading
|
|
106
|
+
const repositoryName = await this.versionControlService.getRepositoryName()
|
|
107
|
+
let reportContent = `# Development Report for ${repositoryName}\n\n`
|
|
103
108
|
|
|
104
109
|
if (includeStatistics) {
|
|
105
110
|
reportContent += this.generateAnalysisSection(commits, statistics, sourceInfo)
|
|
@@ -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.1"
|
|
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
|
|
|
@@ -64,15 +64,61 @@ export class CSVService {
|
|
|
64
64
|
return `"${field.replace(/"/g, '""')}"`
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
+
/**
|
|
68
|
+
* Split CSV content into rows, properly handling quoted fields that may contain newlines
|
|
69
|
+
*/
|
|
70
|
+
private splitCSVIntoRows(content: string): string[] {
|
|
71
|
+
const rows: string[] = []
|
|
72
|
+
let currentRow = ""
|
|
73
|
+
let inQuotes = false
|
|
74
|
+
let i = 0
|
|
75
|
+
|
|
76
|
+
while (i < content.length) {
|
|
77
|
+
const char = content[i]
|
|
78
|
+
const nextChar = content[i + 1]
|
|
79
|
+
|
|
80
|
+
if (char === '"') {
|
|
81
|
+
if (inQuotes && nextChar === '"') {
|
|
82
|
+
// Escaped quote inside quoted field
|
|
83
|
+
currentRow += '""'
|
|
84
|
+
i += 2
|
|
85
|
+
} else {
|
|
86
|
+
// Start or end of quoted field
|
|
87
|
+
inQuotes = !inQuotes
|
|
88
|
+
currentRow += '"'
|
|
89
|
+
i++
|
|
90
|
+
}
|
|
91
|
+
} else if (char === "\n" && !inQuotes) {
|
|
92
|
+
// Row separator outside quotes
|
|
93
|
+
if (currentRow.trim().length > 0) {
|
|
94
|
+
rows.push(currentRow)
|
|
95
|
+
}
|
|
96
|
+
currentRow = ""
|
|
97
|
+
i++
|
|
98
|
+
} else {
|
|
99
|
+
// Regular character (including newlines inside quotes)
|
|
100
|
+
currentRow += char
|
|
101
|
+
i++
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Add the last row if it exists
|
|
106
|
+
if (currentRow.trim().length > 0) {
|
|
107
|
+
rows.push(currentRow)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return rows
|
|
111
|
+
}
|
|
112
|
+
|
|
67
113
|
private parseCSV(content: string): AnalyzedCommit[] {
|
|
68
|
-
const
|
|
114
|
+
const rows = this.splitCSVIntoRows(content)
|
|
69
115
|
|
|
70
|
-
if (
|
|
116
|
+
if (rows.length < 2) {
|
|
71
117
|
throw new Error("Invalid CSV format: no data rows found")
|
|
72
118
|
}
|
|
73
119
|
|
|
74
120
|
// Validate header
|
|
75
|
-
const header =
|
|
121
|
+
const header = rows[0].toLowerCase()
|
|
76
122
|
const expectedHeader = "timestamp,category,summary,description"
|
|
77
123
|
if (header !== expectedHeader) {
|
|
78
124
|
throw new Error(
|
|
@@ -81,7 +127,7 @@ export class CSVService {
|
|
|
81
127
|
}
|
|
82
128
|
|
|
83
129
|
// Skip header row
|
|
84
|
-
const dataRows =
|
|
130
|
+
const dataRows = rows.slice(1)
|
|
85
131
|
const commits: AnalyzedCommit[] = []
|
|
86
132
|
|
|
87
133
|
for (let i = 0; i < dataRows.length; i++) {
|
|
@@ -82,6 +82,33 @@ export class GitAdapter implements IVersionControlService {
|
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
+
async getRepositoryName(): Promise<string> {
|
|
86
|
+
try {
|
|
87
|
+
// Try to get the repository name from remote origin URL
|
|
88
|
+
const remoteUrl = execSync("git config --get remote.origin.url", GitAdapter.EXEC_OPTIONS).trim()
|
|
89
|
+
|
|
90
|
+
// Extract repository name from various URL formats
|
|
91
|
+
// git@github.com:user/repo.git -> repo
|
|
92
|
+
// https://github.com/user/repo.git -> repo
|
|
93
|
+
// https://github.com/user/repo -> repo
|
|
94
|
+
const match = remoteUrl.match(/\/([^\/]+?)(?:\.git)?$/)
|
|
95
|
+
if (match && match[1]) {
|
|
96
|
+
return match[1]
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Fallback: get the directory name
|
|
100
|
+
const dirName = execSync("basename $(git rev-parse --show-toplevel)", GitAdapter.EXEC_OPTIONS).trim()
|
|
101
|
+
return dirName
|
|
102
|
+
} catch (error) {
|
|
103
|
+
// Final fallback: use current directory name
|
|
104
|
+
try {
|
|
105
|
+
return execSync("basename $(pwd)", GitAdapter.EXEC_OPTIONS).trim()
|
|
106
|
+
} catch {
|
|
107
|
+
return "Unknown Project"
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
85
112
|
async getUserAuthoredCommits(params: {
|
|
86
113
|
authorEmail: string
|
|
87
114
|
limit?: number
|