refactor(analysis): refine prompts, language detection, and context building

- Updated Go analysis prompt for clarity, structure, and educational focus.
- Improved buildProjectContext to include shallow key files and cleaner Git remote handling.
- Implemented DetectPrimaryLanguage with counting logic and Go bias; added SupportedLanguages.
- Enhanced LoadAnalysisPrompt with better language handling, fallbacks, and error clarity.
This commit is contained in:
Greg Gauthier 2026-03-28 12:46:04 +00:00
parent b24b86723b
commit 09119ded37
4 changed files with 111 additions and 44 deletions

View File

@ -1,31 +1,29 @@
You are an expert Go educator and code archaeologist. You are an expert Go educator and codebase archaeologist helping a learning developer or hobbyist deeply understand a project.
Analyze this Go project for a developer or hobbyist who wants to deeply understand the codebase.
Produce a single, well-structured Markdown report with these exact sections: Generate a single, clean, educational Markdown report with these exact sections (use proper Markdown):
# Project Analysis: {{Project Name}} # Project Analysis: [Inferred Project Name]
## Tech Stack & Layout ## Tech Stack & Layout
- Primary language, version, build tools - Language/version, build system, key dependencies and why they were chosen
- Key dependencies and why they were chosen - High-level directory structure
- Directory structure overview
## Module / Package Relationships ## Module & Package Relationships
- How packages depend on each other (import graph) - How packages depend on each other
- Public APIs and their purpose - Main public APIs and their purpose
## Function & Method Reference ## Function & Method Reference
For every exported function/method: For each exported function/method (group by package):
- What it does (clear English) - What it does
- How it works (key logic, patterns) - How it works (key logic, patterns, idioms)
- Why it exists (design rationale) - Why it exists (design rationale or problem it solves)
## Object & Data Flow ## Object & Data Flow
- Major structs/types and their relationships - Important structs/types and their relationships
- Any database/ORM mappings if present - Any database/ORM mappings or persistence patterns (if present)
## Learning Path & Gotchas ## Learning Path & Gotchas
- Recommended reading order - Recommended order to read/understand the code
- Common pitfalls for newcomers - Common pitfalls or tricky parts for newcomers
Be educational, encouraging, and precise. Use code snippets only when they clarify. Be precise, encouraging, and educational. Use short code snippets only when they illuminate a concept. Do not add extra commentary outside the sections.

View File

@ -159,18 +159,28 @@ func previewLines(content string, n int) {
// buildProjectContext harvests useful context (tree, go.mod, git remote, etc.). // buildProjectContext harvests useful context (tree, go.mod, git remote, etc.).
// Expand this as needed for other languages. // Expand this as needed for other languages.
// buildProjectContext builds a concise but useful context block for the AI.
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 Directory: " + dir + "\n\n") sb.WriteString("Project Root: " + dir + "\n\n")
sb.WriteString(fmt.Sprintf("Total source files: %d\n\n", len(files)))
// Simple tree summary (or call tree/git ls-files if desired) // Top-level files only (avoid token explosion)
sb.WriteString("Source files found: " + fmt.Sprintf("%d\n\n", len(files))) sb.WriteString("Key files (top level):\n")
for _, f := range files {
// Git info rel, _ := filepath.Rel(dir, f)
if remotes, err := git.Run([]string{"remote", "-v"}); err == nil { if strings.Count(rel, string(filepath.Separator)) <= 2 { // shallow
sb.WriteString("Git Remotes:\n" + remotes + "\n\n") sb.WriteString(" - " + rel + "\n")
}
if sb.Len() > 2500 {
break
}
}
// Git remotes (if present)
if out, err := git.Run([]string{"remote", "-v"}); err == nil && out != "" {
sb.WriteString("\nGit Remotes:\n" + out + "\n")
} }
// Add more (go.mod, package.json, Cargo.toml, etc.) here for richer context
return sb.String() return sb.String()
} }

View File

@ -1,4 +1,55 @@
package linter package linter
// DetectPrimaryLanguage returns the most common language or falls back to "go" import (
func DetectPrimaryLanguage(files []string) string { ... } "path/filepath"
"strings"
)
// DetectPrimaryLanguage returns the dominant language in the given list of files.
// It counts occurrences of each detected language and returns the most frequent one.
// Falls back to "go" (common default) or "unknown".
func DetectPrimaryLanguage(files []string) string {
if len(files) == 0 {
return "unknown"
}
counts := make(map[string]int)
for _, file := range files {
lang, err := DetectLanguage(file)
if err == nil && lang != nil {
name := strings.ToLower(lang.Name)
counts[name]++
}
}
if len(counts) == 0 {
return "unknown"
}
// Find the language with the highest count
var bestLang string
maxCount := -1
for lang, count := range counts {
if count > maxCount {
maxCount = count
bestLang = lang
}
}
// Friendly bias toward Go in mixed repos (common case)
if counts["go"] > 0 && counts["go"] >= maxCount/2 {
return "go"
}
return bestLang
}
// SupportedLanguages returns a list of all languages known to the linter.
// Useful for error messages or future prompt discovery.
func SupportedLanguages() []string {
var langs []string
for _, l := range languages {
langs = append(langs, strings.ToLower(l.Name))
}
return langs
}

View File

@ -3,29 +3,37 @@ package prompts
import ( import (
"os" "os"
"path/filepath" "path/filepath"
"strings"
) )
// LoadAnalysisPrompt returns the path and content of the best matching prompt. // LoadAnalysisPrompt locates and reads a language-specific analysis prompt.
// Search order: {projectRoot}/.grokkit/prompts/{language}.md → ~/.config/grokkit/prompts/{language}.md // Search order (developer-first, project-local preferred):
// 1. {projectRoot}/.grokkit/prompts/{language}.md
// 2. ~/.config/grokkit/prompts/{language}.md
//
// Returns os.ErrNotExist if none found so the command can give a clear, actionable error.
func LoadAnalysisPrompt(projectRoot, language string) (path, content string, err error) { func LoadAnalysisPrompt(projectRoot, language string) (path, content string, err error) {
if language == "" { if language == "" || language == "unknown" {
language = "unknown" language = "go"
}
language = strings.ToLower(language)
// 1. Project-local (preferred for per-project customization)
local := filepath.Join(projectRoot, ".grokkit", "prompts", language+".md")
if data, readErr := os.ReadFile(local); readErr == nil {
return local, string(data), nil
} }
// 1. Project-local (preferred) // 2. Global fallback
localPath := filepath.Join(projectRoot, ".grokkit", "prompts", language+".md")
if data, readErr := os.ReadFile(localPath); readErr == nil {
return localPath, string(data), nil
}
// 2. Global config
home := os.Getenv("HOME") home := os.Getenv("HOME")
if home == "" { if home == "" {
home = "/root" // fallback for containers home = os.Getenv("USERPROFILE") // Windows
} }
globalPath := filepath.Join(home, ".config", "grokkit", "prompts", language+".md") if home != "" {
if data, readErr := os.ReadFile(globalPath); readErr == nil { global := filepath.Join(home, ".config", "grokkit", "prompts", language+".md")
return globalPath, string(data), nil if data, readErr := os.ReadFile(global); readErr == nil {
return global, string(data), nil
}
} }
return "", "", os.ErrNotExist return "", "", os.ErrNotExist