Skip to content

Commit 508441f

Browse files
feat: Add compact diff summary & message generator
1 parent 6de5a2f commit 508441f

3 files changed

Lines changed: 125 additions & 19 deletions

File tree

src/commands/lazycommit.ts

Lines changed: 50 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@ import {
99
isCancel,
1010
} from '@clack/prompts';
1111
import {
12-
assertGitRepo,
13-
getStagedDiff,
14-
getDetectedMessage,
15-
getDiffSummary,
12+
assertGitRepo,
13+
getStagedDiff,
14+
getDetectedMessage,
15+
getDiffSummary,
16+
buildCompactSummary,
1617
} from '../utils/git.js';
1718
import { getConfig } from '../utils/config.js';
18-
import { generateCommitMessageFromChunks } from '../utils/groq.js';
19+
import { generateCommitMessageFromChunks, generateCommitMessageFromSummary } from '../utils/groq.js';
1920
import { KnownError, handleCliError } from '../utils/error.js';
2021

2122
export default async (
@@ -48,7 +49,7 @@ export default async (
4849

4950
// Check if diff is very large and show summary
5051
const diffSummary = await getDiffSummary(excludeFiles);
51-
const isLargeDiff = staged.diff.length > 50000; // ~12.5k tokens
52+
const isLargeDiff = staged.diff.length > 50000; // ~12.5k chars (~3k tokens)
5253

5354
if (isLargeDiff && diffSummary) {
5455
detectingFiles.stop(
@@ -75,20 +76,50 @@ export default async (
7576

7677
const s = spinner();
7778
s.start('The AI is analyzing your changes');
78-
let messages: string[];
79+
let messages: string[];
7980
try {
80-
messages = await generateCommitMessageFromChunks(
81-
config.GROQ_API_KEY,
82-
config.model,
83-
config.locale,
84-
staged.diff,
85-
config.generate,
86-
config['max-length'],
87-
config.type,
88-
config.timeout,
89-
config.proxy,
90-
config['chunk-size']
91-
);
81+
if (isLargeDiff) {
82+
const compact = await buildCompactSummary(excludeFiles, 25);
83+
if (compact) {
84+
messages = await generateCommitMessageFromSummary(
85+
config.GROQ_API_KEY,
86+
config.model,
87+
config.locale,
88+
compact,
89+
config.generate,
90+
config['max-length'],
91+
config.type,
92+
config.timeout,
93+
config.proxy
94+
);
95+
} else {
96+
messages = await generateCommitMessageFromChunks(
97+
config.GROQ_API_KEY,
98+
config.model,
99+
config.locale,
100+
staged.diff,
101+
config.generate,
102+
config['max-length'],
103+
config.type,
104+
config.timeout,
105+
config.proxy,
106+
config['chunk-size']
107+
);
108+
}
109+
} else {
110+
messages = await generateCommitMessageFromChunks(
111+
config.GROQ_API_KEY,
112+
config.model,
113+
config.locale,
114+
staged.diff,
115+
config.generate,
116+
config['max-length'],
117+
config.type,
118+
config.timeout,
119+
config.proxy,
120+
config['chunk-size']
121+
);
122+
}
92123
} finally {
93124
s.stop('Changes analyzed');
94125
}

src/utils/git.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -170,3 +170,31 @@ export const splitDiffByFile = (diff: string): string[] => {
170170
if (current.trim().length > 0) parts.push(current.trim());
171171
return parts;
172172
};
173+
174+
export const buildCompactSummary = async (
175+
excludeFiles?: string[],
176+
maxFiles: number = 20
177+
) => {
178+
const summary = await getDiffSummary(excludeFiles);
179+
if (!summary) return null;
180+
const { fileStats } = summary;
181+
const sorted = [...fileStats].sort((a, b) => b.changes - a.changes);
182+
const top = sorted.slice(0, Math.max(1, maxFiles));
183+
const totalFiles = summary.files.length;
184+
const totalChanges = summary.totalChanges;
185+
const totalAdditions = fileStats.reduce((s, f) => s + (f.additions || 0), 0);
186+
const totalDeletions = fileStats.reduce((s, f) => s + (f.deletions || 0), 0);
187+
188+
const lines: string[] = [];
189+
lines.push(`Files changed: ${totalFiles}`);
190+
lines.push(`Additions: ${totalAdditions}, Deletions: ${totalDeletions}, Total changes: ${totalChanges}`);
191+
lines.push('Top files by changes:');
192+
for (const f of top) {
193+
lines.push(`- ${f.file} (+${f.additions} / -${f.deletions}, ${f.changes} changes)`);
194+
}
195+
if (sorted.length > top.length) {
196+
lines.push(`…and ${sorted.length - top.length} more files`);
197+
}
198+
199+
return lines.join('\n');
200+
};

src/utils/groq.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,3 +339,50 @@ The message should be concise but cover the main aspects of all the changes.`;
339339

340340
return chunkMessages;
341341
};
342+
343+
export const generateCommitMessageFromSummary = async (
344+
apiKey: string,
345+
model: string,
346+
locale: string,
347+
summary: string,
348+
completions: number,
349+
maxLength: number,
350+
type: CommitType,
351+
timeout: number,
352+
proxy?: string
353+
) => {
354+
const prompt = `This is a compact summary of staged changes. Generate a single, concise commit message within ${maxLength} characters that reflects the overall intent.\n\n${summary}`;
355+
const completion = await createChatCompletion(
356+
apiKey,
357+
model,
358+
[
359+
{ role: 'system', content: generatePrompt(locale, maxLength, type) },
360+
{ role: 'user', content: prompt },
361+
],
362+
0.7,
363+
1,
364+
0,
365+
0,
366+
Math.max(200, maxLength * 8),
367+
completions,
368+
timeout,
369+
proxy
370+
);
371+
372+
const messages = (completion.choices || [])
373+
.map((c) => c.message?.content || '')
374+
.map((t) => sanitizeMessage(t as string))
375+
.filter(Boolean);
376+
377+
if (messages.length > 0) return deduplicateMessages(messages);
378+
379+
const reasons = (completion.choices as any[])
380+
.map((c:any)=>c.message?.reasoning || '')
381+
.filter(Boolean) as string[];
382+
for (const r of reasons) {
383+
const derived = deriveMessageFromReasoning(r, maxLength);
384+
if (derived) return [derived];
385+
}
386+
387+
return [];
388+
};

0 commit comments

Comments
 (0)