// Package main is the graphify utility. It walks the codebase, parses Go AST, // and outputs a highly compact symbol summary to server/CODEBASE.md. // This is used for token savings and helping developers/AI agents quickly map out imports and structures. package main import ( "bytes" "fmt" "go/ast" "go/parser" "go/token" "io/fs" "os" "path/filepath" "strings" ) type PackageSummary struct { Name string Path string Doc string Structs []string Interfaces []string Functions []string Methods []string } func main() { rootDir := "." outputFile := "CODEBASE.md" var buf bytes.Buffer buf.WriteString("# x0gp Go Codebase Map (Generated)\n\n") buf.WriteString("This file contains a compiled map of all packages, structures, interfaces, and public functions in the `server` directory. It is optimized for token-efficiency so AI agents and humans can understand the codebase layout without reading all files.\n\n") pkgs := make(map[string]*PackageSummary) fset := token.NewFileSet() err := filepath.WalkDir(rootDir, func(path string, d fs.DirEntry, err error) error { if err != nil { return err } if d.IsDir() { // Skip hidden and build dirs name := d.Name() if (name != "." && strings.HasPrefix(name, ".")) || name == "vendor" || name == "bin" || name == "docs" || name == "web" { return filepath.SkipDir } return nil } if !strings.HasSuffix(d.Name(), ".go") || strings.HasSuffix(d.Name(), "_test.go") { return nil } dir := filepath.Dir(path) pkgSum, exists := pkgs[dir] if !exists { pkgSum = &PackageSummary{ Path: dir, } pkgs[dir] = pkgSum } node, err := parser.ParseFile(fset, path, nil, parser.ParseComments) if err != nil { return nil // ignore unparseable files defensively } pkgSum.Name = node.Name.Name if node.Doc != nil && pkgSum.Doc == "" { pkgSum.Doc = strings.TrimSpace(node.Doc.Text()) } // Inspect AST for declarations ast.Inspect(node, func(n ast.Node) bool { switch decl := n.(type) { case *ast.GenDecl: for _, spec := range decl.Specs { if typeSpec, ok := spec.(*ast.TypeSpec); ok { if !ast.IsExported(typeSpec.Name.Name) { continue } switch typeType := typeSpec.Type.(type) { case *ast.StructType: structDesc := fmt.Sprintf("- **struct %s**", typeSpec.Name.Name) if typeType.Fields != nil && len(typeType.Fields.List) > 0 { fields := []string{} for _, f := range typeType.Fields.List { for _, id := range f.Names { if ast.IsExported(id.Name) { fields = append(fields, id.Name) } } } if len(fields) > 0 { structDesc += fmt.Sprintf(" { %s }", strings.Join(fields, ", ")) } } pkgSum.Structs = append(pkgSum.Structs, structDesc) case *ast.InterfaceType: methods := []string{} if typeType.Methods != nil { for _, m := range typeType.Methods.List { for _, id := range m.Names { if ast.IsExported(id.Name) { methods = append(methods, id.Name) } } } } pkgSum.Interfaces = append(pkgSum.Interfaces, fmt.Sprintf("- **interface %s** { %s }", typeSpec.Name.Name, strings.Join(methods, ", "))) } } } case *ast.FuncDecl: if !ast.IsExported(decl.Name.Name) { return true } // Format function signature params := []string{} if decl.Type.Params != nil { for _, p := range decl.Type.Params.List { for range p.Names { params = append(params, formatType(p.Type)) } if len(p.Names) == 0 { params = append(params, formatType(p.Type)) } } } results := []string{} if decl.Type.Results != nil { for _, r := range decl.Type.Results.List { for range r.Names { results = append(results, formatType(r.Type)) } if len(r.Names) == 0 { results = append(results, formatType(r.Type)) } } } sig := fmt.Sprintf("%s(%s)", decl.Name.Name, strings.Join(params, ", ")) if len(results) > 0 { if len(results) == 1 { sig += " " + results[0] } else { sig += " (" + strings.Join(results, ", ") + ")" } } if decl.Recv != nil && len(decl.Recv.List) > 0 { recvType := formatType(decl.Recv.List[0].Type) pkgSum.Methods = append(pkgSum.Methods, fmt.Sprintf("- func (%s) %s", recvType, sig)) } else { pkgSum.Functions = append(pkgSum.Functions, fmt.Sprintf("- func %s", sig)) } } return true }) return nil }) if err != nil { fmt.Fprintf(os.Stderr, "Error gathering package summaries: %v\n", err) os.Exit(1) } // Write out gathered summaries sorted/grouped by path // (we can sort keys or print them grouped by internal vs cmd) paths := []string{} for p := range pkgs { paths = append(paths, p) } // Simple sort: cmd/ first, then internal/ // We want to present it nicely for _, path := range paths { sum := pkgs[path] cleanPath := filepath.ToSlash(path) buf.WriteString(fmt.Sprintf("## Package `%s` (`%s`)\n\n", sum.Name, cleanPath)) if sum.Doc != "" { buf.WriteString(fmt.Sprintf("%s\n\n", sum.Doc)) } if len(sum.Interfaces) > 0 { buf.WriteString("### Interfaces\n") for _, item := range sum.Interfaces { buf.WriteString(item + "\n") } buf.WriteString("\n") } if len(sum.Structs) > 0 { buf.WriteString("### Structs\n") for _, item := range sum.Structs { buf.WriteString(item + "\n") } buf.WriteString("\n") } if len(sum.Functions) > 0 { buf.WriteString("### Functions\n") for _, item := range sum.Functions { buf.WriteString(item + "\n") } buf.WriteString("\n") } if len(sum.Methods) > 0 { buf.WriteString("### Methods\n") for _, item := range sum.Methods { buf.WriteString(item + "\n") } buf.WriteString("\n") } buf.WriteString("---\n\n") } err = os.WriteFile(outputFile, buf.Bytes(), 0644) if err != nil { fmt.Fprintf(os.Stderr, "Error writing CODEBASE.md: %v\n", err) os.Exit(1) } fmt.Printf("Generated %s successfully!\n", outputFile) } func formatType(expr ast.Expr) string { switch t := expr.(type) { case *ast.Ident: return t.Name case *ast.StarExpr: return "*" + formatType(t.X) case *ast.SelectorExpr: return formatType(t.X) + "." + t.Sel.Name case *ast.ArrayType: return "[]" + formatType(t.Elt) case *ast.MapType: return "map[" + formatType(t.Key) + "]" + formatType(t.Value) case *ast.InterfaceType: return "interface{}" case *ast.FuncType: return "func" default: return "?" } }