summaryrefslogtreecommitdiff
path: root/docs/adr
diff options
context:
space:
mode:
Diffstat (limited to 'docs/adr')
-rw-r--r--docs/adr/001-mode-as-intent.md71
1 files changed, 71 insertions, 0 deletions
diff --git a/docs/adr/001-mode-as-intent.md b/docs/adr/001-mode-as-intent.md
new file mode 100644
index 0000000..c50d28b
--- /dev/null
+++ b/docs/adr/001-mode-as-intent.md
@@ -0,0 +1,71 @@
+# 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 <name> ] → [ modes/<name>.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 <tab>` 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