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
|
# Coding Standards — modal-shell
## Shell
- **Default shell:** bash. Shebang is always `#!/usr/bin/env bash`.
- **Strict mode:** Every file starts with `set -euo pipefail`. No exceptions.
- **No bashisms in base.sh:** `base.sh` must also be compatible with zsh (users may source it from their zsh config).
- **Functions over repetition:** If the same logic appears in two modes, move it to `base.sh`.
- **No global side effects in sourced files:** `base.sh` sets env vars; it does not print output, start processes, or modify files.
## Naming
- Mode files: lowercase, hyphen-separated, `.sh` suffix. e.g. `db.sh`, `build.sh`, `reset.sh`.
- Variables in `base.sh`: `UPPER_SNAKE_CASE`. Exported.
- Local variables in mode files: `lower_snake_case`. Use `local` inside functions.
- The dispatcher: `ms` (no extension, executable).
## Mode File Structure
```sh
#!/usr/bin/env bash
# <one-line description of what this mode does>
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/base.sh"
# mode logic here
```
## Confirmation Pattern for Destructive Modes
```sh
echo "This will destroy and rebuild the environment."
read -rp "Continue? [y/N] " confirm
[[ "$confirm" =~ ^[Yy]$ ]] || { echo "Aborted."; exit 1; }
```
## Error Handling
- Let `set -e` do the work. Do not swallow errors with `|| true` unless intentional and commented.
- On expected failure paths, print a helpful message to stderr before exiting: `echo "error: <message>" >&2`
- Do not use `exit 0` at the end of scripts — implicit success is fine.
## Testing
- Syntax check: `bash -n modes/<name>.sh` before committing any mode file.
- Dry-run: modes that exec into an environment can be tested with `bash -c 'source modes/base.sh && echo $PROJECT_ROOT'`.
- No automated test framework required for this project's scope. Manual verification is acceptable.
## The Dispatcher (`ms`)
- Must stay under 40 lines.
- No logic beyond: find the modes directory, match argument to filename, exec.
- If no argument: list available modes (strip `.sh`, sort).
- If unknown mode: print error to stderr, exit 1.
- Never source the mode file — always exec it (so the mode can itself exec into a shell without forking).
## What Not to Do
- No `eval`.
- No `set +e` blocks.
- No hardcoded paths outside of `base.sh`.
- No secrets in any file tracked by git.
- No modes that call other modes.
|