fix/markdown_awareness_for_edit #7

Merged
gmgauthier merged 4 commits from fix/markdown_awareness_for_edit into master 2026-03-30 13:05:10 +00:00
2 changed files with 8 additions and 33 deletions
Showing only changes of commit edb986dd1a - Show all commits

View File

@ -14,7 +14,7 @@ import (
"gmgauthier.com/grokkit/internal/git" "gmgauthier.com/grokkit/internal/git"
"gmgauthier.com/grokkit/internal/grok" "gmgauthier.com/grokkit/internal/grok"
"gmgauthier.com/grokkit/internal/linter" "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" "gmgauthier.com/grokkit/internal/prompts"
) )
@ -30,22 +30,15 @@ Uses language-specific prompts discovered in .grokkit/prompts/ or ~/.config/grok
} }
output := viper.GetString("output") output := viper.GetString("output")
if output == "" { if output == "" {
// Default to project-local context file inside .grokkit/
output = filepath.Join(".grokkit", "analysis.md") output = filepath.Join(".grokkit", "analysis.md")
} }
// Fixed: config.GetModel takes (commandName, flagModel)
model := config.GetModel("analyze", viper.GetString("model")) model := config.GetModel("analyze", viper.GetString("model"))
yes := viper.GetBool("yes") yes := viper.GetBool("yes")
// Logger (use the package-level logger as done in other commands) if !git.IsRepo() {
// (remove the old "log := logger.Get()")
// Safety check
if !git.IsRepo() { // IsRepo takes no arguments
logger.Warn("Not inside a git repository. Git metadata in report will be limited.") logger.Warn("Not inside a git repository. Git metadata in report will be limited.")
} }
// 1. Discover source files
files, err := discoverSourceFiles(dir) files, err := discoverSourceFiles(dir)
if err != nil { if err != nil {
logger.Error("Failed to discover source files", "dir", dir, "error", err) 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) os.Exit(1)
} }
// 2. Detect primary language
lang := linter.DetectPrimaryLanguage(files) lang := linter.DetectPrimaryLanguage(files)
if lang == "" { if lang == "" {
lang = "unknown" lang = "unknown"
} }
// 3. Load language-specific prompt (project → global)
promptPath, promptContent, err := prompts.LoadAnalysisPrompt(dir, lang) promptPath, promptContent, err := prompts.LoadAnalysisPrompt(dir, lang)
if err != nil { if err != nil {
fmt.Printf("Error: Could not find analysis prompt for language '%s'.\n\n", lang) 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) os.Exit(1)
} }
logger.Info("Loaded analysis prompt", "language", lang, "path", promptPath) logger.Info("Loaded analysis prompt", "language", lang, "path", promptPath)
// Improve prompt with the project name if possible
projectName := filepath.Base(dir) projectName := filepath.Base(dir)
if projectName == "." { if projectName == "." {
projectName = "Current Project" projectName = "Current Project"
} }
promptContent = strings.Replace(promptContent, "[Inferred Project Name]", projectName, 1) promptContent = strings.Replace(promptContent, "[Inferred Project Name]", projectName, 1)
// 4. Build rich project context
context := buildProjectContext(dir, files) context := buildProjectContext(dir, files)
// 5. Call Grok — use the exact working pattern you provided
messages := []map[string]string{ messages := []map[string]string{
{"role": "system", "content": promptContent}, {"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)}, {"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) report := grok.NewClient().StreamSilent(messages, model)
// 6. Transactional preview + confirmation
if !yes { if !yes {
fmt.Println("\n=== Proposed Analysis Report Preview (first 60 lines) ===") fmt.Println("\n=== Proposed Analysis Report Preview (first 60 lines) ===")
previewLines(report, 60) previewLines(report, 60)
@ -110,18 +95,15 @@ Uses language-specific prompts discovered in .grokkit/prompts/ or ~/.config/grok
} }
} }
// 7. Output
if output == "-" { if output == "-" {
fmt.Println(report) fmt.Println(report)
return return
} }
// Ensure .grokkit directory exists
if err := os.MkdirAll(".grokkit", 0755); err != nil { if err := os.MkdirAll(".grokkit", 0755); err != nil {
logger.Error("Failed to create .grokkit directory", "error", err) logger.Error("Failed to create .grokkit directory", "error", err)
os.Exit(1) os.Exit(1)
} }
// Write the file
if err := os.WriteFile(output, []byte(report), 0644); err != nil { if err := os.WriteFile(output, []byte(report), 0644); err != nil {
logger.Error("Failed to write report", "file", output, "error", err) logger.Error("Failed to write report", "file", output, "error", err)
os.Exit(1) os.Exit(1)
@ -138,8 +120,6 @@ func init() {
analyzeCmd.Flags().BoolP("yes", "y", false, "Skip confirmation prompt") 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) { func discoverSourceFiles(root string) ([]string, error) {
var files []string var files []string
err := filepath.Walk(root, func(path string, info os.FileInfo, err error) error { 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 return err
} }
// Skip common hidden/noise directories but still allow descent into source dirs
name := info.Name() name := info.Name()
if info.IsDir() { 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" { name == "node_modules" || name == "vendor" || name == "build" || name == "dist" {
return filepath.SkipDir return filepath.SkipDir
} }
return nil return nil
} }
// Check if this is a supported source file
if _, detectErr := linter.DetectLanguage(path); detectErr == nil { if _, detectErr := linter.DetectLanguage(path); detectErr == nil {
files = append(files, path) files = append(files, path)
} }
@ -166,7 +144,6 @@ func discoverSourceFiles(root string) ([]string, error) {
return files, err return files, err
} }
// previewLines prints the first N lines of the report (unchanged)
func previewLines(content string, n int) { func previewLines(content string, n int) {
scanner := bufio.NewScanner(strings.NewReader(content)) scanner := bufio.NewScanner(strings.NewReader(content))
for i := 0; i < n && scanner.Scan(); i++ { 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 { func buildProjectContext(dir string, files []string) string {
var sb strings.Builder var sb strings.Builder
sb.WriteString("Project Root: " + dir + "\n\n") 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") sb.WriteString("Key files (top-level view):\n")
for _, f := range files { 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 != "" { if out, err := git.Run([]string{"remote", "-v"}); err == nil && out != "" {
sb.WriteString("\nGit Remotes:\n" + out + "\n") sb.WriteString("\nGit Remotes:\n" + out + "\n")
} }

View File

@ -103,10 +103,7 @@ var editCmd = &cobra.Command{
func removeLastModifiedComments(content string) string { func removeLastModifiedComments(content string) string {
lines := strings.Split(content, "\n") lines := strings.Split(content, "\n")
cleanedLines := make([]string, 0, len(lines)) cleanedLines := make([]string, 0, len(lines))
cleanedLines = append(cleanedLines, lines...)
for _, line := range lines {
cleanedLines = append(cleanedLines, line)
}
return strings.Join(cleanedLines, "\n") return strings.Join(cleanedLines, "\n")
} }