blob: f17a80b13189a68d81d46a82e7c81cc599788681 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
# Architecture — modal-shell
## Overview
modal-shell is a three-layer system:
```
[ ms dispatcher ] → [ mode file ] → [ base.sh + runtime ]
```
1. **`ms`** — dumb dispatcher. Reads `modes/` directory, matches argument to filename, executes.
2. **Mode files** — encode intent. Each sources `base.sh` and then does exactly one thing.
3. **`base.sh`** — shared environment foundation. Sets `PROJECT_ROOT`, loads secrets, configures toolchain.
## Component Map
| Component | Role |
|---|---|
| `ms` | Dispatcher. Lists modes if no arg. Exits non-zero on unknown mode. |
| `modes/base.sh` | Foundation. Sourced by all modes. Never executed directly. |
| `modes/db.sh` | Opens an interactive database shell. |
| `modes/build.sh` | Runs the build pipeline to completion. |
| `modes/reset.sh` | Destroys and rebuilds the environment. Requires confirmation. |
| `modes/logs.sh` | Streams runtime logs. |
| `.envrc` | direnv hook. Sources `base.sh` automatically on `cd`. |
## Composition Model
Every mode follows this pattern:
```sh
#!/usr/bin/env bash
set -euo pipefail
source "$(dirname "$0")/base.sh"
# mode-specific logic here
```
Modes do not call other modes. Modes do not share state with each other. If two modes need the same sub-behavior, that behavior belongs in `base.sh`.
## Data Flow
```
user: ms db
ms → finds modes/db.sh
modes/db.sh → source base.sh (sets PROJECT_ROOT, loads env)
modes/db.sh → exec psql / mysql / redis-cli (as configured in base.sh)
user lands in DB shell with correct credentials
```
## Secrets
Secrets are never stored in files. `base.sh` loads them at runtime via:
- `op run --` (1Password CLI) — preferred
- `doppler run --` — alternative
- `.env` file sourced locally — acceptable for non-sensitive local dev only, never committed
## Adding a New Mode
1. Create `modes/<name>.sh`
2. Start with the standard header (shebang + `set -euo pipefail` + `source base.sh`)
3. Implement the mode
4. Add a brief comment at the top describing intent
5. If destructive: add a confirmation prompt before proceeding
6. Run `bash -n modes/<name>.sh` to verify syntax
7. Update `worklog.md`
## ADRs
See `docs/adr/` for decisions on:
- [ADR-001](../docs/adr/001-mode-as-intent.md) — Mode-as-intent architecture
|