Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 120 additions & 34 deletions docs/development/v021-ledger.md

Large diffs are not rendered by default.

126 changes: 126 additions & 0 deletions docs/development/v021-release-runbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
# v0.21.0 release runbook

The exact steps from the v0.21 release candidate to a published v0.21.0, following the v0.20.0 release (PR #392,
tag `v0.20.0` on its merge commit, Homebrew tap PR #3, website sync). State and evidence are in the
[ledger](v021-ledger.md). **Nothing here runs without the maintainer's go-ahead after physical QA.** Tagging, the
GitHub release, the tap and the website deploy are public and hard to undo.

## How a release moves

`dev` → `release/v0.21.0` (one release-metadata commit) → PR into `master`, merged with a regular merge commit →
annotated tag `v0.21.0` on that merge commit → GitHub release (no uploaded assets; the source archive is GitHub's tag
archive) → Homebrew tap PR (source formula: `url` and `sha256` of the tag archive) → website sync PR into `master` →
manual **Publish NMSh website** dispatch, approved in the `nmsh-production` environment. No npm package is published
(`package.json` is `private`). `master` needs the `Verify (22.x)` and `Verify (26.x)` checks; the old GitHub Pages
deploy (`deploy.yml`) is disabled.

## 0. Before starting

- The physical QA results table has a recorded result for every row on the candidate build (or the maintainer has
decided, in writing, which rows a release may go without, as v0.20 did).
- Dispatched CI is green on the **exact** head of `release/v0.21.0`. Code changes need the full dispatch: Fedora and
both timing smokes run only on dispatch and push.
- `npm run verify:release` has run on that head locally, and any failure is explained by evidence.

```sh
git fetch origin
RC=$(git rev-parse origin/release/v0.21.0)
gh run list --workflow CI --commit "$RC" --json event,conclusion,url # a workflow_dispatch run, success
```

## 1. Final release commit (on `release/v0.21.0`)

1. `CHANGELOG.md`: `## [0.21.0] - Unreleased (release candidate)` becomes `## [0.21.0] - YYYY-MM-DD` (the
publication date); remove the candidate sentence; keep Compatibility, Upgrade notes and Known limitations accurate
to the QA results. Link references at the bottom: `[Unreleased]: …/compare/v0.21.0...HEAD` and
`[0.21.0]: …/compare/v0.20.0...v0.21.0`.
2. `docs/testing/v021-physical-qa.md`: the recorded results and the release decision.
3. README, ROADMAP, AGENTS, SECURITY, CONTRIBUTING and `llms.txt`: v0.21.0 is the current stable release.
4. Commit, push, dispatch CI on the new head and wait for it:

```sh
gh workflow run CI --ref release/v0.21.0
FINAL=$(git rev-parse origin/release/v0.21.0)
gh run list --workflow CI --commit "$FINAL" --json event,conclusion,url
```

## 2. Merge into `master`

```sh
gh pr ready <release PR>
gh pr merge <release PR> --merge # a regular merge commit, as #392
git fetch origin
MERGE=$(git rev-parse origin/master)
test "$(git rev-parse "$MERGE^{tree}")" = "$(git rev-parse "$FINAL^{tree}")" && echo "tree matches the verified commit"
```

## 3. Tag and GitHub release

```sh
git tag -a v0.21.0 -m "v0.21.0 — <title>" "$MERGE"
git push origin v0.21.0
gh release create v0.21.0 --verify-tag --latest --title "v0.21.0 — <title>" --notes-file <notes.md>
```

The release body becomes the website's release page at the next sync. Write it from the changelog section (a shorter
summary is fine) and link to the full CHANGELOG.

## 4. Verify the published source

In a shell started with `t7run zsh` (so `$T7_RUN_DIR` is a scratch directory on the T7):

```sh
mkdir -p "$T7_RUN_DIR/v021-archive" && cd "$T7_RUN_DIR/v021-archive"
curl -fsSL -o v0.21.0.tar.gz https://github.com/raiseCatError/notMyShell/archive/refs/tags/v0.21.0.tar.gz
shasum -a 256 v0.21.0.tar.gz # the tap's sha256
tar -xOzf v0.21.0.tar.gz notMyShell-0.21.0/package.json | grep '"version"' # "0.21.0"
mkdir src && tar -xzf v0.21.0.tar.gz -C src && cd src/notMyShell-0.21.0
npm ci && npm run build && node dist/index.js --version # notMyShell 0.21.0, build <MERGE short sha>
```

## 5. Homebrew tap (`raiseCatError/homebrew-tap`)

The scheduled `brew bump` failed daily from 2026-10-13 to 10-17 (`depends_on "python"`); v0.20.0 was a manual PR (#3).
Do the same unless an autobump PR for 0.21.0 has already appeared and is correct:

1. Branch `nmsh-0.21.0`; in `Formula/nmsh.rb` set `url` to `…/archive/refs/tags/v0.21.0.tar.gz` and `sha256` to the
value from step 4. Nothing else needs changing: every file v0.21 reads at runtime is in `dist`, and the formula's
install list is unchanged since v0.20.
2. Open the PR; `tests.yml` (`brew test-bot`) must pass on macOS and Linux; merge (source-only formula, no bottles).
3. Locally: `brew update && brew upgrade raiseCatError/tap/nmsh && nmsh --version`.

## 6. Website

After the tap PR is merged (the sync records the tap's version, and the deploy checks it again):

```sh
cd website && npm ci
npm run sync:upstream -- --published v0.21.0 --version 0.21.0
npm test && npm run check && npm run build:launch && npm run test:browser
```

Open a PR into `master` with the result. Once it is merged, dispatch **Publish NMSh website** from `master` with the
confirmation `nmsh.raisecaterror.com`, approve the `nmsh-production` deployment, and check the live site.

## 7. After the release

- Smoke on the published build: a Homebrew install (`nmsh --version`, start in Ghostty, run a command, `/view
README.md`, `/track df -h`, `/track stop`, `exit`) and a source install's `/update` (offers v0.21.0 as the latest
stable release).
- Bring `dev` to `master`: `git switch dev && git pull --ff-only origin dev && git merge --ff-only origin/master &&
git push origin dev` (`master` = `dev` after v0.20 too).
- Close the issues whose acceptance is met, with the physical results; keep partial ones open with the remaining
scope (ledger, "Issues and PRs").

## Rollback

- **Before the tag:** nothing is public yet. A bad merge into `master` is reverted through a PR.
- **A bad release after publication:** fix forward with v0.21.1. A pushed tag may already be cached by Homebrew,
GitHub's archive and users, so do not move or recreate it. Meanwhile:
- `gh release edit v0.20.0 --latest` makes `/update` (it reads `releases/latest`) offer v0.20.0 again.
- Mark v0.21.0 as a prerelease (`gh release edit v0.21.0 --prerelease`) so it is no longer offered.
- Revert the tap PR (formula back to the v0.20.0 `url` and `sha256`) through a tap PR.
- Website: revert its sync PR on `master` and dispatch the publish again (approval required), or roll the Pages
project back to its previous deployment.
- **The tag points at the wrong commit and nothing has consumed it yet** (no release, tap or sync): delete it
(`git push --delete origin v0.21.0`) and tag the verified merge commit. Otherwise fix forward.
96 changes: 91 additions & 5 deletions docs/testing/v021-physical-qa.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@

## Status

- **Candidate, not released.** Items are added as each v0.21 slice lands ([ledger](../development/v021-ledger.md)).
Nothing below has been run yet.
- **Candidate, not released.** Every v0.21 slice is merged into `dev` (#411–#424, `433af8fd`); see the
[ledger](../development/v021-ledger.md). Nothing below has been run yet. The [walkthrough](#walkthrough) groups the
passes into three sittings with ready-made fixtures.
- **What does not count:** headless PTY fixtures, synthetic keys, captures of a window driven that way, and green CI.
Each item needs a person at a real terminal with a real keyboard and pointer.
- **Build under test:** record the exact commit (`node dist/index.js --version`) built from a clean checkout
(`npm ci && npm run build`).
- **Isolation:** a throwaway `HOME` (or `XDG_CONFIG_HOME`) and a temporary working directory.
- **Build under test:** the release candidate commit, built from a clean checkout (`npm ci && npm run build`); record
`node dist/index.js --version` (version and build commit) in every row.
- **Isolation:** `scripts/qa/v021-fixtures.sh` makes a throwaway folder with its own NMSh configuration
(`XDG_CONFIG_HOME`) and session service (`NMSH_RUNTIME_DIR`), so the QA build neither reads your settings nor
attaches to your running sessions. `HOME` is unchanged: your shell's own startup files run as usual, and copy
items replace your clipboard.

## Results

Expand All @@ -27,6 +31,85 @@
| 7 Themes | Ghostty | macOS | zsh | | Not run | |
| 7 Themes | Terminal.app | macOS | zsh | | Not run | |

Not available on the QA Mac, with the coverage that exists instead: 1.5b (`apt` progress; Linux only: parser and live
PTY tests, Ubuntu and Fedora CI), 4.3 `cargo build` (not installed; Rust diagnostics recognizer tests), 6.2
(`nix-shell`; the module reads only `IN_NIX_SHELL`, unit-tested), 6.3 inside a container (marker files and host
variables, unit-tested; the host half is checked here). Each stays *Not run* in the table, never *Pass*.

## Walkthrough

Three sittings, about 90 minutes in all. Every command below is typed in NMSh unless it says "second tab".

**Prepare once.** In a plain terminal window (not inside NMSh):

```sh
cd /Volumes/T7Tmp/ai-dev/worktrees/notMyShell/v021-qa # the release candidate build
node dist/index.js --version # record version and build commit
scripts/qa/v021-fixtures.sh /Volumes/T7Tmp/ai-dev/q21
```

Each sitting starts from a plain window: `/Volumes/T7Tmp/ai-dev/q21/nmsh` (zsh), `… nmsh fish` or `… nmsh bash`. NMSh
opens in `q21/work`; paths below are relative to it. Quit with `exit`. If a launch offers to resume a detached
session (after 5.8), press R and `exit` it.

**Reporting.** One line per pass, for example `Pass 2 · Ghostty 1.3.1 · zsh 5.9: PASS` or `Pass 2: FAIL 2.7 (the FIFO
hung)`; a screenshot helps. Each result is recorded in the table with the build commit. A skipped item is *Not run*.

### Sitting A: Ghostty, zsh (passes 1–7, about 60 minutes)

1. **Progress.** 1.1 as listed, then at 40 columns. 1.2 `node progress/multirow.mjs`: two `layer` rows count up in
place; `LOG pulling two layers`, `LOG checkpoint 1`–`4` and `LOG done` all stay, in order. 1.3 `node
progress/multirow.mjs 120` (30 s) while you resize. 1.4 `/copy`, then `pbpaste`: the final rows once. 1.5 as listed.
1.5a at 40 columns `node progress/wrapped.mjs`: both `LOG kept line` lines and `LOG done` stay, with one `Downloading
… 100%` and one `eta 0s`. 1.6 `vim view/tabs.txt`, `less view/README.md`, `top` (htop is not installed). 1.7 `sleep
5`, then `ls` Ctrl+Q and `pwd` Ctrl+Q while it runs.
2. **`/view`.** 2.1 `/view view/README.md`. 2.2 `/view view/ViewerPanel.ts`. 2.3 in either. 2.4 as listed. 2.5 `/view
view/tabs.txt`, `c`, Esc, `pbpaste | cat -vet`: `^I` between columns, `$` only at line ends. 2.6 `/view
view/hostile.txt`. 2.7 `/view view/folder`, `view/missing.txt`, `/dev/zero`, `view/fifo` (returns at once),
`/bin/ls`, `view/big.log` (20 MB: the first 8 MiB with a note). 2.8 as listed, with `node progress/multirow.mjs 120`
running. 2.9 in step 8.
3. **`/edit`.** 3.1 `cd edit/empty`, then as listed, with `cat -vet notes.txt` (macOS `cat` has no `-A`): `^I` and a
final `$`. 3.2–3.4 on that file. 3.5 second tab: `echo x >> /Volumes/T7Tmp/ai-dev/q21/work/edit/empty/notes.txt`.
`cd ../..`. 3.6 `/edit edit/crlf-bom.txt`, change a word, save; `xxd edit/crlf-bom.txt | head -1` starts `efbb bf`
and lines end `0d0a`. `/edit edit/mode640.txt`, save, `ls -l edit/mode640.txt` is `-rw-r-----`. `/edit
edit/link.txt`, save, `ls -l edit/link.txt` still points to `target.txt`. 3.7 `/edit /bin/ls`, `/etc/hosts`,
`edit/big.txt` (2 MB), `edit/readonly.txt`, then `/view` each. 3.8 with `view/README.md` and `edit/target.txt`.
3.9 `/edit edit/alias/new.txt` (`alias` links to `real`).
4. **Semantic Output.** 4.1 `cd semantic/pass && npm test` (40 passing tests stand in for this repository's suite).
4.2 `cd ../fail && npm test` (2 failing). 4.3 `cd ../ts && npx --no-install tsc --noEmit bad.ts` (2 errors); `cd
../c && make` (1 error). 4.4 `cd ../../git && git status`, `ls`, `cat tracked.txt`. 4.5 `cd ../semantic/fail &&
npm test`, then as listed, with `pbpaste` after each copy. 4.6 as listed. 4.7 in step 8. `cd ../..` when done.
5. **`/track`.** 5.1 `/track df -h`; `/track --every 5s -- docker system df` is optional (read-only, but it asks your
Docker daemon every 5 s). 5.2 `/track --every 2s -- cat track/f.txt`; second tab: `seq 1 2 >
/Volumes/T7Tmp/ai-dev/q21/work/track/f.txt`, then `seq 1 40 > …`. 5.3–5.7 as listed (5.7's `/tmp/t` commands are
refused and never run; answer No to the ones that ask). 5.8 `/track --every 3s -- sh -c 'sleep 2; echo QA-TRACK'`
(Yes), close the window while it runs, then in a new window `pgrep -fl QA-TRACK` prints nothing. 5.9 and 5.10 as
listed. 5.11 in step 8.
6. **Modules.** 6.1 `cd modules/bun-app`, `/modules`: Bun is recommended; enabled, typing `bun` shows `1.1.30`
requested (Bun is not installed, so nothing active). 6.2 Not run (no Nix). 6.3 on this Mac the container module shows
nothing; the in-container half is Not run. 6.4 and 6.5 as listed. `cd ../..`.
7. **Themes.** 7.1–7.3 as listed. 7.4 in step 8. 7.5 second tab, before and after switching themes: `shasum
"$HOME/Library/Application Support/com.mitchellh.ghostty/config"*`: unchanged. Older configuration: `exit`, then in
a plain window `mkdir -p /Volumes/T7Tmp/ai-dev/q21/config-copy/nmsh && cp "$HOME/Library/Application
Support/notMyShell/config.json" /Volumes/T7Tmp/ai-dev/q21/config-copy/nmsh/` and `/Volumes/T7Tmp/ai-dev/q21/nmsh
copy`: the theme and prompt saved by your v0.20 install load unchanged (only the copy can change).
8. **No color and Safe glyphs** (2.9, 4.7, 5.11, 7.4). `exit`, relaunch as `NO_COLOR=1 /Volumes/T7Tmp/ai-dev/q21/nmsh`,
Settings → Appearance → Safe glyphs, then 2.9 on `view/README.md`, 4.7 with `cd semantic/fail && npm test` at 40
columns (`cd ../..` after), 5.11 with `/track df -h`. Then 7.4's second half: `exit`, relaunch as `env -u COLORTERM TERM=xterm-256color
/Volumes/T7Tmp/ai-dev/q21/nmsh` and switch between two new themes.

### Sitting B: Terminal.app, Fish (passes 1, 3 and 5, about 20 minutes)

`/Volumes/T7Tmp/ai-dev/q21/nmsh fish`, then 1.1–1.7 (not 1.5b), 3.1–3.9 and 5.1–5.10 as in sitting A, and 5.11 with
`NO_COLOR=1 /Volumes/T7Tmp/ai-dev/q21/nmsh fish` as in step 8. Terminal.app is a 256-color terminal: note anything that
reads differently from Ghostty.

### Sitting C: Terminal.app, zsh (passes 2 and 7, about 15 minutes)

`/Volumes/T7Tmp/ai-dev/q21/nmsh`, then 2.1–2.8 and 7.1–7.5 as in sitting A, and 2.9 and 7.4 as in step 8.

When every result is recorded: `rm -rf /Volumes/T7Tmp/ai-dev/q21`.

## 1. Multi-row progress redraws (#388)

- [ ] 1.1 `node -e 'for (let i=0;i<=10;i++){process.stdout.write((i?"\x1b[1A\x1b[2K":"")+"Downloading "+i*10+"%\n")}'`
Expand Down Expand Up @@ -80,6 +163,9 @@
(`chmod 444`) each explain why, and `/view` still opens them.
- [ ] 3.8 `e` in `/view README.md` opens the editor on the same file; `vim notes.txt` and `nano notes.txt` still run as
usual from the composer.
- [ ] 3.9 A new file in a folder reached through a symlink (macOS `/tmp`, or `alias -> real`): `/edit alias/new.txt`,
type a line, Ctrl+S, type another, Ctrl+S: *Saved* both times and no conflict question (#425); `real/new.txt` has
both lines.

## 4. Semantic Output

Expand Down
2 changes: 1 addition & 1 deletion llms.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# notMyShell (NMSh)

Latest published stable release: v0.18.0 (see CHANGELOG.md). Homebrew is the recommended macOS install: brew install raiseCatError/tap/nmsh. Source installation remains available.
Latest published stable release: v0.20.0 (see CHANGELOG.md). Homebrew is the recommended macOS install: brew install raiseCatError/tap/nmsh. Source installation remains available.

notMyShell is a terminal frontend that runs on top of a real persistent shell instead of replacing the shell.

Expand Down
Loading
Loading