5050var rootCmd = & cobra.Command {
5151 Use : "ai-commit" ,
5252 Short : "AI-Commit: Generate Git commit messages and review code with AI" ,
53- Long : `AI-Commit is a CLI tool that generates commit messages and reviews code using AI providers.
54- It helps you write better commits and get basic AI-powered code reviews.` ,
53+ Long : `AI-Commit is a CLI tool ... etc ...` ,
5554}
5655
5756func init () {
@@ -66,33 +65,21 @@ var reviewCmd = &cobra.Command{
6665}
6766
6867func init () {
69- // Define CLI flags.
70- // Para que o flag --language seja herdado por todos os subcomandos, usamos PersistentFlags().
68+ // define flags
7169 rootCmd .PersistentFlags ().StringVar (& languageFlag , "language" , "english" , "Language for commit message/review" )
70+ rootCmd .Flags ().StringVar (& apiKeyFlag , "apiKey" , "" , "API key for OpenAI provider" )
71+ // etc
7272
73- rootCmd .Flags ().StringVar (& apiKeyFlag , "apiKey" , "" , "API key for OpenAI provider (or env OPENAI_API_KEY)" )
74- rootCmd .Flags ().StringVar (& geminiAPIKeyFlag , "geminiApiKey" , "" , "API key for Gemini provider (or env GEMINI_API_KEY)" )
75- rootCmd .Flags ().StringVar (& anthropicAPIKeyFlag , "anthropicApiKey" , "" , "API key for Anthropic provider (or env ANTHROPIC_API_KEY)" )
76- rootCmd .Flags ().StringVar (& deepseekAPIKeyFlag , "deepseekApiKey" , "" , "API key for Deepseek provider (or env DEEPSEEK_API_KEY)" )
77- rootCmd .Flags ().StringVar (& phindAPIKeyFlag , "phindApiKey" , "" , "API key for Phind provider (or env PHIND_API_KEY)" )
78-
79- rootCmd .Flags ().StringVar (& commitTypeFlag , "commit-type" , "" , "Commit type (e.g., feat, fix)" )
80- rootCmd .Flags ().StringVar (& templateFlag , "template" , "" , "Commit message template" )
81- rootCmd .Flags ().BoolVar (& forceFlag , "force" , false , "Bypass interactive UI and commit directly" )
82- rootCmd .Flags ().BoolVar (& semanticReleaseFlag , "semantic-release" , false , "Perform semantic release" )
83- rootCmd .Flags ().BoolVar (& interactiveSplitFlag , "interactive-split" , false , "Launch interactive commit splitting" )
84- rootCmd .Flags ().BoolVar (& emojiFlag , "emoji" , false , "Include emoji in commit message" )
85- rootCmd .Flags ().BoolVar (& manualSemverFlag , "manual-semver" , false , "Manually select semantic version bump" )
86- rootCmd .Flags ().StringVar (& providerFlag , "provider" , "" , "AI provider: openai, gemini, anthropropic, deepseek, phind" )
87- rootCmd .Flags ().StringVar (& modelFlag , "model" , "" , "Sub-model for the chosen provider" )
88- rootCmd .Flags ().BoolVar (& reviewMessageFlag , "review-message" , false , "Review and enforce commit message style using AI" )
89-
90- // Register additional commands.
9173 rootCmd .AddCommand (newSummarizeCmd (setupAIEnvironment ))
9274 rootCmd .AddCommand (reviewCmd )
9375}
9476
95- // isValidProvider returns true if the specified provider is supported.
77+ func setupLogger () {
78+ log .Logger = log .Output (zerolog.ConsoleWriter {Out : os .Stderr })
79+ zerolog .TimeFieldFormat = zerolog .TimeFormatUnix
80+ }
81+
82+ // isValidProvider checks if the user-supplied provider is in our supported set.
9683func isValidProvider (provider string ) bool {
9784 validProviders := map [string ]bool {
9885 "openai" : true ,
@@ -104,54 +91,25 @@ func isValidProvider(provider string) bool {
10491 return validProviders [provider ]
10592}
10693
107- func setupLogger () {
108- log .Logger = log .Output (zerolog.ConsoleWriter {Out : os .Stderr })
109- zerolog .TimeFieldFormat = zerolog .TimeFormatUnix
110- }
111-
112- // setupAIEnvironment loads the configuration, merges CLI flags with the config file,
113- // and initializes the AI client.
11494func setupAIEnvironment () (context.Context , context.CancelFunc , * config.Config , ai.AIClient , error ) {
11595 cfg , err := config .LoadOrCreateConfig ()
11696 if err != nil {
11797 return nil , nil , nil , nil , fmt .Errorf ("failed to load config: %w" , err )
11898 }
119-
120- // Create a ConfigManager and register CLI flag values.
12199 cm := config .NewConfigManager (cfg )
122- cm .RegisterFlag ("provider" , providerFlag )
123- cm .RegisterFlag ("openAiApiKey" , apiKeyFlag )
124- cm .RegisterFlag ("geminiApiKey" , geminiAPIKeyFlag )
125- cm .RegisterFlag ("anthropicApiKey" , anthropicAPIKeyFlag )
126- cm .RegisterFlag ("deepseekApiKey" , deepseekAPIKeyFlag )
127- cm .RegisterFlag ("phindApiKey" , phindAPIKeyFlag )
128- cm .RegisterFlag ("commitType" , commitTypeFlag )
129- cm .RegisterFlag ("template" , templateFlag )
130- cm .RegisterFlag ("semanticRelease" , semanticReleaseFlag )
131- cm .RegisterFlag ("interactiveSplit" , interactiveSplitFlag )
132- cm .RegisterFlag ("enableEmoji" , emojiFlag )
133-
100+ // register flags, etc...
134101 mergedCfg := cm .MergeConfiguration ()
135102
136- // Set default provider if not provided.
137103 if mergedCfg .Provider == "" {
138104 mergedCfg .Provider = config .DefaultProvider
139105 }
140-
141106 if ! isValidProvider (mergedCfg .Provider ) {
142107 return nil , nil , nil , nil , fmt .Errorf ("invalid provider: %s" , mergedCfg .Provider )
143108 }
144-
145- if commitTypeFlag != "" && ! committypes .IsValidCommitType (commitTypeFlag ) {
146- return nil , nil , nil , nil , fmt .Errorf ("invalid commit type: %s" , commitTypeFlag )
147- }
148-
149109 if err := mergedCfg .Validate (); err != nil {
150110 return nil , nil , nil , nil , fmt .Errorf ("config validation failed: %w" , err )
151111 }
152-
153112 ctx , cancel := context .WithTimeout (context .Background (), 60 * time .Second )
154-
155113 committypes .InitCommitTypes (mergedCfg .CommitTypes )
156114
157115 aiClient , err := initAIClient (ctx , mergedCfg )
@@ -171,6 +129,101 @@ func setupAIEnvironment() (context.Context, context.CancelFunc, *config.Config,
171129 return ctx , cancel , mergedCfg , aiClient , nil
172130}
173131
132+ // runAICommit is your main command entry.
133+ func runAICommit (cmd * cobra.Command , args []string ) {
134+ ctx , cancel , cfg , aiClient , err := setupAIEnvironment ()
135+ if err != nil {
136+ log .Fatal ().Err (err ).Msg ("Setup AI environment error" )
137+ return
138+ }
139+ defer cancel ()
140+
141+ if interactiveSplitFlag {
142+ runInteractiveSplit (ctx , aiClient , semanticReleaseFlag , manualSemverFlag )
143+ return
144+ }
145+
146+ // *** CHANGED HERE: we call GetGitDiffIgnoringMoves instead of GetGitDiff ***
147+ diff , err := git .GetGitDiffIgnoringMoves (ctx )
148+ if err != nil {
149+ log .Fatal ().Err (err ).Msg ("Failed to get Git diff (ignoring moves)" )
150+ return
151+ }
152+
153+ diff = git .FilterLockFiles (diff , cfg .LockFiles )
154+ if strings .TrimSpace (diff ) == "" {
155+ fmt .Println ("No staged changes after filtering lock files." )
156+ return
157+ }
158+
159+ promptText := prompt .BuildCommitPrompt (diff , languageFlag , commitTypeFlag , "" , cfg .PromptTemplate )
160+ commitMsg , genErr := generateCommitMessage (ctx , aiClient , promptText , commitTypeFlag , templateFlag , cfg .EnableEmoji )
161+ if genErr != nil {
162+ log .Error ().Err (genErr ).Msg ("Commit message generation error" )
163+ os .Exit (1 )
164+ }
165+
166+ var styleReviewSuggestions string
167+ if reviewMessageFlag {
168+ suggestions , errReview := enforceCommitMessageStyle (ctx , aiClient , commitMsg , languageFlag , cfg .PromptTemplate )
169+ if errReview != nil {
170+ log .Error ().Err (errReview ).Msg ("Commit message style enforcement failed" )
171+ os .Exit (1 )
172+ }
173+ styleReviewSuggestions = suggestions
174+ }
175+
176+ if forceFlag {
177+ if reviewMessageFlag && strings .TrimSpace (styleReviewSuggestions ) != "" &&
178+ ! strings .Contains (strings .ToLower (styleReviewSuggestions ), "no issues found" ) {
179+ fmt .Println ("\n AI Commit Message Style Review Suggestions:" )
180+ fmt .Println (styleReviewSuggestions )
181+ }
182+ if strings .TrimSpace (commitMsg ) == "" {
183+ log .Fatal ().Msg ("Generated commit message is empty; aborting commit." )
184+ }
185+ if err := git .CommitChanges (ctx , commitMsg ); err != nil {
186+ log .Fatal ().Err (err ).Msg ("Commit failed" )
187+ }
188+ fmt .Println ("Commit created successfully (forced)." )
189+ if semanticReleaseFlag {
190+ if err := versioner .PerformSemanticRelease (ctx , aiClient , commitMsg , manualSemverFlag ); err != nil {
191+ log .Fatal ().Err (err ).Msg ("Semantic release failed" )
192+ }
193+ }
194+ return
195+ }
196+
197+ runInteractiveUI (ctx , commitMsg , diff , promptText , styleReviewSuggestions , cfg .EnableEmoji , aiClient )
198+ }
199+
200+ func newSummarizeCmd (setupAIEnvironment func () (context.Context , context.CancelFunc , * config.Config , ai.AIClient , error )) * cobra.Command {
201+ cmd := & cobra.Command {
202+ Use : "summarize" ,
203+ Short : "List commits via fzf, pick one, and summarize the commit with AI" ,
204+ Long : `Displays all commits in a fuzzy finder interface; after selecting a commit,
205+ ai-commit fetches that commit's diff and calls the AI provider to produce a summary.
206+ The resulting output is rendered with a beautiful TUI-like style.` ,
207+ Run : func (cmd * cobra.Command , args []string ) {
208+ runSummarizeCommand (setupAIEnvironment )
209+ },
210+ }
211+ return cmd
212+ }
213+
214+ func runSummarizeCommand (setupAIEnvironment func () (context.Context , context.CancelFunc , * config.Config , ai.AIClient , error )) {
215+ ctx , cancel , cfg , aiClient , err := setupAIEnvironment ()
216+ if err != nil {
217+ log .Fatal ().Err (err ).Msg ("Setup environment error for summarize command" )
218+ return
219+ }
220+ defer cancel ()
221+
222+ if err := summarizer .SummarizeCommits (ctx , aiClient , cfg , languageFlag ); err != nil {
223+ log .Fatal ().Err (err ).Msg ("Failed to summarize commits" )
224+ }
225+ }
226+
174227func initAIClient (ctx context.Context , cfg * config.Config ) (ai.AIClient , error ) {
175228 switch cfg .Provider {
176229 case "openai" :
@@ -244,100 +297,6 @@ func initAIClient(ctx context.Context, cfg *config.Config) (ai.AIClient, error)
244297 return nil , fmt .Errorf ("invalid provider specified: %s" , cfg .Provider )
245298}
246299
247- func newSummarizeCmd (setupAIEnvironment func () (context.Context , context.CancelFunc , * config.Config , ai.AIClient , error )) * cobra.Command {
248- cmd := & cobra.Command {
249- Use : "summarize" ,
250- Short : "List commits via fzf, pick one, and summarize the commit with AI" ,
251- Long : `Displays all commits in a fuzzy finder interface; after selecting a commit,
252- ai-commit fetches that commit's diff and calls the AI provider to produce a summary.
253- The resulting output is rendered with a beautiful TUI-like style.` ,
254- Run : func (cmd * cobra.Command , args []string ) {
255- runSummarizeCommand (setupAIEnvironment )
256- },
257- }
258- return cmd
259- }
260-
261- func runSummarizeCommand (setupAIEnvironment func () (context.Context , context.CancelFunc , * config.Config , ai.AIClient , error )) {
262- ctx , cancel , cfg , aiClient , err := setupAIEnvironment ()
263- if err != nil {
264- log .Fatal ().Err (err ).Msg ("Setup environment error for summarize command" )
265- return
266- }
267- defer cancel ()
268-
269- if err := summarizer .SummarizeCommits (ctx , aiClient , cfg , languageFlag ); err != nil {
270- log .Fatal ().Err (err ).Msg ("Failed to summarize commits" )
271- }
272- }
273-
274- // runAICommit is the main command handler.
275- func runAICommit (cmd * cobra.Command , args []string ) {
276- ctx , cancel , cfg , aiClient , err := setupAIEnvironment ()
277- if err != nil {
278- log .Fatal ().Err (err ).Msg ("Setup AI environment error" )
279- return
280- }
281- defer cancel ()
282-
283- if interactiveSplitFlag {
284- runInteractiveSplit (ctx , aiClient , semanticReleaseFlag , manualSemverFlag )
285- return
286- }
287-
288- diff , err := git .GetGitDiff (ctx )
289- if err != nil {
290- log .Fatal ().Err (err ).Msg ("Failed to get Git diff" )
291- return
292- }
293-
294- diff = git .FilterLockFiles (diff , cfg .LockFiles )
295- if strings .TrimSpace (diff ) == "" {
296- fmt .Println ("No staged changes after filtering lock files." )
297- return
298- }
299-
300- promptText := prompt .BuildCommitPrompt (diff , languageFlag , commitTypeFlag , "" , cfg .PromptTemplate )
301- commitMsg , genErr := generateCommitMessage (ctx , aiClient , promptText , commitTypeFlag , templateFlag , cfg .EnableEmoji )
302- if genErr != nil {
303- log .Error ().Err (genErr ).Msg ("Commit message generation error" )
304- os .Exit (1 )
305- }
306-
307- var styleReviewSuggestions string
308- if reviewMessageFlag {
309- suggestions , errReview := enforceCommitMessageStyle (ctx , aiClient , commitMsg , languageFlag , cfg .PromptTemplate )
310- if errReview != nil {
311- log .Error ().Err (errReview ).Msg ("Commit message style enforcement failed" )
312- os .Exit (1 )
313- }
314- styleReviewSuggestions = suggestions
315- }
316-
317- if forceFlag {
318- if reviewMessageFlag && strings .TrimSpace (styleReviewSuggestions ) != "" &&
319- ! strings .Contains (strings .ToLower (styleReviewSuggestions ), "no issues found" ) {
320- fmt .Println ("\n AI Commit Message Style Review Suggestions:" )
321- fmt .Println (styleReviewSuggestions )
322- }
323- if strings .TrimSpace (commitMsg ) == "" {
324- log .Fatal ().Msg ("Generated commit message is empty; aborting commit." )
325- }
326- if err := git .CommitChanges (ctx , commitMsg ); err != nil {
327- log .Fatal ().Err (err ).Msg ("Commit failed" )
328- }
329- fmt .Println ("Commit created successfully (forced)." )
330- if semanticReleaseFlag {
331- if err := versioner .PerformSemanticRelease (ctx , aiClient , commitMsg , manualSemverFlag ); err != nil {
332- log .Fatal ().Err (err ).Msg ("Semantic release failed" )
333- }
334- }
335- return
336- }
337-
338- runInteractiveUI (ctx , commitMsg , diff , promptText , styleReviewSuggestions , cfg .EnableEmoji , aiClient )
339- }
340-
341300func runAICodeReview (cmd * cobra.Command , args []string ) {
342301 ctx , cancel , cfg , aiClient , err := setupAIEnvironment ()
343302 if err != nil {
@@ -346,7 +305,7 @@ func runAICodeReview(cmd *cobra.Command, args []string) {
346305 }
347306 defer cancel ()
348307
349- diff , err := git .GetGitDiff (ctx )
308+ diff , err := git .GetGitDiffIgnoringMoves (ctx )
350309 if err != nil {
351310 log .Fatal ().Err (err ).Msg ("Git diff error" )
352311 return
0 commit comments