feat: Introduce analyze Command for Deep Educational Codebase Analysis
#6
@ -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.
|
||||
|
||||
@ -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()
|
||||
}
|
||||
|
||||
@ -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
|
||||
}
|
||||
|
||||
@ -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
|
||||
|
||||
Loading…
Reference in New Issue
Block a user