refactor(cli): restructure cmd packages into subdirectories
- Move CLI commands into subdirectories matching command hierarchy:
dev/, go/, php/, build/, ci/, sdk/, pkg/, vm/, docs/, setup/, doctor/, test/, ai/
- Create shared/ package for common styles and utilities
- Add new `core ai` root command with claude subcommand
- Update package declarations and imports across all files
- Create commands.go entry points for each package
- Remove GUI-related files (moved to core-gui repo)
This makes the filesystem structure match the CLI command structure,
improving context capture and code organization.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-29 18:02:43 +00:00
package dev
2025-10-28 12:06:05 +00:00
import (
"bytes"
"fmt"
"go/ast"
"go/parser"
"go/token"
"os"
"path/filepath"
"text/template"
"github.com/leaanthony/clir"
"golang.org/x/text/cases"
"golang.org/x/text/language"
)
2026-01-30 00:22:47 +00:00
// addSyncCommand adds the 'sync' command to the given parent command.
func addSyncCommand ( parent * clir . Command ) {
2025-10-28 12:06:05 +00:00
syncCmd := parent . NewSubCommand ( "sync" , "Synchronizes the public service APIs with their internal implementations." )
syncCmd . LongDescription ( "This command scans the 'pkg' directory for services and ensures that the\ntop-level public API for each service is in sync with its internal implementation.\nIt automatically generates the necessary Go files with type aliases." )
syncCmd . Action ( func ( ) error {
if err := runSync ( ) ; err != nil {
return fmt . Errorf ( "Error: %w" , err )
}
fmt . Println ( "Public APIs synchronized successfully." )
return nil
} )
}
type symbolInfo struct {
Name string
Kind string // "var", "func", "type", "const"
}
func runSync ( ) error {
pkgDir := "pkg"
internalDirs , err := os . ReadDir ( pkgDir )
if err != nil {
return fmt . Errorf ( "failed to read pkg directory: %w" , err )
}
for _ , dir := range internalDirs {
if ! dir . IsDir ( ) || dir . Name ( ) == "core" {
continue
}
serviceName := dir . Name ( )
internalFile := filepath . Join ( pkgDir , serviceName , serviceName + ".go" )
publicDir := serviceName
publicFile := filepath . Join ( publicDir , serviceName + ".go" )
if _ , err := os . Stat ( internalFile ) ; os . IsNotExist ( err ) {
continue
}
symbols , err := getExportedSymbols ( internalFile )
if err != nil {
return fmt . Errorf ( "error getting symbols for service '%s': %w" , serviceName , err )
}
if err := generatePublicAPIFile ( publicDir , publicFile , serviceName , symbols ) ; err != nil {
return fmt . Errorf ( "error generating public API file for service '%s': %w" , serviceName , err )
}
}
return nil
}
func getExportedSymbols ( path string ) ( [ ] symbolInfo , error ) {
fset := token . NewFileSet ( )
node , err := parser . ParseFile ( fset , path , nil , parser . ParseComments )
if err != nil {
return nil , err
}
var symbols [ ] symbolInfo
for name , obj := range node . Scope . Objects {
if ast . IsExported ( name ) {
kind := "unknown"
switch obj . Kind {
case ast . Con :
kind = "const"
case ast . Var :
kind = "var"
case ast . Fun :
kind = "func"
case ast . Typ :
kind = "type"
}
if kind != "unknown" {
symbols = append ( symbols , symbolInfo { Name : name , Kind : kind } )
}
}
}
return symbols , nil
}
const publicAPITemplate = ` // package {{.ServiceName}} provides the public API for the {{.ServiceName}} service.
package { { . ServiceName } }
import (
// Import the internal implementation with an alias.
2026-01-28 15:29:42 +00:00
impl "github.com/host-uk/core/{{.ServiceName}}"
2025-10-28 12:06:05 +00:00
// Import the core contracts to re-export the interface.
2026-01-28 15:29:42 +00:00
"github.com/host-uk/core/core"
2025-10-28 12:06:05 +00:00
)
{ { range . Symbols } }
{ { - if eq . Kind "type" } }
// {{.Name}} is the public type for the {{.Name}} service. It is a type alias
// to the underlying implementation, making it transparent to the user.
type { { . Name } } = impl . { { . Name } }
{ { else if eq . Kind "const" } }
// {{.Name}} is a public constant that points to the real constant in the implementation package.
const { { . Name } } = impl . { { . Name } }
{ { else if eq . Kind "var" } }
// {{.Name}} is a public variable that points to the real variable in the implementation package.
var { { . Name } } = impl . { { . Name } }
{ { else if eq . Kind "func" } }
// {{.Name}} is a public function that points to the real function in the implementation package.
var { { . Name } } = impl . { { . Name } }
{ { end } }
{ { end } }
// {{.InterfaceName}} is the public interface for the {{.ServiceName}} service.
type { { . InterfaceName } } = core . { { . InterfaceName } }
`
func generatePublicAPIFile ( dir , path , serviceName string , symbols [ ] symbolInfo ) error {
if err := os . MkdirAll ( dir , os . ModePerm ) ; err != nil {
return err
}
tmpl , err := template . New ( "publicAPI" ) . Parse ( publicAPITemplate )
if err != nil {
return err
}
tcaser := cases . Title ( language . English )
interfaceName := tcaser . String ( serviceName )
data := struct {
ServiceName string
Symbols [ ] symbolInfo
InterfaceName string
} {
ServiceName : serviceName ,
Symbols : symbols ,
InterfaceName : interfaceName ,
}
var buf bytes . Buffer
if err := tmpl . Execute ( & buf , data ) ; err != nil {
return err
}
return os . WriteFile ( path , buf . Bytes ( ) , 0644 )
}