feat: Introduce analyze Command for Deep Educational Codebase Analysis #6

Merged
gmgauthier merged 9 commits from feature/analyze_cmd into master 2026-03-28 16:11:39 +00:00
Showing only changes of commit 4084315dc1 - Show all commits

View File

@ -36,9 +36,11 @@ Uses language-specific prompts discovered in .grokkit/prompts/ or ~/.config/grok
model := config.GetModel("analyze", viper.GetString("model")) model := config.GetModel("analyze", viper.GetString("model"))
yes := viper.GetBool("yes") yes := viper.GetBool("yes")
// Fixed: use package-level logger funcs (no .Get()) // Logger (use the package-level logger as done in other commands)
// Safety: note if not in git repo (IsRepo takes no args) // (remove the old "log := logger.Get()")
if !git.IsRepo() {
// 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.")
} }
@ -125,28 +127,35 @@ func init() {
rootCmd.AddCommand(analyzeCmd) rootCmd.AddCommand(analyzeCmd)
} }
// discoverSourceFiles walks the directory and collects supported source files. // discoverSourceFiles walks the directory and collects all supported source files.
// Extend this if you want deeper recursion or ignore patterns. // 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 {
if err != nil { if err != nil {
return err return err
} }
if info.IsDir() && (strings.HasPrefix(info.Name(), ".") || info.Name() == "node_modules" || info.Name() == "vendor") {
return filepath.SkipDir // Skip common hidden/noise directories but still allow descent into source dirs
} name := info.Name()
if !info.IsDir() { if info.IsDir() {
if _, err := linter.DetectLanguage(path); err == nil { if strings.HasPrefix(name, ".") && name != "." && name != ".." || // skip .git, .grokkit (except we want .grokkit/prompts? but for source we skip)
files = append(files, path) name == "node_modules" || name == "vendor" || name == "build" || name == "dist" {
return filepath.SkipDir
} }
return nil
}
// Check if this is a supported source file
if _, detectErr := linter.DetectLanguage(path); detectErr == nil {
files = append(files, path)
} }
return nil return nil
}) })
return files, err return files, err
} }
// previewLines prints the first N lines of the report. // 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++ {
@ -157,19 +166,16 @@ func previewLines(content string, n int) {
} }
} }
// buildProjectContext harvests useful context (tree, go.mod, git remote, etc.). // buildProjectContext — small improvement for better context (optional but nice)
// 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 Root: " + dir + "\n\n") sb.WriteString("Project Root: " + dir + "\n\n")
sb.WriteString(fmt.Sprintf("Total source files: %d\n\n", len(files))) sb.WriteString(fmt.Sprintf("Total source files discovered: %d\n\n", len(files)))
// Top-level files only (avoid token explosion) sb.WriteString("Key files (top-level view):\n")
sb.WriteString("Key files (top level):\n")
for _, f := range files { for _, f := range files {
rel, _ := filepath.Rel(dir, f) rel, _ := filepath.Rel(dir, f)
if strings.Count(rel, string(filepath.Separator)) <= 2 { // shallow if strings.Count(rel, string(filepath.Separator)) <= 2 {
sb.WriteString(" - " + rel + "\n") sb.WriteString(" - " + rel + "\n")
} }
if sb.Len() > 2500 { if sb.Len() > 2500 {
@ -177,7 +183,7 @@ func buildProjectContext(dir string, files []string) string {
} }
} }
// Git remotes (if present) // 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")
} }