2026-02-28 22:29:16 +00:00
package cmd
import (
"fmt"
"os"
"path/filepath"
chore(build): add Makefile and tests for commands and utilities
- Introduced Makefile with targets for testing (all, coverage, agent-specific), building, installing, cleaning, and help
- Added unit and integration tests for agent command, edit command, and CleanCodeResponse function
- Refactored CleanCodeResponse to use regex for robust markdown fence removal in agent and client modules
- Ensured tests cover code cleaning, plan generation placeholders, and file editing functionality
2026-03-01 00:24:48 +00:00
"regexp"
2026-02-28 22:29:16 +00:00
"strings"
"github.com/fatih/color"
"github.com/spf13/cobra"
"gmgauthier.com/grokkit/config"
"gmgauthier.com/grokkit/internal/grok"
)
var agentCmd = & cobra . Command {
Use : "agent INSTRUCTION" ,
Short : "Multi-file agent — Grok intelligently edits multiple files with preview" ,
Args : cobra . ExactArgs ( 1 ) ,
Run : func ( cmd * cobra . Command , args [ ] string ) {
instruction := args [ 0 ]
modelFlag , _ := cmd . Flags ( ) . GetString ( "model" )
2026-03-02 16:56:56 +00:00
model := config . GetModel ( "agent" , modelFlag )
2026-02-28 22:29:16 +00:00
client := grok . NewClient ( )
color . Yellow ( "🔍 Agent mode activated. Scanning project..." )
var files [ ] string
2026-03-01 14:10:24 +00:00
err := filepath . Walk ( "." , func ( path string , info os . FileInfo , err error ) error {
2026-02-28 22:29:16 +00:00
if err != nil || info . IsDir ( ) {
return err
}
if strings . HasSuffix ( path , ".go" ) {
files = append ( files , path )
}
return nil
} )
2026-03-01 14:10:24 +00:00
if err != nil {
color . Red ( "Failed to scan project: %v" , err )
os . Exit ( 1 )
}
2026-02-28 22:29:16 +00:00
if len ( files ) == 0 {
color . Yellow ( "No .go files found." )
return
}
color . Yellow ( "📄 Found %d files. Asking Grok for a plan..." , len ( files ) )
planMessages := [ ] map [ string ] string {
{ "role" : "system" , "content" : "You are an expert software engineer. Given an instruction and list of files, return a clear plan: which files to change and a brief description of what to do in each. List files one per line." } ,
{ "role" : "user" , "content" : fmt . Sprintf ( "Instruction: %s\n\nFiles:\n%s" , instruction , strings . Join ( files , "\n" ) ) } ,
}
plan := client . Stream ( planMessages , model )
color . Cyan ( "\nGrok's Plan:\n%s" , plan )
fmt . Print ( "\nProceed with changes? (y/n): " )
var confirm string
2026-03-01 14:10:24 +00:00
if _ , err := fmt . Scanln ( & confirm ) ; err != nil {
color . Red ( "Failed to read input: %v" , err )
return
}
2026-02-28 22:29:16 +00:00
if confirm != "y" && confirm != "Y" {
color . Yellow ( "Aborted." )
return
}
color . Yellow ( "Applying changes file by file...\n" )
applyAll := false
for i , file := range files {
2026-02-28 22:40:31 +00:00
color . Yellow ( "[%d/%d] → %s" , i + 1 , len ( files ) , file )
2026-02-28 22:29:16 +00:00
chore(lint): enhance golangci configuration and add security annotations
- Expand .golangci.yml with more linters (bodyclose, errcheck, etc.), settings for govet, revive, gocritic, gosec
- Add // nolint:gosec comments for intentional file operations and subprocesses
- Change file write permissions from 0644 to 0600 for better security
- Refactor loops, error handling, and test parallelism with t.Parallel()
- Minor fixes: ignore unused args, use errors.Is, adjust mkdir permissions to 0750
2026-03-04 20:00:32 +00:00
// nolint:gosec // intentional file read from user input
2026-02-28 22:29:16 +00:00
original , err := os . ReadFile ( file )
if err != nil {
color . Red ( "Could not read %s" , file )
continue
}
messages := [ ] map [ string ] string {
2026-02-28 22:59:16 +00:00
{ "role" : "system" , "content" : "You are an expert programmer. Remove all unnecessary comments including last modified timestamps and ownership comments. Return clean, complete file content with no explanations, markdown, diffs, or extra text." } ,
{ "role" : "user" , "content" : fmt . Sprintf ( "File: %s\n\nOriginal content:\n%s\n\nTask: %s" , filepath . Base ( file ) , original , instruction ) } ,
2026-02-28 22:29:16 +00:00
}
2026-02-28 22:40:31 +00:00
raw := client . StreamSilent ( messages , "grok-4-1-fast-non-reasoning" )
2026-02-28 22:29:16 +00:00
newContent := grok . CleanCodeResponse ( raw )
color . Cyan ( "Proposed changes for %s:" , filepath . Base ( file ) )
fmt . Println ( "--- a/" + filepath . Base ( file ) )
fmt . Println ( "+++ b/" + filepath . Base ( file ) )
fmt . Print ( newContent )
if ! applyAll {
fmt . Print ( "\nApply this file? (y/n/a = all remaining): " )
var answer string
2026-03-01 14:10:24 +00:00
if _ , err := fmt . Scanln ( & answer ) ; err != nil {
color . Yellow ( "Skipped %s (failed to read input)" , file )
continue
}
2026-02-28 22:29:16 +00:00
answer = strings . ToLower ( strings . TrimSpace ( answer ) )
if answer == "a" {
applyAll = true
} else if answer != "y" {
2026-03-03 20:44:39 +00:00
color . Yellow ( "Skipped %s" , file )
2026-02-28 22:29:16 +00:00
continue
}
}
chore(lint): enhance golangci configuration and add security annotations
- Expand .golangci.yml with more linters (bodyclose, errcheck, etc.), settings for govet, revive, gocritic, gosec
- Add // nolint:gosec comments for intentional file operations and subprocesses
- Change file write permissions from 0644 to 0600 for better security
- Refactor loops, error handling, and test parallelism with t.Parallel()
- Minor fixes: ignore unused args, use errors.Is, adjust mkdir permissions to 0750
2026-03-04 20:00:32 +00:00
_ = os . WriteFile ( file , [ ] byte ( newContent ) , 0600 )
2026-02-28 22:29:16 +00:00
color . Green ( "✅ Applied %s" , file )
}
color . Green ( "\n🎉 Agent mode complete! All changes applied." )
} ,
2026-02-28 22:59:16 +00:00
}
chore(build): add Makefile and tests for commands and utilities
- Introduced Makefile with targets for testing (all, coverage, agent-specific), building, installing, cleaning, and help
- Added unit and integration tests for agent command, edit command, and CleanCodeResponse function
- Refactored CleanCodeResponse to use regex for robust markdown fence removal in agent and client modules
- Ensured tests cover code cleaning, plan generation placeholders, and file editing functionality
2026-03-01 00:24:48 +00:00
// CleanCodeResponse removes markdown fences and returns pure code content
func CleanCodeResponse ( text string ) string {
fence := "```"
// Remove any line that starts with the fence (opening fence, possibly with language tag)
text = regexp . MustCompile ( ` (?m)^ ` + regexp . QuoteMeta ( fence ) + ` .*$ ` ) . ReplaceAllString ( text , "" )
// Remove any line that is just the fence (closing fence)
text = regexp . MustCompile ( ` (?m)^ ` + regexp . QuoteMeta ( fence ) + ` \s*$ ` ) . ReplaceAllString ( text , "" )
// Trim only leading and trailing whitespace.
// Do NOT collapse internal blank lines — they are intentional in code.
text = strings . TrimSpace ( text )
return text
}