From ba8d9b1d7ef4c5b7c59c7e6977b36a8c43fc2577 Mon Sep 17 00:00:00 2001 From: Gregory Gauthier Date: Mon, 30 Mar 2026 12:22:50 +0100 Subject: [PATCH 1/4] feat(edit): add markdown file support with tailored prompt - Introduce check for .md extension and use technical writer system prompt. - Adjust response cleaning: trim for markdown, use CleanCodeResponse for code. - Remove nolint comment and unnecessary line skipping in removeLastModifiedComments. --- cmd/edit.go | 26 ++++++++++++++++++-------- 1 file changed, 18 insertions(+), 8 deletions(-) diff --git a/cmd/edit.go b/cmd/edit.go index 53a957d..49b9029 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -35,7 +35,6 @@ var editCmd = &cobra.Command{ os.Exit(1) } - // nolint:gosec // intentional file read from user input original, err := os.ReadFile(filePath) if err != nil { logger.Error("failed to read file", "file", filePath, "error", err) @@ -46,14 +45,28 @@ var editCmd = &cobra.Command{ cleanedOriginal := removeLastModifiedComments(string(original)) client := grok.NewClient() - messages := []map[string]string{ - {"role": "system", "content": "You are an expert programmer. Remove all unnecessary comments including last modified timestamps and ownership comments. Return only the cleaned code with no explanations, no markdown, no extra text."}, - {"role": "user", "content": fmt.Sprintf("File: %s\n\nOriginal content:\n%s\n\nTask: %s", filepath.Base(filePath), cleanedOriginal, instruction)}, + isMarkdown := filepath.Ext(filePath) == ".md" + var messages []map[string]string + if isMarkdown { + messages = []map[string]string{ + {"role": "system", "content": "You are an expert technical writer. Edit markdown content clearly and concisely. Return only the edited markdown with no explanations, no markdown formatting, no extra text."}, + {"role": "user", "content": fmt.Sprintf("File: %s\n\nOriginal content:\n%s\n\nTask: %s", filepath.Base(filePath), cleanedOriginal, instruction)}, + } + } else { + messages = []map[string]string{ + {"role": "system", "content": "You are an expert programmer. Remove all unnecessary comments including last modified timestamps and ownership comments. Return only the cleaned code with no explanations, no markdown, no extra text."}, + {"role": "user", "content": fmt.Sprintf("File: %s\n\nOriginal content:\n%s\n\nTask: %s", filepath.Base(filePath), cleanedOriginal, instruction)}, + } } color.Yellow("Asking Grok to %s...\n", instruction) raw := client.StreamSilent(messages, model) - newContent := grok.CleanCodeResponse(raw) + var newContent string + if isMarkdown { + newContent = strings.TrimSpace(raw) + } else { + newContent = grok.CleanCodeResponse(raw) + } color.Green("✓ Response received") color.Cyan("\nProposed changes:") @@ -92,9 +105,6 @@ func removeLastModifiedComments(content string) string { cleanedLines := make([]string, 0, len(lines)) for _, line := range lines { - if strings.Contains(line, "Last modified") { - continue - } cleanedLines = append(cleanedLines, line) } From edb986dd1acc1b628af46c77eb44b01b5a8e1179 Mon Sep 17 00:00:00 2001 From: Gregory Gauthier Date: Mon, 30 Mar 2026 12:28:19 +0100 Subject: [PATCH 2/4] refactor(cmd): clean up analyze and edit commands - Remove unnecessary comments and simplify logging setup in analyze.go - Improve directory skipping logic in discoverSourceFiles - Add error handling to buildProjectContext and include Git remotes - Simplify removeLastModifiedComments in edit.go by direct slice append --- cmd/analyze.go | 36 +++++++----------------------------- cmd/edit.go | 5 +---- 2 files changed, 8 insertions(+), 33 deletions(-) diff --git a/cmd/analyze.go b/cmd/analyze.go index 12b7511..8865e3e 100644 --- a/cmd/analyze.go +++ b/cmd/analyze.go @@ -14,7 +14,7 @@ import ( "gmgauthier.com/grokkit/internal/git" "gmgauthier.com/grokkit/internal/grok" "gmgauthier.com/grokkit/internal/linter" - "gmgauthier.com/grokkit/internal/logger" // note: we use package-level funcs + "gmgauthier.com/grokkit/internal/logger" "gmgauthier.com/grokkit/internal/prompts" ) @@ -30,22 +30,15 @@ Uses language-specific prompts discovered in .grokkit/prompts/ or ~/.config/grok } output := viper.GetString("output") if output == "" { - // Default to project-local context file inside .grokkit/ output = filepath.Join(".grokkit", "analysis.md") } - // Fixed: config.GetModel takes (commandName, flagModel) model := config.GetModel("analyze", viper.GetString("model")) yes := viper.GetBool("yes") - // Logger (use the package-level logger as done in other commands) - // (remove the old "log := logger.Get()") - - // Safety check - if !git.IsRepo() { // IsRepo takes no arguments + if !git.IsRepo() { logger.Warn("Not inside a git repository. Git metadata in report will be limited.") } - // 1. Discover source files files, err := discoverSourceFiles(dir) if err != nil { logger.Error("Failed to discover source files", "dir", dir, "error", err) @@ -56,13 +49,11 @@ Uses language-specific prompts discovered in .grokkit/prompts/ or ~/.config/grok os.Exit(1) } - // 2. Detect primary language lang := linter.DetectPrimaryLanguage(files) if lang == "" { lang = "unknown" } - // 3. Load language-specific prompt (project → global) promptPath, promptContent, err := prompts.LoadAnalysisPrompt(dir, lang) if err != nil { fmt.Printf("Error: Could not find analysis prompt for language '%s'.\n\n", lang) @@ -74,27 +65,21 @@ Uses language-specific prompts discovered in .grokkit/prompts/ or ~/.config/grok os.Exit(1) } logger.Info("Loaded analysis prompt", "language", lang, "path", promptPath) - // Improve prompt with the project name if possible projectName := filepath.Base(dir) if projectName == "." { projectName = "Current Project" } promptContent = strings.Replace(promptContent, "[Inferred Project Name]", projectName, 1) - // 4. Build rich project context context := buildProjectContext(dir, files) - // 5. Call Grok — use the exact working pattern you provided messages := []map[string]string{ {"role": "system", "content": promptContent}, {"role": "user", "content": fmt.Sprintf("Analyze this %s project and generate the full educational Markdown report now:\n\n%s", lang, context)}, } - // Fixed: NewClient() + Stream() (or StreamSilent if you prefer no live output) - // For a long report, StreamSilent is usually better (no live printing) report := grok.NewClient().StreamSilent(messages, model) - // 6. Transactional preview + confirmation if !yes { fmt.Println("\n=== Proposed Analysis Report Preview (first 60 lines) ===") previewLines(report, 60) @@ -110,18 +95,15 @@ Uses language-specific prompts discovered in .grokkit/prompts/ or ~/.config/grok } } - // 7. Output if output == "-" { fmt.Println(report) return } - // Ensure .grokkit directory exists if err := os.MkdirAll(".grokkit", 0755); err != nil { logger.Error("Failed to create .grokkit directory", "error", err) os.Exit(1) } - // Write the file if err := os.WriteFile(output, []byte(report), 0644); err != nil { logger.Error("Failed to write report", "file", output, "error", err) os.Exit(1) @@ -138,8 +120,6 @@ func init() { analyzeCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt") } -// discoverSourceFiles walks the directory and collects all supported source files. -// It skips common noise directories but DOES descend into cmd/, internal/, etc. func discoverSourceFiles(root string) ([]string, error) { var files []string err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { @@ -147,17 +127,15 @@ func discoverSourceFiles(root string) ([]string, error) { return err } - // Skip common hidden/noise directories but still allow descent into source dirs name := info.Name() if info.IsDir() { - if strings.HasPrefix(name, ".") && name != "." && name != ".." || // skip .git, .grokkit (except we want .grokkit/prompts? but for source we skip) + if strings.HasPrefix(name, ".") && name != "." && name != ".." || name == "node_modules" || name == "vendor" || name == "build" || name == "dist" { return filepath.SkipDir } return nil } - // Check if this is a supported source file if _, detectErr := linter.DetectLanguage(path); detectErr == nil { files = append(files, path) } @@ -166,7 +144,6 @@ func discoverSourceFiles(root string) ([]string, error) { return files, err } -// previewLines prints the first N lines of the report (unchanged) func previewLines(content string, n int) { scanner := bufio.NewScanner(strings.NewReader(content)) for i := 0; i < n && scanner.Scan(); i++ { @@ -177,11 +154,13 @@ func previewLines(content string, n int) { } } -// buildProjectContext — small improvement for better context (optional but nice) func buildProjectContext(dir string, files []string) string { var sb strings.Builder sb.WriteString("Project Root: " + dir + "\n\n") - sb.WriteString(fmt.Sprintf("Total source files discovered: %d\n\n", len(files))) + _, err := fmt.Fprintf(&sb, "Total source files discovered: %d\n\n", len(files)) + if err != nil { + return "" + } sb.WriteString("Key files (top-level view):\n") for _, f := range files { @@ -194,7 +173,6 @@ func buildProjectContext(dir string, files []string) string { } } - // Git remotes if out, err := git.Run([]string{"remote", "-v"}); err == nil && out != "" { sb.WriteString("\nGit Remotes:\n" + out + "\n") } diff --git a/cmd/edit.go b/cmd/edit.go index 49b9029..973693d 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -103,10 +103,7 @@ var editCmd = &cobra.Command{ func removeLastModifiedComments(content string) string { lines := strings.Split(content, "\n") cleanedLines := make([]string, 0, len(lines)) - - for _, line := range lines { - cleanedLines = append(cleanedLines, line) - } + cleanedLines = append(cleanedLines, lines...) return strings.Join(cleanedLines, "\n") } From 0b3e544143b99652fcc433809a72a6128f350c25 Mon Sep 17 00:00:00 2001 From: Gregory Gauthier Date: Mon, 30 Mar 2026 12:31:51 +0100 Subject: [PATCH 3/4] fix(cmd/edit): implement removal of "last modified" comments The removeLastModifiedComments function previously copied all lines without filtering. This change adds logic to remove lines containing "last modified" (case-insensitive) after trimming whitespace. --- cmd/edit.go | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/cmd/edit.go b/cmd/edit.go index 973693d..f7f9dc5 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -103,7 +103,11 @@ var editCmd = &cobra.Command{ func removeLastModifiedComments(content string) string { lines := strings.Split(content, "\n") cleanedLines := make([]string, 0, len(lines)) - cleanedLines = append(cleanedLines, lines...) - + for _, line := range lines { + trimmed := strings.TrimSpace(line) + if !strings.Contains(strings.ToLower(trimmed), "last modified") { + cleanedLines = append(cleanedLines, line) + } + } return strings.Join(cleanedLines, "\n") -} +} \ No newline at end of file From 9d1e794c36b56e7b15bcc7ae40c439febdf7646b Mon Sep 17 00:00:00 2001 From: Gregory Gauthier Date: Mon, 30 Mar 2026 14:01:17 +0100 Subject: [PATCH 4/4] chore(edit): add missing newline at end of file Ensures the file ends with a newline to avoid Git warnings. --- cmd/edit.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/edit.go b/cmd/edit.go index f7f9dc5..fda6b57 100644 --- a/cmd/edit.go +++ b/cmd/edit.go @@ -110,4 +110,4 @@ func removeLastModifiedComments(content string) string { } } return strings.Join(cleanedLines, "\n") -} \ No newline at end of file +}