Skip to content

Commit 4c18cb1

Browse files
committed
feat(scm): add commit message settings and profiles
1 parent 411160c commit 4c18cb1

53 files changed

Lines changed: 3469 additions & 162 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

packages/types/src/global-settings.ts

Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,190 @@ export const defaultCommitMessageGitContextSettings: Required<CommitMessageGitCo
4949
recentCommitDiffCount: 1,
5050
}
5151

52+
export const DEFAULT_COMMIT_MESSAGE_ATTRIBUTION_TEMPLATE = "Assisted-by: ${agentName}:${providerModel} [${toolName}]"
53+
54+
export const commitMessageAttributionSchema = z.object({
55+
enabled: z.boolean().optional(),
56+
template: z.string().optional(),
57+
})
58+
59+
export type CommitMessageAttributionSettings = z.infer<typeof commitMessageAttributionSchema>
60+
61+
export const defaultCommitMessageAttributionSettings: Required<CommitMessageAttributionSettings> = {
62+
enabled: false,
63+
template: DEFAULT_COMMIT_MESSAGE_ATTRIBUTION_TEMPLATE,
64+
}
65+
66+
export const MAX_COMMIT_MESSAGE_PROFILES = 5
67+
export const DEFAULT_COMMIT_MESSAGE_PROFILE_ID = "default"
68+
69+
export const commitMessageProfileSchema = z.object({
70+
id: z.string().optional(),
71+
name: z.string().optional(),
72+
prompt: z.string().optional(),
73+
apiConfigId: z.string().optional(),
74+
gitContext: commitMessageGitContextSchema.optional(),
75+
attribution: commitMessageAttributionSchema.optional(),
76+
})
77+
78+
export const commitMessageProfilesSchema = z.object({
79+
activeProfileId: z.string().optional(),
80+
profiles: z.array(commitMessageProfileSchema).max(MAX_COMMIT_MESSAGE_PROFILES).optional(),
81+
})
82+
83+
export type CommitMessageProfileSettings = z.infer<typeof commitMessageProfileSchema>
84+
export type CommitMessageProfilesSettings = z.infer<typeof commitMessageProfilesSchema>
85+
86+
export type NormalizedCommitMessageProfile = Omit<
87+
CommitMessageProfileSettings,
88+
"id" | "name" | "gitContext" | "attribution"
89+
> & {
90+
id: string
91+
name: string
92+
gitContext: Required<CommitMessageGitContextSettings>
93+
attribution: Required<CommitMessageAttributionSettings>
94+
}
95+
96+
export interface NormalizedCommitMessageProfiles {
97+
activeProfileId: string
98+
profiles: NormalizedCommitMessageProfile[]
99+
}
100+
101+
export interface CommitMessageProfileFallbackSettings {
102+
prompt?: string
103+
apiConfigId?: string
104+
gitContext?: CommitMessageGitContextSettings
105+
attribution?: CommitMessageAttributionSettings
106+
}
107+
108+
export function normalizeCommitMessageGitContextSettings(
109+
settings?: CommitMessageGitContextSettings,
110+
): Required<CommitMessageGitContextSettings> {
111+
return {
112+
...defaultCommitMessageGitContextSettings,
113+
...settings,
114+
diffContextLines: clampNumberSetting(
115+
settings?.diffContextLines,
116+
0,
117+
20,
118+
defaultCommitMessageGitContextSettings.diffContextLines,
119+
),
120+
recentCommitCount: clampNumberSetting(
121+
settings?.recentCommitCount,
122+
1,
123+
20,
124+
defaultCommitMessageGitContextSettings.recentCommitCount,
125+
),
126+
recentCommitDiffCount: clampNumberSetting(
127+
settings?.recentCommitDiffCount,
128+
1,
129+
5,
130+
defaultCommitMessageGitContextSettings.recentCommitDiffCount,
131+
),
132+
}
133+
}
134+
135+
export function normalizeCommitMessageAttributionSettings(
136+
settings?: CommitMessageAttributionSettings,
137+
): Required<CommitMessageAttributionSettings> {
138+
return {
139+
...defaultCommitMessageAttributionSettings,
140+
...settings,
141+
template: normalizeOptionalString(settings?.template) ?? defaultCommitMessageAttributionSettings.template,
142+
}
143+
}
144+
145+
export function normalizeCommitMessageProfiles(
146+
settings?: CommitMessageProfilesSettings,
147+
fallback: CommitMessageProfileFallbackSettings = {},
148+
): NormalizedCommitMessageProfiles {
149+
const sourceProfiles = settings?.profiles?.length
150+
? settings.profiles.slice(0, MAX_COMMIT_MESSAGE_PROFILES)
151+
: [
152+
{
153+
id: DEFAULT_COMMIT_MESSAGE_PROFILE_ID,
154+
name: "Default",
155+
prompt: fallback.prompt,
156+
apiConfigId: fallback.apiConfigId,
157+
gitContext: fallback.gitContext,
158+
attribution: fallback.attribution,
159+
},
160+
]
161+
162+
const profiles: NormalizedCommitMessageProfile[] = sourceProfiles.map((profile, index) => ({
163+
id: normalizeProfileId(profile.id, index),
164+
name: normalizeProfileName(profile.name, index),
165+
prompt: profile.prompt,
166+
apiConfigId: normalizeOptionalString(profile.apiConfigId),
167+
gitContext: normalizeCommitMessageGitContextSettings(profile.gitContext),
168+
attribution: normalizeCommitMessageAttributionSettings(profile.attribution),
169+
}))
170+
const firstProfile = profiles[0]!
171+
172+
const activeProfileId = profiles.some((profile) => profile.id === settings?.activeProfileId)
173+
? settings!.activeProfileId!
174+
: firstProfile.id
175+
176+
return {
177+
activeProfileId,
178+
profiles,
179+
}
180+
}
181+
182+
export function getActiveCommitMessageProfile(
183+
settings?: CommitMessageProfilesSettings,
184+
fallback?: CommitMessageProfileFallbackSettings,
185+
): NormalizedCommitMessageProfile {
186+
const normalized = normalizeCommitMessageProfiles(settings, fallback)
187+
return normalized.profiles.find((profile) => profile.id === normalized.activeProfileId) ?? normalized.profiles[0]!
188+
}
189+
190+
export function createCommitMessageProfileId(): string {
191+
return `profile-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`
192+
}
193+
194+
export function createCommitMessageProfileName(profiles: Array<{ name?: string }>): string {
195+
for (let index = profiles.length + 1; index <= MAX_COMMIT_MESSAGE_PROFILES + 1; index++) {
196+
const candidate = `Profile ${index}`
197+
if (!profiles.some((profile) => profile.name === candidate)) {
198+
return candidate
199+
}
200+
}
201+
202+
return `Profile ${profiles.length + 1}`
203+
}
204+
205+
function normalizeProfileId(id: string | undefined, index: number): string {
206+
const normalized = normalizeOptionalString(id)
207+
if (normalized) {
208+
return normalized
209+
}
210+
211+
return index === 0 ? DEFAULT_COMMIT_MESSAGE_PROFILE_ID : `profile-${index + 1}`
212+
}
213+
214+
function normalizeProfileName(name: string | undefined, index: number): string {
215+
const normalized = normalizeOptionalString(name)
216+
return normalized || (index === 0 ? "Default" : `Profile ${index + 1}`)
217+
}
218+
219+
function normalizeOptionalString(value: string | undefined): string | undefined {
220+
if (typeof value !== "string") {
221+
return undefined
222+
}
223+
224+
const trimmed = value.trim()
225+
return trimmed.length > 0 ? trimmed : undefined
226+
}
227+
228+
function clampNumberSetting(value: number | undefined, min: number, max: number, fallback: number): number {
229+
if (typeof value !== "number" || !Number.isFinite(value)) {
230+
return fallback
231+
}
232+
233+
return Math.min(Math.max(Math.trunc(value), min), max)
234+
}
235+
52236
/**
53237
* Terminal output preview size options for persisted command output.
54238
*
@@ -261,6 +445,8 @@ export const globalSettingsSchema = z.object({
261445

262446
commitMessageApiConfigId: z.string().optional(),
263447
commitMessageGitContext: commitMessageGitContextSchema.optional(),
448+
commitMessageAttribution: commitMessageAttributionSchema.optional(),
449+
commitMessageProfiles: commitMessageProfilesSchema.optional(),
264450
})
265451

266452
export type GlobalSettings = z.infer<typeof globalSettingsSchema>

packages/types/src/vscode-extension-host.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,6 +285,8 @@ export type ExtensionState = Pick<
285285
| "enhancementApiConfigId"
286286
| "commitMessageApiConfigId"
287287
| "commitMessageGitContext"
288+
| "commitMessageAttribution"
289+
| "commitMessageProfiles"
288290
| "customCondensingPrompt"
289291
| "codebaseIndexConfig"
290292
| "codebaseIndexModels"

src/core/webview/ClineProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2059,6 +2059,8 @@ export class ClineProvider
20592059
enhancementApiConfigId,
20602060
commitMessageApiConfigId,
20612061
commitMessageGitContext,
2062+
commitMessageAttribution,
2063+
commitMessageProfiles,
20622064
autoApprovalEnabled,
20632065
customModes,
20642066
experiments,
@@ -2213,6 +2215,8 @@ export class ClineProvider
22132215
enhancementApiConfigId,
22142216
commitMessageApiConfigId,
22152217
commitMessageGitContext,
2218+
commitMessageAttribution,
2219+
commitMessageProfiles,
22162220
autoApprovalEnabled: autoApprovalEnabled ?? false,
22172221
customModes,
22182222
experiments: experiments ?? experimentDefault,
@@ -2421,6 +2425,8 @@ export class ClineProvider
24212425
enhancementApiConfigId: stateValues.enhancementApiConfigId,
24222426
commitMessageApiConfigId: stateValues.commitMessageApiConfigId,
24232427
commitMessageGitContext: stateValues.commitMessageGitContext,
2428+
commitMessageAttribution: stateValues.commitMessageAttribution,
2429+
commitMessageProfiles: stateValues.commitMessageProfiles,
24242430
experiments: stateValues.experiments ?? experimentDefault,
24252431
autoApprovalEnabled: stateValues.autoApprovalEnabled ?? false,
24262432
customModes,

src/core/webview/webviewMessageHandler.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1605,7 +1605,6 @@ export const webviewMessageHandler = async (
16051605
await updateGlobalState("enhancementApiConfigId", message.text)
16061606
await provider.postStateToWebview()
16071607
break
1608-
16091608
case "autoApprovalEnabled":
16101609
await updateGlobalState("autoApprovalEnabled", message.bool ?? false)
16111610
await provider.postStateToWebview()

src/i18n/locales/ca/common.json

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/i18n/locales/de/common.json

Lines changed: 53 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)