Skip to content

Commit 48a5bad

Browse files
committed
fix(scm): 🐛 address commit message generator review feedback
1 parent b871b80 commit 48a5bad

3 files changed

Lines changed: 103 additions & 1 deletion

File tree

src/services/commit-message/CommitMessageGenerator.ts

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,26 +8,38 @@ import { TelemetryEventName, type ProviderSettings } from "@roo-code/types"
88

99
import { GenerateMessageParams, PromptOptions, ProgressUpdate } from "./types/core"
1010

11+
/** Provides the extension settings needed to generate commit messages. */
1112
export interface CommitMessageContextProxy {
13+
/** Whether the underlying extension configuration is ready to read. */
1214
isInitialized: boolean
15+
/** Returns the active provider settings used as the default generation profile. */
1316
getProviderSettings(): ProviderSettings
17+
/** Reads a persisted extension setting by key. */
1418
getValue(key: any): unknown
1519
}
1620

21+
/** Overrides used to isolate commit message generation in tests and integrations. */
1722
export interface CommitMessageGeneratorDependencies {
23+
/** Supplies the context proxy that owns provider settings and user configuration. */
1824
getContextProxy?: () => CommitMessageContextProxy
25+
/** Completes the prepared commit-message prompt with the selected provider. */
1926
completePrompt?: (apiConfiguration: ProviderSettings, promptText: string) => Promise<string>
27+
/** Adds repository-specific custom instructions to the commit-message prompt. */
2028
addCustomInstructions?: typeof defaultAddCustomInstructions
29+
/** Records successful commit-message generation telemetry. */
2130
captureGenerated?: () => void
31+
/** Receives non-fatal generation warnings, such as profile fallback failures. */
2232
logger?: Pick<Console, "warn">
2333
}
2434

35+
/** Builds prompts, selects provider settings, and extracts AI generated commit messages. */
2536
export class CommitMessageGenerator {
2637
private readonly providerSettingsManager: ProviderSettingsManager
2738
private readonly dependencies: Required<CommitMessageGeneratorDependencies>
2839
private previousGitContext: string | null = null
2940
private previousCommitMessage: string | null = null
3041

42+
/** Creates a generator using the provider settings manager and optional test seams. */
3143
constructor(
3244
providerSettingsManager: ProviderSettingsManager,
3345
dependencies: CommitMessageGeneratorDependencies = {},
@@ -44,10 +56,13 @@ export class CommitMessageGenerator {
4456
}
4557
}
4658

59+
/** Generates a commit message for the supplied Git context. */
4760
async generateMessage(params: GenerateMessageParams): Promise<string> {
4861
const { gitContext, onProgress } = params
4962

5063
try {
64+
this.validateGitContext(gitContext)
65+
5166
onProgress?.({
5267
message: "Generating commit message...",
5368
percentage: 75,
@@ -72,6 +87,7 @@ export class CommitMessageGenerator {
7287
}
7388
}
7489

90+
/** Creates the final model prompt, including custom and regeneration instructions. */
7591
async buildPrompt(gitContext: string, options: PromptOptions, workspacePath: string): Promise<string> {
7692
const { customSupportPrompts = {}, previousContext, previousMessage } = options
7793

@@ -128,6 +144,7 @@ FINAL REMINDER: Your message MUST be COMPLETELY DIFFERENT from the previous mess
128144
}
129145
}
130146

147+
/** Calls the configured AI provider and returns the cleaned commit message text. */
131148
private async callAIForCommitMessage(
132149
gitContextString: string,
133150
workspacePath: string,
@@ -190,9 +207,34 @@ FINAL REMINDER: Your message MUST be COMPLETELY DIFFERENT from the previous mess
190207
return this.extractCommitMessage(response)
191208
}
192209

210+
/** Throws when there is no meaningful Git change data to describe. */
211+
private validateGitContext(gitContext: string): void {
212+
if (!this.hasGitChanges(gitContext)) {
213+
throw new Error("No changes to generate a commit message for")
214+
}
215+
}
216+
217+
/** Detects whether collected Git context includes at least one changed file. */
218+
private hasGitChanges(gitContext: string): boolean {
219+
const normalizedContext = gitContext.trim()
220+
221+
if (!normalizedContext || normalizedContext.includes("(No changes matched selection)")) {
222+
return false
223+
}
224+
225+
return (
226+
/^diff --git /m.test(normalizedContext) ||
227+
/^Binary file /m.test(normalizedContext) ||
228+
/^(Added|Modified|Deleted|Renamed|Copied|Updated|Untracked|Unknown) \((staged|unstaged)\): .+$/m.test(
229+
normalizedContext,
230+
)
231+
)
232+
}
233+
234+
/** Cleans formatting wrappers from an AI response without enforcing message style. */
193235
private extractCommitMessage(response: string): string {
194236
const cleaned = response.trim()
195-
const withoutCodeBlocks = cleaned.replace(/```[a-z]*\n|```/g, "")
237+
const withoutCodeBlocks = cleaned.replace(/^```[a-zA-Z0-9_-]*\r?\n/, "").replace(/\r?\n```$/, "")
196238
const withoutQuotes = withoutCodeBlocks.replace(/^["'`]|["'`]$/g, "")
197239
return withoutQuotes.trim()
198240
}

src/services/commit-message/__tests__/CommitMessageGenerator.spec.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ describe("CommitMessageGenerator", () => {
1919
const captureGenerated = vi.fn()
2020
const warn = vi.fn()
2121

22+
/** Creates a generator with mocked provider and configuration dependencies. */
2223
const createGenerator = () =>
2324
new CommitMessageGenerator(providerSettingsManager as any, {
2425
getContextProxy: () => contextProxy,
@@ -50,6 +51,33 @@ describe("CommitMessageGenerator", () => {
5051
providerSettingsManager.getProfile.mockResolvedValue({ name: "Commit profile", ...commitConfig })
5152
})
5253

54+
it("fails before progress or AI calls when git context has no changes", async () => {
55+
const onProgress = vi.fn()
56+
const generator = createGenerator()
57+
58+
await expect(
59+
generator.generateMessage({
60+
workspacePath: "/repo",
61+
selectedFiles: [],
62+
gitContext: `## Git Context
63+
64+
### Full Diff of Staged Changes
65+
\`\`\`diff
66+
\`\`\`
67+
68+
### Change Summary
69+
\`\`\`
70+
(No changes matched selection)
71+
\`\`\``,
72+
onProgress,
73+
}),
74+
).rejects.toThrow("No changes to generate a commit message for")
75+
76+
expect(onProgress).not.toHaveBeenCalled()
77+
expect(completePrompt).not.toHaveBeenCalled()
78+
expect(captureGenerated).not.toHaveBeenCalled()
79+
})
80+
5381
it("sends the full git context to the LLM and returns cleaned commit text", async () => {
5482
const gitContext = `## Git Context
5583
@@ -153,4 +181,23 @@ new file mode 100644
153181
expect(secondPrompt).toContain('The previous message was: "feat(git): collect git context"')
154182
expect(secondPrompt).toContain(gitContext)
155183
})
184+
185+
it("cleans formatting wrappers without enforcing conventional commit format", async () => {
186+
completePrompt.mockResolvedValue(`\`\`\`
187+
Update Git context parsing for staged-only entries
188+
189+
Keep unstaged commit context focused on worktree changes.
190+
\`\`\``)
191+
const generator = createGenerator()
192+
193+
const message = await generator.generateMessage({
194+
workspacePath: "/repo",
195+
selectedFiles: ["src/file.ts"],
196+
gitContext: "Modified (staged): src/file.ts",
197+
})
198+
199+
expect(message).toBe(`Update Git context parsing for staged-only entries
200+
201+
Keep unstaged commit context focused on worktree changes.`)
202+
})
156203
})
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,31 @@
1+
/** Parameters required to generate a commit message for selected Git changes. */
12
export interface GenerateMessageParams {
3+
/** Absolute workspace path used to resolve repository custom instructions. */
24
workspacePath: string
5+
/** File paths included in the Git context used for generation. */
36
selectedFiles: string[]
7+
/** Markdown Git context describing the changes to summarize. */
48
gitContext: string
9+
/** Optional progress callback for UI updates during generation. */
510
onProgress?: (progress: ProgressUpdate) => void
611
}
712

13+
/** Prompt customization and regeneration context for commit-message prompts. */
814
export interface PromptOptions {
15+
/** User-defined support prompt templates keyed by prompt type. */
916
customSupportPrompts?: Record<string, string>
17+
/** Previous Git context used to detect regeneration for the same changes. */
1018
previousContext?: string
19+
/** Previous generated message to avoid repeating during regeneration. */
1120
previousMessage?: string
1221
}
1322

23+
/** Incremental status update emitted while generating a commit message. */
1424
export interface ProgressUpdate {
25+
/** Human-readable status message for the current generation step. */
1526
message?: string
27+
/** Absolute progress percentage for the current generation step. */
1628
percentage?: number
29+
/** Relative progress increment for the current generation step. */
1730
increment?: number
1831
}

0 commit comments

Comments
 (0)