# Force Reapply Implementation Plan (Plan 4b) > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. **Goal:** Add `archduino install --force` that reapplies every selected module unconditionally — new `PlanForce` method on `module.Module`, new `Reinstall` helpers on `platform.Pacman`/`platform.Yay`, engine + wizard + CLI wiring. **Architecture:** Interface-level `PlanForce(ctx) (Plan, error)` gives each module an explicit "redo everything" path. Engine branches on `RunOptions.Force` to skip `Detect` and call `PlanForce`. Wizard mirrors that branch in its preview cache. Package-only modules reinstall without `--needed`; config-writing modules rewrite managed files. **Tech Stack:** Go, existing cobra + bubbletea stack. No new dependencies. **Spec:** `docs/superpowers/specs/2026-04-14-force-reapply-design.md` --- ## File Structure **Modified (no new files):** - `internal/platform/pacman.go` + `_test.go` — add `Reinstall` - `internal/platform/aur.go` + `_test.go` — add `Reinstall` - `internal/module/module.go` — add `PlanForce` to interface - `internal/engine/engine.go` + `_test.go` — add `RunOptions.Force` - `internal/tui/state.go` — add `WizardState.Force` - `internal/tui/wizard/wizard.go` + `_test.go` — force-aware preview - `cmd/archduino/main.go` — add `--force` flag - 13 module files + their tests — implement `PlanForce` --- ## Task 1: Platform layer — `Reinstall` on Pacman and Yay **Files:** - Modify: `internal/platform/pacman.go` - Modify: `internal/platform/pacman_test.go` - Modify: `internal/platform/aur.go` - Modify: `internal/platform/aur_test.go` - [ ] **Step 1: Inspect existing Install methods** Read the existing `Install` methods in `pacman.go` and `aur.go` to know their exact command construction. The new `Reinstall` must be identical except for the `--needed` flag being absent. ```bash grep -n -A 15 "func.*Install" internal/platform/pacman.go internal/platform/aur.go ``` Read the test files to understand how commands are asserted (likely via `FakeRunner` recording executed commands). - [ ] **Step 2: Write failing test for `Pacman.Reinstall`** Add to `internal/platform/pacman_test.go`: ```go func TestPacmanReinstall(t *testing.T) { fr := &FakeRunner{} p := NewPacman(fr) if err := p.Reinstall(context.Background(), []string{"git", "curl"}); err != nil { t.Fatalf("Reinstall: %v", err) } if len(fr.Calls) != 1 { t.Fatalf("expected 1 call, got %d", len(fr.Calls)) } got := fr.Calls[0] // Must include -S and --noconfirm but NOT --needed. if !containsAll(got.Args, "-S", "--noconfirm") { t.Errorf("expected -S and --noconfirm in args; got %v", got.Args) } for _, a := range got.Args { if a == "--needed" { t.Errorf("Reinstall must not pass --needed; got args %v", got.Args) } } if !containsAll(got.Args, "git", "curl") { t.Errorf("expected packages git, curl in args; got %v", got.Args) } } ``` Adjust `containsAll` / `FakeRunner` / `Calls` field names to match what the existing tests already use. Read `pacman_test.go` first to confirm the exact helper names — do NOT invent names. - [ ] **Step 3: Run test, verify it fails** ```bash cd /home/marvin/git-clones/ArduinoArchSetup go test ./internal/platform/ -run TestPacmanReinstall -v ``` Expected: FAIL with `undefined: Pacman.Reinstall` or similar. - [ ] **Step 4: Implement `Pacman.Reinstall`** Add to `internal/platform/pacman.go`, mirroring the existing `Install`: ```go // Reinstall runs `pacman -S --noconfirm pkg...` without --needed so pacman // unconditionally reinstalls each package. Used by force-reapply. func (p Pacman) Reinstall(ctx context.Context, pkgs []string) error { if len(pkgs) == 0 { return nil } args := append([]string{"pacman", "-S", "--noconfirm"}, pkgs...) return p.runner.RunSudo(ctx, args[0], args[1:]...) } ``` Adjust the runner method (`RunSudo` / `Run`) to whatever the existing `Install` uses. Read `Install` first. - [ ] **Step 5: Run test, verify it passes** ```bash go test ./internal/platform/ -run TestPacmanReinstall -v ``` Expected: PASS. - [ ] **Step 6: Repeat Steps 2–5 for `Yay.Reinstall`** Add `TestYayReinstall` to `internal/platform/aur_test.go` (parallel structure; `yay` command instead of `pacman`). Then implement `Yay.Reinstall` in `aur.go`. Run `go test ./internal/platform/ -run TestYayReinstall -v`, verify it passes. - [ ] **Step 7: Commit** ```bash git add internal/platform/pacman.go internal/platform/pacman_test.go \ internal/platform/aur.go internal/platform/aur_test.go git commit -m "feat(platform): add Reinstall helpers to Pacman and Yay" ``` No Co-Authored-By trailer. --- ## Task 2: Extend Module interface + stub all 13 modules Adding `PlanForce` to the interface breaks compilation until every module implements it. This task adds the interface method and a *stub* implementation on every module that returns `module.Plan{}, errors.New("PlanForce not implemented")`. Compilation stays green, engine is not wired yet, and each module's real implementation lands in a later task with its own test. **Files:** - Modify: `internal/module/module.go` - Modify: 13 module files listed below - [ ] **Step 1: Add `PlanForce` to the interface** Edit `internal/module/module.go`. In the `Module` interface block: ```go type Module interface { Meta() Meta Detect(ctx context.Context) (State, error) Plan(ctx context.Context, current State) (Plan, error) PlanForce(ctx context.Context) (Plan, error) Apply(ctx context.Context, plan Plan, r Reporter) error Verify(ctx context.Context) error } ``` Add a doc comment above `PlanForce`: ```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 a from-scratch // install — reinstall packages without pacman's --needed guard and // rewrite managed config files. ``` - [ ] **Step 2: Verify compile fails** ```bash cd /home/marvin/git-clones/ArduinoArchSetup go build ./... 2>&1 | head -20 ``` Expected: compile errors like `*starship.Module does not implement module.Module (missing method PlanForce)` for every module. - [ ] **Step 3: Add stub `PlanForce` to every module** For each of the 13 module files below, append this stub at the bottom of the file (replace `Module` receiver type if the file uses something different — read each file first to confirm): ```go func (m Module) PlanForce(ctx context.Context) (module.Plan, error) { return module.Plan{}, errors.New("PlanForce not implemented") } ``` Ensure `errors` is imported. The module files: 1. `internal/modules/packages/packages.go` 2. `internal/modules/gittools/gittools.go` 3. `internal/modules/fonts/fonts.go` 4. `internal/modules/renode/renode.go` 5. `internal/modules/hello/hello.go` 6. `internal/modules/starship/starship.go` 7. `internal/modules/yazi/yazi.go` 8. `internal/modules/zsh/zsh.go` 9. `internal/modules/zellij/zellij.go` 10. `internal/modules/shellconfig/shellconfig.go` 11. `internal/modules/themes/themes.go` 12. `internal/modules/neovim/neovim.go` 13. `internal/modules/arduinocli/arduinocli.go` - [ ] **Step 4: Verify compile + existing tests pass** ```bash go build ./... go test ./... ``` Expected: compile clean, all existing tests green. The new stubs aren't exercised yet. - [ ] **Step 5: Commit** ```bash git add internal/module/module.go internal/modules/ git commit -m "feat(module): add PlanForce to Module interface with stubs" ``` --- ## Task 3: Engine — `RunOptions.Force` + branch **Files:** - Modify: `internal/engine/engine.go` - Modify: `internal/engine/engine_test.go` - [ ] **Step 1: Write failing test** In `internal/engine/engine_test.go`, add: ```go func TestRunForceSkipsDetectAndCallsPlanForce(t *testing.T) { fake := &fakeModule{ id: "fake", planForce: module.Plan{Actions: []module.Action{{ Description: "forced action", Exec: func(ctx context.Context) error { return nil }, }}}, } reg := module.NewRegistry() if err := reg.Register(fake); err != nil { t.Fatal(err) } var buf bytes.Buffer sum, err := engine.Run(context.Background(), reg, engine.RunOptions{ Selected: []string{"fake"}, Force: true, Reporter: engine.NewPlainReporter(&buf), }) if err != nil { t.Fatalf("Run: %v", err) } if fake.detectCalls != 0 { t.Errorf("Detect should not be called under Force; got %d calls", fake.detectCalls) } if fake.planForceCalls != 1 { t.Errorf("PlanForce should be called once; got %d", fake.planForceCalls) } if fake.planCalls != 0 { t.Errorf("Plan should not be called under Force; got %d", fake.planCalls) } if sum.Applied != 1 { t.Errorf("expected Applied=1, got %d", sum.Applied) } } ``` `fakeModule` already exists in `engine_test.go`; extend it with: - `planForce module.Plan` - `planForceCalls int` - Implement `PlanForce(ctx) (module.Plan, error)` that increments the counter and returns `planForce`. - If `detectCalls`/`planCalls` don't already exist on the fake, add them too (they may already be there — read the file first). Read `engine_test.go` before editing to confirm the fake's field layout. - [ ] **Step 2: Run test, verify it fails** ```bash go test ./internal/engine/ -run TestRunForceSkipsDetectAndCallsPlanForce -v ``` Expected: FAIL — likely `unknown field Force in RunOptions` at compile time. - [ ] **Step 3: Add `Force` to `RunOptions`** In `internal/engine/engine.go`, find the `RunOptions` struct and add: ```go // Force, when true, skips Detect and calls PlanForce on every module // so each one produces a from-scratch plan regardless of current state. Force bool ``` - [ ] **Step 4: Branch `Run` on `Force`** Find the per-module loop inside `Run`. The current shape is roughly: ```go cur, err := mod.Detect(ctx) if err != nil { /* handle */ } plan, err := mod.Plan(ctx, cur) ``` Replace with: ```go var ( plan module.Plan err error ) if opts.Force { plan, err = mod.PlanForce(ctx) } else { cur, derr := mod.Detect(ctx) if derr != nil { // preserve existing Detect error handling verbatim err = derr } else { plan, err = mod.Plan(ctx, cur) } } ``` Read the current block first and preserve its error-handling shape (reporter calls, halt-on-error, summary bookkeeping). Do not inline a different handling style. - [ ] **Step 5: Run test, verify it passes** ```bash go test ./internal/engine/ -run TestRunForceSkipsDetectAndCallsPlanForce -v go test ./internal/engine/ -v ``` Expected: both green. - [ ] **Step 6: Commit** ```bash git add internal/engine/engine.go internal/engine/engine_test.go git commit -m "feat(engine): add RunOptions.Force to skip Detect and call PlanForce" ``` --- ## Task 4: Package-only modules — real `PlanForce` Package-only modules' `PlanForce` produces a plan that calls `Reinstall` on the full package list. **Modules covered:** `packages`, `gittools`, `fonts`, `renode`, `hello`. **Files:** for each module, both `.go` and `_test.go`. For the four real modules (packages, gittools, fonts, renode), follow the same pattern. Do this **one module at a time** (5 cycles of write-test → verify-fail → implement → verify-pass → next). For `hello`, since it's a placeholder, make `PlanForce` return a fixed non-empty plan (see below). ### Pattern (use for each of packages / gittools / fonts / renode) - [ ] **Step 1: Write failing `TestPlanForce` in the module's `_test.go`** Template (adapt field names to the module under test — read the existing `TestPlan` test for each module to see how the fake pacman/yay is wired): ```go func TestPlanForceReinstallsAllPackages(t *testing.T) { fp := &FakePacman{installed: map[string]bool{"git": true, "curl": true}} m := New([]string{"git", "curl"}, fp) plan, err := m.PlanForce(context.Background()) if err != nil { t.Fatalf("PlanForce: %v", err) } if plan.IsEmpty() { t.Fatal("force plan must not be empty even when packages are installed") } // Execute to confirm it calls Reinstall, not Install. for _, a := range plan.Actions { if err := a.Exec(context.Background()); err != nil { t.Fatalf("Exec: %v", err) } } if fp.ReinstallCalls != 1 { t.Errorf("expected 1 Reinstall call, got %d", fp.ReinstallCalls) } } ``` If `FakePacman` doesn't already have a `ReinstallCalls` counter and a `Reinstall` method, add them to the fake first (read the fake in the same test file — most modules share a local fake or use a common one in `internal/platform`). - [ ] **Step 2: Run test, verify it fails** ```bash go test ./internal/modules// -run TestPlanForce -v ``` Expected: FAIL — the stub returns an error. - [ ] **Step 3: Replace stub with real `PlanForce`** In `.go`, replace the stub with (adjust receiver/field names): ```go func (m Module) PlanForce(ctx context.Context) (module.Plan, error) { if len(m.pkgs) == 0 { return module.Plan{}, nil } pkgs := append([]string{}, m.pkgs...) return module.Plan{Actions: []module.Action{{ Description: "pacman -S (force) " + strings.Join(pkgs, ", "), Kind: module.ActionPackageInstall, Exec: func(ctx context.Context) error { return m.pm.Reinstall(ctx, pkgs) }, }}}, nil } ``` Ensure the module's stored `pm` field (or equivalent) has the `Reinstall` method available — it will, because Task 1 added it to both `Pacman` and `Yay`, and the fake was extended in Step 1. - [ ] **Step 4: Run test, verify it passes** ```bash go test ./internal/modules// -v ``` Expected: PASS. - [ ] **Step 5: Commit** ```bash git add internal/modules// git commit -m "feat(): implement PlanForce to reinstall all packages" ``` ### `hello` module The `hello` module is a placeholder. Implement `PlanForce` as: ```go func (m Module) PlanForce(ctx context.Context) (module.Plan, error) { return module.Plan{Actions: []module.Action{{ Description: "hello force reapply", Exec: func(ctx context.Context) error { return nil }, }}}, nil } ``` Add a test that just asserts the plan is non-empty. Same commit cadence as above. - [ ] **Step 6: Complete all 5 modules before moving on** After each of packages / gittools / fonts / renode / hello has its own commit, move to Task 5. Do NOT batch them into one commit. --- ## Task 5: Hybrid modules — `starship`, `yazi` These modules install packages AND (optionally) write configs. Their existing `Plan` already produces a plan that may include both; `PlanForce` should produce both unconditionally. For each of `starship` and `yazi`: - [ ] **Step 1: Read the existing `Plan` to see what it does** ```bash grep -n -A 30 "func.*Module.*Plan\b" internal/modules/starship/starship.go grep -n -A 30 "func.*Module.*Plan\b" internal/modules/yazi/yazi.go ``` Identify the branches: package install vs. config write (if any). - [ ] **Step 2: Write failing `TestPlanForce`** The test must arrange a "fully satisfied" state (packages installed, any config files already present) and assert the force plan is non-empty AND its actions reinstall the packages (via Reinstall call counter) AND rewrite the config files (via a writefile call counter or file presence assertion). Use the same pattern as Task 4 for the package side. For the config side, use whatever the module's existing tests use to assert config writes (likely `WriteFile` mock or a tempdir). - [ ] **Step 3: Run test, verify it fails** ```bash go test ./internal/modules// -run TestPlanForce -v ``` - [ ] **Step 4: Replace stub with real `PlanForce`** ```go func (m Module) PlanForce(ctx context.Context) (module.Plan, error) { var actions []module.Action if len(m.pkgs) > 0 { pkgs := append([]string{}, m.pkgs...) actions = append(actions, module.Action{ Description: "pacman -S (force) " + strings.Join(pkgs, ", "), Kind: module.ActionPackageInstall, Exec: func(ctx context.Context) error { return m.pm.Reinstall(ctx, pkgs) }, }) } // Any config-write actions the module normally conditions on Detect: actions = append(actions, /* unconditional config-write actions */ ...) return module.Plan{Actions: actions}, nil } ``` Fill in the config-write actions by lifting them from `Plan` but stripping the "already exists" short-circuit. Reuse the same helper functions (e.g. `writeConfig`) that `Plan` uses. - [ ] **Step 5: Run test, verify it passes, commit** ```bash go test ./internal/modules// -v git add internal/modules// git commit -m "feat(): implement PlanForce (reinstall + rewrite config)" ``` Repeat for both modules, one commit each. --- ## Task 6: Config-writing simple modules — `zsh`, `zellij`, `shellconfig`, `themes` Same pattern as Task 5 — read existing `Plan`, lift its actions, strip conditionals. Do one module per commit. For each of `zsh`, `zellij`, `shellconfig`, `themes`: - [ ] **Step 1: Write failing `TestPlanForce`** — satisfied-system fixture → assert non-empty plan with all the module's write/install actions. - [ ] **Step 2: Run test, verify it fails.** - [ ] **Step 3: Replace stub with real implementation.** - [ ] **Step 4: Run test, verify it passes.** - [ ] **Step 5: Commit** with message `feat(): implement PlanForce`. Module-specific notes: - **`shellconfig`** has no packages; `PlanForce` only produces file-write actions. - **`themes`** writes under `~/.config/archduino/overlay.yaml` — force rewrites the overlay's theme fields unconditionally. - **`zsh`** clones plugins (oh-my-zsh, etc.) via `git`; `PlanForce` should clone even if the target already exists — add a pre-step that removes the directory, or use the `git` helper's `ForceClone` if one exists (grep `internal/platform/git.go`; if missing, note it as a concern and fall back to rm-then-clone via an action that uses `os.RemoveAll` before the clone). --- ## Task 7: Config-writing complex modules — `neovim`, `arduinocli` Same pattern as Task 6, but these modules have multiple sub-steps (neovim: clone NvChad, write chadrc, run post-install; arduinocli: install binary, update index, install cores, install libraries). For each module: - [ ] **Step 1: Enumerate the existing `Plan`'s sub-actions.** Every conditional "already done" branch in `Plan` becomes an unconditional branch in `PlanForce`. - [ ] **Step 2: Write failing `TestPlanForce`** — satisfied-system fixture → assert ALL sub-actions are present (count-based is fine if exact equality is fragile). - [ ] **Step 3: Run test, verify it fails.** - [ ] **Step 4: Replace stub with real implementation.** - [ ] **Step 5: Run test, verify it passes.** - [ ] **Step 6: Commit** with message `feat(): implement PlanForce`. **Neovim specifics:** - If `NvChad` is already cloned, `PlanForce` should either `git pull` + rewrite `chadrc.lua` OR `rm -rf && git clone`. Pick the simpler: rewrite `chadrc.lua` unconditionally, do NOT delete the existing clone. Users' local NvChad modifications shouldn't be wiped by `--force`. Document this in a code comment. **arduinocli specifics:** - `PlanForce` should re-download the binary tarball, re-run `arduino-cli core update-index`, and re-install every configured core/library regardless of current installation. Cores install is idempotent at the arduino-cli layer; no issue. --- ## Task 8: TUI wizard — force-aware preview **Files:** - Modify: `internal/tui/state.go` - Modify: `internal/tui/wizard/wizard.go` - Modify: `internal/tui/wizard/wizard_test.go` - [ ] **Step 1: Add `Force bool` to `WizardState`** In `internal/tui/state.go`, find the `WizardState` struct (or wherever the wizard's global state lives) and add a `Force bool` field with a doc comment: ```go // Force mirrors the --force CLI flag: when true, the wizard's per-module // plan preview uses PlanForce instead of Detect + Plan, and the header // shows a "Force mode" banner. Force bool ``` If `WizardState` is constructed in a factory (e.g., `NewWizardState(cfg, reg)`), leave the constructor signature alone — the flag is set by the CLI after construction. - [ ] **Step 2: Write failing test for wizard preview under force** In `internal/tui/wizard/wizard_test.go`, add a test that dispatches the wizard's plan-preview code path with `Force: true` on a fake module whose `Detect` panics (proving it's not called) and whose `PlanForce` returns a non-empty plan. The exact test shape depends on how the wizard currently tests previews — read the existing preview tests before writing. Bare minimum: ```go func TestWizardPreviewUsesPlanForceWhenForceSet(t *testing.T) { mod := &fakeModule{ planForce: module.Plan{Actions: []module.Action{{Description: "forced"}}}, detect: func() (module.State, error) { t.Fatal("Detect should not be called"); return nil, nil }, } // ... drive the preview path and assert the displayed plan is the PlanForce output } ``` - [ ] **Step 3: Run test, verify it fails** ```bash go test ./internal/tui/wizard/ -run TestWizardPreviewUsesPlanForceWhenForceSet -v ``` - [ ] **Step 4: Branch the preview cache on `Force`** In `internal/tui/wizard/wizard.go`, find the `planCache` / preview logic (Plan 3a Task 1 added this — search for `planCache` or `Detect` call sites inside the wizard). Add a branch: ```go if state.Force { plan, err = mod.PlanForce(ctx) } else { cur, derr := mod.Detect(ctx) if derr != nil { /* existing error path */ } plan, err = mod.Plan(ctx, cur) } ``` Also: the cache key must include `force` so a force run doesn't reuse a non-force cached plan. If the cache is keyed by module ID only today, extend it to `(moduleID, force)`. - [ ] **Step 5: Add a header banner** Find the wizard's view/render code (likely a `View()` method). When `state.Force`, prepend a banner line to the rendered output: ```go if state.Force { sb.WriteString(styles.Warning.Render("⚠ Force mode: all selected modules will be reapplied")) sb.WriteString("\n\n") } ``` Use whatever Lipgloss style the codebase already has for warnings (grep for `Warning`, `warnStyle`, or similar). If none exists, use a plain bold-red or bold string — do NOT create a new style module just for this. - [ ] **Step 6: Run test, verify it passes + rest of wizard tests** ```bash go test ./internal/tui/wizard/ -v go test ./internal/tui/ -v ``` Expected: all green. - [ ] **Step 7: Commit** ```bash git add internal/tui/state.go internal/tui/wizard/ git commit -m "feat(tui): wizard uses PlanForce preview when Force is set" ``` --- ## Task 9: CLI — `--force` flag **Files:** - Modify: `cmd/archduino/main.go` - [ ] **Step 1: Add the flag** In `newInstallCmd`, alongside the other flag declarations (`noTUI`, `dryRun`, `halt`, `noSudo`, `only`, `skip`, `overlayPath`), add: ```go var force bool // ... cmd.Flags().BoolVar(&force, "force", false, "Reapply every selected module, ignoring current state") ``` - [ ] **Step 2: Propagate to engine and wizard** In the `RunE` body: - Where `engine.RunOptions{…}` is constructed (there are currently two call sites: no-TUI path and TUI path — both must set `Force: force`). - Where `tui.NewWizardState(cfg, reg)` is called: after construction, set `initial.Force = force`. - [ ] **Step 3: Smoke-test** ```bash cd /home/marvin/git-clones/ArduinoArchSetup go build ./cmd/archduino ./archduino install --help | grep -A 2 force ./archduino install --force --dry-run --no-tui --no-sudo 2>&1 | head -20 ``` Expected: `--help` shows the new flag; a dry-run with `--force` completes without error and prints per-module force plans. - [ ] **Step 4: Commit** ```bash git add cmd/archduino/main.go git commit -m "feat(cmd): add --force flag to install command" ``` --- ## Task 10: End-to-end validation **Files:** none (validation only) - [ ] **Step 1: Full test suite with race detector** ```bash cd /home/marvin/git-clones/ArduinoArchSetup go test -race ./... ``` Expected: every package `ok`. Failures here = STOP and fix. - [ ] **Step 2: Full build** ```bash go build ./... ``` Expected: exit 0. - [ ] **Step 3: gofmt + vet** ```bash diff=$(gofmt -l .); [ -z "$diff" ] && echo "gofmt clean" || { echo "$diff"; exit 1; } go vet ./... ``` Expected: both clean. - [ ] **Step 4: Dry-run smoke (without and with force)** ```bash ./archduino install --dry-run --no-tui --no-sudo 2>&1 | tail -10 ./archduino install --dry-run --no-tui --no-sudo --force 2>&1 | tail -10 ``` Expected: second output's per-module plan is non-empty for every selected module (even ones the first output reports as satisfied). - [ ] **Step 5: No commit — validation is informational** --- ## Notes for the executing agent - **Interface change order matters.** Task 2's stubs MUST land before any per-module `PlanForce` test runs (Task 3 onward), otherwise the whole package is red. - **Read each module's existing `Plan` before writing `PlanForce`.** The force version is almost always "strip the conditionals from Plan". Don't invent new actions. - **Do not delete users' cloned repos during force.** Especially neovim's NvChad clone. Rewriting `chadrc.lua` is enough; use `git pull` if the user wants updates, not `rm -rf && git clone`. - **If a module's test file doesn't already have a fake pacman/yay with a `Reinstall` method,** extend the fake in that file (or, if it lives in `internal/platform/testing.go` or similar, extend it once there). Do NOT duplicate fakes — reuse. - **Conventional Commits:** every module's force implementation gets its own `feat():` commit. This keeps the changelog readable and svu happy. - **CI is now live** (Plan 4d merged into this branch). Expect the workflow to run on every push of this plan — if it goes red, the task isn't done.