feat(lint): add lint command with AI-powered fixes
Introduce new `grokkit lint` command for automatic language detection,
linting, and AI-suggested fixes. Supports 9 languages including Go, Python,
JavaScript, TypeScript, Rust, Ruby, Java, C/C++, and Shell.
- Add cmd/lint.go for command implementation
- Create internal/linter package with detection and execution logic
- Update README.md with usage examples and workflows
- Enhance docs/ARCHITECTURE.md and docs/TROUBLESHOOTING.md
- Add comprehensive tests for linter functionality
2026-03-01 13:21:44 +00:00
|
|
|
package cmd
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"fmt"
|
|
|
|
|
"os"
|
|
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
|
|
"github.com/fatih/color"
|
|
|
|
|
"github.com/spf13/cobra"
|
|
|
|
|
"gmgauthier.com/grokkit/config"
|
|
|
|
|
"gmgauthier.com/grokkit/internal/grok"
|
|
|
|
|
"gmgauthier.com/grokkit/internal/linter"
|
|
|
|
|
"gmgauthier.com/grokkit/internal/logger"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var (
|
|
|
|
|
dryRun bool
|
|
|
|
|
autoFix bool
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
var lintCmd = &cobra.Command{
|
|
|
|
|
Use: "lint <file>",
|
|
|
|
|
Short: "Lint a file and optionally apply AI-suggested fixes",
|
|
|
|
|
Long: `Automatically detects the programming language of a file, runs the appropriate
|
|
|
|
|
linter, and optionally uses Grok AI to apply suggested fixes.
|
|
|
|
|
|
|
|
|
|
The command will:
|
|
|
|
|
1. Detect the file's programming language
|
|
|
|
|
2. Check for available linters
|
|
|
|
|
3. Run the linter and report issues
|
|
|
|
|
4. (Optional) Use Grok AI to generate and apply fixes
|
|
|
|
|
|
|
|
|
|
Safety features:
|
|
|
|
|
- Creates backup before modifying files
|
|
|
|
|
- Shows preview of changes before applying
|
|
|
|
|
- Requires confirmation unless --auto-fix is used`,
|
|
|
|
|
Args: cobra.ExactArgs(1),
|
|
|
|
|
Run: runLint,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
|
rootCmd.AddCommand(lintCmd)
|
|
|
|
|
lintCmd.Flags().BoolVar(&dryRun, "dry-run", false, "Show linting issues without fixing")
|
|
|
|
|
lintCmd.Flags().BoolVar(&autoFix, "auto-fix", false, "Apply fixes without confirmation")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func runLint(cmd *cobra.Command, args []string) {
|
|
|
|
|
filePath := args[0]
|
|
|
|
|
|
|
|
|
|
logger.Info("starting lint operation", "file", filePath, "dry_run", dryRun, "auto_fix", autoFix)
|
|
|
|
|
|
|
|
|
|
// Check file exists
|
|
|
|
|
if _, err := os.Stat(filePath); os.IsNotExist(err) {
|
|
|
|
|
logger.Error("file not found", "file", filePath, "error", err)
|
|
|
|
|
color.Red("❌ File not found: %s", filePath)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Get absolute path for better logging
|
|
|
|
|
absPath, err := filepath.Abs(filePath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Warn("could not get absolute path", "file", filePath, "error", err)
|
|
|
|
|
absPath = filePath
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Run linter
|
|
|
|
|
color.Cyan("🔍 Detecting language and running linter...")
|
|
|
|
|
result, err := linter.LintFile(absPath)
|
|
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
// Check if it's because no linter is available
|
|
|
|
|
if result != nil && !result.LinterExists {
|
|
|
|
|
logger.Warn("no linter available", "file", absPath, "language", result.Language)
|
|
|
|
|
color.Yellow("⚠️ %s", result.Output)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
logger.Error("linting failed", "file", absPath, "error", err)
|
|
|
|
|
color.Red("❌ Failed to lint file: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Display results
|
|
|
|
|
color.Cyan("📋 Language: %s", result.Language)
|
|
|
|
|
color.Cyan("🔧 Linter: %s", result.LinterUsed)
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
if !result.HasIssues {
|
|
|
|
|
logger.Info("no linting issues found", "file", absPath, "linter", result.LinterUsed)
|
|
|
|
|
color.Green("✅ No issues found!")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Show linter output
|
|
|
|
|
logger.Info("linting issues found", "file", absPath, "exit_code", result.ExitCode)
|
|
|
|
|
color.Yellow("⚠️ Issues found:")
|
|
|
|
|
fmt.Println(strings.TrimSpace(result.Output))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Stop here if dry-run
|
|
|
|
|
if dryRun {
|
|
|
|
|
logger.Info("dry-run mode, skipping fixes", "file", absPath)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Read original file content
|
|
|
|
|
originalContent, err := os.ReadFile(absPath)
|
|
|
|
|
if err != nil {
|
|
|
|
|
logger.Error("failed to read file", "file", absPath, "error", err)
|
|
|
|
|
color.Red("❌ Failed to read file: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use Grok AI to generate fixes
|
|
|
|
|
color.Cyan("🤖 Asking Grok AI for fixes...")
|
|
|
|
|
logger.Info("requesting AI fixes", "file", absPath, "original_size", len(originalContent))
|
|
|
|
|
|
|
|
|
|
modelFlag, _ := cmd.Flags().GetString("model")
|
2026-03-02 16:56:56 +00:00
|
|
|
model := config.GetModel("lint", modelFlag)
|
feat(lint): add lint command with AI-powered fixes
Introduce new `grokkit lint` command for automatic language detection,
linting, and AI-suggested fixes. Supports 9 languages including Go, Python,
JavaScript, TypeScript, Rust, Ruby, Java, C/C++, and Shell.
- Add cmd/lint.go for command implementation
- Create internal/linter package with detection and execution logic
- Update README.md with usage examples and workflows
- Enhance docs/ARCHITECTURE.md and docs/TROUBLESHOOTING.md
- Add comprehensive tests for linter functionality
2026-03-01 13:21:44 +00:00
|
|
|
|
2026-03-02 20:47:16 +00:00
|
|
|
client := newGrokClient()
|
feat(lint): add lint command with AI-powered fixes
Introduce new `grokkit lint` command for automatic language detection,
linting, and AI-suggested fixes. Supports 9 languages including Go, Python,
JavaScript, TypeScript, Rust, Ruby, Java, C/C++, and Shell.
- Add cmd/lint.go for command implementation
- Create internal/linter package with detection and execution logic
- Update README.md with usage examples and workflows
- Enhance docs/ARCHITECTURE.md and docs/TROUBLESHOOTING.md
- Add comprehensive tests for linter functionality
2026-03-01 13:21:44 +00:00
|
|
|
messages := buildLintFixMessages(result, string(originalContent))
|
|
|
|
|
response := client.StreamSilent(messages, model)
|
|
|
|
|
|
|
|
|
|
if response == "" {
|
|
|
|
|
logger.Error("AI returned empty response", "file", absPath)
|
|
|
|
|
color.Red("❌ Failed to get AI response")
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Clean the response
|
|
|
|
|
fixedCode := grok.CleanCodeResponse(response)
|
|
|
|
|
logger.Info("received AI fixes", "file", absPath, "fixed_size", len(fixedCode))
|
|
|
|
|
|
|
|
|
|
// Show preview if not auto-fix
|
|
|
|
|
if !autoFix {
|
|
|
|
|
fmt.Println()
|
|
|
|
|
color.Cyan("📝 Preview of fixes:")
|
|
|
|
|
fmt.Println(strings.Repeat("-", 80))
|
|
|
|
|
// Show first 50 lines of fixed code
|
|
|
|
|
lines := strings.Split(fixedCode, "\n")
|
|
|
|
|
previewLines := 50
|
|
|
|
|
if len(lines) < previewLines {
|
|
|
|
|
previewLines = len(lines)
|
|
|
|
|
}
|
|
|
|
|
for i := 0; i < previewLines; i++ {
|
|
|
|
|
fmt.Println(lines[i])
|
|
|
|
|
}
|
|
|
|
|
if len(lines) > previewLines {
|
|
|
|
|
fmt.Printf("... (%d more lines)\n", len(lines)-previewLines)
|
|
|
|
|
}
|
|
|
|
|
fmt.Println(strings.Repeat("-", 80))
|
|
|
|
|
fmt.Println()
|
|
|
|
|
|
|
|
|
|
// Ask for confirmation
|
|
|
|
|
color.Yellow("Apply these fixes? (y/N): ")
|
|
|
|
|
var response string
|
2026-03-01 14:14:55 +00:00
|
|
|
if _, err := fmt.Scanln(&response); err != nil {
|
|
|
|
|
logger.Info("failed to read user input", "file", absPath, "error", err)
|
2026-03-03 20:44:39 +00:00
|
|
|
color.Yellow("❌ Cancelled. No changes made.")
|
2026-03-01 14:14:55 +00:00
|
|
|
return
|
|
|
|
|
}
|
feat(lint): add lint command with AI-powered fixes
Introduce new `grokkit lint` command for automatic language detection,
linting, and AI-suggested fixes. Supports 9 languages including Go, Python,
JavaScript, TypeScript, Rust, Ruby, Java, C/C++, and Shell.
- Add cmd/lint.go for command implementation
- Create internal/linter package with detection and execution logic
- Update README.md with usage examples and workflows
- Enhance docs/ARCHITECTURE.md and docs/TROUBLESHOOTING.md
- Add comprehensive tests for linter functionality
2026-03-01 13:21:44 +00:00
|
|
|
response = strings.ToLower(strings.TrimSpace(response))
|
|
|
|
|
|
|
|
|
|
if response != "y" && response != "yes" {
|
|
|
|
|
logger.Info("user cancelled fix application", "file", absPath)
|
2026-03-03 20:44:39 +00:00
|
|
|
color.Yellow("❌ Cancelled. No changes made.")
|
feat(lint): add lint command with AI-powered fixes
Introduce new `grokkit lint` command for automatic language detection,
linting, and AI-suggested fixes. Supports 9 languages including Go, Python,
JavaScript, TypeScript, Rust, Ruby, Java, C/C++, and Shell.
- Add cmd/lint.go for command implementation
- Create internal/linter package with detection and execution logic
- Update README.md with usage examples and workflows
- Enhance docs/ARCHITECTURE.md and docs/TROUBLESHOOTING.md
- Add comprehensive tests for linter functionality
2026-03-01 13:21:44 +00:00
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Apply fixes
|
|
|
|
|
if err := os.WriteFile(absPath, []byte(fixedCode), 0644); err != nil {
|
|
|
|
|
logger.Error("failed to write fixed file", "file", absPath, "error", err)
|
|
|
|
|
color.Red("❌ Failed to write file: %v", err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
logger.Info("fixes applied successfully", "file", absPath)
|
|
|
|
|
color.Green("✅ Fixes applied successfully!")
|
|
|
|
|
|
|
|
|
|
// Optionally run linter again to verify
|
|
|
|
|
color.Cyan("\n🔍 Re-running linter to verify fixes...")
|
|
|
|
|
verifyResult, err := linter.LintFile(absPath)
|
|
|
|
|
if err == nil && !verifyResult.HasIssues {
|
|
|
|
|
logger.Info("verification successful, no issues remain", "file", absPath)
|
|
|
|
|
color.Green("✅ Verification passed! No issues remaining.")
|
|
|
|
|
} else if err == nil && verifyResult.HasIssues {
|
|
|
|
|
logger.Warn("verification found remaining issues", "file", absPath, "exit_code", verifyResult.ExitCode)
|
|
|
|
|
color.Yellow("⚠️ Some issues may remain:")
|
|
|
|
|
fmt.Println(strings.TrimSpace(verifyResult.Output))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func buildLintFixMessages(result *linter.LintResult, originalCode string) []map[string]string {
|
|
|
|
|
systemPrompt := "You are a code quality expert. Fix linter issues and return only the corrected code with no explanations, markdown, or extra text."
|
|
|
|
|
|
|
|
|
|
userPrompt := fmt.Sprintf(`A linter has found issues in the following %s code.
|
|
|
|
|
|
|
|
|
|
Linter: %s
|
|
|
|
|
Issues found:
|
|
|
|
|
%s
|
|
|
|
|
|
|
|
|
|
Original code:
|
|
|
|
|
%s
|
|
|
|
|
|
|
|
|
|
Please fix all the issues identified by the linter. Return ONLY the corrected code without any explanations, markdown formatting, or code fences. The output should be ready to write directly to the file.
|
|
|
|
|
|
|
|
|
|
Important:
|
|
|
|
|
- Fix ALL issues reported by the linter
|
|
|
|
|
- Preserve all functionality
|
|
|
|
|
- Maintain the original code style as much as possible
|
|
|
|
|
- Do NOT include markdown code fences
|
|
|
|
|
- Do NOT include explanations or comments about the changes`,
|
|
|
|
|
result.Language,
|
|
|
|
|
result.LinterUsed,
|
|
|
|
|
strings.TrimSpace(result.Output),
|
|
|
|
|
originalCode)
|
|
|
|
|
|
|
|
|
|
return []map[string]string{
|
|
|
|
|
{"role": "system", "content": systemPrompt},
|
|
|
|
|
{"role": "user", "content": userPrompt},
|
|
|
|
|
}
|
|
|
|
|
}
|