docs(spec): Plan 4b force-reapply design
This commit is contained in:
161
docs/superpowers/specs/2026-04-14-force-reapply-design.md
Normal file
161
docs/superpowers/specs/2026-04-14-force-reapply-design.md
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
# Force Reapply Design (Plan 4b)
|
||||||
|
|
||||||
|
**Date:** 2026-04-14
|
||||||
|
**Scope:** Plan 4b of the Archduino Go rewrite — add `archduino install --force` that re-runs every selected module's Apply unconditionally.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Let the user bypass the four-phase contract's idempotency guard when they explicitly want to reapply everything — typically after a manual edit, corruption, or "I'm not sure what's broken, redo it" scenario. Scope is narrow: force a full reapply of currently-selected modules. No uninstall, no revert, no selective force-by-category.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
- No uninstall / reset / revert behavior. Modules have no `Revert` method today; adding one is a separate, much larger plan.
|
||||||
|
- No selective force (`--force-configs`, `--force-packages`). The user picked "full re-everything" during brainstorming; single flag only.
|
||||||
|
- No new subcommand (`archduino reinstall`). A flag on `install` is enough.
|
||||||
|
- No changes to `Verify`; force affects only what `Apply` does, not how we confirm it.
|
||||||
|
|
||||||
|
## Interface change
|
||||||
|
|
||||||
|
Add one method to `module.Module`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// PlanForce returns a plan that reapplies the module unconditionally,
|
||||||
|
// ignoring current detected state. Used by `archduino install --force`.
|
||||||
|
// Implementations should produce a plan equivalent to "install from
|
||||||
|
// scratch" — reinstall all packages (without pacman's --needed guard)
|
||||||
|
// and rewrite all managed config files.
|
||||||
|
PlanForce(ctx context.Context) (Plan, error)
|
||||||
|
```
|
||||||
|
|
||||||
|
Every existing module implements this. Two categories:
|
||||||
|
|
||||||
|
### Config-writing modules
|
||||||
|
`zsh`, `neovim`, `themes`, `starship`, `shellconfig`, `zellij`, `yazi` (config bits), `arduinocli` (sketch dirs, indexes).
|
||||||
|
|
||||||
|
`PlanForce` produces a plan that:
|
||||||
|
1. Reinstalls packages via `platform.Pacman.Reinstall` / `platform.Yay.Reinstall` (no `--needed`).
|
||||||
|
2. Rewrites every managed config file, overwriting existing content.
|
||||||
|
3. Re-runs any post-install steps (e.g., arduino-cli core update, zsh plugin clone).
|
||||||
|
|
||||||
|
### Package-only modules
|
||||||
|
`packages`, `gittools`, `fonts`, `renode`, and the package-only portions of `starship` / `yazi`.
|
||||||
|
|
||||||
|
`PlanForce` produces a plan that calls `platform.Pacman.Reinstall(ctx, pkgs)` — identical to the install step but without `--needed`, so pacman physically re-fetches and reinstalls the package even if already present.
|
||||||
|
|
||||||
|
## Platform layer additions
|
||||||
|
|
||||||
|
`internal/platform/pacman.go`:
|
||||||
|
```go
|
||||||
|
// Reinstall runs `pacman -S --noconfirm pkg...` without the --needed flag
|
||||||
|
// so pacman unconditionally reinstalls each package.
|
||||||
|
func (p Pacman) Reinstall(ctx context.Context, pkgs []string) error
|
||||||
|
```
|
||||||
|
|
||||||
|
`internal/platform/aur.go`:
|
||||||
|
```go
|
||||||
|
// Reinstall runs `yay -S --noconfirm pkg...` without the --needed flag.
|
||||||
|
func (y Yay) Reinstall(ctx context.Context, pkgs []string) error
|
||||||
|
```
|
||||||
|
|
||||||
|
Both are straight copies of the existing `Install` methods with `--needed` omitted from the command-line args.
|
||||||
|
|
||||||
|
## Engine changes
|
||||||
|
|
||||||
|
`internal/engine/engine.go`:
|
||||||
|
|
||||||
|
- `RunOptions` gains a field:
|
||||||
|
```go
|
||||||
|
Force bool
|
||||||
|
```
|
||||||
|
- `Run` per-module path becomes:
|
||||||
|
```go
|
||||||
|
var plan module.Plan
|
||||||
|
if opts.Force {
|
||||||
|
plan, err = mod.PlanForce(ctx)
|
||||||
|
} else {
|
||||||
|
cur, derr := mod.Detect(ctx)
|
||||||
|
if derr != nil { /* existing error handling */ }
|
||||||
|
plan, err = mod.Plan(ctx, cur)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
When `Force` is true, `Detect` is not called.
|
||||||
|
|
||||||
|
The Apply and Verify phases are unchanged. Force does not affect DAG ordering, reporter output, or halt-on-error behavior.
|
||||||
|
|
||||||
|
## CLI changes
|
||||||
|
|
||||||
|
`cmd/archduino/main.go::newInstallCmd()`:
|
||||||
|
|
||||||
|
```go
|
||||||
|
var force bool
|
||||||
|
cmd.Flags().BoolVar(&force, "force", false,
|
||||||
|
"Reapply every selected module, ignoring current state")
|
||||||
|
```
|
||||||
|
|
||||||
|
In `RunE`, propagate to both engine and wizard:
|
||||||
|
```go
|
||||||
|
runOpts.Force = force
|
||||||
|
initialState.Force = force
|
||||||
|
```
|
||||||
|
|
||||||
|
## Wizard / TUI integration
|
||||||
|
|
||||||
|
`internal/tui/state.go` — `WizardState` gains:
|
||||||
|
```go
|
||||||
|
Force bool
|
||||||
|
```
|
||||||
|
|
||||||
|
`internal/tui/wizard/wizard.go` — the `planCache` path branches:
|
||||||
|
- When `state.Force` is true, call `PlanForce(ctx)` instead of `Detect` + `Plan`.
|
||||||
|
- When a user toggles a module's selection, the cache key includes `force` so caches from non-force runs are not reused.
|
||||||
|
- Header gains a persistent banner: `⚠ Force mode: all selected modules will be reapplied`.
|
||||||
|
|
||||||
|
## Data flow
|
||||||
|
|
||||||
|
```
|
||||||
|
user: archduino install --force
|
||||||
|
→ main.go: force=true → RunOptions.Force = true, WizardState.Force = true
|
||||||
|
→ wizard.Run: per-module preview uses PlanForce (skips Detect)
|
||||||
|
→ user confirms
|
||||||
|
→ engine.Run: per-module PlanForce → Apply → Verify
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error handling
|
||||||
|
|
||||||
|
No new failure modes. `PlanForce` errors are handled identically to `Plan` errors: reported via the reporter, counted in the engine summary, respected by `--halt-on-error`.
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
**Per-module tests** — each existing `*_test.go` gains a `TestPlanForce` case. The test arranges a "satisfied" system state (mocked platform says everything is installed, configs exist), calls `PlanForce(ctx)`, and asserts the returned plan is non-empty and contains the expected reinstall/rewrite actions. This is the core behavioral contract — without this test, a module could silently "forget" to implement force.
|
||||||
|
|
||||||
|
**Engine test** — `engine_test.go` gets a case using a fake module that records whether `Detect`, `Plan`, and `PlanForce` were called. Runs with `RunOptions.Force: true` and asserts `Detect` was NOT called and `PlanForce` WAS.
|
||||||
|
|
||||||
|
**Platform tests** — `pacman_test.go` and `aur_test.go` each gain a `TestReinstall` that verifies the command-line args include `-S` and `--noconfirm` but do NOT include `--needed`.
|
||||||
|
|
||||||
|
**No TUI test for the banner** — visual-only, covered by the existing wizard tests for preview-cache behavior which will get a new "force preview" case.
|
||||||
|
|
||||||
|
## Interaction with existing features
|
||||||
|
|
||||||
|
- `--dry-run --force` prints the force plan and exits. Existing dry-run code path is unchanged.
|
||||||
|
- `--only`/`--skip` respect force: force applies only to the selected set.
|
||||||
|
- `--halt-on-error` respects force identically.
|
||||||
|
- Theme application at end of install (Plan 4a feature) runs unchanged; force doesn't skip it.
|
||||||
|
|
||||||
|
## File touch list
|
||||||
|
|
||||||
|
**Modified:**
|
||||||
|
- `internal/module/module.go` — add `PlanForce` to `Module` interface
|
||||||
|
- `internal/engine/engine.go` — add `RunOptions.Force`, branch in `Run`
|
||||||
|
- `internal/engine/engine_test.go` — new force test
|
||||||
|
- `internal/tui/state.go` — add `WizardState.Force`
|
||||||
|
- `internal/tui/wizard/wizard.go` — force-aware preview cache + banner
|
||||||
|
- `internal/tui/wizard/wizard_test.go` — new preview test with force
|
||||||
|
- `internal/platform/pacman.go` — add `Reinstall` method
|
||||||
|
- `internal/platform/pacman_test.go` — new test
|
||||||
|
- `internal/platform/aur.go` — add `Reinstall` method
|
||||||
|
- `internal/platform/aur_test.go` — new test
|
||||||
|
- `cmd/archduino/main.go` — add `--force` flag, wire through
|
||||||
|
- 13 module files (`internal/modules/*/*.go`) — implement `PlanForce`
|
||||||
|
- 13 module test files — `TestPlanForce` each
|
||||||
|
|
||||||
|
**New:** none.
|
||||||
Reference in New Issue
Block a user