# ADR-001: Mode-as-Intent Architecture **Status:** Accepted **Date:** 2026-03-27 --- ## Context Shell environments accumulate ambient state. Developers memorize incantations. Onboarding is tribal knowledge. The original modal-shell (circa 2014) had the right instinct — encode intent at the entry point — but buried it inside a Vagrant/PHP project with one-level composition via `VAGRANT_OPTS`. The question: what is the minimal, portable, modern expression of the same idea? ## Decision modal-shell is organized around **named modes**. Each mode is a shell script that: 1. Sources a shared `base.sh` (environment foundation) 2. Does exactly one thing (opens a DB shell, runs a build, streams logs, etc.) A dumb dispatcher (`ms`) maps a name to a file in `modes/` and executes it. ``` [ ms ] → [ modes/.sh ] → [ source base.sh + do the thing ] ``` ### What This Replaces | Old pattern | New pattern | |---|---| | Remember the right `vagrant ssh` incantation | `ms db` | | Set `VAGRANT_OPTS` before calling `up.zsh` | Mode file sets its own context, sources base | | Hardcoded paths and VM-specific env vars | `PROJECT_ROOT` in `base.sh`, configurable | | Secrets in env var assignments in scripts | Secrets loaded at runtime via `op run --` | | Destructive script with no confirmation | `reset.sh` prompts before proceeding | | Vagrant + Phing | Any runtime: Docker, nix, k8s, bare metal | ### Why Not Use an Existing Tool? - **direnv alone:** handles automatic env loading, not intent-encoded entry points. - **`just` / `make` alone:** task runners, not environment launchers. Good for build steps, not for "drop me into a database shell." - **devcontainer:** heavy, IDE-coupled. Good for standardizing dev environments, not for mode dispatch within one. - **A framework:** violates the minimal-footprint principle. The dispatcher is 30 lines of shell. There is nothing to install. The right answer is composing these tools: `direnv` handles automatic base env, `ms` handles explicit mode entry, `just`/`make` handles build tasks invoked from within modes. ## Consequences - **Positive:** Any project member who knows `ms ` knows all available modes. - **Positive:** Modes are self-documenting — the filename is the intent, the file is the implementation. - **Positive:** Adding a mode requires creating one file. Removing one requires deleting it. - **Positive:** Works identically in CI — `ms build` in a CI job is the same as locally. - **Negative:** `base.sh` becomes a coordination point. It must be kept minimal; project-specific customization should be documented clearly. - **Negative:** No state between modes. If a mode needs to know what another mode set up, that coordination must happen externally (e.g., a running container, a written file). ## Alternatives Considered **1. Single script with subcommands (`ms db`, `ms build` all in one file)** Rejected. Concentrates all mode logic in one file; adding a mode requires editing the dispatcher; violates the "dispatcher stays dumb" principle. **2. Environment variables to select mode (`MODE=db ms`)** Rejected. Opaque, harder to tab-complete, non-idiomatic. **3. Aliases per mode in shell config** Rejected. Not portable across machines, not version-controlled with the project, requires per-machine setup. ## Related - `modes/base.sh` — the shared foundation - `ms` — the dispatcher - `.envrc` — direnv integration