10 KiB
Flow CLI Refactor Plan
Based on code review (2026-03-22) and architecture discussion. Spec:
docs/superpowers/specs/2026-03-16-flow-architecture-redesign.md
Current State
The rewrite from the original "vibe-coded" codebase is largely complete. The four-layer architecture (core -> domain -> services -> commands) is in place, 303 tests pass, and the major structural problems from the old codebase (duplicated code, monkeypatching, dead modules, singleton abuse) have been resolved.
What remains is a second pass: finishing incomplete features, unifying the repo abstraction, and trimming redundant commands.
Agreed Command Surface
From the architecture discussion. This is the target.
flow remote enter <target> # Host only. SSH+tmux into VM.
flow remote list # List configured targets.
flow dev create <name> -i <image> # VM only. Create+start container.
flow dev attach <name> # Attach to container tmux session.
flow dev exec <name> [cmd...] # Run command in container.
flow dev enter <name> # Interactive shell in container.
flow dev list # List dev containers.
flow dev stop <name> # Stop container.
flow dev rm <name> # Remove container.
flow dev respawn <name> # Respawn tmux panes.
flow dotfiles init --repo <url> # Clone dotfiles repo + all module repos.
flow dotfiles link [--profile p] # Reconcile symlinks (creates, fixes broken, removes stale).
flow dotfiles unlink [packages...] # Remove managed symlinks.
flow dotfiles status [packages...] # Show packages, link health, module info.
flow dotfiles edit <package> # Pull -> $EDITOR -> commit+push.
flow dotfiles repos list # List ALL managed repos (dotfiles + modules).
flow dotfiles repos status [--repo=x] # Git status for one or all repos.
flow dotfiles repos pull [--repo=x] # Pull one or all repos.
flow dotfiles repos push [--repo=x] # Push one or all repos.
flow setup run [--profile p] # Bootstrap a machine.
flow setup list # List profiles.
flow setup show <profile> # Show profile plan.
flow packages install <name...> # Install packages from manifest.
flow packages list [--all] # List packages.
flow packages remove <name...> # Remove packages.
flow projects check [--fetch] # VM only. Git health across ~/projects.
flow projects fetch # Fetch all project remotes.
flow projects summary # Quick status overview.
Aliases
dotfiles->dotpackages->package,pkgprojects->projectsetup->bootstrapdev attach->dev connectdev rm->dev removedotfiles repos->dotfiles repo
Global flags
--version--quiet/-q--no-color
Commands removed (vs current implementation)
| Removed | Reason |
|---|---|
dotfiles sync |
Redundant: repos pull + link |
dotfiles relink |
Redundant: link is idempotent |
dotfiles undo |
Redundant: unlink is the inverse of link |
dotfiles clean |
Folded into link (reconciliation handles broken symlinks) |
dotfiles modules list |
Replaced by dotfiles repos list |
dotfiles modules sync |
Replaced by dotfiles repos pull |
Key design decision: modules are repos
The _module.yaml files define external git repos that provide content for dotfiles packages.
These module repos are not git submodules -- they are regular git clones managed by flow.
The dotfiles repo itself is also a git clone managed by flow. So all managed git repos (the dotfiles repo + every module repo) share the same abstraction and the same commands.
dotfiles repos is the single entry point for all repo operations. There is no separate
dotfiles modules subcommand group. The --repo=<name> flag filters to a specific repo when
needed (dotfiles repo is named dotfiles, module repos are named by their package, e.g. nvim).
1. Unified Repos Abstraction
This is the most impactful change. Currently:
repo_status/pull/pushoperate only on the dotfiles reposync_moduleshandles module repos separatelylist_modulesis a standalone method
Target: a single _discover_repos() -> list[RepoInfo] that returns all managed repos, and
repo commands iterate over them.
1.1 Add RepoInfo model
# domain/dotfiles/models.py
@dataclass(frozen=True)
class RepoInfo:
name: str # "dotfiles" or module package name (e.g. "nvim")
path: Path # Local clone path
remote: str # Remote URL
is_module: bool # False for dotfiles repo, True for module repos
1.2 Implement _discover_repos
In DotfilesService: walk packages to find _module.yaml files, build RepoInfo for each
module repo, plus one for the dotfiles repo itself.
1.3 Refactor repo commands
Replace repo_status, repo_pull, repo_push with methods that iterate _discover_repos(),
filtered by --repo. Add repos_list.
1.4 Remove dotfiles modules subcommand group
Delete modules list and modules sync subparsers. Remove sync_modules, list_modules
methods. Remove from completion candidates.
1.5 Update dotfiles init
init should clone the dotfiles repo, then discover _module.yaml files and clone all module
repos. Currently it calls sync_modules() -- this should call repos_pull() instead (which
pulls/clones all repos).
Files: models.py, services/dotfiles.py, commands/dotfiles.py, commands/completion.py.
2. Remove Redundant Commands
2.1 Remove dotfiles sync
Currently does git pull --ff-only + sync_modules + optional relink. After the repos
unification, this is just repos pull + link. No need for a dedicated command.
2.2 Remove dotfiles relink
Currently calls link(). link is already idempotent -- calling it again reconciles state.
2.3 Remove dotfiles undo
unlink is the inverse of link. The backup/undo transaction machinery (_save_backup,
_load_backup, _backup_path) can be deleted.
2.4 Fold dotfiles clean into dotfiles link
link should detect and remove broken symlinks as part of reconciliation, not require a separate
clean step. Modify plan_link in domain/dotfiles/planning.py to include broken link removal
in its plan.
Files: services/dotfiles.py, commands/dotfiles.py, commands/completion.py,
domain/dotfiles/planning.py.
3. Previously Incomplete Features -- DONE
3.1 dotfiles edit -- DONE
Implemented: pull -> $EDITOR/$VISUAL -> scoped git add -> commit+push.
Flag: --no-commit to skip auto-commit/push.
3.2 dotfiles status -- DONE
Enhanced: shows module info (branch:main), link health (ok/broken/not linked),
package name filtering via positional args.
3.3 dotfiles repos list -- DONE
Shows all managed repos (dotfiles + modules) with: name, type, local path, clone status.
4. Spec Deviations
4.1 --no-color global flag -- DONE
Added to cli.py.
4.2 --dry-run coverage -- DONE for dotfiles
repos pull and repos push now have --dry-run. Remaining:
| Command | Has it | Should have |
|---|---|---|
dev stop |
No | Consider |
dev rm |
No | Consider |
4.3 Improvements over spec
These are correct deviations -- the implementation improved on the spec:
core/containers.py+core/tmux.pyextracted as adapters (spec had them inline)core/config_parse.py+core/yaml.pyextracted for config parsingSystemRuntimeextended with.containersand.tmuxfields
5. Code Quality (done)
These were fixed in this session:
FakeRunnerconsolidated from 3 test files intotests/fakes.pyservices/dotfiles.pynow usesflow.core.yaml.load_yaml_fileinstead of rawyamlContainerRuntime.binaryno longer eagerly validates PATH for explicit modes
6. Future (defer)
6.1 Bootstrap as orchestrator
The spec envisions bootstrap as a pure orchestrator over packages + dotfiles + setup modules. Current implementation works but has its own package resolution logic. Defer until dotfiles and packages domains are fully stable.
6.2 Global --dry-run
If per-command --dry-run becomes a maintenance burden, promote to a global flag in cli.py.
7. Execution Status
All phases complete. 315 tests pass, 0 failures.
Phase A: Unify repos + trim commands -- DONE
RepoInfomodel withmodule_reffield_discover_repos()finds dotfiles + module reposrepos_list/status/pull/pushiterate all repos with--repofilterrepos listsubcommand addeddotfiles modulessubcommand removed entirelydotfiles sync,relink,undo,cleanremoved- Broken-symlink handling folded into
plan_link dotfiles inituses unifiedrepos_pull()
Phase B: Complete features -- DONE
dotfiles editimplemented (pull -> $EDITOR -> commit+push, scoped git add)dotfiles statusenhanced (module info, link health, package filtering)
Phase C: CLI polish -- DONE
--no-colorglobal flag added tocli.py--dry-runadded torepos pullandrepos push- Zsh completion updated for new command surface
Phase D: Code quality -- DONE
- Dispatch patterns: completion
complete(), dotfiles_git_checkout_ref, bootstrap phases FakeRunnerconsolidated to singletests/fakes.py(all 4 test files)- Bootstrap
VALID_PHASESas single source of truth in models - Bootstrap models:
Anytypes replaced withProfilePackageEntryandPackageDef - Canonical
ssh-keysfield (removedssh-keygenalias) getattrdefensive patterns removed from command handlers- Test coverage added:
repos_status,repos_push,repos_pull --dry-run, status filtering, broken symlink repair - README updated to reflect new command surface
Remaining (deferred)
- Bootstrap as orchestrator (section 6.1)
- Global
--dry-run(section 6.2)