feat(init): devstack init workspace authoring wizard (spec 22) - #80
Merged
Conversation
Adds the file-authoring front door: pick shared engines (postgres/redis/minio), fill typed params, and write a structurally-validated workspace.yaml. No lock, no Docker, no ledger — pure config authorship. Structured as the spec's "two faces, one builder": the flag path and the TUI both feed internal/scaffold. Foundation (internal/scaffold, fully unit-tested): - EmitWorkspaceYAML: deterministic ordered goccy emitter (sorted keys; params kept as strings so 16 never renders 16.0). - BuildWorkspace/resolveParams: Provides-filter, drop-at-default, required fail-fast, store-seed. - SanitizeName: CWD-basename -> valid dsname. - config.ValidateWorkspaceBytes: in-memory structural validation reusing the unexported structValidate/formatStructErr — a bad file is caught pre-write. Flag path (internal/cli/init.go): --name/--service engine@ver/--param svc.key=val/--alias/--project/--from-store/--out/--dry-run/--force/--json/ --no-input; atomic no-clobber write (+ backup); parent-workspace guard; RefuseWindowsMount. Interactive face (internal/prompt + internal/cli/init_tui.go): a Bubble Tea v2 wizard via charm.land/huh/v2 (engine multi-select, per-engine param forms with defaults, a confirm screen previewing the YAML in a lipgloss box), behind a shared prompt.IsInteractive gate so --json/--quiet/--no-input/non-TTY/CI always reach the builder without bubbletea. The charm v2 stack is pure-Go (CGO_ENABLED=0 4-target cross-build clean; no v1 charm). Tests: scaffold golden/determinism/build matrix, config validate, cli init (happy/json/dry-run/no-clobber+force/parent-guard/from-store/non-TTY gate), prompt IsInteractive matrix. make ci + determinism green. Deferred: the internal/migrate consolidation onto EmitWorkspaceYAML (pinned by the emit golden); a live two-pane preview is a polish increment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements feature #14 / spec 22 — the workspace authoring front door. Pick shared engines, fill typed params, write a structurally-validated
workspace.yaml. Pure config authorship: no lock, no Docker, no ledger.Structured as the spec's "two faces, one builder" — the flag path and the Bubble Tea TUI both populate one
scaffold.Inputsand call the same builder.Foundation (
internal/scaffold, fully unit-tested)EmitWorkspaceYAML— deterministic ordered goccyMapSliceemitter (sorted keys; params kept as strings so16never renders16.0).BuildWorkspace/resolveParams—Provides-filter (only engines that declareprovides:are shareable), drop-at-default, required-param fail-fast, store-seed.SanitizeName— CWD-basename → validdsname.config.ValidateWorkspaceBytes— in-memory structural validation reusing the unexportedstructValidate/formatStructErr; a bad file is caught pre-write, not as agenerateparse failure.Flag path (
internal/cli/init.go)--name --service engine@ver --param svc.key=val --alias --project name=path[,git=url] --from-store --out --dry-run --force --json --no-input. Atomic no-clobber write (temp+rename,--forcebacks up), parent-workspace guard,RefuseWindowsMount.Interactive face (
internal/prompt+internal/cli/init_tui.go)A Bubble Tea v2 wizard via
charm.land/huh/v2: engine multi-select (store-seeded defaults), per-engine param forms pre-filled with template defaults, and a confirm screen previewing the YAML in alipglossbox. Behind a sharedprompt.IsInteractivegate so--json/--quiet/--no-input/non-TTY/CI always reach the builder without launching bubbletea. The charm v2 stack is pure-Go —CGO_ENABLED=04-target cross-build is clean and no v1 charm entersgo.mod.(emitted block-style; collapsed here for brevity)
Tests / verification
internal/scaffold: golden bytes, determinism, int-not-float, Provides-filter, drop-at-default, required fail-fast, store-seed+override.internal/config:ValidateWorkspaceBytespass/fail matrix.internal/cli: registered, happy-path →LoadWorkspaceOnly, deterministic,--json,--dry-runwrites nothing, no-clobber +--forcebackup, parent-workspace refused,--from-store(never writes the store), non-TTY gate.internal/prompt:IsInteractivematrix.make ci+make determinismgreen; CGO=0 cross-build green.Manual TTY check still needed: the interactive wizard's keyboard flow (verified compiling + gated; the builder it feeds is fully unit-tested).
Deferred (next)
internal/migrateconsolidation ontoEmitWorkspaceYAML(low-risk — migrate tests are structural; byte-shape pinned by the emit golden).🤖 Generated with Claude Code