commit-analyzer 1.0.2 → 1.1.0

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 (62) hide show
  1. package/.claude/settings.local.json +11 -1
  2. package/README.md +33 -2
  3. package/commits.csv +2 -0
  4. package/eslint.config.mts +45 -0
  5. package/package.json +17 -9
  6. package/src/1.domain/analysis.ts +93 -0
  7. package/src/1.domain/analyzed-commit.ts +97 -0
  8. package/src/1.domain/application-error.ts +32 -0
  9. package/src/1.domain/category.ts +52 -0
  10. package/src/1.domain/commit-analysis-service.ts +92 -0
  11. package/src/1.domain/commit-hash.ts +40 -0
  12. package/src/1.domain/commit.ts +99 -0
  13. package/src/1.domain/date-formatting-service.ts +81 -0
  14. package/src/1.domain/date-range.ts +76 -0
  15. package/src/1.domain/report-generation-service.ts +292 -0
  16. package/src/2.application/analyze-commits.usecase.ts +307 -0
  17. package/src/2.application/generate-report.usecase.ts +204 -0
  18. package/src/2.application/llm-service.ts +54 -0
  19. package/src/2.application/resume-analysis.usecase.ts +123 -0
  20. package/src/3.presentation/analysis-repository.interface.ts +27 -0
  21. package/src/3.presentation/analyze-command.ts +128 -0
  22. package/src/3.presentation/cli-application.ts +255 -0
  23. package/src/3.presentation/command-handler.interface.ts +4 -0
  24. package/src/3.presentation/commit-analysis-controller.ts +101 -0
  25. package/src/3.presentation/commit-repository.interface.ts +47 -0
  26. package/src/3.presentation/console-formatter.ts +129 -0
  27. package/src/3.presentation/progress-repository.interface.ts +49 -0
  28. package/src/3.presentation/report-command.ts +50 -0
  29. package/src/3.presentation/resume-command.ts +59 -0
  30. package/src/3.presentation/storage-repository.interface.ts +33 -0
  31. package/src/3.presentation/storage-service.interface.ts +32 -0
  32. package/src/3.presentation/version-control-service.interface.ts +41 -0
  33. package/src/4.infrastructure/cache-service.ts +271 -0
  34. package/src/4.infrastructure/cached-analysis-repository.ts +46 -0
  35. package/src/4.infrastructure/claude-llm-adapter.ts +124 -0
  36. package/src/4.infrastructure/csv-service.ts +206 -0
  37. package/src/4.infrastructure/file-storage-repository.ts +108 -0
  38. package/src/4.infrastructure/file-system-storage-adapter.ts +87 -0
  39. package/src/4.infrastructure/gemini-llm-adapter.ts +46 -0
  40. package/src/4.infrastructure/git-adapter.ts +116 -0
  41. package/src/4.infrastructure/git-commit-repository.ts +85 -0
  42. package/src/4.infrastructure/json-progress-tracker.ts +182 -0
  43. package/src/4.infrastructure/llm-adapter-factory.ts +26 -0
  44. package/src/4.infrastructure/llm-adapter.ts +455 -0
  45. package/src/4.infrastructure/llm-analysis-repository.ts +38 -0
  46. package/src/4.infrastructure/openai-llm-adapter.ts +57 -0
  47. package/src/di.ts +108 -0
  48. package/src/main.ts +63 -0
  49. package/src/utils/app-paths.ts +36 -0
  50. package/src/utils/concurrency.ts +81 -0
  51. package/src/utils.ts +77 -0
  52. package/tsconfig.json +7 -1
  53. package/src/cli.ts +0 -170
  54. package/src/csv-reader.ts +0 -180
  55. package/src/csv.ts +0 -40
  56. package/src/errors.ts +0 -49
  57. package/src/git.ts +0 -112
  58. package/src/index.ts +0 -395
  59. package/src/llm.ts +0 -396
  60. package/src/progress.ts +0 -84
  61. package/src/report-generator.ts +0 -286
  62. package/src/types.ts +0 -24
@@ -0,0 +1,57 @@
1
+ import { execSync } from "child_process"
2
+
3
+ import { LLMAdapter } from "./llm-adapter"
4
+
5
+ export class OpenAILLMAdapter extends LLMAdapter {
6
+ private static readonly MAX_PROMPT_LENGTH = parseInt(
7
+ process.env.OPENAI_MAX_PROMPT_LENGTH || "100000",
8
+ 10,
9
+ )
10
+
11
+ protected getMaxPromptLength(): number {
12
+ return OpenAILLMAdapter.MAX_PROMPT_LENGTH
13
+ }
14
+
15
+ async detectAvailableModels(): Promise<string[]> {
16
+ try {
17
+ execSync("command -v codex", { stdio: "ignore" })
18
+ return ["codex -q", "openai"]
19
+ } catch {
20
+ return []
21
+ }
22
+ }
23
+
24
+ async isAvailable(): Promise<boolean> {
25
+ const available = await this.detectAvailableModels()
26
+ return available.length > 0
27
+ }
28
+
29
+ protected async executeModelCommand(prompt: string): Promise<string> {
30
+ const truncatedPrompt = this.truncatePrompt(prompt)
31
+
32
+ if (this.verbose) {
33
+ console.log(` - Prompt length: ${truncatedPrompt.length} characters`)
34
+ }
35
+
36
+ const modelCommand = this.model || "codex -q"
37
+
38
+ // Handle codex -q quirk: requires prompt as command line argument
39
+ if (modelCommand.includes("-q")) {
40
+ const escapedPrompt = truncatedPrompt.replace(/"/g, '\\"')
41
+ return execSync(`${modelCommand} "${escapedPrompt}"`, {
42
+ encoding: "utf8",
43
+ stdio: ["pipe", "pipe", "pipe"],
44
+ timeout: LLMAdapter.DEFAULT_TIMEOUT,
45
+ })
46
+ }
47
+
48
+ // Fallback for non-quiet mode commands
49
+ return execSync(modelCommand, {
50
+ input: truncatedPrompt,
51
+ encoding: "utf8",
52
+ stdio: ["pipe", "pipe", "pipe"],
53
+ timeout: LLMAdapter.DEFAULT_TIMEOUT,
54
+ })
55
+ }
56
+ }
57
+
package/src/di.ts ADDED
@@ -0,0 +1,108 @@
1
+ import { CommitAnalysisService } from "@domain/commit-analysis-service"
2
+ import { DateFormattingService } from "@domain/date-formatting-service"
3
+ import { ReportGenerationService } from "@domain/report-generation-service"
4
+
5
+ import { AnalyzeCommitsUseCase } from "@app/analyze-commits.usecase"
6
+ import { GenerateReportUseCase } from "@app/generate-report.usecase"
7
+ import { ResumeAnalysisUseCase } from "@app/resume-analysis.usecase"
8
+
9
+ import { AnalyzeCommand } from "@presentation/analyze-command"
10
+ import { CLIApplication } from "@presentation/cli-application"
11
+ import { CommitAnalysisController } from "@presentation/commit-analysis-controller"
12
+ import { ReportCommand } from "@presentation/report-command"
13
+ import { ResumeCommand } from "@presentation/resume-command"
14
+
15
+ import { CacheService } from "@infra/cache-service"
16
+ import { CachedAnalysisRepository } from "@infra/cached-analysis-repository"
17
+ import { FileStorageRepository } from "@infra/file-storage-repository"
18
+ import { FileSystemStorageAdapter } from "@infra/file-system-storage-adapter"
19
+ import { GitAdapter } from "@infra/git-adapter"
20
+ import { GitCommitRepository } from "@infra/git-commit-repository"
21
+ import { JSONProgressTracker } from "@infra/json-progress-tracker"
22
+ import { LLMAdapterFactory } from "@infra/llm-adapter-factory"
23
+ import { LLMAnalysisRepository } from "@infra/llm-analysis-repository"
24
+
25
+ export interface DIContainerOptions {
26
+ llm?: string
27
+ noCache?: boolean
28
+ }
29
+
30
+ export class DIContainer {
31
+ private readonly gitAdapter = new GitAdapter()
32
+ private readonly llmAdapter = LLMAdapterFactory.create(this.options?.llm)
33
+ private readonly storageAdapter = new FileSystemStorageAdapter()
34
+ private readonly cacheService = (() => {
35
+ const service = new CacheService()
36
+ if (this.options?.noCache) {
37
+ service.setCacheEnabled(false)
38
+ }
39
+ return service
40
+ })()
41
+ private readonly progressTracker = new JSONProgressTracker(
42
+ this.storageAdapter,
43
+ )
44
+
45
+ private readonly commitRepository = new GitCommitRepository(this.gitAdapter)
46
+ private readonly llmAnalysisRepository = new LLMAnalysisRepository(
47
+ this.llmAdapter,
48
+ )
49
+ private readonly analysisRepository = this.options?.noCache
50
+ ? this.llmAnalysisRepository
51
+ : new CachedAnalysisRepository(
52
+ this.llmAnalysisRepository,
53
+ this.cacheService,
54
+ )
55
+ private readonly storageRepository = new FileStorageRepository(
56
+ this.storageAdapter,
57
+ )
58
+
59
+ private readonly commitAnalysisService = new CommitAnalysisService(
60
+ this.commitRepository,
61
+ this.analysisRepository,
62
+ )
63
+ private readonly reportGenerationService = new ReportGenerationService()
64
+ private readonly dateFormattingService = new DateFormattingService()
65
+
66
+ private readonly analyzeCommitsUseCase = new AnalyzeCommitsUseCase(
67
+ this.commitAnalysisService,
68
+ this.progressTracker,
69
+ this.storageRepository,
70
+ )
71
+
72
+ private readonly generateReportUseCase = new GenerateReportUseCase(
73
+ this.reportGenerationService,
74
+ this.storageRepository,
75
+ this.llmAdapter,
76
+ this.dateFormattingService,
77
+ )
78
+
79
+ private readonly resumeAnalysisUseCase = new ResumeAnalysisUseCase(
80
+ this.progressTracker,
81
+ this.analyzeCommitsUseCase,
82
+ )
83
+
84
+ private readonly analyzeCommand = new AnalyzeCommand(
85
+ this.analyzeCommitsUseCase,
86
+ this.commitAnalysisService,
87
+ this.commitRepository,
88
+ )
89
+ private readonly reportCommand = new ReportCommand(this.generateReportUseCase)
90
+ private readonly resumeCommand = new ResumeCommand(this.resumeAnalysisUseCase)
91
+
92
+ private readonly controller = new CommitAnalysisController(
93
+ this.analyzeCommand,
94
+ this.reportCommand,
95
+ this.resumeCommand,
96
+ this.progressTracker,
97
+ this.cacheService,
98
+ this.commitRepository,
99
+ )
100
+
101
+ private readonly application = new CLIApplication(this.controller)
102
+
103
+ constructor(private readonly options?: DIContainerOptions) {}
104
+
105
+ getApplication(): CLIApplication {
106
+ return this.application
107
+ }
108
+ }
package/src/main.ts ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env bun
2
+
3
+ import { ApplicationError } from "@domain/application-error"
4
+
5
+ import { ConsoleFormatter } from "@presentation/console-formatter"
6
+
7
+ import { DIContainer } from "./di"
8
+
9
+ async function main(): Promise<void> {
10
+ try {
11
+ // Extract options from command line args before creating container
12
+ const llmOption = extractLLMOption(process.argv)
13
+ const noCacheOption = extractNoCacheOption(process.argv)
14
+ const container = new DIContainer({
15
+ llm: llmOption,
16
+ noCache: noCacheOption
17
+ })
18
+ const app = container.getApplication()
19
+
20
+ await app.run(process.argv)
21
+ } catch (error) {
22
+ if (error instanceof ApplicationError) {
23
+ ConsoleFormatter.logError(`[${error.code}]: ${error.message}`)
24
+ process.exit(1)
25
+ }
26
+
27
+ if (error instanceof Error) {
28
+ ConsoleFormatter.logError(`Unexpected error: ${error.message}`)
29
+ process.exit(1)
30
+ }
31
+
32
+ ConsoleFormatter.logError("Unknown error occurred")
33
+ process.exit(1)
34
+ }
35
+ }
36
+
37
+ /**
38
+ * Extract the --llm option from command line arguments
39
+ * This is needed before creating the DI container
40
+ */
41
+ function extractLLMOption(args: string[]): string | undefined {
42
+ const llmIndex = args.findIndex(arg => arg === '--llm')
43
+ if (llmIndex !== -1 && llmIndex + 1 < args.length) {
44
+ return args[llmIndex + 1]
45
+ }
46
+ return undefined
47
+ }
48
+
49
+ /**
50
+ * Extract the --no-cache option from command line arguments
51
+ * This is needed before creating the DI container
52
+ */
53
+ function extractNoCacheOption(args: string[]): boolean {
54
+ return args.includes('--no-cache')
55
+ }
56
+
57
+ // Run the application if this file is executed directly
58
+ if (require.main === module) {
59
+ main().catch((error) => {
60
+ ConsoleFormatter.logError(`Failed to bootstrap application: ${error}`)
61
+ process.exit(1)
62
+ })
63
+ }
@@ -0,0 +1,36 @@
1
+ import path from "path"
2
+
3
+ /**
4
+ * Utility for managing application data directory paths
5
+ */
6
+ export class AppPaths {
7
+ private static readonly APP_DATA_DIR = ".commit-analyzer"
8
+
9
+ /**
10
+ * Get the application data directory path
11
+ */
12
+ static getAppDataDir(baseDir: string = process.cwd()): string {
13
+ return path.join(baseDir, AppPaths.APP_DATA_DIR)
14
+ }
15
+
16
+ /**
17
+ * Get the cache directory path
18
+ */
19
+ static getCacheDir(baseDir: string = process.cwd()): string {
20
+ return path.join(AppPaths.getAppDataDir(baseDir), "cache")
21
+ }
22
+
23
+ /**
24
+ * Get the progress file path
25
+ */
26
+ static getProgressFilePath(baseDir: string = process.cwd()): string {
27
+ return path.join(AppPaths.getAppDataDir(baseDir), "progress.json")
28
+ }
29
+
30
+ /**
31
+ * Get any file path within the app data directory
32
+ */
33
+ static getAppDataFilePath(fileName: string, baseDir: string = process.cwd()): string {
34
+ return path.join(AppPaths.getAppDataDir(baseDir), fileName)
35
+ }
36
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Utility for managing concurrent operations with semaphore-like behavior
3
+ */
4
+ export class ConcurrencyManager {
5
+ private running = 0
6
+ private queue: (() => void)[] = []
7
+
8
+ constructor(private readonly maxConcurrency: number) {}
9
+
10
+ /**
11
+ * Execute a function with concurrency control
12
+ */
13
+ async execute<T>(fn: () => Promise<T>): Promise<T> {
14
+ return new Promise((resolve, reject) => {
15
+ const runTask = async () => {
16
+ this.running++
17
+ try {
18
+ const result = await fn()
19
+ resolve(result)
20
+ } catch (error) {
21
+ reject(error)
22
+ } finally {
23
+ this.running--
24
+ this.processQueue()
25
+ }
26
+ }
27
+
28
+ if (this.running < this.maxConcurrency) {
29
+ runTask()
30
+ } else {
31
+ this.queue.push(runTask)
32
+ }
33
+ })
34
+ }
35
+
36
+ private processQueue(): void {
37
+ if (this.queue.length > 0 && this.running < this.maxConcurrency) {
38
+ const nextTask = this.queue.shift()!
39
+ nextTask()
40
+ }
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Process items in parallel with controlled concurrency
46
+ */
47
+ export async function processInParallel<T, R>(
48
+ items: T[],
49
+ processor: (item: T, index: number) => Promise<R>,
50
+ maxConcurrency: number = 5
51
+ ): Promise<R[]> {
52
+ const manager = new ConcurrencyManager(maxConcurrency)
53
+ const promises = items.map((item, index) =>
54
+ manager.execute(() => processor(item, index))
55
+ )
56
+ return Promise.all(promises)
57
+ }
58
+
59
+ /**
60
+ * Process items in batches with parallel processing within each batch
61
+ */
62
+ export async function processInBatches<T, R>(
63
+ items: T[],
64
+ processor: (item: T, index: number) => Promise<R>,
65
+ batchSize: number = 10,
66
+ maxConcurrencyPerBatch: number = 5
67
+ ): Promise<R[]> {
68
+ const results: R[] = []
69
+
70
+ for (let i = 0; i < items.length; i += batchSize) {
71
+ const batch = items.slice(i, i + batchSize)
72
+ const batchResults = await processInParallel(
73
+ batch,
74
+ (item, batchIndex) => processor(item, i + batchIndex),
75
+ maxConcurrencyPerBatch
76
+ )
77
+ results.push(...batchResults)
78
+ }
79
+
80
+ return results
81
+ }
package/src/utils.ts ADDED
@@ -0,0 +1,77 @@
1
+ import readline from "readline"
2
+
3
+ /**
4
+ * Common utility functions used across the application
5
+ */
6
+
7
+ /**
8
+ * Extracts error message from unknown error type
9
+ */
10
+ export function getErrorMessage(error: unknown): string {
11
+ return error instanceof Error ? error.message : "Unknown error"
12
+ }
13
+
14
+ /**
15
+ * Creates a promise-based readline interface for user input
16
+ */
17
+ export function createPromiseReadline(): {
18
+ question: (prompt: string) => Promise<string>
19
+ close: () => void
20
+ } {
21
+ const rl = readline.createInterface({
22
+ input: process.stdin,
23
+ output: process.stdout,
24
+ })
25
+
26
+ return {
27
+ question: (prompt: string): Promise<string> => {
28
+ return new Promise((resolve) => {
29
+ rl.question(prompt, (answer: string) => {
30
+ resolve(answer.trim())
31
+ })
32
+ })
33
+ },
34
+ close: () => rl.close(),
35
+ }
36
+ }
37
+
38
+ /**
39
+ * Sleep utility for async delays
40
+ */
41
+ export function sleep(ms: number): Promise<void> {
42
+ return new Promise((resolve) => setTimeout(resolve, ms))
43
+ }
44
+
45
+ /**
46
+ * Formats file size in human readable format
47
+ */
48
+ export function formatFileSize(bytes: number): string {
49
+ const units = ["B", "KB", "MB", "GB"]
50
+ let size = bytes
51
+ let unitIndex = 0
52
+
53
+ while (size >= 1024 && unitIndex < units.length - 1) {
54
+ size /= 1024
55
+ unitIndex++
56
+ }
57
+
58
+ return `${size.toFixed(1)} ${units[unitIndex]}`
59
+ }
60
+
61
+ /**
62
+ * Truncates text to specified length with ellipsis
63
+ */
64
+ export function truncateText(text: string, maxLength: number): string {
65
+ if (text.length <= maxLength) {
66
+ return text
67
+ }
68
+ return text.substring(0, maxLength - 3) + "..."
69
+ }
70
+
71
+ /**
72
+ * Calculates percentage with proper rounding
73
+ */
74
+ export function calculatePercentage(part: number, total: number): number {
75
+ if (total === 0) return 0
76
+ return Math.round((part / total) * 100)
77
+ }
package/tsconfig.json CHANGED
@@ -12,7 +12,13 @@
12
12
  "resolveJsonModule": true,
13
13
  "declaration": true,
14
14
  "declarationMap": true,
15
- "sourceMap": true
15
+ "sourceMap": true,
16
+ "paths": {
17
+ "@domain/*": ["./src/1.domain/*"],
18
+ "@app/*": ["./src/2.application/*"],
19
+ "@presentation/*": ["./src/3.presentation/*"],
20
+ "@infra/*": ["./src/4.infrastructure/*"]
21
+ }
16
22
  },
17
23
  "include": ["src/**/*"],
18
24
  "exclude": ["node_modules", "dist"]
package/src/cli.ts DELETED
@@ -1,170 +0,0 @@
1
- import { readFileSync, mkdirSync } from "fs"
2
- import { Command } from "commander"
3
- import { join, dirname } from "path"
4
-
5
- export interface CLIOptions {
6
- output?: string
7
- outputDir?: string
8
- file?: string
9
- commits: string[]
10
- author?: string
11
- limit?: number
12
- useDefaults: boolean
13
- resume?: boolean
14
- clear?: boolean
15
- model?: string
16
- report?: boolean
17
- inputCsv?: string
18
- verbose?: boolean
19
- }
20
-
21
- export class CLIService {
22
- static parseArguments(): CLIOptions {
23
- const program = new Command()
24
-
25
- program
26
- .name("commit-analyzer")
27
- .description(
28
- "Analyze user authored git commits and generate rich commit descriptions and stakeholder reports from them.",
29
- )
30
- .version("1.0.2")
31
- .option("-o, --output <file>", "Output CSV file (default: commits.csv)")
32
- .option(
33
- "--output-dir <dir>",
34
- "Output directory for CSV and report files (default: current directory)",
35
- )
36
- .option(
37
- "-f, --file <file>",
38
- "Read commit hashes from file (one per line)",
39
- )
40
- .option(
41
- "-a, --author <email>",
42
- "Filter commits by author email (defaults to current user)",
43
- )
44
- .option(
45
- "-l, --limit <number>",
46
- "Limit number of commits to analyze",
47
- parseInt,
48
- )
49
- .option("-r, --resume", "Resume from last checkpoint if available")
50
- .option("-c, --clear", "Clear any existing progress checkpoint")
51
- .option("-m, --model <model>", "LLM model to use (claude, gemini, codex)")
52
- .option(
53
- "--report",
54
- "Generate condensed markdown report from existing CSV",
55
- )
56
- .option(
57
- "--input-csv <file>",
58
- "Input CSV file to read for report generation",
59
- )
60
- .option(
61
- "-v, --verbose",
62
- "Enable verbose logging (shows detailed error information)",
63
- )
64
- .argument(
65
- "[commits...]",
66
- "Commit hashes to analyze (if none provided, uses current user's commits)",
67
- )
68
- .parse()
69
-
70
- const options = program.opts()
71
- const args = program.args
72
-
73
- let commits: string[] = []
74
- let useDefaults = false
75
-
76
- if (options.file) {
77
- commits = this.readCommitsFromFile(options.file)
78
- } else if (args.length > 0) {
79
- commits = args
80
- } else {
81
- useDefaults = true
82
- }
83
-
84
- return {
85
- output:
86
- options.output ||
87
- CLIService.resolveOutputPath("commits.csv", options.outputDir),
88
- outputDir: options.outputDir,
89
- file: options.file,
90
- commits,
91
- author: options.author,
92
- limit: options.limit,
93
- useDefaults,
94
- resume: options.resume,
95
- clear: options.clear,
96
- model: options.model,
97
- report: options.report,
98
- inputCsv: options.inputCsv,
99
- verbose: options.verbose,
100
- }
101
- }
102
-
103
- private static readCommitsFromFile(filename: string): string[] {
104
- try {
105
- const content = readFileSync(filename, "utf8")
106
- return content
107
- .split("\n")
108
- .map((line) => line.trim())
109
- .filter((line) => line.length > 0)
110
- } catch (error) {
111
- throw new Error(
112
- `Failed to read commits from file ${filename}: ${error instanceof Error ? error.message : "Unknown error"}`,
113
- )
114
- }
115
- }
116
-
117
- /**
118
- * Resolve the full file path with optional output directory.
119
- */
120
- static resolveOutputPath(filename: string, outputDir?: string): string {
121
- if (outputDir) {
122
- // Ensure output directory exists
123
- try {
124
- mkdirSync(outputDir, { recursive: true })
125
- } catch (error) {
126
- throw new Error(
127
- `Failed to create output directory ${outputDir}: ${error instanceof Error ? error.message : "Unknown error"}`,
128
- )
129
- }
130
- return join(outputDir, filename)
131
- }
132
- return filename
133
- }
134
-
135
- static showHelp(): void {
136
- console.log(`
137
- Usage: commit-analyzer [options] [commits...]
138
-
139
- Analyze git commits and generate categorized summaries using LLM.
140
- If no commits are specified, analyzes all commits authored by the current user.
141
-
142
- Options:
143
- -o, --output <file> Output file (default: commits.csv for analysis, report.md for reports)
144
- --output-dir <dir> Output directory for CSV and report files (default: current directory)
145
- -f, --file <file> Read commit hashes from file (one per line)
146
- -a, --author <email> Filter commits by author email (defaults to current user)
147
- -l, --limit <number> Limit number of commits to analyze
148
- -r, --resume Resume from last checkpoint if available
149
- -c, --clear Clear any existing progress checkpoint
150
- --report Generate condensed markdown report from existing CSV
151
- --input-csv <file> Input CSV file to read for report generation
152
- -v, --verbose Enable verbose logging (shows detailed error information)
153
- -h, --help Display help for command
154
- -V, --version Display version number
155
-
156
- Examples:
157
- commit-analyzer # Analyze your authored commits
158
- commit-analyzer --limit 10 # Analyze your last 10 commits
159
- commit-analyzer --author user@example.com # Analyze specific user's commits
160
- commit-analyzer abc123 def456 ghi789 # Analyze specific commits
161
- commit-analyzer --file commits.txt # Read commits from file
162
- commit-analyzer --output analysis.csv --limit 20 # Analyze last 20 commits to custom file
163
- commit-analyzer --resume # Resume from last checkpoint
164
- commit-analyzer --clear # Clear checkpoint and start fresh
165
- commit-analyzer --report # Analyze commits, generate CSV, then generate report
166
- commit-analyzer --input-csv data.csv --report # Skip analysis, generate report from existing CSV
167
- commit-analyzer --report -o custom-report.md # Analyze commits, generate CSV, then generate custom report
168
- `)
169
- }
170
- }