diff --git a/docs/adr/ADR-010-reconcile-engine-single-source-of-truth.md b/docs/adr/ADR-010-reconcile-engine-single-source-of-truth.md new file mode 100644 index 0000000..59ddd29 --- /dev/null +++ b/docs/adr/ADR-010-reconcile-engine-single-source-of-truth.md @@ -0,0 +1,85 @@ +# ADR-010: Reconcile Engine as Single Source of Truth for Duplicate-Install Cleanup + +**Status:** Accepted +**Date:** 2026-07-23 +**Deciders:** AI CLI Preparation Team +**Tags:** reconciliation, cli, safety, makefile +**Builds on:** ADR-009 (first-class multi-install data model), ADR-002 +(package-manager preference policy — consumed here, not redefined) + +## Context + +The removal engine `cli_audit/reconcile.py` (preference ranking, +aggressive/parallel modes, version + `active` marker, and the +`SYSTEM_TOOL_SAFELIST` critical-tools protect list) existed but was not +reachable from the user-facing surfaces: + +- `guide.sh`'s duplicate warning was a listing-only view built on the thin + shell `detect_all_installations` (`scripts/lib/capability.sh`) — no + versions, no indication which install actually runs, no cleanup action. +- `make reconcile-` called the install-script `reconcile` action rather + than the Python engine. +- `make upgrade-managed` / `upgrade-all` silently moved tools past the + versions pinned in `pins.json` and any project lockfiles. + +ADR-009 established multi-install as a first-class concern and warned against +a "parallel detection universe" in shell. This decision (design approved +2026-07-08, implemented in `guide.sh`, `audit.py`, `Makefile.d/user.mk`, +`scripts/auto_update.sh`) records how the engine was surfaced. + +## Decision + +1. **Single source of truth.** `cli_audit/reconcile.py` owns multi-install + listing, the active/preferred markers, and removal planning. All shell + surfaces (guide, make targets) consume it through the + `audit.py --reconcile [|--all] [--apply] [--yes]` JSON entrypoint + (`CLI_AUDIT_JSON=1`). Detection is never re-implemented in shell; the shell + `detect_all_installations` remains only as a cheap pre-check. + +2. **Safety model: confirm-each + dry-run.** + - Nothing is removed without an explicit `y`; `make reconcile-all` + confirms per tool; `make reconcile-all-dry-run` previews and removes + nothing. + - Removing the **active** install prints an extra heads-up naming the + binary that will run afterwards. + - The `SYSTEM_TOOL_SAFELIST` protect list is **always** honored: + `FORCE=1` / `--yes` bypass prompts, never the protect list. Protected + duplicates are listed but not removed. + +3. **Pin/lock override warning.** `upgrade-managed` / `upgrade-all` print a + warning + `Continue?` prompt before running native manager upgrades, + because those upgrades move tools past `pins.json` and project lockfiles + (pins are NOT enforced there). Bypassed only by `FORCE=1` or `--dry-run`. + +4. **Discoverability hints are advisory.** Hints (`→ make reconcile-all` when + duplicates exist; `→ make upgrade-managed`) are printed only when relevant + and never block. + +## Alternatives Considered + +- **Enriching the shell `detect_all_installations`** — rejected: duplicates + the Python engine, recreating the parallel detection universe ADR-009 + warned about. +- **Automatic/batch removal without per-tool confirmation** — rejected: + removal is destructive; explicit confirmation per tool is required. + +## Consequences + +**Positive:** +- One engine; guide and make targets render the same plan. +- ADR-009's aggressive reconcile is user-reachable (`make reconcile-all`, + guide option `c`). + +**Negative:** +- The guide gains a Python-process dependency per duplicate-listing render. + +**Follow-ups:** +- ADR-009 status note that aggressive reconcile is now user-reachable. + +## References + +- Design spec (removed after conversion to this ADR): + `docs/superpowers/specs/2026-07-08-reconcile-and-upgrade-discoverability-design.md` +- `scripts/guide.sh` (`check_multi_installs`), `Makefile.d/user.mk` + (`reconcile-all`, `reconcile-all-dry-run`), `scripts/auto_update.sh` + (pin/lock warning), `audit.py` (`--reconcile`) diff --git a/docs/adr/README.md b/docs/adr/README.md index a807fbd..2d9f0d9 100644 --- a/docs/adr/README.md +++ b/docs/adr/README.md @@ -104,6 +104,8 @@ ADRs are **immutable** once accepted. To change a decision: | [006](ADR-006-configuration-file-format.md) | Configuration File Format | Accepted | 2025-10-09 | configuration, yaml | | [007](ADR-007-generic-tool-installation-architecture.md) | Generic Tool Installation Architecture | Accepted | 2026-02-25 | installation, catalog, generic-installer | | [008](ADR-008-makefile-pattern-target-fallback.md) | Makefile Pattern Target Fallback Chain | Accepted | 2026-02-25 | makefile, installation, pattern-targets | +| [009](ADR-009-first-class-multi-install.md) | First-Class Support for Multiple Installations | Proposed | 2026-04-21 | detection, audit, upgrade, pinning, reconciliation | +| [010](ADR-010-reconcile-engine-single-source-of-truth.md) | Reconcile Engine as Single Source of Truth | Accepted | 2026-07-23 | reconciliation, cli, safety, makefile | --- @@ -125,13 +127,17 @@ ADRs are **immutable** once accepted. To change a decision: - ADR-005: Environment Detection Logic - ADR-006: Configuration File Format +**Multi-Install and Reconciliation:** +- ADR-009: First-Class Support for Multiple Installations +- ADR-010: Reconcile Engine as Single Source of Truth + ### By Tag **installation:** ADR-001, ADR-002, ADR-003, ADR-007, ADR-008 **environment-detection:** ADR-001, ADR-005 **versioning:** ADR-004 **configuration:** ADR-006 -**reconciliation:** ADR-003 +**reconciliation:** ADR-003, ADR-009, ADR-010 **package-managers:** ADR-002 **catalog:** ADR-007 **makefile:** ADR-008 diff --git a/docs/superpowers/specs/2026-07-08-reconcile-and-upgrade-discoverability-design.md b/docs/superpowers/specs/2026-07-08-reconcile-and-upgrade-discoverability-design.md deleted file mode 100644 index 50f292b..0000000 --- a/docs/superpowers/specs/2026-07-08-reconcile-and-upgrade-discoverability-design.md +++ /dev/null @@ -1,199 +0,0 @@ -# Design: Multi-install cleanup + upgrade discoverability - -**Date:** 2026-07-08 -**Status:** Approved (pre-implementation) -**Related:** [ADR-009 first-class multi-install](../../adr/ADR-009-first-class-multi-install.md), -[ADR-002 package-manager-hierarchy](../../adr/ADR-002-package-manager-hierarchy.md), -[ADR-003 parallel-installation](../../adr/ADR-003-parallel-installation-approach.md) - -## Context - -Two user needs surfaced from real `make upgrade` runs: - -1. **Bulk-upgrade discoverability.** A command to run every package manager's own - full upgrade already exists — `make upgrade-managed` / `make upgrade-all`, - backed by `scripts/auto_update.sh` (18 managers incl. `gup`, `composer`, - `poetry`, `cargo`/`rustup`, `uv`/`pip`/`pipx`, `npm`/`pnpm`/`yarn`). It is not - discoverable from the `update`/`upgrade` flow, and it does not warn that - running managers' native upgrades moves tools past the versions this tool - pins (`pins.json`) and any project lockfiles. - -2. **Multi-install cleanup.** `guide.sh`'s `check_multi_installs()` detects - duplicate installs (e.g. `git-absorb` at `~/.local/bin` **and** - `~/.cargo/bin`) and prints a `⚠️` warning, but: - - the listing shows only `method: path` — no version, and no indication of - which install actually runs when the command is called without a path; - - it offers no action to remove the redundant install; - - the removal engine that *does* exist (`cli_audit/reconcile.py`, with - parallel/aggressive modes, version + `active` marker, preference ranking, - and a critical-tools protect list) is not reachable from the guide, and - `make reconcile-` calls the install-script `reconcile` action rather - than `reconcile.py`. - -ADR-009 already establishes multi-install as a first-class concern and notes -`reconcile.py` "exists solely to delete duplicates" and re-scans from scratch. -This design surfaces that engine and closes the discoverability gaps. - -## Goals - -- Make `reconcile.py` the single source of truth for multi-install listing, - the active/preferred markers, and removal planning. -- Show version + active + preferred per install in the guide's warning. -- Offer inline cleanup in the guide, and a `make reconcile-all` sweep. -- Add discoverability hints and a pin/lock override warning. - -## Non-goals - -- No change to how single-install tools are audited/upgraded. -- No new package-manager coverage in `auto_update.sh` (already comprehensive). -- No automatic removal without explicit confirmation (see Safety model). -- No change to ADR-002 preference policy (vendor/user-level wins); this design - consumes it, it does not redefine it. - -## Design - -### Component 1 — Richer multi-install listing (source of truth: reconcile.py) - -`reconcile.py` already computes, per install, `Installation.version`, -`Installation.path`, `Installation.active` (the `which()`-resolved binary — the -"default when called without a path"), and `preference_score`; `reconcile_tool()` -yields the `preferred` install. A new JSON CLI entrypoint (Component 6) exposes -this. `check_multi_installs()` in `guide.sh` switches from the thin shell -`detect_all_installations` (`scripts/lib/capability.sh`) to this data and renders: - -``` -⚠️ Multiple installations detected (2): - • yq 4.44.3 manual /home/sme/.local/bin/yq → active ✓ keep - • yq 4.40.1 manual /usr/local/bin/yq -``` - -- `→ active` marks the PATH-resolved install (`Installation.active`). -- `✓ keep` marks the preferred install reconcile would retain. -- When `active` and `keep` are **different** installs, print one extra line: - `note: cleanup would change which yq runs (active → preferred).` -- Version per install comes from reconcile's detection; if a version can't be - read, show `?` rather than suppressing the row. - -### Component 2 — Inline cleanup action in the guide (#3) - -When `check_multi_installs()` reports >1 install, the guide's option list gains: - -``` -c = clean up duplicates (keep preferred, remove the rest) -``` - -Selecting `c`: -1. Prints the keep/remove plan (reuses Component 1 rendering). -2. Confirms (per Safety model). Removing the **active** install prints an extra - heads-up naming the new active binary. -3. Calls `reconcile_tool(..., mode="aggressive")` for that tool. -4. Re-audits and reloads JSON (same pattern as the existing update path). - -Single-install tools never show option `c`. Critical tools (reconcile.py protect -list) show the plan but decline to remove, with a reason line. - -### Component 3 — `make reconcile-all` (#4) - -Sweeps all installed tools, selects those with >1 install, and reconciles each -via `bulk_reconcile()` in aggressive mode. Per the Safety model it shows the plan -and confirms **per tool** before removing. Targets: - -- `make reconcile-all` — interactive confirm-each. -- `make reconcile-all-dry-run` — preview only, removes nothing (`DRY_RUN=1`). - -Wraps `cli_audit/reconcile.py::bulk_reconcile`; honors the critical-tools protect -list; reports a summary (reconciled / skipped / protected / failed). - -### Component 4 — Discoverability hints (#1) - -After the `make update` / `make upgrade` / guide summary, print 1–2 lines **only -when relevant**: - -- If any tool has duplicate installs: - `N tool(s) have duplicate installs → make reconcile-all` -- `Upgrade the package managers themselves → make upgrade-managed`: shown in the - audit summary when outdated tools exist, and always in the interactive - upgrade (guide) summary footer. - -Hints are informational, never block, and are suppressed when nothing applies -(no duplicates → no reconcile hint). - -### Component 5 — Pin/lock override warning (#2) - -`make upgrade-managed` / `upgrade-all` print before running native manager -upgrades: - -``` -⚠️ This runs each package manager's own upgrade. It moves tools PAST the - versions pinned in cli-audit (pins.json) and any project lockfiles — - pins are NOT enforced here. -Continue? [y/N] -``` - -- Bypass with `FORCE=1` (non-interactive/CI) or when `--dry-run`. -- Emitted once per invocation, before the first manager runs. -- Lives in `scripts/auto_update.sh` (or its Make wrapper), guarded so - `upgrade-all-dry-run` never prompts. - -### Component 6 — reconcile.py JSON CLI entrypoint (enabler) - -Add a `reconcile` subcommand to `audit.py` exposing `reconcile.py`: - -- `audit.py reconcile --plan --json` → `{installations:[{version,method, - path,active,preferred}], preferred, active, protected}` for one tool (feeds - Components 1 & 2). -- `audit.py reconcile --all --plan --json` → same, per tool with >1 install - (feeds Component 3). -- `audit.py reconcile --apply [--yes]` / `--all --apply` → performs - aggressive reconciliation (feeds the actual removal in Components 2 & 3). - -`CLI_AUDIT_JSON=1` conventions and existing snapshot reuse apply. This entrypoint -is the seam that lets shell surfaces (guide, make targets) consume the Python -engine without re-implementing detection. - -## Safety model (chosen: confirm-each + dry-run) - -- Nothing is removed without an explicit `y`. -- `make reconcile-all` confirms **per tool**; `reconcile-all-dry-run` previews. -- Removing the **active** install prints an extra heads-up (what will run after). -- Critical-tools protect list (`reconcile.py`) is always honored; protected - duplicates are listed but never removed. -- `FORCE=1` / `--yes` bypass prompts for CI, but respect the protect list. - -## Data flow - -``` -reconcile.detect_installations(tool) - → [Installation(version, path, active, preference_score)] - → sort_by_preference() → preferred - → audit.py reconcile --json (Component 6) - → guide.sh check_multi_installs() render (C1) + option 'c' (C2) - → make reconcile-all → bulk_reconcile() (C3) - → reconcile_tool(mode="aggressive") → _uninstall_installation() (removal) -``` - -## Files touched (anticipated) - -- `cli_audit/reconcile.py` — plan/JSON marks each install's `preferred` boolean - (derived from `ReconciliationResult.preferred` + `Installation.active`); no - engine rewrite. -- `audit.py` — new `reconcile` subcommand (Component 6). -- `scripts/guide.sh` — `check_multi_installs()` rendering (C1), option `c` (C2), - summary hints (C4). -- `scripts/auto_update.sh` — pin/lock override warning (C5). -- `Makefile.d/user.mk` — `reconcile-all`, `reconcile-all-dry-run` targets; - `.PHONY` list. -- `docs/adr/ADR-009-*` — status note that aggressive reconcile is now - user-reachable (follow-up, not blocking). - -## Testing - -- **Unit (`cli_audit`):** plan output with active ≠ preferred; critical-tool - protection (listed, not removed); single-install → empty plan; `--all` selects - only multi-install tools; JSON shape. -- **Shell (`tests/test_guide_multi_install.sh`):** listing shows version + - `→ active` + `✓ keep`; option `c` appears only with duplicates; confirm/decline - paths (mocked removal). -- **Make target:** `reconcile-all-dry-run` removes nothing and lists a plan; - `FORCE=1` path is non-interactive. -- **Regression:** existing single-install and up-to-date paths unchanged. diff --git a/docs/superpowers/specs/2026-07-22-blesh-and-bash-completion-design.md b/docs/superpowers/specs/2026-07-22-blesh-and-bash-completion-design.md deleted file mode 100644 index c4830a2..0000000 --- a/docs/superpowers/specs/2026-07-22-blesh-and-bash-completion-design.md +++ /dev/null @@ -1,222 +0,0 @@ -# Design: ble.sh tool + bash-completion installation support - -**Date:** 2026-07-22 -**Status:** Approved (design), pending implementation -**Delivery:** Two independent PRs - -## Problem - -1. **ble.sh** (Bash Line Editor — syntax highlighting, autosuggestions, richer - completion UI for interactive Bash) is not in the catalog. It should be - installable/upgradable/removable through the standard `make install-blesh` - flow. -2. The toolset installs many CLIs but never installs their **bash completion** - scripts, so tab-completion for `gh`, `glab`, `uv`, `rg`, etc. is missing. - We want completions installed automatically as part of a tool's lifecycle, - plus a way to backfill completions for tools already installed. - -These are thematically related (interactive-shell ergonomics) but technically -independent, so they ship as **two PRs**. - ---- - -## PR 1 — Add ble.sh - -ble.sh is *sourced* into an interactive shell, not placed on `PATH` as a -binary. Recommended upstream install is `git clone --recursive` + -`make install PREFIX=~/.local`, then a `source` line in `.bashrc`. This matches -the repo's `dedicated_script` pattern (cf. `install_tmux.sh`, built from source). - -### Catalog: `catalog/blesh.json` - -```jsonc -{ - "name": "blesh", - "category": "general", - "install_method": "dedicated_script", - "script": "install_blesh.sh", - "description": "Bash Line Editor — full-featured line editor for interactive Bash (syntax highlighting, autosuggestions, completion UI)", - "homepage": "https://github.com/akinomyoga/ble.sh", - "github_repo": "akinomyoga/ble.sh", - "binary_name": "ble.sh", - "version_command": "bash \"$HOME/.local/share/blesh/ble.sh\" --version 2>/dev/null", - "notes": "Not a PATH binary — sourced from ~/.local/share/blesh/ble.sh via .bashrc. Built from source (git clone --recursive + make install). Requires git, make, gawk.", - "tags": [] -} -``` - -**Unknowns — RESOLVED with evidence (2026-07-22, real install into a scratch prefix):** - -- **Installed-version detection.** ✅ `bash "$HOME/.local/share/blesh/ble.sh" - --version` prints `ble.sh (Bash Line Editor), version 0.4.0-devel4+d69e4d5` - (exit 0). `version_command` runs via `shell=True`, and detection falls back to - the standalone `` path when no PATH binary is found (ble.sh's - `binary_name` is never on PATH). `extract_version_number(...)` yields `0.4.0`. - No `version_regex` needed (not a real catalog field — unused in code). -- **Upstream version tracking.** ✅ `collect_github('akinomyoga','ble.sh')` - returns `('v0.4.0-devel3', '0.4.0')` via the `/releases/latest` redirect — - the `nightly` tag does not pollute it. Installed `0.4.0-develN` and upstream - both normalize to `0.4.0` and align. **No `skip_upstream` needed.** The - installer clones the default (`master`) branch, matching upstream detection. - -### Installer: `scripts/install_blesh.sh` - -Structure mirrors `install_tmux.sh`. Supports `install` | `update` | `uninstall`. - -- **Deps check:** ensure `git`, `make`, `gawk` (ble.sh needs gawk). Install via - apt (with `sudo`) only if missing, same pattern as `install_tmux.sh`. -- **install/update:** - - Clone to a fixed source dir (e.g. `~/.local/src/ble.sh`) with - `git clone --recursive --depth 1 --shallow-submodules`; if already present, - fetch/reset like `github_clone.sh` does. - - `make -C install PREFIX="$HOME/.local"` → installs to - `~/.local/share/blesh/`. - - Idempotently ensure a **single managed block** in `~/.bashrc`: - ```bash - # >>> cli-audit: ble.sh >>> - [[ $- == *i* ]] && source ~/.local/share/blesh/ble.sh - # <<< cli-audit: ble.sh <<< - ``` - Guarded by the delimiter comments; never appended twice. - - Report before/after version; call `refresh_snapshot "$TOOL"`. -- **uninstall:** remove `~/.local/share/blesh/`, the source dir, and the managed - `.bashrc` block (delete the delimited region only — never rewrite unrelated - lines). - -### Tests (PR 1) - -- Catalog validity test already iterates `catalog/*.json`; `blesh.json` must - pass its schema assertions (valid `install_method`, referenced `script` - exists and is executable). -- Add a focused assertion (pytest or `test_smoke.sh`) that - `install_blesh.sh` prints usage for an unknown action and that its - `.bashrc`-block insertion is idempotent (run the insert helper twice → one - block). Network/clone is **not** exercised in CI. - -### Docs (PR 1) - -- Regenerate / update `catalog/CATALOG_SUMMARY.md` and `catalog/COVERAGE.md` - if they are generated from the catalog; otherwise add the `blesh` row. - ---- - -## PR 2 — Bash-completion installation framework - -### Catalog schema: optional `bash_completion` - -Add an **optional** object to any catalog entry. Two shapes: - -```jsonc -// Shape A — generate to stdout (most modern CLIs) -"bash_completion": { "command": "gh completion -s bash" } - -// Shape B — copy a file shipped inside the tool's install/clone -"bash_completion": { "source_path": "shell/completion.bash" } -``` - -- Absent field ⇒ tool has no bash completion (or none worth shipping). -- `command` is run with the tool on `PATH`; stdout is captured. -- `source_path` is resolved relative to the tool's install/clone dir. -- Schema is additive and backward-compatible; existing entries unaffected. - -### Installer lib: `scripts/lib/completion.sh` - -Pure functions, sourced by the orchestrator and the make targets: - -- `completion_dir()` → `${XDG_DATA_HOME:-$HOME/.local/share}/bash-completion/completions` -- `install_completion `: - 1. Read `.bash_completion` from `catalog/.json`; no field ⇒ no-op (0). - 2. Shape A: run `command`, capture stdout to a temp file. - Shape B: locate `source_path`, copy to temp file. - 3. **Validate** the captured output is non-empty and looks like a bash - completion script (e.g. contains `complete ` or `_` / `compgen`); - refuse to install garbage. Log and return non-zero on validation failure. - 4. Move into `completion_dir()/`. -- `remove_completion `: delete `completion_dir()/` if present. -- `ensure_bash_completion_framework`: - - Ensure the `bash-completion` package is installed (apt, best-effort with - `sudo`; skip silently if unavailable/no sudo). - - Ensure a **single managed block** in `~/.bashrc` sources the framework - loader if the distro doesn't already (`/usr/share/bash-completion/bash_completion`), - same delimiter-guard technique as ble.sh. - - Called once before writing any completion file. - -All completion operations are **best-effort**: a failure logs a warning and -returns non-zero but must **never** abort or fail the tool install/uninstall -that invoked it. - -### Entry point: `scripts/install_completion.sh` - -Thin CLI wrapper: `install_completion.sh [install|remove]` → sources the -lib and dispatches. Used by make targets and callable standalone. - -### Lifecycle hooks: `scripts/install_tool.sh` - -- After a **successful** `install`/`update`/`reconcile` of a tool, call - `ensure_bash_completion_framework` (once) then `install_completion ` - — guarded so failure only warns. -- In the `uninstall` path, call `remove_completion `. -- Hooks are additive; they wrap existing success/exit points without changing - current control flow or exit codes. - -### Make targets - -In `Makefile.d/user.mk` (+ `.PHONY` in `Makefile`): - -- `make completions` — for every **installed** tool (per `local_state.json`) - that declares `bash_completion`, run `install_completion`. Ensures the - framework first. This is the backfill for pre-existing installs. -- `make completion-` — (re)install completion for one tool. - -### Coverage — broad sweep (this PR) - -Audit **all** catalog entries and declare `bash_completion` for every tool that -supports it: - -- Classify each tool as **command** / **source_path** / **none**. -- For `command` tools, use the tool's real, documented generator (verified by - running it where the tool is installed locally; otherwise from official docs). - Common conventions: ` completion bash`, ` completion -s bash`, - ` --completion bash`, ` generate-shell-completion bash`, - ` --generate complete-bash`. **Do not assume** a convention — verify - per tool. -- Record the classification result (tool → mechanism / none) in - `catalog/COVERAGE.md` so coverage is auditable. - -**Scope note:** the broad sweep makes PR 2 exceed the repo's ~300 net-LOC -guideline. This is a deliberate, accepted trade-off (chosen over a curated -subset); the framework code stays small and the bulk is mechanical, additive, -per-tool JSON plus a coverage table. - -### Tests (PR 2) - -- Unit test `install_completion` against a **fake tool** fixture for both - shapes (command via a stub script that echoes a known completion; source_path - via a fixture file) — assert the file lands in a temp `XDG_DATA_HOME` and that - validation rejects empty/garbage output. -- Test `remove_completion` deletes the file and is a no-op when absent. -- Test the `.bashrc` managed-block insert is idempotent. -- Test that `bash_completion` values in the catalog are schema-valid (object - with exactly one of `command` | `source_path`) — extend the catalog-validity - test. -- No network; no real tool execution in CI (use stubs/fixtures). - ---- - -## Non-goals / YAGNI - -- No zsh/fish completion (bash only, per request). -- No completion for tools that don't natively provide one (no hand-written - completion scripts maintained in-repo). -- No auto-refresh daemon; refresh is explicit (`make completions`) or happens - on the next install/upgrade of a tool. - -## Risks - -- **ble.sh version detection / upstream parsing** — RESOLVED with evidence from - a real scratch install (see PR 1 section); no residual risk. -- **bash-completion framework absence** — mitigated by - `ensure_bash_completion_framework`; on systems without apt/sudo it degrades to - a warning and the XDG files still work once a framework is present. -- **Wrong generator command per tool** — mitigated by output validation before - install and by verifying each generator during the broad sweep.