Files
Archduino/docs/superpowers/specs/2026-04-14-force-reapply-design.md
2026-04-14 21:39:32 +02:00

6.7 KiB

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:

// 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:

// 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:

// 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:
    Force bool
    
  • Run per-module path becomes:
    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():

var force bool
cmd.Flags().BoolVar(&force, "force", false,
    "Reapply every selected module, ignoring current state")

In RunE, propagate to both engine and wizard:

runOpts.Force = force
initialState.Force = force

Wizard / TUI integration

internal/tui/state.go — WizardState gains:

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.