Modules: cmd/devstack, internal/cli, internal/xdg (+ release/self-update) · Milestone: M0 · Effort: ~7w
The command surface, the multi-alias mechanism (one binary invocable as rq, uranus, …), config plumbing, completions, packaging/release, and self-update — the scaffolding everything hangs off.
- Engine:
github.com/spf13/cobra, wrapped byfang(charm.land/fang/v2— vanity import, notgithub.com/charmbracelet/fang) for styled help/errors, auto--version,man,completion. fang is a thin, removable layer — your commands stay vanilla cobra (drop back in a day if fang stalls). - Config:
github.com/knadh/koanf/v2(+providers/file,env,posflag;parsers/yaml); precedenceflags > env > project > defaults. Not viper (force-lowercases keys → breaks env-var names +${ref}keys; bloats the binary). koanf is not goroutine-safe — load once, treat immutable; env keys arrive lowercased so add an env-provider transform callback when merging cased sources. - Multi-alias =
argv[0]dispatch: readfilepath.Base(os.Args[0]); if it matches a registered alias, set branding (prompt name, env-var prefix) but run the identical command tree.devstack alias add rqsymlinks the binary inXDG_BIN_HOMEand records it in the global config (this installer also replaces devdock's direnv/bootstrap activation).--as <name>override for tests. (Verified:rq/uranussymlinks both dispatch from one binary — the git/busybox pattern.) - Tech-gated dynamic subcommands: register stack-specific commands (e.g.
composer,artisan,npm) only when the workspace uses that stack —cmd.AddCommandconditionally, mirroring devdock's gating. - Completions: cobra's engine via fang's
completion; registerValidArgsFunctiononshell <container>,docker exec <container>,ws git … <name>so completions resolve live service/project names from the parsed config. - State/XDG:
github.com/adrg/xdgforConfigHome/DataHome/StateHome/CacheHome/<tool>. Global shared-service registry here; per-project config committed in the repo. Template cache needs a GC/TTL (grows unbounded). WSL2: refuse/mnt/*working dirs; keep state on the Linux side. - Layout:
/cmd/<tool>/main.go(thin: alias dispatch +fang.Execute+ ldflags version),/internal/*(all impl),/pkg/pluginsdk(reserved, v2),/templates(go:embed). - Global flags & contract:
--json/--quiet/--debug/--verbose/--no-telemetry(opt-out for the default-OFF telemetry and the update notifier — spec 20/spec 14), unified error rendering, structured logging vialog/slog;--debuglogs every external command run (docker/git/sql/platform-CLI). Define the machine-readable--jsonoutput contract forup/status/doctor/logs. - Release: goreleaser OSS + GitHub Actions on tag →
darwin/{amd64,arm64}+linux/{amd64,arm64}(CGO_ENABLED=0, one Linux runner), archives + checksums + Homebrew tap +.deb/.rpm(nfpm) + cosign/minisign-signed checksums (signing deferrable post-v1). Version/commit/date via-ldflags. - Self-update:
github.com/creativeprojects/go-selfupdateagainst goreleaser releases; detect install method (Homebrew Cellar / dpkg ownership) and refuse to self-replace a package-managed binary; after update, re-point alias symlinks + run the state-DB migration. - Plugins: deferred to v2 — built-in providers compile in-process behind Go interfaces; no
go-plugin/gRPC in v1. Native Gopluginpackage rejected (OS-limited, version-brittle, panics crash host).
devstack up [project...] [--profile P]... [--build] [--rebuild] [--no-hooks] [--skip-clone] [--no-preflight] [--health-timeout D] [--json]
devstack down [project...] # whole-project teardown; autostop default off
devstack status # multi-repo git + service + ref-graph + last-saga table
devstack shell <service>
devstack logs [service...] [-f] [--since D] [--tail N] [--timestamps] [--json] # spec 16
devstack doctor [--fix] [--rebuild-state] [--json]
devstack ws clone|sync|status [all|<name>...]
devstack shared status|gc|doctor
devstack db gc # reclaim ORPHANED provisioned roles/dbs/buckets (confirm); v2 adds snapshot/restore/reset/list/pull (spec 15)
devstack secrets login <provider> | keygen
devstack trust install|uninstall|status
devstack dns setup # opt-in .test
devstack tunnel login|create|route|up|down
devstack alias add|remove <name>
devstack template init|lint|test # v2 registry verbs: push|add|update|diff|ls|verify (spec 19)
devstack import <path/to/devdock/project.yaml> [--dry-run] [--out DIR] [--force] # optional migrator
devstack self update [--force] # refuses on package-managed installs
devstack workspace destroy [--purge-data] [--yes] # this workspace's stacks/refs (+ data with --purge-data)
devstack uninstall [--yes] # reverse ALL machine-global artifacts (see ARCHITECTURE §7.3)
# Post-1.0 verbs (reserved in the tree now so completions/branding/help stay consistent):
devstack db snapshot|restore|reset|list|pull # v2 — per-project data lifecycle (spec 15)
devstack dashboard # v2 — live TUI cockpit (spec 16)
devstack ide gen [--target T] [--offline] # v2 — devcontainer/.code-workspace/launch (spec 17)
devstack template push|add|update|diff|ls|verify # v2 — versioned registry (spec 19)
devstack telemetry enable|disable|status|show|reset # later — opt-in, default OFF (spec 20)
Verb semantics live in their feature specs:
up/down/status+ saga flags (spec 09);--profileslices (spec 12); health gating /--health-timeout(spec 10);--no-hooks(spec 11);doctor/workspace destroy/uninstall/db gc(spec 13);self update/import(spec 14).
- fang import path
charm.land/fang/v2(vanity), Go 1.25, "experimental" — wrap, pin. - Windows (non-WSL2) lacks reliable symlinks for aliasing → generate
.cmdshims or steer Windows users to WSL2 (where symlink dispatch works). Native Windows is best-effort (Q-PLATFORM). - Self-update + aliases + state schema can brick the install — run a forward-only state migration (with backup) on first launch after a version change; re-point all alias symlinks; refuse on package-managed installs.
docker/docker/clientis deprecated — the SDK wrapper usesmoby/moby/client(spec 03, DECISIONS D4).
- One built binary, symlinked as
rqanduranus, dispatches the same command tree under each name with per-alias branding. -
alias add rqcreates the symlink inXDG_BIN_HOMEand records it;alias removereverses it. - Tech-gated
composerappears only when the workspace uses a PHP stack. -
shell <TAB>completes live service names from the current workspace config. -
goreleaserproduces all 4 OS/arch artifacts from one Linux runner withCGO_ENABLED=0. -
self updaterefuses on a Homebrew-installed binary and directs the user tobrew upgrade. -
--jsononstatus/doctoremits the documented machine-readable schema.
Foundational — consumed by everything. Owns the global flag/error/log contract that other modules render through (ARCHITECTURE §7.6).
Q-NAME (canonical name + alias set — needed to wire installer/completion), Q-PLATFORM (native Windows scope).