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