diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-27 23:23:43 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-27 23:23:43 +0000 |
| commit | 7519de4e56323230d06cfc64b00df52339eb2434 (patch) | |
| tree | 87712be5ef45a6913601e729c18a67c20fee9f09 /docs/adr/001-mode-as-intent.md | |
Add modal-shell project with .agent/ config, mode stubs, ms dispatcher,
bare repo at /site/git.terst.org/repos/modal-shell.git, and ADR-001.
Diffstat (limited to 'docs/adr/001-mode-as-intent.md')
| -rw-r--r-- | docs/adr/001-mode-as-intent.md | 71 |
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 |
