feat(cli): add core go command group

Add Go development tools under `core go`:
- test: Run tests with coverage (CGO_ENABLED=0)
- fmt: Format code with goimports/gofmt
- lint: Run golangci-lint
- mod: Module management (tidy, download, verify, graph)
- work: Workspace management (sync, init, use)

Update SKILL.md with Go Development section.
Keep `core test` at root for backward compatibility.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Snider 2026-01-29 12:22:01 +00:00
parent ecee1635cd
commit 2d2b63af39
3 changed files with 490 additions and 49 deletions

View file

@ -13,8 +13,12 @@ The `core` command provides a unified interface for Go/Wails development, multi-
| Task | Command | Notes |
|------|---------|-------|
| Run Go tests | `core test` | Sets macOS deployment target, filters warnings |
| Run Go tests with coverage | `core test --coverage` | Per-package breakdown |
| Run Go tests | `core go test` | Sets macOS deployment target, filters warnings |
| Run Go tests with coverage | `core go test --coverage` | Per-package breakdown |
| Format Go code | `core go fmt --fix` | Uses goimports/gofmt |
| Lint Go code | `core go lint` | Uses golangci-lint |
| Tidy Go modules | `core go mod tidy` | go mod tidy wrapper |
| Sync Go workspace | `core go work sync` | go work sync wrapper |
| Run PHP tests | `core php test` | Auto-detects Pest/PHPUnit |
| Start PHP dev server | `core php dev` | FrankenPHP + Vite + Horizon + Reverb |
| Format PHP code | `core php fmt --fix` | Laravel Pint |
@ -38,51 +42,6 @@ The `core` command provides a unified interface for Go/Wails development, multi-
| Update packages | `core pkg update` | Pull latest for all packages |
| Run VM | `core vm run <image>` | Run LinuxKit VM |
## Testing
**Always use `core test` instead of `go test`.**
```bash
# Run all tests with coverage summary
core test
# Detailed per-package coverage
core test --coverage
# Test specific packages
core test --pkg ./pkg/crypt
# Run specific tests
core test --run TestHash
core test --run "Test.*Good"
# Skip integration tests
core test --short
# Race detection
core test --race
# JSON output for CI/parsing
core test --json
```
**Why:** Sets `MACOSX_DEPLOYMENT_TARGET=26.0` to suppress linker warnings, filters noise from output, provides colour-coded coverage.
### JSON Output
For programmatic use:
```json
{
"passed": 14,
"failed": 0,
"skipped": 0,
"coverage": 75.1,
"exit_code": 0,
"failed_packages": []
}
```
## Building
**Always use `core build` instead of `go build`.**
@ -218,6 +177,102 @@ core pkg update core-api # Update specific package
core pkg outdated
```
## Go Development
**Always use `core go` commands instead of raw go commands.**
### Quick Reference
| Task | Command | Notes |
|------|---------|-------|
| Run tests | `core go test` | CGO_ENABLED=0, filters warnings |
| Run tests with coverage | `core go test --coverage` | Per-package breakdown |
| Format code | `core go fmt --fix` | Uses goimports if available |
| Lint code | `core go lint` | Uses golangci-lint |
| Tidy modules | `core go mod tidy` | go mod tidy |
| Sync workspace | `core go work sync` | go work sync |
### Testing
```bash
# Run all tests
core go test
# With coverage
core go test --coverage
# Specific package
core go test --pkg ./pkg/errors
# Run specific tests
core go test --run TestHash
# Short tests only
core go test --short
# Race detection
core go test --race
# JSON output for CI
core go test --json
# Verbose
core go test -v
```
**Why:** Sets `CGO_ENABLED=0` and `MACOSX_DEPLOYMENT_TARGET=26.0`, filters linker warnings, provides colour-coded coverage.
### Formatting & Linting
```bash
# Check formatting
core go fmt
# Fix formatting
core go fmt --fix
# Show diff
core go fmt --diff
# Run linter
core go lint
# Lint with auto-fix
core go lint --fix
```
### Module Management
```bash
# Tidy go.mod
core go mod tidy
# Download dependencies
core go mod download
# Verify dependencies
core go mod verify
# Show dependency graph
core go mod graph
```
### Workspace Management
```bash
# Sync workspace
core go work sync
# Initialize workspace
core go work init
# Add module to workspace
core go work use ./pkg/mymodule
# Auto-add all modules
core go work use
```
## PHP Development
**Always use `core php` commands instead of raw artisan/composer/phpunit.**
@ -416,7 +471,10 @@ core vm templates vars <name> # Show template variables
```
Go project?
└── Run tests: core test [--coverage]
└── Run tests: core go test [--coverage]
└── Format: core go fmt --fix
└── Lint: core go lint
└── Tidy modules: core go mod tidy
└── Build: core build [--targets <os/arch>]
└── Release: core release
@ -454,7 +512,9 @@ Managing packages?
| Wrong | Right | Why |
|-------|-------|-----|
| `go test ./...` | `core test` | Missing deployment target, noisy output |
| `go test ./...` | `core go test` | CGO disabled, filters warnings, coverage |
| `go fmt ./...` | `core go fmt --fix` | Uses goimports, consistent |
| `golangci-lint run` | `core go lint` | Consistent interface |
| `go build` | `core build` | Missing cross-compile, signing, checksums |
| `php artisan serve` | `core php dev` | Missing Vite, Horizon, Reverb, Redis |
| `./vendor/bin/pest` | `core php test` | Inconsistent invocation |

380
cmd/core/cmd/go.go Normal file
View file

@ -0,0 +1,380 @@
package cmd
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"regexp"
"strings"
"github.com/charmbracelet/lipgloss"
"github.com/leaanthony/clir"
)
// AddGoCommands adds Go development commands.
func AddGoCommands(parent *clir.Cli) {
goCmd := parent.NewSubCommand("go", "Go development tools")
goCmd.LongDescription("Go development tools with enhanced output and environment setup.\n\n" +
"Commands:\n" +
" test Run tests with coverage\n" +
" fmt Format Go code\n" +
" lint Run golangci-lint\n" +
" mod Module management (tidy, download, verify)\n" +
" work Workspace management")
addGoTestCommand(goCmd)
addGoFmtCommand(goCmd)
addGoLintCommand(goCmd)
addGoModCommand(goCmd)
addGoWorkCommand(goCmd)
}
func addGoTestCommand(parent *clir.Command) {
var (
coverage bool
pkg string
run string
short bool
race bool
json bool
verbose bool
)
testCmd := parent.NewSubCommand("test", "Run tests with coverage")
testCmd.LongDescription("Run Go tests with coverage reporting.\n\n" +
"Sets MACOSX_DEPLOYMENT_TARGET=26.0 to suppress linker warnings.\n" +
"Filters noisy output and provides colour-coded coverage.\n\n" +
"Examples:\n" +
" core go test\n" +
" core go test --coverage\n" +
" core go test --pkg ./pkg/crypt\n" +
" core go test --run TestHash")
testCmd.BoolFlag("coverage", "Show detailed per-package coverage", &coverage)
testCmd.StringFlag("pkg", "Package to test (default: ./...)", &pkg)
testCmd.StringFlag("run", "Run only tests matching regexp", &run)
testCmd.BoolFlag("short", "Run only short tests", &short)
testCmd.BoolFlag("race", "Enable race detector", &race)
testCmd.BoolFlag("json", "Output JSON results", &json)
testCmd.BoolFlag("v", "Verbose output", &verbose)
testCmd.Action(func() error {
return runGoTest(coverage, pkg, run, short, race, json, verbose)
})
}
func runGoTest(coverage bool, pkg, run string, short, race, jsonOut, verbose bool) error {
if pkg == "" {
pkg = "./..."
}
args := []string{"test"}
if coverage {
args = append(args, "-cover")
} else {
args = append(args, "-cover")
}
if run != "" {
args = append(args, "-run", run)
}
if short {
args = append(args, "-short")
}
if race {
args = append(args, "-race")
}
if verbose {
args = append(args, "-v")
}
args = append(args, pkg)
if !jsonOut {
fmt.Printf("%s Running tests\n", dimStyle.Render("Test:"))
fmt.Printf(" %s %s\n", dimStyle.Render("Package:"), pkg)
fmt.Println()
}
cmd := exec.Command("go", args...)
cmd.Env = append(os.Environ(), "MACOSX_DEPLOYMENT_TARGET=26.0", "CGO_ENABLED=0")
cmd.Dir, _ = os.Getwd()
output, err := cmd.CombinedOutput()
outputStr := string(output)
// Filter linker warnings
lines := strings.Split(outputStr, "\n")
var filtered []string
for _, line := range lines {
if !strings.Contains(line, "ld: warning:") {
filtered = append(filtered, line)
}
}
outputStr = strings.Join(filtered, "\n")
// Parse results
passed, failed, skipped := parseTestResults(outputStr)
cov := parseOverallCoverage(outputStr)
if jsonOut {
fmt.Printf(`{"passed":%d,"failed":%d,"skipped":%d,"coverage":%.1f,"exit_code":%d}`,
passed, failed, skipped, cov, cmd.ProcessState.ExitCode())
fmt.Println()
return err
}
// Print filtered output if verbose or failed
if verbose || err != nil {
fmt.Println(outputStr)
}
// Summary
if err == nil {
fmt.Printf(" %s %d passed\n", successStyle.Render("✓"), passed)
} else {
fmt.Printf(" %s %d passed, %d failed\n", errorStyle.Render("✗"), passed, failed)
}
if cov > 0 {
covStyle := successStyle
if cov < 50 {
covStyle = errorStyle
} else if cov < 80 {
covStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("#f59e0b"))
}
fmt.Printf("\n %s %s\n", dimStyle.Render("Coverage:"), covStyle.Render(fmt.Sprintf("%.1f%%", cov)))
}
if err == nil {
fmt.Printf("\n%s\n", successStyle.Render("PASS All tests passed"))
} else {
fmt.Printf("\n%s\n", errorStyle.Render("FAIL Some tests failed"))
}
return err
}
func parseTestResults(output string) (passed, failed, skipped int) {
passRe := regexp.MustCompile(`(?m)^ok\s+`)
failRe := regexp.MustCompile(`(?m)^FAIL\s+`)
skipRe := regexp.MustCompile(`(?m)^\?\s+`)
passed = len(passRe.FindAllString(output, -1))
failed = len(failRe.FindAllString(output, -1))
skipped = len(skipRe.FindAllString(output, -1))
return
}
func parseOverallCoverage(output string) float64 {
re := regexp.MustCompile(`coverage:\s+([\d.]+)%`)
matches := re.FindAllStringSubmatch(output, -1)
if len(matches) == 0 {
return 0
}
var total float64
for _, m := range matches {
var cov float64
fmt.Sscanf(m[1], "%f", &cov)
total += cov
}
return total / float64(len(matches))
}
func addGoFmtCommand(parent *clir.Command) {
var (
fix bool
diff bool
check bool
)
fmtCmd := parent.NewSubCommand("fmt", "Format Go code")
fmtCmd.LongDescription("Format Go code using gofmt or goimports.\n\n" +
"Examples:\n" +
" core go fmt # Check formatting\n" +
" core go fmt --fix # Fix formatting\n" +
" core go fmt --diff # Show diff")
fmtCmd.BoolFlag("fix", "Fix formatting in place", &fix)
fmtCmd.BoolFlag("diff", "Show diff of changes", &diff)
fmtCmd.BoolFlag("check", "Check only, exit 1 if not formatted", &check)
fmtCmd.Action(func() error {
args := []string{}
if fix {
args = append(args, "-w")
}
if diff {
args = append(args, "-d")
}
if !fix && !diff {
args = append(args, "-l")
}
args = append(args, ".")
// Try goimports first, fall back to gofmt
var cmd *exec.Cmd
if _, err := exec.LookPath("goimports"); err == nil {
cmd = exec.Command("goimports", args...)
} else {
cmd = exec.Command("gofmt", args...)
}
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
}
func addGoLintCommand(parent *clir.Command) {
var fix bool
lintCmd := parent.NewSubCommand("lint", "Run golangci-lint")
lintCmd.LongDescription("Run golangci-lint on the codebase.\n\n" +
"Examples:\n" +
" core go lint\n" +
" core go lint --fix")
lintCmd.BoolFlag("fix", "Fix issues automatically", &fix)
lintCmd.Action(func() error {
args := []string{"run"}
if fix {
args = append(args, "--fix")
}
cmd := exec.Command("golangci-lint", args...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
}
func addGoModCommand(parent *clir.Command) {
modCmd := parent.NewSubCommand("mod", "Module management")
modCmd.LongDescription("Go module management commands.\n\n" +
"Commands:\n" +
" tidy Add missing and remove unused modules\n" +
" download Download modules to local cache\n" +
" verify Verify dependencies\n" +
" graph Print module dependency graph")
// tidy
tidyCmd := modCmd.NewSubCommand("tidy", "Tidy go.mod")
tidyCmd.Action(func() error {
cmd := exec.Command("go", "mod", "tidy")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
// download
downloadCmd := modCmd.NewSubCommand("download", "Download modules")
downloadCmd.Action(func() error {
cmd := exec.Command("go", "mod", "download")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
// verify
verifyCmd := modCmd.NewSubCommand("verify", "Verify dependencies")
verifyCmd.Action(func() error {
cmd := exec.Command("go", "mod", "verify")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
// graph
graphCmd := modCmd.NewSubCommand("graph", "Print dependency graph")
graphCmd.Action(func() error {
cmd := exec.Command("go", "mod", "graph")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
}
func addGoWorkCommand(parent *clir.Command) {
workCmd := parent.NewSubCommand("work", "Workspace management")
workCmd.LongDescription("Go workspace management commands.\n\n" +
"Commands:\n" +
" sync Sync go.work with modules\n" +
" init Initialize go.work\n" +
" use Add module to workspace")
// sync
syncCmd := workCmd.NewSubCommand("sync", "Sync workspace")
syncCmd.Action(func() error {
cmd := exec.Command("go", "work", "sync")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
// init
initCmd := workCmd.NewSubCommand("init", "Initialize workspace")
initCmd.Action(func() error {
cmd := exec.Command("go", "work", "init")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
// Auto-add current module if go.mod exists
if _, err := os.Stat("go.mod"); err == nil {
cmd = exec.Command("go", "work", "use", ".")
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
}
return nil
})
// use
useCmd := workCmd.NewSubCommand("use", "Add module to workspace")
useCmd.Action(func() error {
args := useCmd.OtherArgs()
if len(args) == 0 {
// Auto-detect modules
modules := findGoModules(".")
if len(modules) == 0 {
return fmt.Errorf("no go.mod files found")
}
for _, mod := range modules {
cmd := exec.Command("go", "work", "use", mod)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
return err
}
fmt.Printf("Added %s\n", mod)
}
return nil
}
cmdArgs := append([]string{"work", "use"}, args...)
cmd := exec.Command("go", cmdArgs...)
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
return cmd.Run()
})
}
func findGoModules(root string) []string {
var modules []string
filepath.Walk(root, func(path string, info os.FileInfo, err error) error {
if err != nil {
return nil
}
if info.Name() == "go.mod" && path != "go.mod" {
modules = append(modules, filepath.Dir(path))
}
return nil
})
return modules
}

View file

@ -46,6 +46,7 @@ func Execute() error {
AddPkgCommands(app)
AddReleaseCommand(app)
AddContainerCommands(app)
AddGoCommands(app)
AddPHPCommands(app)
AddSDKCommand(app)
AddTestCommand(app)