Skip to content

Commit 3d1d049

Browse files
author
Kilo Code
committed
feat(ai-prompt): enhance AI message regeneration logic
Introduces mechanisms to guide the AI in generating distinct commit messages upon user request for regeneration. This prevents the AI from suggesting identical messages for the same staged changes. - Adds state to `CommitMessageProvider` to store the last generated message and its associated Git context. - Implements a method to clear this tracking state, ensuring fresh starts when appropriate. - Modifies the prompt construction to include explicit instructions for the AI to produce a novel message when a previous one for the same context exists. - Includes new unit tests to validate the system's ability to prompt for different messages and to confirm the tracking reset functionality.
1 parent 0b63b43 commit 3d1d049

File tree

3 files changed

+101
-5
lines changed

3 files changed

+101
-5
lines changed

src/assets/icons/kilo-dark.svg

Lines changed: 1 addition & 1 deletion
Loading

src/services/commit-message/CommitMessageProvider.ts

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { loadRuleFiles } from "../../core/prompts/sections/custom-instructions"
1111
*/
1212
export class CommitMessageProvider {
1313
private gitService: GitExtensionService
14+
private previousGitContext: string | null = null
15+
private previousCommitMessage: string | null = null
1416

1517
constructor(
1618
private context: vscode.ExtensionContext,
@@ -41,6 +43,15 @@ export class CommitMessageProvider {
4143
this.context.subscriptions.push(disposable)
4244
}
4345

46+
/**
47+
* Resets the tracking of previous commit message and context.
48+
* This should be called when a commit is submitted or when the context changes.
49+
*/
50+
public resetPreviousMessageTracking(): void {
51+
this.previousGitContext = null
52+
this.previousCommitMessage = null
53+
}
54+
4455
/**
4556
* Generates an AI-powered commit message based on staged changes.
4657
*/
@@ -74,6 +85,10 @@ export class CommitMessageProvider {
7485
const generatedMessage = await this.callAIForCommitMessage(gitContextString)
7586
this.gitService.setCommitMessage(generatedMessage)
7687

88+
// Store the current context and message for future reference
89+
this.previousGitContext = gitContextString
90+
this.previousCommitMessage = generatedMessage
91+
7792
progress.report({ increment: 100, message: "Complete!" })
7893
vscode.window.showInformationMessage("✨ Kilo: Commit message generated!")
7994
} catch (error) {
@@ -88,15 +103,17 @@ export class CommitMessageProvider {
88103
/**
89104
* Calls the AI service to generate a commit message based on the provided context.
90105
*/
91-
private async callAIForCommitMessage(context: string): Promise<string> {
106+
private async callAIForCommitMessage(gitContextString: string): Promise<string> {
107+
console.log("🚀 ~ CommitMessageProvider ~ callAIForCommitMessage ~ gitContextString:", gitContextString)
92108
const apiConfiguration = ContextProxy.instance.getProviderSettings()
93109

94110
const { kilocodeToken } = apiConfiguration
95111
if (!kilocodeToken) {
96112
throw new Error("Kilo Code token is required for AI commit message generation")
97113
}
98114

99-
const prompt = await this.buildCommitMessagePrompt(context)
115+
const prompt = await this.buildCommitMessagePrompt(gitContextString)
116+
console.log("🚀 ~ CommitMessageProvider ~ callAIForCommitMessage ~ prompt:", prompt)
100117
const response = await singleCompletionHandler(
101118
{
102119
apiProvider: "kilocode",
@@ -117,7 +134,30 @@ export class CommitMessageProvider {
117134
const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath
118135
const rules = workspaceRoot ? await loadRuleFiles(workspaceRoot) : ""
119136

120-
const basePrompt = `# Conventional Commit Message Generator
137+
// Check if we should generate a different message than the previous one
138+
const shouldGenerateDifferentMessage =
139+
this.previousGitContext === context && this.previousCommitMessage !== null
140+
141+
// Create a different message instruction if needed
142+
let differentMessagePrefix = ""
143+
if (shouldGenerateDifferentMessage) {
144+
differentMessagePrefix = `# CRITICAL INSTRUCTION: GENERATE A COMPLETELY DIFFERENT COMMIT MESSAGE
145+
146+
The user has requested a new commit message for the same changes.
147+
The previous message was: "${this.previousCommitMessage}"
148+
149+
YOU MUST create a message that is COMPLETELY DIFFERENT by:
150+
- Using entirely different wording and phrasing
151+
- Focusing on different aspects of the changes
152+
- Using a different structure or format if appropriate
153+
- Possibly using a different type or scope if justifiable
154+
155+
This is the MOST IMPORTANT requirement for this task.
156+
157+
`
158+
}
159+
160+
const basePrompt = `${differentMessagePrefix}# Conventional Commit Message Generator
121161
122162
## System Instructions
123163
@@ -192,7 +232,12 @@ For significant changes, include a detailed body explaining the changes.`
192232
// Append rules if they exist
193233
const rulesSection = rules ? `\n\nAdditional Rules:${rules}` : ""
194234

195-
return `${basePrompt}${rulesSection}\n\nReturn ONLY the commit message in the conventional format, nothing else.`
235+
// Add a final reminder if we need a different message
236+
const finalReminder = shouldGenerateDifferentMessage
237+
? `\n\nFINAL REMINDER: Your message MUST be COMPLETELY DIFFERENT from the previous message: "${this.previousCommitMessage}". This is a critical requirement.`
238+
: ""
239+
240+
return `${basePrompt}${rulesSection}${finalReminder}\n\nReturn ONLY the commit message in the conventional format, nothing else.`
196241
}
197242

198243
/**

src/services/commit-message/__tests__/CommitMessageProvider.test.ts

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,5 +224,56 @@ and improve security measures.
224224
225225
Fixes #456`)
226226
})
227+
228+
it("should generate a different message when called twice with the same context", async () => {
229+
// Mock staged changes
230+
const mockChanges: GitChange[] = [{ filePath: "/path/to/file1.ts", status: "Modified" }]
231+
mockGitService.gatherStagedChanges.mockResolvedValue(mockChanges)
232+
233+
// First call to generate message
234+
await commitMessageProvider.generateCommitMessage()
235+
236+
// Verify first call to AI
237+
expect(singleCompletionHandler).toHaveBeenCalledWith(
238+
expect.any(Object),
239+
expect.not.stringContaining("DIFFERENT from the previous message"),
240+
)
241+
242+
// Reset mock to check second call
243+
;(singleCompletionHandler as jest.Mock).mockClear()
244+
245+
// Second call with same context
246+
await commitMessageProvider.generateCommitMessage()
247+
248+
// Verify second call includes instruction to make message different
249+
expect(singleCompletionHandler).toHaveBeenCalledWith(
250+
expect.anything(),
251+
expect.stringContaining("DIFFERENT from the previous message"),
252+
)
253+
})
254+
255+
it("should reset tracking when resetPreviousMessageTracking is called", async () => {
256+
// Mock staged changes
257+
const mockChanges: GitChange[] = [{ filePath: "/path/to/file1.ts", status: "Modified" }]
258+
mockGitService.gatherStagedChanges.mockResolvedValue(mockChanges)
259+
260+
// First call to generate message
261+
await commitMessageProvider.generateCommitMessage()
262+
263+
// Reset tracking
264+
;(commitMessageProvider as any).resetPreviousMessageTracking()
265+
266+
// Reset mock to check next call
267+
;(singleCompletionHandler as jest.Mock).mockClear()
268+
269+
// Call generate again
270+
await commitMessageProvider.generateCommitMessage()
271+
272+
// Verify call doesn't include instruction to make message different
273+
expect(singleCompletionHandler).toHaveBeenCalledWith(
274+
expect.any(Object),
275+
expect.not.stringContaining("DIFFERENT from the previous message"),
276+
)
277+
})
227278
})
228279
})

0 commit comments

Comments
 (0)