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.
- package/.claude/settings.local.json +11 -1
- package/README.md +33 -2
- package/commits.csv +2 -0
- package/eslint.config.mts +45 -0
- package/package.json +17 -9
- package/src/1.domain/analysis.ts +93 -0
- package/src/1.domain/analyzed-commit.ts +97 -0
- package/src/1.domain/application-error.ts +32 -0
- package/src/1.domain/category.ts +52 -0
- package/src/1.domain/commit-analysis-service.ts +92 -0
- package/src/1.domain/commit-hash.ts +40 -0
- package/src/1.domain/commit.ts +99 -0
- package/src/1.domain/date-formatting-service.ts +81 -0
- package/src/1.domain/date-range.ts +76 -0
- package/src/1.domain/report-generation-service.ts +292 -0
- package/src/2.application/analyze-commits.usecase.ts +307 -0
- package/src/2.application/generate-report.usecase.ts +204 -0
- package/src/2.application/llm-service.ts +54 -0
- package/src/2.application/resume-analysis.usecase.ts +123 -0
- package/src/3.presentation/analysis-repository.interface.ts +27 -0
- package/src/3.presentation/analyze-command.ts +128 -0
- package/src/3.presentation/cli-application.ts +255 -0
- package/src/3.presentation/command-handler.interface.ts +4 -0
- package/src/3.presentation/commit-analysis-controller.ts +101 -0
- package/src/3.presentation/commit-repository.interface.ts +47 -0
- package/src/3.presentation/console-formatter.ts +129 -0
- package/src/3.presentation/progress-repository.interface.ts +49 -0
- package/src/3.presentation/report-command.ts +50 -0
- package/src/3.presentation/resume-command.ts +59 -0
- package/src/3.presentation/storage-repository.interface.ts +33 -0
- package/src/3.presentation/storage-service.interface.ts +32 -0
- package/src/3.presentation/version-control-service.interface.ts +41 -0
- package/src/4.infrastructure/cache-service.ts +271 -0
- package/src/4.infrastructure/cached-analysis-repository.ts +46 -0
- package/src/4.infrastructure/claude-llm-adapter.ts +124 -0
- package/src/4.infrastructure/csv-service.ts +206 -0
- package/src/4.infrastructure/file-storage-repository.ts +108 -0
- package/src/4.infrastructure/file-system-storage-adapter.ts +87 -0
- package/src/4.infrastructure/gemini-llm-adapter.ts +46 -0
- package/src/4.infrastructure/git-adapter.ts +116 -0
- package/src/4.infrastructure/git-commit-repository.ts +85 -0
- package/src/4.infrastructure/json-progress-tracker.ts +182 -0
- package/src/4.infrastructure/llm-adapter-factory.ts +26 -0
- package/src/4.infrastructure/llm-adapter.ts +455 -0
- package/src/4.infrastructure/llm-analysis-repository.ts +38 -0
- package/src/4.infrastructure/openai-llm-adapter.ts +57 -0
- package/src/di.ts +108 -0
- package/src/main.ts +63 -0
- package/src/utils/app-paths.ts +36 -0
- package/src/utils/concurrency.ts +81 -0
- package/src/utils.ts +77 -0
- package/tsconfig.json +7 -1
- package/src/cli.ts +0 -170
- package/src/csv-reader.ts +0 -180
- package/src/csv.ts +0 -40
- package/src/errors.ts +0 -49
- package/src/git.ts +0 -112
- package/src/index.ts +0 -395
- package/src/llm.ts +0 -396
- package/src/progress.ts +0 -84
- package/src/report-generator.ts +0 -286
- package/src/types.ts +0 -24
package/src/llm.ts
DELETED
|
@@ -1,396 +0,0 @@
|
|
|
1
|
-
import { execSync } from "child_process"
|
|
2
|
-
import { CommitInfo, LLMAnalysis } from "./types"
|
|
3
|
-
|
|
4
|
-
export class LLMService {
|
|
5
|
-
private static model: string
|
|
6
|
-
private static verbose: boolean = false
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Detect available LLM models by checking CLI commands.
|
|
10
|
-
*/
|
|
11
|
-
static detectAvailableModels(): string[] {
|
|
12
|
-
const models = ["claude", "gemini", "codex"]
|
|
13
|
-
return models.filter((model) => {
|
|
14
|
-
try {
|
|
15
|
-
execSync(`command -v ${model}`, { stdio: "ignore" })
|
|
16
|
-
return true
|
|
17
|
-
} catch {
|
|
18
|
-
return false
|
|
19
|
-
}
|
|
20
|
-
})
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Determine default LLM model based on availability.
|
|
25
|
-
*/
|
|
26
|
-
static detectDefaultModel(): string {
|
|
27
|
-
const available = this.detectAvailableModels()
|
|
28
|
-
if (available.length === 0) {
|
|
29
|
-
throw new Error(
|
|
30
|
-
"No supported LLM models found. Please install claude, gemini, or codex.",
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
// Default to sonnet if claude is available
|
|
34
|
-
if (available.includes('claude')) {
|
|
35
|
-
return 'claude --model sonnet'
|
|
36
|
-
}
|
|
37
|
-
return available[0]
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Set the LLM model command to use.
|
|
42
|
-
*/
|
|
43
|
-
static setModel(model: string): void {
|
|
44
|
-
this.model = model
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* Set verbose mode for detailed error logging.
|
|
49
|
-
*/
|
|
50
|
-
static setVerbose(verbose: boolean): void {
|
|
51
|
-
this.verbose = verbose
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Get the configured LLM model or detect default.
|
|
56
|
-
*/
|
|
57
|
-
static getModel(): string {
|
|
58
|
-
if (!this.model) {
|
|
59
|
-
this.model = this.detectDefaultModel()
|
|
60
|
-
}
|
|
61
|
-
return this.model
|
|
62
|
-
}
|
|
63
|
-
private static readonly MAX_RETRIES = parseInt(
|
|
64
|
-
process.env.LLM_MAX_RETRIES || "3",
|
|
65
|
-
10,
|
|
66
|
-
)
|
|
67
|
-
private static readonly INITIAL_RETRY_DELAY = parseInt(
|
|
68
|
-
process.env.LLM_INITIAL_RETRY_DELAY || "5000",
|
|
69
|
-
10,
|
|
70
|
-
)
|
|
71
|
-
private static readonly MAX_RETRY_DELAY = parseInt(
|
|
72
|
-
process.env.LLM_MAX_RETRY_DELAY || "30000",
|
|
73
|
-
10,
|
|
74
|
-
)
|
|
75
|
-
private static readonly RETRY_MULTIPLIER = parseFloat(
|
|
76
|
-
process.env.LLM_RETRY_MULTIPLIER || "2",
|
|
77
|
-
)
|
|
78
|
-
// Claude-specific configuration with backward compatibility
|
|
79
|
-
private static readonly CLAUDE_MAX_PROMPT_LENGTH = parseInt(
|
|
80
|
-
process.env.CLAUDE_MAX_PROMPT_LENGTH || process.env.LLM_MAX_PROMPT_LENGTH || "100000",
|
|
81
|
-
10,
|
|
82
|
-
)
|
|
83
|
-
private static readonly CLAUDE_MAX_DIFF_LENGTH = parseInt(
|
|
84
|
-
process.env.CLAUDE_MAX_DIFF_LENGTH || process.env.LLM_MAX_DIFF_LENGTH || "80000",
|
|
85
|
-
10,
|
|
86
|
-
)
|
|
87
|
-
|
|
88
|
-
static async analyzeCommit(commit: CommitInfo): Promise<LLMAnalysis> {
|
|
89
|
-
const currentModel = this.getModel()
|
|
90
|
-
const prompt = this.buildPrompt(commit.message, commit.diff, currentModel)
|
|
91
|
-
|
|
92
|
-
// Log prompt length for debugging - only for Claude models
|
|
93
|
-
if (this.isClaudeModel(currentModel)) {
|
|
94
|
-
console.log(` - Prompt length: ${prompt.length} characters`)
|
|
95
|
-
if (prompt.length > this.CLAUDE_MAX_PROMPT_LENGTH) {
|
|
96
|
-
console.log(` - Warning: Prompt exceeds Claude max length (${this.CLAUDE_MAX_PROMPT_LENGTH})`)
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
let lastError: Error | null = null
|
|
101
|
-
|
|
102
|
-
for (let attempt = 1; attempt <= this.MAX_RETRIES; attempt++) {
|
|
103
|
-
try {
|
|
104
|
-
const output = execSync(currentModel, {
|
|
105
|
-
input: prompt,
|
|
106
|
-
encoding: "utf8",
|
|
107
|
-
stdio: ["pipe", "pipe", "pipe"],
|
|
108
|
-
timeout: 60000,
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
return this.parseResponse(output)
|
|
112
|
-
} catch (error) {
|
|
113
|
-
lastError = error instanceof Error ? error : new Error("Unknown error")
|
|
114
|
-
|
|
115
|
-
// Check if this is a rate limit error
|
|
116
|
-
const rateLimitInfo = this.isRateLimitError(error)
|
|
117
|
-
|
|
118
|
-
if (rateLimitInfo.isRateLimit) {
|
|
119
|
-
// For rate limits, show user-friendly message immediately
|
|
120
|
-
const friendlyMessage = this.getRateLimitMessage(rateLimitInfo.service, rateLimitInfo.limitType)
|
|
121
|
-
console.log(` - ${friendlyMessage}`)
|
|
122
|
-
|
|
123
|
-
// Show detailed error info only in verbose mode
|
|
124
|
-
if (this.verbose) {
|
|
125
|
-
console.log(` - Verbose error details for commit ${commit.hash.substring(0, 8)}:`)
|
|
126
|
-
console.log(` Command: ${currentModel}`)
|
|
127
|
-
console.log(` Error message: ${lastError.message}`)
|
|
128
|
-
if (this.isClaudeModel(currentModel)) {
|
|
129
|
-
console.log(` Prompt length: ${prompt.length} characters`)
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// If it's an exec error, log additional details
|
|
133
|
-
if (error && typeof error === 'object' && 'stderr' in error) {
|
|
134
|
-
const execError = error as any
|
|
135
|
-
console.log(` Exit code: ${execError.status || 'unknown'}`)
|
|
136
|
-
console.log(` Signal: ${execError.signal || 'none'}`)
|
|
137
|
-
if (execError.stderr) {
|
|
138
|
-
console.log(` Stderr: ${execError.stderr.substring(0, 1000)}${execError.stderr.length > 1000 ? '...' : ''}`)
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
// If it's a daily quota error, don't retry - fail immediately
|
|
144
|
-
if (rateLimitInfo.limitType === "daily quota") {
|
|
145
|
-
throw new Error(
|
|
146
|
-
`Daily quota exceeded for ${rateLimitInfo.service || 'LLM service'}. Retrying will not help until quota resets.`,
|
|
147
|
-
)
|
|
148
|
-
}
|
|
149
|
-
} else {
|
|
150
|
-
// For non-rate-limit errors, show detailed info based on verbose mode
|
|
151
|
-
if (this.verbose) {
|
|
152
|
-
console.log(` - Error details for commit ${commit.hash.substring(0, 8)}:`)
|
|
153
|
-
console.log(` Command: ${currentModel}`)
|
|
154
|
-
console.log(` Error message: ${lastError.message}`)
|
|
155
|
-
if (this.isClaudeModel(currentModel)) {
|
|
156
|
-
console.log(` Prompt length: ${prompt.length} characters`)
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
// If it's an exec error, log additional details
|
|
160
|
-
if (error && typeof error === 'object' && 'stderr' in error) {
|
|
161
|
-
const execError = error as any
|
|
162
|
-
console.log(` Exit code: ${execError.status || 'unknown'}`)
|
|
163
|
-
console.log(` Signal: ${execError.signal || 'none'}`)
|
|
164
|
-
console.log(` Stderr: ${execError.stderr || 'none'}`)
|
|
165
|
-
console.log(` Stdout: ${execError.stdout || 'none'}`)
|
|
166
|
-
}
|
|
167
|
-
} else {
|
|
168
|
-
// In non-verbose mode, show concise error message
|
|
169
|
-
console.log(` - Analysis failed: ${lastError.message}`)
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
if (attempt < this.MAX_RETRIES) {
|
|
174
|
-
const delay = Math.min(
|
|
175
|
-
this.INITIAL_RETRY_DELAY *
|
|
176
|
-
Math.pow(this.RETRY_MULTIPLIER, attempt - 1),
|
|
177
|
-
this.MAX_RETRY_DELAY,
|
|
178
|
-
)
|
|
179
|
-
|
|
180
|
-
console.log(
|
|
181
|
-
` - Attempt ${attempt}/${this.MAX_RETRIES} failed for commit ${commit.hash.substring(0, 8)}. Retrying in ${delay / 1000}s...`,
|
|
182
|
-
)
|
|
183
|
-
|
|
184
|
-
await this.sleep(delay)
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
throw new Error(
|
|
190
|
-
`Failed to analyze commit ${commit.hash} after ${this.MAX_RETRIES} attempts: ${lastError?.message || "Unknown error"}`,
|
|
191
|
-
)
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
private static sleep(ms: number): Promise<void> {
|
|
195
|
-
return new Promise((resolve) => setTimeout(resolve, ms))
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
static getMaxRetries(): number {
|
|
200
|
-
return this.MAX_RETRIES
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
/**
|
|
204
|
-
* Check if the current model is Claude-based
|
|
205
|
-
*/
|
|
206
|
-
private static isClaudeModel(model?: string): boolean {
|
|
207
|
-
const currentModel = model || this.getModel()
|
|
208
|
-
return currentModel.toLowerCase().includes('claude')
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
private static buildPrompt(commitMessage: string, diff: string, model: string): string {
|
|
212
|
-
// Only truncate for Claude models
|
|
213
|
-
let truncatedDiff = diff
|
|
214
|
-
let diffTruncated = false
|
|
215
|
-
|
|
216
|
-
if (this.isClaudeModel(model) && diff.length > this.CLAUDE_MAX_DIFF_LENGTH) {
|
|
217
|
-
truncatedDiff = diff.substring(0, this.CLAUDE_MAX_DIFF_LENGTH) + "\n\n[DIFF TRUNCATED - Original length: " + diff.length + " characters]"
|
|
218
|
-
diffTruncated = true
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const basePrompt = `Analyze this git commit and provide a categorization:
|
|
222
|
-
|
|
223
|
-
COMMIT MESSAGE:
|
|
224
|
-
${commitMessage}
|
|
225
|
-
|
|
226
|
-
COMMIT DIFF:
|
|
227
|
-
${truncatedDiff}
|
|
228
|
-
|
|
229
|
-
Based on the commit message and code changes, categorize this commit as one of:
|
|
230
|
-
- "tweak": Minor adjustments, bug fixes, small improvements
|
|
231
|
-
- "feature": New functionality, major additions
|
|
232
|
-
- "process": Build system, CI/CD, tooling, configuration changes
|
|
233
|
-
|
|
234
|
-
Provide:
|
|
235
|
-
1. Category: [tweak|feature|process]
|
|
236
|
-
2. Summary: One-line description (max 80 chars)
|
|
237
|
-
3. Description: Detailed explanation (2-3 sentences)
|
|
238
|
-
|
|
239
|
-
Format as JSON:
|
|
240
|
-
\`\`\`json
|
|
241
|
-
{
|
|
242
|
-
"category": "...",
|
|
243
|
-
"summary": "...",
|
|
244
|
-
"description": "..."
|
|
245
|
-
}
|
|
246
|
-
\`\`\``
|
|
247
|
-
|
|
248
|
-
// Final length check - only for Claude models
|
|
249
|
-
if (this.isClaudeModel(model) && basePrompt.length > this.CLAUDE_MAX_PROMPT_LENGTH) {
|
|
250
|
-
// Further truncate the diff if needed
|
|
251
|
-
const overhead = basePrompt.length - this.CLAUDE_MAX_PROMPT_LENGTH
|
|
252
|
-
const newDiffLength = Math.max(1000, this.CLAUDE_MAX_DIFF_LENGTH - overhead - 200) // Keep at least 1000 chars, subtract extra for safety
|
|
253
|
-
truncatedDiff = diff.substring(0, newDiffLength) + "\n\n[DIFF HEAVILY TRUNCATED - Original length: " + diff.length + " characters]"
|
|
254
|
-
|
|
255
|
-
return `Analyze this git commit and provide a categorization:
|
|
256
|
-
|
|
257
|
-
COMMIT MESSAGE:
|
|
258
|
-
${commitMessage}
|
|
259
|
-
|
|
260
|
-
COMMIT DIFF:
|
|
261
|
-
${truncatedDiff}
|
|
262
|
-
|
|
263
|
-
Based on the commit message and code changes, categorize this commit as one of:
|
|
264
|
-
- "tweak": Minor adjustments, bug fixes, small improvements
|
|
265
|
-
- "feature": New functionality, major additions
|
|
266
|
-
- "process": Build system, CI/CD, tooling, configuration changes
|
|
267
|
-
|
|
268
|
-
Provide:
|
|
269
|
-
1. Category: [tweak|feature|process]
|
|
270
|
-
2. Summary: One-line description (max 80 chars)
|
|
271
|
-
3. Description: Detailed explanation (2-3 sentences)
|
|
272
|
-
|
|
273
|
-
Format as JSON:
|
|
274
|
-
\`\`\`json
|
|
275
|
-
{
|
|
276
|
-
"category": "...",
|
|
277
|
-
"summary": "...",
|
|
278
|
-
"description": "..."
|
|
279
|
-
}
|
|
280
|
-
\`\`\``
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
return basePrompt
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
private static parseResponse(response: string): LLMAnalysis {
|
|
287
|
-
try {
|
|
288
|
-
const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/)
|
|
289
|
-
if (!jsonMatch) {
|
|
290
|
-
throw new Error("No JSON block found in response")
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
const parsed = JSON.parse(jsonMatch[1])
|
|
294
|
-
|
|
295
|
-
if (!this.isValidCategory(parsed.category)) {
|
|
296
|
-
throw new Error(`Invalid category: ${parsed.category}`)
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
if (!parsed.summary || !parsed.description) {
|
|
300
|
-
throw new Error("Missing required fields in response")
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
return {
|
|
304
|
-
category: parsed.category,
|
|
305
|
-
summary: parsed.summary.substring(0, 80),
|
|
306
|
-
description: parsed.description,
|
|
307
|
-
}
|
|
308
|
-
} catch (error) {
|
|
309
|
-
// Log the raw response for debugging
|
|
310
|
-
console.log(` - Raw LLM response (first 1000 chars): ${response.substring(0, 1000)}`)
|
|
311
|
-
if (response.length > 1000) {
|
|
312
|
-
console.log(` - Response truncated (total length: ${response.length} chars)`)
|
|
313
|
-
}
|
|
314
|
-
|
|
315
|
-
// Try to extract and log the JSON block if it exists but is malformed
|
|
316
|
-
const jsonMatch = response.match(/```json\s*([\s\S]*?)\s*```/)
|
|
317
|
-
if (jsonMatch) {
|
|
318
|
-
console.log(` - Extracted JSON block: ${jsonMatch[1]}`)
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
throw new Error(
|
|
322
|
-
`Failed to parse LLM response: ${error instanceof Error ? error.message : "Unknown error"}`,
|
|
323
|
-
)
|
|
324
|
-
}
|
|
325
|
-
}
|
|
326
|
-
|
|
327
|
-
private static isValidCategory(
|
|
328
|
-
category: string,
|
|
329
|
-
): category is "tweak" | "feature" | "process" {
|
|
330
|
-
return ["tweak", "feature", "process"].includes(category)
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
/**
|
|
334
|
-
* Check if an error is related to rate limiting or quota exceeded.
|
|
335
|
-
*/
|
|
336
|
-
private static isRateLimitError(error: any): { isRateLimit: boolean; service?: string; limitType?: string } {
|
|
337
|
-
const errorMessage = error?.message?.toLowerCase() || ""
|
|
338
|
-
const stderr = error?.stderr?.toLowerCase() || ""
|
|
339
|
-
const stdout = error?.stdout?.toLowerCase() || ""
|
|
340
|
-
const combinedOutput = `${errorMessage} ${stderr} ${stdout}`
|
|
341
|
-
|
|
342
|
-
// Check for Gemini rate limit patterns
|
|
343
|
-
if (combinedOutput.includes("quota exceeded") && combinedOutput.includes("gemini")) {
|
|
344
|
-
if (combinedOutput.includes("requests per day")) {
|
|
345
|
-
return { isRateLimit: true, service: "Gemini", limitType: "daily quota" }
|
|
346
|
-
}
|
|
347
|
-
if (combinedOutput.includes("requests per minute")) {
|
|
348
|
-
return { isRateLimit: true, service: "Gemini", limitType: "per-minute rate limit" }
|
|
349
|
-
}
|
|
350
|
-
return { isRateLimit: true, service: "Gemini", limitType: "quota limit" }
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Check for Claude rate limit patterns
|
|
354
|
-
if (combinedOutput.includes("rate limit") && combinedOutput.includes("claude")) {
|
|
355
|
-
return { isRateLimit: true, service: "Claude", limitType: "rate limit" }
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// Check for generic rate limit indicators
|
|
359
|
-
if (combinedOutput.includes("429") ||
|
|
360
|
-
combinedOutput.includes("too many requests") ||
|
|
361
|
-
combinedOutput.includes("rate limit") ||
|
|
362
|
-
combinedOutput.includes("quota exceeded")) {
|
|
363
|
-
// Try to determine service from model name
|
|
364
|
-
const currentModel = this.getModel().toLowerCase()
|
|
365
|
-
if (currentModel.includes("gemini")) {
|
|
366
|
-
return { isRateLimit: true, service: "Gemini", limitType: "rate/quota limit" }
|
|
367
|
-
}
|
|
368
|
-
if (currentModel.includes("claude")) {
|
|
369
|
-
return { isRateLimit: true, service: "Claude", limitType: "rate limit" }
|
|
370
|
-
}
|
|
371
|
-
return { isRateLimit: true, limitType: "rate/quota limit" }
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
return { isRateLimit: false }
|
|
375
|
-
}
|
|
376
|
-
|
|
377
|
-
/**
|
|
378
|
-
* Get user-friendly error message for rate limit errors.
|
|
379
|
-
*/
|
|
380
|
-
private static getRateLimitMessage(service?: string, limitType?: string): string {
|
|
381
|
-
if (service === "Gemini" && limitType === "daily quota") {
|
|
382
|
-
return "⚠️ Gemini daily quota exceeded. The limit resets at midnight Pacific Time. Consider switching to a different model or resuming tomorrow."
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
if (service === "Gemini" && limitType === "per-minute rate limit") {
|
|
386
|
-
return "⚠️ Gemini rate limit exceeded. Wait a minute before retrying, or consider switching to a different model."
|
|
387
|
-
}
|
|
388
|
-
|
|
389
|
-
if (service === "Claude") {
|
|
390
|
-
return "⚠️ Claude rate limit exceeded. Wait a moment before retrying, or consider switching to a different model."
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
const serviceMsg = service ? `${service} ` : ""
|
|
394
|
-
return `⚠️ ${serviceMsg}rate limit exceeded. Consider switching models or waiting before retrying.`
|
|
395
|
-
}
|
|
396
|
-
}
|
package/src/progress.ts
DELETED
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import { writeFileSync, readFileSync, existsSync, unlinkSync } from "fs"
|
|
2
|
-
import { AnalyzedCommit } from "./types"
|
|
3
|
-
|
|
4
|
-
interface ProgressState {
|
|
5
|
-
totalCommits: string[]
|
|
6
|
-
processedCommits: string[]
|
|
7
|
-
analyzedCommits: AnalyzedCommit[]
|
|
8
|
-
lastProcessedIndex: number
|
|
9
|
-
startTime: string
|
|
10
|
-
outputFile: string
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export class ProgressTracker {
|
|
14
|
-
private static readonly CHECKPOINT_FILE = ".commit-analyzer-progress.json"
|
|
15
|
-
|
|
16
|
-
static saveProgress(
|
|
17
|
-
totalCommits: string[],
|
|
18
|
-
processedCommits: string[],
|
|
19
|
-
analyzedCommits: AnalyzedCommit[],
|
|
20
|
-
outputFile: string,
|
|
21
|
-
): void {
|
|
22
|
-
// Preserve the original start time if this is an update to existing progress
|
|
23
|
-
let startTime = new Date().toISOString()
|
|
24
|
-
const existingState = this.loadProgress()
|
|
25
|
-
if (existingState) {
|
|
26
|
-
startTime = existingState.startTime
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
const state: ProgressState = {
|
|
30
|
-
totalCommits,
|
|
31
|
-
processedCommits,
|
|
32
|
-
analyzedCommits,
|
|
33
|
-
lastProcessedIndex: processedCommits.length - 1,
|
|
34
|
-
startTime,
|
|
35
|
-
outputFile,
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
writeFileSync(this.CHECKPOINT_FILE, JSON.stringify(state, null, 2))
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
static loadProgress(): ProgressState | null {
|
|
42
|
-
if (!existsSync(this.CHECKPOINT_FILE)) {
|
|
43
|
-
return null
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
try {
|
|
47
|
-
const content = readFileSync(this.CHECKPOINT_FILE, "utf8")
|
|
48
|
-
return JSON.parse(content)
|
|
49
|
-
} catch (error) {
|
|
50
|
-
console.error("Failed to load progress file:", error)
|
|
51
|
-
return null
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
static hasProgress(): boolean {
|
|
56
|
-
return existsSync(this.CHECKPOINT_FILE)
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
static clearProgress(): void {
|
|
60
|
-
if (existsSync(this.CHECKPOINT_FILE)) {
|
|
61
|
-
unlinkSync(this.CHECKPOINT_FILE)
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
static getRemainingCommits(state: ProgressState): string[] {
|
|
66
|
-
const processedSet = new Set(state.processedCommits)
|
|
67
|
-
return state.totalCommits.filter(hash => !processedSet.has(hash))
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
static formatProgressSummary(state: ProgressState): string {
|
|
71
|
-
const processed = state.processedCommits.length
|
|
72
|
-
const total = state.totalCommits.length
|
|
73
|
-
const remaining = total - processed
|
|
74
|
-
const percentComplete = Math.round((processed / total) * 100)
|
|
75
|
-
|
|
76
|
-
return `
|
|
77
|
-
Previous session:
|
|
78
|
-
- Started: ${new Date(state.startTime).toLocaleString()}
|
|
79
|
-
- Progress: ${processed}/${total} commits (${percentComplete}%)
|
|
80
|
-
- Remaining: ${remaining} commits
|
|
81
|
-
- Output file: ${state.outputFile}
|
|
82
|
-
`.trim()
|
|
83
|
-
}
|
|
84
|
-
}
|