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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "commit-analyzer",
3
- "version": "1.1.0",
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
- let reportContent = "# Development Summary Report\n\n"
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.0"
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
 
@@ -29,6 +29,11 @@ export interface IVersionControlService {
29
29
  */
30
30
  getCurrentUserName(): Promise<string>
31
31
 
32
+ /**
33
+ * Gets the repository/project name
34
+ */
35
+ getRepositoryName(): Promise<string>
36
+
32
37
  /**
33
38
  * Gets commits authored by a specific user
34
39
  */
@@ -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 lines = content.split("\n").filter((line) => line.trim().length > 0)
114
+ const rows = this.splitCSVIntoRows(content)
69
115
 
70
- if (lines.length < 2) {
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 = lines[0].toLowerCase()
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 = lines.slice(1)
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
package/src/di.ts CHANGED
@@ -74,6 +74,7 @@ export class DIContainer {
74
74
  this.storageRepository,
75
75
  this.llmAdapter,
76
76
  this.dateFormattingService,
77
+ this.gitAdapter,
77
78
  )
78
79
 
79
80
  private readonly resumeAnalysisUseCase = new ResumeAnalysisUseCase(