mirror of
https://github.com/IS1DI/x0gp.git
synced 2026-07-18 21:37:04 +00:00
251 lines
6.5 KiB
Go
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 "?"
|
|
}
|
|
}
|