CLI buat scaffold Go project yang dirancang buat AI agent kerja di dalamnya. User go install, lalu wick init <name>, dapat project lengkap: cmd/ di root handle server/worker/module registration, Makefile install tailwind.exe + templ, agent.md kasih agent konteks project.
- Primary: AI coding agent (Claude Code, Cursor, dll). Konvensi, naming, file layout optimized biar agent langsung paham tanpa explorasi panjang.
- Secondary: human developer yang collaborate sama agent.
- Scaffolding only. Wick nggak di-import runtime. Sekali pakai pas
init, user OWN semua file hasil scaffold. - Distribution:
go install github.com/you/wick@latest. - Template source: folder
example/— nested module, runnable, dual-purpose (template + integration test fixture). - Bundling:
//go:embed all:example. - Filter pas init: skip
*_test.go,testdata/,bin/,.env. - Dev toolchain via Makefile:
make installdownloadtailwindcss.exe+templ.exeke./bin/(version-pinned, reproducible, no global install). agent.mdwajib di scaffolded project — agent-facing docs.
myapp/
├── go.mod
├── Makefile # install tailwind+templ, dev/build/test commands
├── main.go # entry — panggil cmd.Execute()
├── cmd/ # ROOT cmd: init, server, worker, modules
│ ├── root.go # cobra root
│ ├── init.go # bootstrap hook (load env, seed, dll)
│ ├── server.go # `myapp server` — start HTTP
│ ├── worker.go # `myapp worker` — start background jobs
│ └── modules.go # register tools + jobs (SATU tempat)
├── internal/
│ ├── config/ # config loader
│ ├── server/ # chi + middleware stack
│ ├── worker/ # job runner
│ ├── tools/
│ │ └── sample/ # NewTools() pattern
│ └── jobs/
│ └── sample/ # NewJob() pattern
├── web/
│ ├── input.css # tailwind source
│ ├── layout.templ
│ └── static/
│ └── output.css # tailwind output (generated)
├── agent.md # AI agent instructions — WAJIB
├── README.md # human-facing
├── .claude/
│ ├── settings.json
│ └── skills/
├── .vscode/
│ ├── settings.json
│ ├── launch.json
│ └── extensions.json
├── .gitignore # ignore bin/, output.css, dll
├── Dockerfile
├── tailwind.config.js
└── bin/ # generated by `make install` — NOT scaffolded
├── tailwindcss.exe
└── templ.exe
wick/
├── go.mod # module: github.com/you/wick
├── main.go # CLI entry
├── cmd/
│ ├── root.go # cobra root (`wick`)
│ ├── init.go # `wick init <name>`
│ ├── init_test.go # scaffold integration test
│ └── version.go # `wick version`
└── example/ # template + test fixture (mirror scaffolded layout)
├── go.mod # module: example
├── Makefile
├── main.go
├── cmd/
├── internal/
├── web/
├── agent.md
├── README.md
├── .claude/
├── .vscode/
├── .gitignore
├── Dockerfile
├── tailwind.config.js
└── *_test.go # integration tests (skipped pas init)
package main
import "example/cmd"
func main() { cmd.Execute() }Declare root command. Semua subcommand di-register via init() di file lain di package cmd.
Subcommand myapp init (optional): seed DB, generate secret, first-run setup. BUKAN runtime init (itu di server.go/worker.go).
var serverCmd = &cobra.Command{
Use: "server",
RunE: func(c *cobra.Command, args []string) error {
cfg, err := config.Load()
if err != nil { return err }
s := server.New(cfg)
w := worker.New(cfg)
registerModules(s, w) // <- dari modules.go
return s.Start(c.Context())
},
}
func init() { rootCmd.AddCommand(serverCmd) }Sama pattern — panggil registerModules(nil, w) lalu w.Start(ctx).
package cmd
import (
tsample "example/internal/tools/sample"
jsample "example/internal/jobs/sample"
"example/internal/server"
"example/internal/worker"
)
func registerModules(s *server.Server, w *worker.Worker) {
if s != nil {
s.RegisterTool(tsample.NewTools())
}
if w != nil {
w.RegisterJob(jsample.NewJob())
}
}Kenapa dipisah: agent tau persis di mana nambah tool/job baru. Satu grep registerModules → ketahuan semua wiring.
Template content:
# Agent Instructions — myapp
## Project Overview
[1-2 lines — apa yang app ini lakuin]
## Stack
- Go + cobra (cmd/)
- chi router + templ + tailwindcss (web/)
- Background worker untuk jobs
## Where to Add What
- **Tool (UI/handler):** `internal/tools/<name>/` dengan `NewTools()` constructor, register di `cmd/modules.go`
- **Job (background):** `internal/jobs/<name>/` dengan `NewJob()` constructor, register di `cmd/modules.go`
- **Route tambahan manual:** `internal/server/routes.go`
- **Config field baru:** `internal/config/config.go`
## Commands (via Makefile)
- `make install` — install tailwindcss.exe + templ.exe ke ./bin/ (sekali di awal)
- `make dev` — templ watch + tailwind watch + `go run . server`
- `make build` — generate templ, build tailwind, compile binary
- `make test` — `go test ./...`
## Dev Loop
1. Edit `.templ` file → `templ generate` (auto via `make dev`)
2. Edit `web/input.css` → tailwind regenerate `web/static/output.css` (auto)
3. Restart server untuk Go changes
## Testing Convention
- Unit test di `*_test.go` sebelahan file-nya
- Integration test pakai stub session (`internal/auth/testhelper.go`)
- SSO/external service → mock via interfaceTAILWIND_VERSION := 3.4.0
TEMPL_VERSION := 0.2.747
BIN := bin
TAILWIND := $(BIN)/tailwindcss.exe
TEMPL := $(BIN)/templ.exe
.PHONY: install dev build test clean
install: $(TAILWIND) $(TEMPL)
$(TAILWIND):
@mkdir -p $(BIN)
curl -L -o $@ https://github.com/tailwindlabs/tailwindcss/releases/download/v$(TAILWIND_VERSION)/tailwindcss-windows-x64.exe
$(TEMPL):
@mkdir -p $(BIN)
GOBIN=$(abspath $(BIN)) go install github.com/a-h/templ/cmd/templ@v$(TEMPL_VERSION)
dev: install
$(TEMPL) generate --watch &
$(TAILWIND) -i web/input.css -o web/static/output.css --watch &
go run . server
build: install
$(TEMPL) generate
$(TAILWIND) -i web/input.css -o web/static/output.css --minify
go build -o $(BIN)/myapp.exe .
test:
go test ./...
clean:
rm -rf $(BIN) web/static/output.cssKenapa ./bin/ bukan global install:
- Version-pinned per project
- Agent nggak perlu check system PATH
- Reproducible di CI
make cleanbenar-benar clean
wick init myapp- Walk
embed.FSrooted diexample/ - Per file, filter:
- Skip
*_test.go(test-only, Wick-internal) - Skip
testdata/directory - Skip
bin/directory (generated) - Skip
.env - Skip
web/static/output.css(generated)
- Skip
- Path rewrite:
example/...→myapp/... - Content rewrite:
module example→module myapp"example/internal/...→"myapp/internal/...- Makefile:
-o $(BIN)/example.exe→-o $(BIN)/myapp.exe
- Print next steps (agent-friendly):
✓ Scaffolded myapp/ Next: cd myapp make install # install tailwindcss.exe + templ.exe make dev # start server + watchers Read agent.md untuk konvensi project.
//go:embed all:example
var tmpl embed.FS
func runInit(name string) error {
if _, err := os.Stat(name); err == nil {
return fmt.Errorf("directory %q already exists", name)
}
return fs.WalkDir(tmpl, "example", func(path string, d fs.DirEntry, err error) error {
if err != nil { return err }
base := filepath.Base(path)
if strings.HasSuffix(base, "_test.go") { return nil }
if d.IsDir() && (base == "testdata" || base == "bin") { return fs.SkipDir }
if base == ".env" { return nil }
if path == "example/web/static/output.css" { return nil }
target := strings.Replace(path, "example", name, 1)
if d.IsDir() {
return os.MkdirAll(target, 0755)
}
data, err := tmpl.ReadFile(path)
if err != nil { return err }
data = bytes.ReplaceAll(data, []byte("module example"), []byte("module "+name))
data = bytes.ReplaceAll(data, []byte(`"example/`), []byte(`"`+name+`/`))
data = bytes.ReplaceAll(data, []byte("example.exe"), []byte(name+".exe"))
return os.WriteFile(target, data, 0644)
})
}cd example
make install
make devgo install .rm -rf /tmp/foo
wick init /tmp/foo
cd /tmp/foo
make install && make buildTable-driven. Scaffold ke t.TempDir(), assert struktur + build sukses.
func TestInitScaffold(t *testing.T) {
tests := []struct {
name string
projectName string
mustExist []string
mustNotExist []string
runCmds [][]string
}{
{
name: "agent-ready scaffold",
projectName: "myapp",
mustExist: []string{
"go.mod", "main.go", "Makefile",
"agent.md", // critical untuk AI
"cmd/root.go", "cmd/modules.go", "cmd/server.go",
"internal/tools/sample", "internal/jobs/sample",
"web/input.css", "web/layout.templ",
".claude/settings.json",
".vscode/settings.json",
".gitignore", "Dockerfile", "tailwind.config.js",
},
mustNotExist: []string{
"testdata", "bin",
"cmd/init_test.go",
"web/static/output.css",
},
runCmds: [][]string{
{"go", "mod", "tidy"},
{"go", "build", "./..."},
{"go", "vet", "./..."},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
dir := t.TempDir()
target := filepath.Join(dir, tt.projectName)
require.NoError(t, runInit(target))
for _, p := range tt.mustExist {
_, err := os.Stat(filepath.Join(target, p))
assert.NoError(t, err, "missing %s", p)
}
for _, p := range tt.mustNotExist {
_, err := os.Stat(filepath.Join(target, p))
assert.Error(t, err, "should not exist: %s", p)
}
for _, args := range tt.runCmds {
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = target
out, err := cmd.CombinedOutput()
assert.NoError(t, err, "%v failed:\n%s", args, out)
}
})
}
}Di-skip pas init. Cover real routing/middleware/authz pakai stub session (guest/user/admin matrix, 401/403/404). SSO → mock only.
CI step terpisah: wick init /tmp/x && cd /tmp/x && make install && make build. Verify tailwind + templ beneran jalan. Lambat (network download) — jalanin nightly, bukan per-PR.
//go:embed default skip file/folder diawali . atau _. Fix: prefix all::
//go:embed all:example
var tmpl embed.FSScaffold integration test cover gotcha ini via mustExist: {".claude", ".vscode", ".gitignore"}.
| Command | Fungsi |
|---|---|
wick init <name> |
Scaffold project baru |
wick version |
Print versi Wick |
Scaffolded project punya command sendiri (via Makefile + cobra):
make install |
Download tailwindcss.exe + templ.exe ke ./bin/ |
make dev |
Watchers + server |
make build |
Build production binary |
./bin/myapp.exe server |
Run HTTP server |
./bin/myapp.exe worker |
Run background worker |
wick upgrade— template migration. User own hasil scaffold, upgrade manual.- Multi-template (
--template=api-only) — start single template, split nanti. - Cross-platform Makefile — windows-focused dulu (
.exe,curl). Nanti detect OS. - SSO integration testing — mock only, butuh secrets.
- Wick as runtime lib — scaffolding only, user OWN code hasil scaffold.
- Bikin repo
wick/—main.go+cmd/root.go+cmd/init.go+cmd/version.go. - Bikin
example/lengkap:- Makefile (install tailwind + templ)
main.go,cmd/{root,init,server,worker,modules}.gointernal/{config,server,worker,tools/sample,jobs/sample}/web/{input.css,layout.templ}+ sample pageagent.md(isi: overview, konvensi, commands, dev loop).claude/,.vscode/,.gitignore,Dockerfile,tailwind.config.js,README.md
- Pastiin
cd example && make install && make devjalan end-to-end. - Implement
cmd/init.go(embed + walk + rewrite + filter). - Scaffold integration test (
cmd/init_test.go) — table-driven, build output. - Write behavior tests di
example/(guest/user/admin matrix). - Polish:
wick version, README di wick repo (pitch "agent-first scaffolding"), semver v0.1.0.