# 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 # Host only. SSH+tmux into VM. flow remote list # List configured targets. flow dev create -i # VM only. Create+start container. flow dev attach # Attach to container tmux session. flow dev exec [cmd...] # Run command in container. flow dev enter # Interactive shell in container. flow dev list # List dev containers. flow dev stop # Stop container. flow dev rm # Remove container. flow dev respawn # Respawn tmux panes. flow dotfiles init --repo # 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 # 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 # Show profile plan. flow packages install # Install packages from manifest. flow packages list [--all] # List packages. flow packages remove # 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` -> `dot` - `packages` -> `package`, `pkg` - `projects` -> `project` - `setup` -> `bootstrap` - `dev attach` -> `dev connect` - `dev rm` -> `dev remove` - `dotfiles 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=` 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/push` operate only on the dotfiles repo - `sync_modules` handles module repos separately - `list_modules` is 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 ```python # 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.py` extracted as adapters (spec had them inline) - `core/config_parse.py` + `core/yaml.py` extracted for config parsing - `SystemRuntime` extended with `.containers` and `.tmux` fields --- ## 5. Code Quality (done) These were fixed in this session: - `FakeRunner` consolidated from 3 test files into `tests/fakes.py` - `services/dotfiles.py` now uses `flow.core.yaml.load_yaml_file` instead of raw `yaml` - `ContainerRuntime.binary` no 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 1. `RepoInfo` model with `module_ref` field 2. `_discover_repos()` finds dotfiles + module repos 3. `repos_list/status/pull/push` iterate all repos with `--repo` filter 4. `repos list` subcommand added 5. `dotfiles modules` subcommand removed entirely 6. `dotfiles sync`, `relink`, `undo`, `clean` removed 7. Broken-symlink handling folded into `plan_link` 8. `dotfiles init` uses unified `repos_pull()` ### Phase B: Complete features -- DONE 9. `dotfiles edit` implemented (pull -> $EDITOR -> commit+push, scoped git add) 10. `dotfiles status` enhanced (module info, link health, package filtering) ### Phase C: CLI polish -- DONE 11. `--no-color` global flag added to `cli.py` 12. `--dry-run` added to `repos pull` and `repos push` 13. Zsh completion updated for new command surface ### Phase D: Code quality -- DONE 14. Dispatch patterns: completion `complete()`, dotfiles `_git_checkout_ref`, bootstrap phases 15. `FakeRunner` consolidated to single `tests/fakes.py` (all 4 test files) 16. Bootstrap `VALID_PHASES` as single source of truth in models 17. Bootstrap models: `Any` types replaced with `ProfilePackageEntry` and `PackageDef` 18. Canonical `ssh-keys` field (removed `ssh-keygen` alias) 19. `getattr` defensive patterns removed from command handlers 20. Test coverage added: `repos_status`, `repos_push`, `repos_pull --dry-run`, status filtering, broken symlink repair 21. README updated to reflect new command surface ### Remaining (deferred) - Bootstrap as orchestrator (section 6.1) - Global `--dry-run` (section 6.2)