Files
x0gp/server/scripts/graphify/main.go
T

251 lines
6.5 KiB
Go

// 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 "?"
}
}