forked from Zoo-Code-Org/Zoo-Code
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCommitMessageGenerator.ts
More file actions
199 lines (168 loc) · 6.89 KB
/
Copy pathCommitMessageGenerator.ts
File metadata and controls
199 lines (168 loc) · 6.89 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
import { ContextProxy } from "../../core/config/ContextProxy"
import { ProviderSettingsManager } from "../../core/config/ProviderSettingsManager"
import { singleCompletionHandler as defaultSingleCompletionHandler } from "../../utils/single-completion-handler"
import { supportPrompt } from "../../shared/support-prompt"
import { addCustomInstructions as defaultAddCustomInstructions } from "../../core/prompts/sections/custom-instructions"
import { TelemetryService } from "@roo-code/telemetry"
import { TelemetryEventName, type ProviderSettings } from "@roo-code/types"
import { GenerateMessageParams, PromptOptions, ProgressUpdate } from "./types/core"
export interface CommitMessageContextProxy {
isInitialized: boolean
getProviderSettings(): ProviderSettings
getValue(key: any): unknown
}
export interface CommitMessageGeneratorDependencies {
getContextProxy?: () => CommitMessageContextProxy
completePrompt?: (apiConfiguration: ProviderSettings, promptText: string) => Promise<string>
addCustomInstructions?: typeof defaultAddCustomInstructions
captureGenerated?: () => void
logger?: Pick<Console, "warn">
}
export class CommitMessageGenerator {
private readonly providerSettingsManager: ProviderSettingsManager
private readonly dependencies: Required<CommitMessageGeneratorDependencies>
private previousGitContext: string | null = null
private previousCommitMessage: string | null = null
constructor(
providerSettingsManager: ProviderSettingsManager,
dependencies: CommitMessageGeneratorDependencies = {},
) {
this.providerSettingsManager = providerSettingsManager
this.dependencies = {
getContextProxy: dependencies.getContextProxy ?? (() => ContextProxy.instance),
completePrompt: dependencies.completePrompt ?? defaultSingleCompletionHandler,
addCustomInstructions: dependencies.addCustomInstructions ?? defaultAddCustomInstructions,
captureGenerated:
dependencies.captureGenerated ??
(() => TelemetryService.instance.captureEvent(TelemetryEventName.COMMIT_MSG_GENERATED)),
logger: dependencies.logger ?? console,
}
}
async generateMessage(params: GenerateMessageParams): Promise<string> {
const { gitContext, onProgress } = params
try {
onProgress?.({
message: "Generating commit message...",
percentage: 75,
})
const generatedMessage = await this.callAIForCommitMessage(gitContext, params.workspacePath, onProgress)
this.previousGitContext = gitContext
this.previousCommitMessage = generatedMessage
this.dependencies.captureGenerated()
onProgress?.({
message: "Commit message generated successfully",
percentage: 100,
})
return generatedMessage
} catch (error) {
const errorMessage = error instanceof Error ? error.message : "Unknown error occurred"
throw new Error(`Failed to generate commit message: ${errorMessage}`)
}
}
async buildPrompt(gitContext: string, options: PromptOptions, workspacePath: string): Promise<string> {
const { customSupportPrompts = {}, previousContext, previousMessage } = options
const customInstructions = await this.dependencies.addCustomInstructions("", "", workspacePath, "commit", {
language: "en",
})
const shouldGenerateDifferentMessage =
(previousContext === gitContext || this.previousGitContext === gitContext) &&
(previousMessage !== null || this.previousCommitMessage !== null)
const targetPreviousMessage = previousMessage || this.previousCommitMessage
if (shouldGenerateDifferentMessage && targetPreviousMessage) {
const differentMessagePrefix = `# CRITICAL INSTRUCTION: GENERATE A COMPLETELY DIFFERENT COMMIT MESSAGE
The user has requested a new commit message for the same changes.
The previous message was: "${targetPreviousMessage}"
YOU MUST create a message that is COMPLETELY DIFFERENT by:
- Using entirely different wording and phrasing
- Focusing on different aspects of the changes
- Using a different structure or format if appropriate
- Possibly using a different type or scope if justifiable
This is the MOST IMPORTANT requirement for this task.
`
const baseTemplate = supportPrompt.get(customSupportPrompts, "COMMIT_MESSAGE")
const modifiedTemplate =
differentMessagePrefix +
baseTemplate +
`
FINAL REMINDER: Your message MUST be COMPLETELY DIFFERENT from the previous message: "${targetPreviousMessage}". This is a critical requirement.`
return supportPrompt.create(
"COMMIT_MESSAGE",
{
gitContext,
customInstructions: customInstructions || "",
},
{
...customSupportPrompts,
COMMIT_MESSAGE: modifiedTemplate,
},
)
} else {
return supportPrompt.create(
"COMMIT_MESSAGE",
{
gitContext,
customInstructions: customInstructions || "",
},
customSupportPrompts,
)
}
}
private async callAIForCommitMessage(
gitContextString: string,
workspacePath: string,
onProgress?: (progress: ProgressUpdate) => void,
): Promise<string> {
const contextProxy = this.dependencies.getContextProxy()
if (!contextProxy.isInitialized) {
throw new Error("ContextProxy not initialized. Please try again after the extension has fully loaded.")
}
const apiConfiguration = contextProxy.getProviderSettings()
const commitMessageApiConfigId = contextProxy.getValue("commitMessageApiConfigId") as string | undefined
const listApiConfigMeta = (contextProxy.getValue("listApiConfigMeta") || []) as Array<{ id: string }>
const customSupportPrompts = (contextProxy.getValue("customSupportPrompts") || {}) as Record<
string,
string | undefined
>
let configToUse: ProviderSettings = apiConfiguration
if (commitMessageApiConfigId && listApiConfigMeta.find(({ id }) => id === commitMessageApiConfigId)) {
try {
await this.providerSettingsManager.initialize()
const { name: _, ...providerSettings } = await this.providerSettingsManager.getProfile({
id: commitMessageApiConfigId,
})
if (providerSettings.apiProvider) {
configToUse = providerSettings
}
} catch (error) {
this.dependencies.logger.warn(
`Failed to load commit message API profile ${commitMessageApiConfigId}; falling back to current API configuration`,
error,
)
}
}
const filteredPrompts = Object.fromEntries(
Object.entries(customSupportPrompts).filter(([_, value]) => value !== undefined),
) as Record<string, string>
const prompt = await this.buildPrompt(
gitContextString,
{ customSupportPrompts: filteredPrompts },
workspacePath,
)
onProgress?.({
message: "Calling AI service...",
increment: 10,
})
const response = await this.dependencies.completePrompt(configToUse, prompt)
onProgress?.({
message: "Processing AI response...",
increment: 10,
})
return this.extractCommitMessage(response)
}
private extractCommitMessage(response: string): string {
const cleaned = response.trim()
const withoutCodeBlocks = cleaned.replace(/```[a-z]*\n|```/g, "")
const withoutQuotes = withoutCodeBlocks.replace(/^["'`]|["'`]$/g, "")
return withoutQuotes.trim()
}
}