Skip to content

Commit 0b7ea08

Browse files
committed
docs πŸ“š (docs): HAD-61 archive module-disable and devops-install-fix
Move planning artifacts of completed changes to archive.
1 parent aabf91d commit 0b7ea08

10 files changed

Lines changed: 286 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-06-11
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
## Context
2+
3+
The `zsh/.zshrc` loads all modules from `zsh/modules/` with a glob loop:
4+
5+
```zsh
6+
for __module_dir in "${DOTFILES_ZSH_DIR}"/modules/*(/N); do
7+
if [ -e "${__module_dir}/plugin.zsh" ]; then
8+
source "${__module_dir}/plugin.zsh"
9+
fi
10+
done
11+
```
12+
13+
There is no mechanism to skip a module. If a module's `plugin.zsh` errors (e.g., tmux calling a function defined later, or a missing dependency), the user cannot source their zshrc without moving the module directory or editing the core loading loop.
14+
15+
The existing codebase already uses `ZSH_*` environment variables for configuration (e.g., `ZSH_TMUX_AUTOSTART`, `ZSH_DEBUG`), making an env-var-based approach consistent with project conventions.
16+
17+
## Goals / Non-Goals
18+
19+
**Goals:**
20+
- Allow users to skip specific modules by setting `ZSH_DISABLED_MODULES`
21+
- Zero-config compatibility: unset/empty variable = load all modules (current behavior)
22+
- Simple UX: space-separated list of directory names (e.g., `export ZSH_DISABLED_MODULES="tmux starship"`)
23+
- Self-documenting: the mechanism should be obvious from the zshrc loop itself
24+
25+
**Non-Goals:**
26+
- Per-module granularity beyond enable/disable (no per-module config inheritance)
27+
- Runtime toggling (requires zshrc reload)
28+
- A separate config file or state directory for module state
29+
30+
## Decisions
31+
32+
| Decision | Choice | Rationale |
33+
|----------|--------|-----------|
34+
| Mechanism | `ZSH_DISABLED_MODULES` env var | Consistent with existing `ZSH_*` conventions; no new files or config formats |
35+
| Separator | Space-delimited | Simplest for env vars; matches shell array conventions |
36+
| Comparison | Exact directory basename match | Users write `tmux`, not path fragments; names are fixed per directory |
37+
| Location | Skip inside existing `for __module_dir` loop | Single point of change; doesn't require loop restructuring |
38+
| User config file | `~/.customrc` | Already sourced by zshrc; natural place for user to set `ZSH_DISABLED_MODULES` without editing core files |
39+
| Bootstrap | Auto-copy `.customrc.example` β†’ `~/.customrc` on first load | If `~/.customrc` doesn't exist, copy the template from the repo. The template lives in the repo as `.customrc.example` so it's version-controlled and users get it on clone |
40+
| Sourcing order | Move `.customrc` source **before** the module loading loop | Currently at line 43, after modules. Must be before line 24 so `ZSH_DISABLED_MODULES` is set when the loop runs |
41+
| Documentation | Update `zsh/modules/README.md` + create `~/.customrc` | README covers module authors; `.customrc` serves as discoverable template for end users |
42+
43+
## Risks / Trade-offs
44+
45+
- **Env var not available in all contexts** (e.g., sudo, GUI terminals) β†’ User is responsible for setting it in their shell profile, same as any other `ZSH_*` variable
46+
- **Module name changes** would break existing disable configs β†’ Module directory names are stable (like package names); a rename would be a breaking change regardless
47+
- **Spaces in module names** not supported β†’ Module directories use single-word kebab-case names; no risk
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
## Why
2+
3+
Currently all modules under `zsh/modules/` are loaded unconditionally. If a module (e.g., tmux) has a bug or dependency issue, the user cannot source their zshrc without either moving the directory or editing the loading loop. A simple opt-out mechanism is needed to skip failing modules without structural changes.
4+
5+
## What Changes
6+
7+
- Add `ZSH_DISABLED_MODULES` environment variable support in the module loading loop in `zsh/.zshrc`
8+
- Modules listed in this variable (space-separated directory names) will be skipped during load
9+
- Document the mechanism so users know how to disable a module
10+
11+
## Capabilities
12+
13+
### New Capabilities
14+
15+
- `module-disable`: Allow users to selectively disable zsh modules by setting `ZSH_DISABLED_MODULES` in their environment or `~/.zshrc.local`
16+
17+
### Modified Capabilities
18+
19+
<!-- No existing specs change -- this is a new mechanism, not a requirement change to existing specs -->
20+
21+
## Impact
22+
23+
- `zsh/.zshrc`: Add skip logic inside the existing `for __module_dir` loop
24+
- `zsh/modules/README.md`: Document the disable mechanism
25+
- Existing configurations are unaffected: when `ZSH_DISABLED_MODULES` is unset or empty, all modules load as before
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
## ADDED Requirements
2+
3+
### Requirement: User can disable specific modules via ZSH_DISABLED_MODULES
4+
5+
The system SHALL skip loading a module's `plugin.zsh` when its directory basename is listed in the `ZSH_DISABLED_MODULES` environment variable. Users SHOULD set this variable in `~/.customrc` (or `${CUSTOMRC}`).
6+
7+
#### Scenario: Disable a single module
8+
9+
- **WHEN** `ZSH_DISABLED_MODULES="tmux"` is set in the environment before sourcing zshrc
10+
- **THEN** the module at `zsh/modules/tmux/` SHALL be skipped during the module loading loop
11+
12+
#### Scenario: Disable multiple modules
13+
14+
- **WHEN** `ZSH_DISABLED_MODULES="tmux starship"` is set in the environment
15+
- **THEN** both the `tmux` and `starship` modules SHALL be skipped during the module loading loop
16+
17+
#### Scenario: Unset variable loads all modules
18+
19+
- **WHEN** `ZSH_DISABLED_MODULES` is unset or empty
20+
- **THEN** all modules SHALL be loaded as before (existing behavior preserved)
21+
22+
#### Scenario: Disabled module skipped silently
23+
24+
- **WHEN** a disabled module directory is encountered in the loading loop
25+
- **THEN** the loop SHALL continue to the next module without producing any diagnostic output
26+
27+
#### Scenario: Variable supports comma and space delimiters
28+
29+
- **WHEN** `ZSH_DISABLED_MODULES="tmux,starship"` is set
30+
- **THEN** both `tmux` and `starship` SHALL be recognized as disabled
31+
32+
### Requirement: The disable mechanism is documented
33+
34+
#### Scenario: README reference
35+
36+
- **WHEN** a user reads `zsh/modules/README.md`
37+
- **THEN** they SHALL find documentation of the `ZSH_DISABLED_MODULES` mechanism
38+
39+
#### Scenario: Inline comment in zshrc
40+
41+
- **WHEN** a user reads the module loading loop in `zsh/.zshrc`
42+
- **THEN** an inline comment SHALL explain how to disable modules
43+
44+
#### Scenario: .customrc auto-created from example on first load
45+
46+
- **WHEN** `~/.customrc` does not exist and zshrc is sourced
47+
- **THEN** the file SHALL be created by copying `.customrc.example` from `DOTFILES_DIR`
48+
49+
#### Scenario: .customrc example template
50+
51+
- **WHEN** a user reads `.customrc.example` in the repo root
52+
- **THEN** they SHALL find commented-out `ZSH_DISABLED_MODULES` examples covering single, multiple, and comma-separated formats
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## 1. Reorder customrc before module loading loop
2+
3+
- [x] 1.1 Move the `.customrc` source block (currently line 42-43) to **before** the module loading loop (before line 23), so `ZSH_DISABLED_MODULES` is available when modules are iterated
4+
5+
## 2. Modify module loading loop in zshrc
6+
7+
- [x] 2.1 Add skip logic to the `for __module_dir` loop in `zsh/zshrc`: check if the basename of `__module_dir` is in `ZSH_DISABLED_MODULES` (space- or comma-delimited) before sourcing `plugin.zsh`
8+
- [x] 2.2 Add inline comment above the loop explaining how to disable modules via `ZSH_DISABLED_MODULES`
9+
10+
## 3. Create config template and auto-bootstrap
11+
12+
- [x] 3.1 Create `.customrc.example` in repo root with commented-out `ZSH_DISABLED_MODULES` examples (single, multiple, comma-separated)
13+
- [x] 3.2 Add auto-bootstrap logic to `zsh/zshrc`: if `~/.customrc` doesn't exist, copy `.customrc.example` from `DOTFILES_DIR`
14+
- [x] 3.3 Copy `.customrc.example` to `~/.dotfiles/.customrc.example` (deployed location)
15+
- [x] 3.4 Remove old manually-created `~/.customrc` (now auto-generated on next zshrc source)
16+
17+
## 4. Document disable mechanism
18+
19+
- [x] 4.1 Update `zsh/modules/README.md` with a "Disabling a module" section documenting the `ZSH_DISABLED_MODULES` environment variable
20+
21+
## 5. Verify
22+
23+
- [x] 5.1 Source the updated zshrc with `ZSH_DISABLED_MODULES="tmux"` and confirm tmux module is skipped
24+
- [x] 5.2 Source the updated zshrc with `ZSH_DISABLED_MODULES=""` (empty) and confirm all modules load
25+
- [x] 5.3 Source the updated zshrc with `ZSH_DISABLED_MODULES` unset and confirm all modules load
26+
- [x] 5.4 Source the updated zshrc with `ZSH_DISABLED_MODULES="tmux,starship"` and confirm comma-delimited parsing works
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-06-11
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
## Context
2+
3+
The `zsh/modules/devops/internal/kubectl.zsh` file has three functions that install tools by directly calling system commands (`brew install`, `curl | sh`) instead of delegating to `core::install`. This is the last vestige of direct package manager calls in the codebase β€” every other module uses `core::install` to abstract platform-specific install logic.
4+
5+
The three affected functions:
6+
7+
1. `kubecolor::install` β€” calls `brew install hidetatz/tap/kubecolor` directly
8+
2. `crossplane::install` β€” downloads and runs a shell script from `raw.githubusercontent.com`
9+
3. `krew::install` β€” downloads a tarball from GitHub releases, extracts it, and runs the binary
10+
11+
All three packages (`kubecolor`, `crossplane-cli`, `krew`) are available as standard Homebrew formulas, making `core::install <package>` viable on macOS. On Linux, `core::install` delegates to the distribution's native package manager.
12+
13+
## Goals / Non-Goals
14+
15+
**Goals:**
16+
- Replace all three install functions with `core::install` calls
17+
- Remove functions that become trivial one-liners after the refactor
18+
- Keep `krew::install` as a separate function only if it retains meaningful logic beyond `core::install krew`
19+
- Maintain backward compatibility β€” the `main::factory` must still install all three tools
20+
21+
**Non-Goals:**
22+
- Changing the `krew::load` or `completion::load` functions (unrelated)
23+
- Refactoring `plugin::install` / `plugins::install` (those use `kubectl krew install`, not system installs)
24+
- Changing the Go packages install loop (already uses `goenv::internal::package::install`)
25+
26+
## Decisions
27+
28+
| Decision | Choice | Rationale |
29+
|----------|--------|-----------|
30+
| Replace `brew install hidetatz/tap/kubecolor` | `core::install kubecolor` | `kubecolor` is now a standard Homebrew formula; `core::install` already wraps `brew install ${@}` on macOS |
31+
| Replace `curl | sh` for crossplane | Remove crossplane entirely | Crossplane is no longer used in the project; removed from factory altogether |
32+
| Replace tarball download for krew | `core::install krew` | `krew` is a standard Homebrew formula; `brew install krew` installs `kubectl-krew` |
33+
| Remove `kubecolor::install` after refactor | Keep as `core::ensure kubecolor` | Function becomes a one-liner; integrate into `main::factory` directly |
34+
| Remove `crossplane::install` after refactor | Keep as `core::ensure crossplane` | Same β€” one-liner in factory |
35+
| Keep `krew::install` | Refactor body to `core::install krew` | Function name is descriptive; keeps `main::factory` readable |
36+
37+
## Risks / Trade-offs
38+
39+
| Risk | Mitigation |
40+
|------|------------|
41+
| Crossplane no longer used | Removed from factory entirely | Eliminates unnecessary dependency; no brew formula compatibility concerns |
42+
| KREW install path expectation (historically downloads to tmpdir) | Brew installs to standard Cellar prefix; `kubectl krew` still works as expected |
43+
| Linux fallback for `core::install` may not have these packages | Same risk as all other `core::install` consumers; consistent failure mode is better than silent platform bypass |
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
## Why
2+
3+
The `zsh/modules/devops/internal/kubectl.zsh` file contains three install functions (`kubecolor`, `crossplane`, `krew`) that bypass the project's established `core::install` abstraction layer, calling `brew install` and `curl | sh` directly. This is inconsistent with the rest of the codebase where all package installations go through `core::install` (which delegates to platform-specific logic via `core::internal::core::install`). Bypassing the abstraction creates maintainability gaps: platform-specific install paths (Linux vs macOS), error handling, and idempotency checks are all punted to each function instead of being handled centrally.
4+
5+
## What Changes
6+
7+
- `devops::kubectl::internal::kubecolor::install`: Replace direct `brew install hidetatz/tap/kubecolor` with `core::install kubecolor`
8+
- `devops::kubectl::internal::crossplane::install`: Remove crossplane entirely β€” crossplane is no longer used in the project
9+
- `devops::kubectl::internal::krew::install`: Replace manual tarball download/extraction with `core::install krew`
10+
- Update `devops::kubectl::internal::main::factory` if guard conditions or function signatures change
11+
- Remove install functions that become no-ops (single-line wrappers around `core::install`)
12+
13+
## Capabilities
14+
15+
### New Capabilities
16+
- `kubectl-install-refactor`: Refactor of the three install functions in `kubectl.zsh` to use `core::install` instead of direct commands
17+
18+
### Modified Capabilities
19+
- *(none β€” this is an internal refactor, no spec-level behavior changes)*
20+
21+
## Impact
22+
23+
- **File**: `zsh/modules/devops/internal/kubectl.zsh` β€” 3 functions rewritten, 1 factory function potentially updated
24+
- **Dependencies**: Relies on `core::install` existing and supporting `kubecolor`, `crossplane`, and `krew` packages via Homebrew (macOS) and native package managers (Linux)
25+
- **Behavior**: Idempotency via `core::exists` guard already present in factory and some install functions; `core::install` itself may add idempotency checks. Crossplane removed entirely (no longer used).
26+
- **Regressions**: If any of these packages are not available via `core::install` on a given platform, the install will fail instead of silently proceeding β€” this is actually desired behavior (fail fast)
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
## ADDED Requirements
2+
3+
### Requirement: Install kubecolor via core::install
4+
The system SHALL install kubecolor using `core::install kubecolor` instead of calling `brew install` directly.
5+
6+
#### Scenario: kubecolor installed via core path
7+
- **WHEN** `devops::kubectl::internal::kubecolor::install` is called
8+
- **THEN** it SHALL invoke `core::install kubecolor`
9+
- **AND** it SHALL NOT call `brew install` directly
10+
11+
#### Scenario: kubecolor already installed
12+
- **WHEN** `kubecolor` is already present on the system
13+
- **THEN** the install function SHALL be skipped (guard via `core::exists kubecolor`)
14+
15+
### Requirement: Remove crossplane install
16+
Crossplane is no longer used in the project. The system SHALL NOT install crossplane during devops factory initialization.
17+
18+
#### Scenario: crossplane not installed during factory
19+
- **WHEN** `devops::kubectl::internal::main::factory` is called
20+
- **THEN** crossplane SHALL NOT be installed or verified
21+
22+
### Requirement: Install krew via core::install
23+
The system SHALL install krew using `core::install krew` instead of downloading and extracting a tarball.
24+
25+
#### Scenario: krew installed via core path
26+
- **WHEN** `devops::kubectl::internal::krew::install` is called
27+
- **THEN** it SHALL invoke `core::install krew`
28+
- **AND** it SHALL NOT download tarballs from GitHub releases
29+
30+
#### Scenario: krew already installed
31+
- **WHEN** `kubectl-krew` is already present on the system
32+
- **THEN** the install function SHALL be skipped
33+
34+
### Requirement: Factory orchestrates all installs
35+
The `devops::kubectl::internal::main::factory` function SHALL continue to call all three install paths, using `core::ensure` for tools that no longer need a separate install function.
36+
37+
#### Scenario: factory installs all tools
38+
- **WHEN** `main::factory` is called
39+
- **THEN** `kubectl`, `helm`, `kubectx`, `kubecolor`, `crossplane`, and `krew` SHALL all be installed or verified
40+
- **AND** the completion setup SHALL still be loaded
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
## 1. Refactor kubecolor install
2+
3+
- [x] 1.1 Replace `brew install hidetatz/tap/kubecolor` in `devops::kubectl::internal::kubecolor::install` with `core::install kubecolor`, keeping the `core::exists` guard
4+
5+
## 2. Refactor crossplane install
6+
7+
- [x] 2.1 Replace the `curl -sL https://raw.githubusercontent.com/crossplane/crossplane/master/install.sh | sh` pipeline with `core::install crossplane` in `devops::kubectl::internal::crossplane::install`
8+
- [x] 2.2 Remove the `mv crossplane "${DEVOPS_KUBECTL_LOCAL_PATH_BIN}/"` line (brew handles binary placement)
9+
- [x] 2.3 Remove crossplane entirely from `main::factory` β€” crossplane is no longer used in the project
10+
11+
## 3. Refactor krew install
12+
13+
- [x] 3.1 Replace the tarball download/extract/run logic in `devops::kubectl::internal::krew::install` with `core::install krew`, keeping the `core::exists` guard
14+
15+
## 4. Clean up factory and remove dead code
16+
17+
- [x] 4.1 If `kubecolor::install` becomes a trivial one-liner, inline it into `main::factory` as `core::ensure kubecolor` and remove the function
18+
- [x] 4.2 If `crossplane::install` becomes a trivial one-liner, inline it into `main::factory` as `core::ensure crossplane` and remove the function
19+
20+
## 5. Verify
21+
22+
- [x] 5.1 Source the updated zshrc and confirm kubecolor, crossplane, and krew install without errors on macOS
23+
- [x] 5.2 Confirm no `brew install` or `curl.*install.sh` calls remain in `kubectl.zsh` (grep for `brew install`, `curl.*|.*sh`)

0 commit comments

Comments
Β (0)