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.
Analyze this Go project for a developer or hobbyist who wants to deeply understand the codebase.
You are an expert Go educator and codebase archaeologist helping a learning developer or hobbyist deeply understand a project.
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
- Primary language, version, build tools
- Key dependencies and why they were chosen
- Directory structure overview
- Language/version, build system, key dependencies and why they were chosen
- High-level directory structure
## Module / Package Relationships
- How packages depend on each other (import graph)
- Public APIs and their purpose
## Module & Package Relationships
- How packages depend on each other
- Main public APIs and their purpose
## Function & Method Reference
For every exported function/method:
- What it does (clear English)
- How it works (key logic, patterns)
- Why it exists (design rationale)
For each exported function/method (group by package):
- What it does
- How it works (key logic, patterns, idioms)
- Why it exists (design rationale or problem it solves)
## Object & Data Flow
- Major structs/types and their relationships
- Any database/ORM mappings if present
- Important structs/types and their relationships
- Any database/ORM mappings or persistence patterns (if present)
## Learning Path & Gotchas
- Recommended reading order
- Common pitfalls for newcomers
- Recommended order to read/understand the code
- 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.).
// 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 {
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)
sb.WriteString("Source files found: " + fmt.Sprintf("%d\n\n", len(files)))
// Git info
if remotes, err := git.Run([]string{"remote", "-v"}); err == nil {
sb.WriteString("Git Remotes:\n" + remotes + "\n\n")
// Top-level files only (avoid token explosion)
sb.WriteString("Key files (top level):\n")
for _, f := range files {
rel, _ := filepath.Rel(dir, f)
if strings.Count(rel, string(filepath.Separator)) <= 2 { // shallow
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()
}

View File

@ -1,4 +1,55 @@
package linter
// DetectPrimaryLanguage returns the most common language or falls back to "go"
func DetectPrimaryLanguage(files []string) string { ... }
import (
"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 (
"os"
"path/filepath"
"strings"
)
// LoadAnalysisPrompt returns the path and content of the best matching prompt.
// Search order: {projectRoot}/.grokkit/prompts/{language}.md → ~/.config/grokkit/prompts/{language}.md
// LoadAnalysisPrompt locates and reads a language-specific analysis prompt.
// 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) {
if language == "" {
language = "unknown"
if language == "" || 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)
localPath := filepath.Join(projectRoot, ".grokkit", "prompts", language+".md")
if data, readErr := os.ReadFile(localPath); readErr == nil {
return localPath, string(data), nil
}
// 2. Global config
// 2. Global fallback
home := os.Getenv("HOME")
if home == "" {
home = "/root" // fallback for containers
home = os.Getenv("USERPROFILE") // Windows
}
if home != "" {
global := filepath.Join(home, ".config", "grokkit", "prompts", language+".md")
if data, readErr := os.ReadFile(global); readErr == nil {
return global, string(data), nil
}
globalPath := filepath.Join(home, ".config", "grokkit", "prompts", language+".md")
if data, readErr := os.ReadFile(globalPath); readErr == nil {
return globalPath, string(data), nil
}
return "", "", os.ErrNotExist