@ree-auz/nook 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +422 -410
  3. package/bin/nk.mjs +3 -3
  4. package/docs/setup-hooks.md +107 -107
  5. package/integrations/linear/README.md +84 -84
  6. package/integrations/linear/coding-tools.json +7 -7
  7. package/integrations/linear/open-issue.mjs +147 -147
  8. package/package.json +34 -34
  9. package/skills/nook/SKILL.md +71 -71
  10. package/src/agents.mjs +92 -92
  11. package/src/backup.mjs +74 -74
  12. package/src/ci.mjs +378 -378
  13. package/src/cli.mjs +151 -151
  14. package/src/commands/check-hooks.mjs +128 -128
  15. package/src/commands/clean.mjs +175 -175
  16. package/src/commands/doctor.mjs +100 -91
  17. package/src/commands/forget.mjs +57 -57
  18. package/src/commands/hooks.mjs +34 -27
  19. package/src/commands/init.mjs +154 -154
  20. package/src/commands/ls.mjs +107 -107
  21. package/src/commands/rm.mjs +190 -190
  22. package/src/commands/workspace.mjs +163 -163
  23. package/src/commands/write-hooks.mjs +185 -185
  24. package/src/compose-plan.mjs +228 -228
  25. package/src/compose.mjs +123 -123
  26. package/src/config.mjs +115 -115
  27. package/src/context.mjs +189 -189
  28. package/src/errors.mjs +8 -8
  29. package/src/forge.mjs +75 -75
  30. package/src/generate-hooks.mjs +367 -367
  31. package/src/git.mjs +86 -86
  32. package/src/home.mjs +48 -48
  33. package/src/hooks.mjs +135 -135
  34. package/src/names.mjs +65 -65
  35. package/src/occupancy-windows.ps1 +59 -59
  36. package/src/occupancy.mjs +131 -131
  37. package/src/ports.mjs +89 -45
  38. package/src/prompt.mjs +100 -100
  39. package/src/shared.mjs +97 -97
  40. package/src/status.mjs +30 -30
  41. package/src/tools.mjs +23 -23
  42. package/src/ui.mjs +60 -60
package/README.md CHANGED
@@ -1,410 +1,422 @@
1
- # Nook
2
-
3
- `nk` keeps every git worktree you work in under one home folder, `~/nook/`.
4
- It creates a workspace for a branch, copies the ignored files the project needs
5
- (such as `.env`), gives each workspace its own block of ports, runs the
6
- repository's setup hooks, and can start a command-line agent inside it.
7
-
8
- > Version 0.1: an early release. Commands and config may still change.
9
-
10
- ## Install
11
-
12
- ```
13
- npm install -g @ree-auz/nook
14
- nk doctor
15
- ```
16
-
17
- Needs Node.js 20.12 or later and git. Docker is used when a repository's hooks
18
- start services, and the [GitHub CLI](https://cli.github.com/) (`gh`) for pull
19
- requests and CI in `nk ls` and `nk clean`; both are optional. `nk doctor`
20
- checks them and changes nothing.
21
-
22
- ## Quick start
23
-
24
- ```
25
- cd ~/code/my-app any git repository
26
- nk init register it (once)
27
- nk hooks write setup hooks from its CI and Compose file
28
- nk hooks test try them in a temporary workspace
29
- nk -b fix-login codex new workspace on branch fix-login, with an agent
30
- cd "$(nk fix-login)" or just go there
31
- nk ls every workspace, with its PR and CI
32
- nk rm fix-login back up and remove it when you are done
33
- ```
34
-
35
- ## Commands
36
-
37
- | Command | What it does |
38
- |---|---|
39
- | `nk init` | Register the current repository (once per repository; safe to rerun) |
40
- | `nk -b <name> [agent] [-p <prompt>] [-y]` | Create a workspace; start the agent if given (`-y` keeps a converted name without asking) |
41
- | `nk <name> [agent] [-p <prompt>]` | Open a workspace; start the agent if given |
42
- | `nk ls [--json]` | List workspaces: uncommitted changes, commits ahead of and behind the default branch, PR and CI |
43
- | `nk rm <name>... [-n] [-y]` | Back up and remove one or more workspaces, asking once (`-n` shows what would happen, `-y` skips the question) |
44
- | `nk clean [-n] [-y]` | Remove every workspace whose work is merged, and folders left from earlier removals |
45
- | `nk hooks` | The repository's `.nook` hooks: allowed, and tested since they last changed |
46
- | `nk hooks write [agent]` | Write the hooks from the repository's CI; with an agent, also starts it in the same folder to finish what CI could not tell |
47
- | `nk hooks test` | Run the hooks of the folder you are in, in a temporary workspace; fails if remove leaves anything behind |
48
- | `nk forget [<short-name>]` | Stop knowing a repository: remove its registration and shared files; its folder and backups stay |
49
- | `nk doctor` | Check nk's tools (node, git, gh, docker, agents) without changing anything |
50
-
51
- Like git, every command acts on the repository you are in: its main
52
- checkout or one of its workspaces. Outside a repository, `nk -b`, opening,
53
- `nk rm` and `nk clean` refuse to guess; pass `--repo <short-name>` to name
54
- one. `nk ls` only reads, so outside a repository it lists all of them.
55
-
56
- Without an agent, `nk -b` and `nk <name>` print the workspace path, so
57
- `cd "$(nk my-branch)"` works. Arguments after `--` go to the agent.
58
-
59
- `nk init` records the agents it finds on PATH and asks nothing about them:
60
- each one starts with its own default command. To always pass extra
61
- arguments to one agent, set `args` under `[agents.<name>]` in `config.toml`.
62
-
63
- ## Home folder
64
-
65
- ```
66
- ~/nook/ (set NOOK_HOME to move it)
67
- config.toml repositories, agents, workspaces, allowed hooks
68
- workspace/<repo>/<name>/ one git worktree per workspace
69
- shared/<repo>/ files copied into every new workspace
70
- backup/<repo>/ what nk rm saved before deleting
71
- logs/ output of every hook run
72
- ```
73
-
74
- ## Names
75
-
76
- A workspace name is also its branch name and folder name. It uses lowercase
77
- letters, digits, `-` and `.` only, and cannot be a command name (`init`, `ls`,
78
- `rm`, `clean`, `hooks`, `forget`, `doctor`, `help`, `version`).
79
-
80
- You can pass any branch text; `nk` turns it into a valid name and says so:
81
- uppercase becomes lowercase, and `/`, spaces, non-ASCII and other characters
82
- become `-`. For example `REE-12-修复登录/Login` becomes `ree-12-login`. This
83
- way any tool that calls `nk` can pass its own branch name as it is.
84
-
85
- When a branch with exactly the text you passed already exists (locally or on
86
- `origin`), `nk -b` checks out that branch and only the workspace and folder use
87
- the converted name: `nk -b feat/Login` makes workspace `feat-login` on branch
88
- `feat/Login`, the same way worktrunk sanitizes `/` in folder names. `nk ls`,
89
- `nk rm` and `nk clean` then use that branch for git and pull requests.
90
-
91
- When `nk -b` runs in a terminal and had to change the name, it shows the
92
- converted name ready to edit; press Enter to keep it. Without a terminal it uses
93
- the converted name.
94
-
95
- ## Shared files
96
-
97
- New worktrees do not contain ignored files such as `.env`. `nk init` prepares
98
- `~/nook/shared/<repo>/` once, and every new workspace copies it.
99
-
100
- - Files are picked with `.worktreeinclude` (gitignore syntax) when the
101
- repository has one, otherwise with `.env*`.
102
- - Only files git ignores are used, so a shared file never replaces a tracked one.
103
- - Every committed template for an ignored file starts a shared file: init
104
- copies `X.example`, `X.sample`, `X.template` or `X.dist` to the shared `X`
105
- (in any folder) and opens it for you to fill in once. Examples: `.env.example`,
106
- `.dev.vars.example`, `config/database.yml.dist`. The same file in the main
107
- checkout is not copied then, since it may be production.
108
- - A template written for running the app inside Docker names Compose services:
109
- `DATABASE_URL=...@postgres:5432/app`. When the Compose file publishes that
110
- service on this machine (`127.0.0.1:5432:5432`), init points the value at
111
- `127.0.0.1:5432` and leaves the user, password and database name as they
112
- are; each workspace then moves the port to its own. Secrets stay for you to
113
- fill in.
114
- - Other ignored files in the main checkout are copied only if you say yes.
115
- - Running `nk init` again never replaces a shared file that is already there;
116
- it only adds new ones. Edit the files in `~/nook/shared/<repo>/` directly.
117
-
118
- ## Hooks
119
-
120
- Put scripts in `.nook/` in the repository and commit them. Nook reads them from
121
- the main checkout and runs them inside the workspace, so a workspace on an old
122
- branch that predates the hooks still gets them (worktrunk likewise reads its
123
- project config from where the command runs). The file name is the moment it
124
- runs:
125
-
126
- | File | Runs | On failure |
127
- |---|---|---|
128
- | `create` | after a workspace is created and shared files are copied | workspace is kept, agent is not started |
129
- | `open` | every time a workspace is opened (and once after `create`) | agent is not started |
130
- | `remove` | before a workspace is deleted | nothing is deleted |
131
-
132
- Nook picks one file per moment: on Windows `.ps1`, then `.cmd`; on macOS and
133
- Linux `.sh`; on every system `.mjs` (run with Node) and `.py`. One `.mjs` file
134
- works everywhere.
135
-
136
- Hooks see `NOOK_WORKSPACE`, `NOOK_PATH`, `NOOK_REPO`, `NOOK_SHARED` and
137
- `NOOK_PORT` (the first of 10 ports reserved for the workspace).
138
-
139
- ### Writing the hooks from CI and Compose
140
-
141
- CI already says, step by step, how a clean machine gets the project running,
142
- and it is kept working because it runs on every change. Nook turns it into
143
- hooks without AI, and tests them.
144
-
145
- The whole cycle:
146
-
147
- ```
148
- nk init once per repository
149
- nk hooks write hooks from CI (GitHub Actions) and the Compose file
150
- nk hooks test run them in a temporary workspace; nothing may be left behind
151
- nk -b <name> codex new workspace; nk <name> codex reopens one
152
- nk ls PR and CI of every workspace; merge or close on GitHub
153
- nk clean | nk rm <name> remove merged or chosen workspaces
154
- ```
155
-
156
- nk picks the CI job most like "set up and test the project": it runs on pull
157
- requests or pushes, runs tests, and has the most setup steps and services. Jobs
158
- that release, deploy, lint or only build are passed over; the report lists the
159
- others. Local composite actions (`uses: ./.github/actions/setup`) are read
160
- step by step, as if their steps were in the job. What it takes from that job:
161
-
162
- | In CI | In the hooks |
163
- |---|---|
164
- | `actions/setup-node`, `setup-python`, `pnpm/action-setup`, `astral-sh/setup-uv`... | create checks the tool is installed and warns about another version |
165
- | `corepack enable` | also a tool check: Node 25 and later no longer include corepack (`npm install -g corepack`) |
166
- | `run:` steps before the first test, lint or build step | create runs them in the workspace, and stops with a plain message when a step's program is not installed; an install CI skips on a cache hit (`if: steps.cache.outputs.cache-hit != 'true'`) runs, since a new workspace has no cache |
167
- | a job that `needs:` another and downloads its artifact | the other job's installs come first |
168
- | a setup action nk cannot read (`pnpm/setup`, a project's own) followed by `pnpm exec ...` or `npx ...` | create runs the install first (`pnpm install --frozen-lockfile`, `npm ci`...), since the action most likely did it |
169
- | `services:` (a database, say) | create starts one container per workspace on the workspace's own ports; open starts it again; remove deletes it with its volumes |
170
- | `env:` | added to the workspace's `.env`, with `localhost:<CI port>` changed to the workspace's port |
171
- | `run:` steps with `if: always()` (cleanup) | remove runs them |
172
- | checkout, caches, uploads, tests, lint, builds | left out |
173
-
174
- When the repository has a Compose file (`compose.yaml`, `docker-compose.yml`...),
175
- nk also reads it, together with its override file (`compose.override.yaml`...)
176
- as `docker compose` does, since many projects start their database there rather than
177
- in CI. A base file that cannot run on its own (a service with neither an image
178
- nor a build) is read with its development file instead
179
- (`docker-compose.yml` + `docker-compose.dev.yml`), as such projects document:
180
-
181
- | In the Compose file | In the hooks |
182
- |---|---|
183
- | a database or similar service used as it is (`postgres`, `redis`, `rabbitmq`, `minio`, `mailpit`...) | create starts it in the workspace's own Compose project, `nook-<repo>-<workspace>`; open starts it again; remove runs `docker compose down --volumes` for that project |
184
- | a host port written as a variable, `${API_PORT:-3001}:3001` | the variable gets one of the workspace's ports in its `.env`, so you can run that service too without a clash |
185
- | a value it needs, `${POSTGRES_PASSWORD:?...}` | create stops and names it when `.env` does not have it; set it once in the shared `.env` |
186
- | a service built from the repository, the project's own image (the app), or one under a profile | not started; start it yourself when you need it |
187
- | more services than the workspace's ten ports hold | the last ones are not started, and listed |
188
- | a fixed host port, `5432:5432` | moved to one of the workspace's ports (`127.0.0.1:<port>:5432`) by a small Compose file nk writes into the worktree's git folder; `COMPOSE_FILE` in `.env` lists it, so a `docker compose` you run there uses it too |
189
- | a fixed `container_name`, or a network or volume with a fixed `name` | given the workspace's own name in that same file, so two workspaces never share a container, network or data; `external` ones are shared on purpose and stay |
190
- | `network_mode: host` | the service runs on the workspace's own network instead, its ports moved to the workspace's, since on the host network it would take the main checkout's ports |
191
-
192
- When the chosen CI job has services of its own, nk starts those and none from
193
- Compose, so the same database is not started twice.
194
-
195
- The workspace's `.env` also gets `COMPOSE_PROJECT_NAME` set to the
196
- workspace's project, replacing whatever the copied file said, so a
197
- `docker compose` you run there never reaches the main checkout's project.
198
- Values in the copied `.env` that point at a moved port are pointed at the
199
- workspace's port: `DATABASE_URL=postgres://...@localhost:5432/app` becomes
200
- `localhost:<workspace port>`, and so does `DB_PORT=5432`. A service's own
201
- port counts too: when CI publishes MySQL's 3306 on 32768, `DB_PORT=3306` in the
202
- copied `.env` is moved as well.
203
- Other env files at the root that many projects load after `.env`, such as
204
- `.env.development` or `.env.test`, are pointed at the workspace's ports the
205
- same way; templates (`.env.example`, `.env.sample`) are left alone. When the
206
- repository commits one of these files, the workspace's copy comes from git and
207
- is changed in place; nk marks it `skip-worktree`, so these changes never show
208
- in `git status` or reach a commit.
209
-
210
- CI runs on a throwaway machine; a developer machine also has the real
211
- database and other workspaces. So nk copies less than CI does, never more:
212
-
213
- - Only commands that install dependencies into the folder (`pnpm install`,
214
- `npm ci`, `uv sync`, `pip install`...) are copied. A script, a `docker`
215
- command or a migration is listed for review instead, since nk cannot tell
216
- what it would touch here.
217
- - An `env:` value that points at a local port that is not one of CI's
218
- services (say `localhost:5432` when CI starts Postgres itself), or at a
219
- database elsewhere, is left out: on this machine it may be the real one.
220
- - Services get containers named after the workspace, on the workspace's own
221
- ports, bound to 127.0.0.1. Every step runs with `COMPOSE_PROJECT_NAME` set
222
- to the workspace's own project.
223
- - Generated hooks refuse to run unless nk runs them in a workspace, so they
224
- never run in the main checkout.
225
-
226
- What it cannot translate (a CI expression such as `${{ secrets.X }}`, a step
227
- that runs only under a condition, an action it does not know) is listed too,
228
- not guessed. `nk hooks write <agent>` does the same, then starts the agent in
229
- the same folder (usually the main checkout) to finish those parts; the agent
230
- runs `nk hooks test` until it passes and leaves the changes for you to review.
231
- To keep the agent out of the main checkout, run it inside a workspace instead
232
- (`nk -b hooks`, then `nk hooks write <agent>` there). Generated hooks start with a
233
- line saying so; `nk hooks write` replaces only those, never hooks written by
234
- hand.
235
-
236
- `nk hooks test` runs the hooks of the folder you are in (the agent's branch,
237
- before it is merged) in a temporary workspace: create, open twice, then
238
- remove. It fails when a hook fails, when open cannot run twice, when the
239
- hooks change files git would commit (files a fresh checkout already shows as
240
- changed, such as line endings `.gitattributes` would change, are the
241
- repository's and do not count), or when remove leaves a Docker
242
- container, volume, network or built image behind.
243
-
244
- A passing test records the hooks, the CI files and the Compose file. If any of them changes, `nk -b`
245
- stops before creating a workspace (pass `--untested` to start anyway), and
246
- `nk <name>`, `nk hooks`, `nk doctor` and `nk rm` say so: run `nk hooks write`
247
- again for a CI or Compose change, then `nk hooks test`.
248
-
249
- [docs/setup-hooks.md](docs/setup-hooks.md) has the rules and examples for
250
- pnpm and npm, uv, and a Docker Compose project per workspace.
251
-
252
- Before hooks run for the first time, and again after one changes, Nook lists
253
- them (event and file, not the contents) and asks once: "Allow and remember?".
254
- Rerunning `nk init` also asks. `nk init --yes` skips the other questions but
255
- not this one; `nk init --allow-hooks` allows them without asking, for a person
256
- who has read them (in a script, say). Each run's output is saved in `~/nook/logs/`.
257
-
258
- Output lines start with ◎ (running now), ✓ (done), ▲ (needs attention) or
259
- ○ (information), as in worktrunk. Colors are used on a terminal unless
260
- `NO_COLOR` is set.
261
-
262
- ## Removing a workspace
263
-
264
- `nk rm <name>` refuses while you are inside the workspace. It lists
265
- uncommitted changes, unpushed commits, an unmerged branch and programs still
266
- running in the folder (terminals, dev servers, agents: any process whose
267
- working directory is inside it), asks once, then:
268
-
269
- 1. stops those programs and their child processes;
270
- 2. saves changes and unpushed commits to `~/nook/backup/<repo>/<name>-<time>/`
271
- (`changes.patch`, `untracked/`, `commits.bundle`);
272
- 3. runs the `remove` hook, if the repository has one;
273
- 4. removes the Docker Compose projects started inside the folder: their
274
- containers, volumes, networks and built images;
275
- 5. deletes the folder and the branch.
276
-
277
- Nothing has to be written down when the workspace is created. Compose labels
278
- every container with the folder it was started from, so nk asks Docker at
279
- removal time and also finds services an agent started later. A project that
280
- also has containers started elsewhere (the main checkout's, for example) is
281
- kept. `nk doctor` lists Compose projects left behind by workspaces that no
282
- longer exist.
283
-
284
- This is a safety net: the `remove` hook should still undo everything `create`
285
- did, and `nk hooks test` checks that it does.
286
-
287
- ## Listing and cleaning up
288
-
289
- `nk ls` shows each workspace's uncommitted changes, its commits ahead (↑) and
290
- behind (↓) the default branch, and its pull request with the CI result
291
- (`#12 open running`, `#12 merged`). PR and CI come from the
292
- [GitHub CLI](https://cli.github.com/) (`gh`), one request per repository. When
293
- `gh` is missing or not logged in, the PR column stays empty and a ▲ line says
294
- why. Merged rows are dimmed. `nk ls --json` prints the same data for scripts
295
- and agents.
296
-
297
- Git is the source of truth, as in worktrunk: `nk ls` shows every worktree of
298
- the repository, including ones made by other tools (Orca, Conductor, plain
299
- `git worktree add`). Those have no port, and are named by their branch with
300
- the same conversion as `nk -b` (`ree-auz/REE-228` becomes `ree-auz-ree-228`).
301
- The first column marks the main checkout `^`, the worktree you are in `@`, and
302
- the others `+`. `nk rm`, `nk <name>` and `nk clean` work on all of them; a name
303
- matches the branch or the folder. The main checkout and worktrees locked with
304
- `git worktree lock` are never removed, but `nk main [agent]` opens the main
305
- checkout like any workspace.
306
-
307
- `nk clean` removes workspaces whose work is merged, like worktrunk's
308
- `wt step prune` and gh-poi. It first fetches the default branch, then counts a
309
- workspace as merged when:
310
-
311
- - its pull request is merged and the local branch has nothing the PR did not
312
- have (so squash merges count), or
313
- - it has no pull request, its branch is already in the default branch, and it
314
- is at least a day old (so a workspace you just created is kept).
315
-
316
- It keeps locked worktrees, workspaces with uncommitted changes, with commits made after the PR
317
- was merged, with files git ignores that are not rebuilt or shared (a local
318
- database, say; `nk rm` removes them after listing them), and the one you are inside. It lists what it would remove, what it
319
- keeps, and the programs running in the ones it removes, asks once, then stops
320
- those programs and removes each workspace the way `nk rm` does (backup,
321
- `remove` hook, folder and branch). `-n` only shows
322
- the list; `-y` removes without asking.
323
-
324
- It also deletes folders left from earlier removals: on Windows, git can
325
- remove a worktree and still leave ignored folders such as `node_modules` or
326
- `.venv` behind. Such a folder is neither a workspace nor a git worktree any
327
- more; `nk clean` deletes it, with any Compose project started in it, unless a
328
- program is still running there. `nk rm` itself now deletes whatever git leaves
329
- and stops with the reason when Windows keeps a file open.
330
-
331
- ## Forgetting a repository
332
-
333
- `nk forget` (in the repository, or `nk forget <short-name>` from anywhere)
334
- undoes `nk init`: it removes the repository's registration and its shared
335
- files in `~/nook/shared/<short-name>/`, after one confirmation. The repository
336
- folder and the backups `nk rm` made stay.
337
-
338
- It refuses while the repository has workspaces, so their work is backed up by
339
- `nk rm` first. If the repository folder is already deleted, git can no longer
340
- remove its workspaces; then `nk forget` lists what is left in
341
- `~/nook/workspace/<short-name>/` and deletes it too, with the containers,
342
- volumes, networks and built images started there (Compose projects, and the
343
- service containers generated hooks start). Their remove hooks cannot run, since
344
- they were in the deleted folder.
345
-
346
- ## Linear
347
-
348
- To open a Linear issue in a new workspace with one click, see
349
- [integrations/linear](integrations/linear/README.md).
350
-
351
- ## For agents
352
-
353
- [`skills/nook/SKILL.md`](skills/nook/SKILL.md) tells a coding agent how to work
354
- inside a workspace: the `NOOK_*` variables, which ports to use, which files not
355
- to commit, and which commands are safe to run. Copy the `skills/nook` folder
356
- into your agent's skills folder (for example `~/.claude/skills/`).
357
-
358
- ## Development
359
-
360
- ```
361
- npm ci
362
- npm test
363
- ```
364
-
365
- CI runs the tests on Linux, macOS and Windows.
366
-
367
- To release, set the new `version` in `package.json` on main, then publish a
368
- GitHub Release tagged `v<version>`. `.github/workflows/release.yml` checks the
369
- tag against `package.json`, runs the tests and publishes to npm with trusted
370
- publishing (no npm token is stored). npm lets only an existing package trust a
371
- workflow, so the first version was published by hand; a release whose version
372
- is already on npm is not published again.
373
-
374
- `npm run corpus` checks what `nk hooks write` makes of real repositories
375
- (`tests/corpus/repos.json`, each at a pinned commit; only their CI, Compose and
376
- env template files are fetched, and nothing in them is run) against the
377
- reviewed results in `tests/corpus/expected/`. CI runs it too. After a change to
378
- how hooks are written, review the lines it prints and accept them with
379
- `npm run corpus -- --update`.
380
-
381
- The repositories marked `"full": true`, plus about ten popular ones nk has not
382
- seen (`scripts/real-explore.mjs` picks different ones each time, each with a
383
- root Compose file or CI services), also get a real run every Monday
384
- (`.github/workflows/real-repos.yml`; start it by hand from the Actions tab with
385
- Run workflow, optionally for some repositories only). On a fresh GitHub runner
386
- with the tool versions its CI uses, `scripts/real-run.mjs` runs `nk init`,
387
- `nk hooks write` and `nk hooks test`. Then, with the ports a main checkout's
388
- services usually take (5432, 3306, 6379 and others) held as if they were
389
- running, and a stand-in for the main checkout's own database container and
390
- volume, it creates two workspaces, `try1` and `try2`. Each must be clean in
391
- git, have no address or port in its `.env` files that still points at the
392
- usual port of a service it runs itself (5432 when it has its own Postgres),
393
- and have its own services answering. Then:
394
-
395
- - the two share no port, container or volume, and `try1` still answers once
396
- `try2` runs;
397
- - a mark written into each workspace's volumes stays in that workspace;
398
- - with its services stopped (as after a restart), `nk try1` brings them back
399
- on the same ports with their data;
400
- - `nk rm try1` leaves `try2` answering with its data, and the main checkout's
401
- container and volume untouched;
402
- - after `nk rm try2`, no container, volume, network or folder is left.
403
-
404
- The run's summary is one table: each repository, the step it failed at, and
405
- that step's last lines. New repositories that failed are added to
406
- `tests/corpus/repos.json` once fixed, so the fixed list keeps growing.
407
-
408
- ## License
409
-
410
- MIT
1
+ # Nook
2
+
3
+ `nk` keeps every git worktree you work in under one home folder, `~/nook/`.
4
+ It creates a workspace for a branch, copies the ignored files the project needs
5
+ (such as `.env`), gives each workspace its own block of ports, runs the
6
+ repository's setup hooks, and can start a command-line agent inside it.
7
+
8
+ > Version 0.1: an early release. Commands and config may still change.
9
+
10
+ ## Install
11
+
12
+ ```
13
+ npm install -g @ree-auz/nook
14
+ nk doctor
15
+ ```
16
+
17
+ Needs Node.js 20.12 or later and git. Docker is used when a repository's hooks
18
+ start services, and the [GitHub CLI](https://cli.github.com/) (`gh`) for pull
19
+ requests and CI in `nk ls` and `nk clean`; both are optional. `nk doctor`
20
+ checks them and changes nothing.
21
+
22
+ ## Quick start
23
+
24
+ ```
25
+ cd ~/code/my-app any git repository
26
+ nk init register it (once)
27
+ nk hooks write setup hooks from its CI and Compose file
28
+ nk hooks test try them in a temporary workspace
29
+ nk -b fix-login codex new workspace on branch fix-login, with an agent
30
+ cd "$(nk fix-login)" or just go there
31
+ nk ls every workspace, with its PR and CI
32
+ nk rm fix-login back up and remove it when you are done
33
+ ```
34
+
35
+ ## Commands
36
+
37
+ | Command | What it does |
38
+ |---|---|
39
+ | `nk init` | Register the current repository (once per repository; safe to rerun) |
40
+ | `nk -b <name> [agent] [-p <prompt>] [-y]` | Create a workspace; start the agent if given (`-y` keeps a converted name without asking) |
41
+ | `nk <name> [agent] [-p <prompt>]` | Open a workspace; start the agent if given |
42
+ | `nk ls [--json]` | List workspaces: uncommitted changes, commits ahead of and behind the default branch, PR and CI |
43
+ | `nk rm <name>... [-n] [-y]` | Back up and remove one or more workspaces, asking once (`-n` shows what would happen, `-y` skips the question) |
44
+ | `nk clean [-n] [-y]` | Remove every workspace whose work is merged, and folders left from earlier removals |
45
+ | `nk hooks` | The repository's `.nook` hooks: allowed, and tested since they last changed |
46
+ | `nk hooks write [agent]` | Write the hooks from the repository's CI; with an agent, also starts it in the same folder to finish what CI could not tell |
47
+ | `nk hooks test` | Run the hooks of the folder you are in, in a temporary workspace; fails if remove leaves anything behind |
48
+ | `nk forget [<short-name>]` | Stop knowing a repository: remove its registration and shared files; its folder and backups stay |
49
+ | `nk doctor` | Check nk's tools (node, git, gh, docker, agents) without changing anything |
50
+
51
+ Like git, every command acts on the repository you are in: its main
52
+ checkout or one of its workspaces. Outside a repository, `nk -b`, opening,
53
+ `nk rm` and `nk clean` refuse to guess; pass `--repo <short-name>` to name
54
+ one. `nk ls` only reads, so outside a repository it lists all of them.
55
+
56
+ Without an agent, `nk -b` and `nk <name>` print the workspace path, so
57
+ `cd "$(nk my-branch)"` works. Arguments after `--` go to the agent.
58
+
59
+ `nk init` records the agents it finds on PATH and asks nothing about them:
60
+ each one starts with its own default command. To always pass extra
61
+ arguments to one agent, set `args` under `[agents.<name>]` in `config.toml`.
62
+
63
+ ## Home folder
64
+
65
+ ```
66
+ ~/nook/ (set NOOK_HOME to move it)
67
+ config.toml repositories, agents, workspaces, allowed hooks
68
+ workspace/<repo>/<name>/ one git worktree per workspace
69
+ shared/<repo>/ files copied into every new workspace
70
+ backup/<repo>/ what nk rm saved before deleting
71
+ logs/ output of every hook run
72
+ ```
73
+
74
+ ## Names
75
+
76
+ A workspace name is also its branch name and folder name. It uses lowercase
77
+ letters, digits, `-` and `.` only, and cannot be a command name (`init`, `ls`,
78
+ `rm`, `clean`, `hooks`, `forget`, `doctor`, `help`, `version`).
79
+
80
+ You can pass any branch text; `nk` turns it into a valid name and says so:
81
+ uppercase becomes lowercase, and `/`, spaces, non-ASCII and other characters
82
+ become `-`. For example `REE-12-修复登录/Login` becomes `ree-12-login`. This
83
+ way any tool that calls `nk` can pass its own branch name as it is.
84
+
85
+ When a branch with exactly the text you passed already exists (locally or on
86
+ `origin`), `nk -b` checks out that branch and only the workspace and folder use
87
+ the converted name: `nk -b feat/Login` makes workspace `feat-login` on branch
88
+ `feat/Login`, the same way worktrunk sanitizes `/` in folder names. `nk ls`,
89
+ `nk rm` and `nk clean` then use that branch for git and pull requests.
90
+
91
+ When `nk -b` runs in a terminal and had to change the name, it shows the
92
+ converted name ready to edit; press Enter to keep it. Without a terminal it uses
93
+ the converted name.
94
+
95
+ ## Shared files
96
+
97
+ New worktrees do not contain ignored files such as `.env`. `nk init` prepares
98
+ `~/nook/shared/<repo>/` once, and every new workspace copies it.
99
+
100
+ - Files are picked with `.worktreeinclude` (gitignore syntax) when the
101
+ repository has one, otherwise with `.env*`.
102
+ - Only files git ignores are used, so a shared file never replaces a tracked one.
103
+ - Every committed template for an ignored file starts a shared file: init
104
+ copies `X.example`, `X.sample`, `X.template` or `X.dist` to the shared `X`
105
+ (in any folder) and opens it for you to fill in once. Examples: `.env.example`,
106
+ `.dev.vars.example`, `config/database.yml.dist`. The same file in the main
107
+ checkout is not copied then, since it may be production.
108
+ - A template written for running the app inside Docker names Compose services:
109
+ `DATABASE_URL=...@postgres:5432/app`. When the Compose file publishes that
110
+ service on this machine (`127.0.0.1:5432:5432`), init points the value at
111
+ `127.0.0.1:5432` and leaves the user, password and database name as they
112
+ are; each workspace then moves the port to its own. Secrets stay for you to
113
+ fill in.
114
+ - Other ignored files in the main checkout are copied only if you say yes.
115
+ - Running `nk init` again never replaces a shared file that is already there;
116
+ it only adds new ones. Edit the files in `~/nook/shared/<repo>/` directly.
117
+
118
+ ## Hooks
119
+
120
+ Put scripts in `.nook/` in the repository and commit them. Nook reads them from
121
+ the main checkout and runs them inside the workspace, so a workspace on an old
122
+ branch that predates the hooks still gets them (worktrunk likewise reads its
123
+ project config from where the command runs). The file name is the moment it
124
+ runs:
125
+
126
+ | File | Runs | On failure |
127
+ |---|---|---|
128
+ | `create` | after a workspace is created and shared files are copied | workspace is kept, agent is not started |
129
+ | `open` | every time a workspace is opened (and once after `create`) | agent is not started |
130
+ | `remove` | before a workspace is deleted | nothing is deleted |
131
+
132
+ Nook picks one file per moment: on Windows `.ps1`, then `.cmd`; on macOS and
133
+ Linux `.sh`; on every system `.mjs` (run with Node) and `.py`. One `.mjs` file
134
+ works everywhere.
135
+
136
+ Hooks see `NOOK_WORKSPACE`, `NOOK_PATH`, `NOOK_REPO`, `NOOK_SHARED` and
137
+ `NOOK_PORT` (the first of 10 ports reserved for the workspace).
138
+ Blocks start at 20000 (`port_base` in `config.toml` changes that) and skip
139
+ ports something already listens on, as well as the range the system hands to
140
+ outgoing connections, since any program could take one of those at any time.
141
+ Some software moves Windows' range down from 49152; `nk doctor` says so.
142
+
143
+ ### Writing the hooks from CI and Compose
144
+
145
+ CI already says, step by step, how a clean machine gets the project running,
146
+ and it is kept working because it runs on every change. Nook turns it into
147
+ hooks without AI, and tests them.
148
+
149
+ The whole cycle:
150
+
151
+ ```
152
+ nk init once per repository
153
+ nk hooks write hooks from CI (GitHub Actions) and the Compose file
154
+ nk hooks test run them in a temporary workspace; nothing may be left behind
155
+ nk -b <name> codex new workspace; nk <name> codex reopens one
156
+ nk ls PR and CI of every workspace; merge or close on GitHub
157
+ nk clean | nk rm <name> remove merged or chosen workspaces
158
+ ```
159
+
160
+ nk picks the CI job most like "set up and test the project": it runs on pull
161
+ requests or pushes, runs tests, and has the most setup steps and services. Jobs
162
+ that release, deploy, lint or only build are passed over; the report lists the
163
+ others. Local composite actions (`uses: ./.github/actions/setup`) are read
164
+ step by step, as if their steps were in the job. What it takes from that job:
165
+
166
+ | In CI | In the hooks |
167
+ |---|---|
168
+ | `actions/setup-node`, `setup-python`, `pnpm/action-setup`, `astral-sh/setup-uv`... | create checks the tool is installed and warns about another version |
169
+ | `corepack enable` | also a tool check: Node 25 and later no longer include corepack (`npm install -g corepack`) |
170
+ | `run:` steps before the first test, lint or build step | create runs them in the workspace, and stops with a plain message when a step's program is not installed; an install CI skips on a cache hit (`if: steps.cache.outputs.cache-hit != 'true'`) runs, since a new workspace has no cache |
171
+ | a job that `needs:` another and downloads its artifact | the other job's installs come first |
172
+ | a setup action nk cannot read (`pnpm/setup`, a project's own) followed by `pnpm exec ...` or `npx ...` | create runs the install first (`pnpm install --frozen-lockfile`, `npm ci`...), since the action most likely did it |
173
+ | `services:` (a database, say) | create starts one container per workspace on the workspace's own ports; open starts it again; remove deletes it with its volumes |
174
+ | `env:` | added to the workspace's `.env`, with `localhost:<CI port>` changed to the workspace's port |
175
+ | `run:` steps with `if: always()` (cleanup) | remove runs them |
176
+ | checkout, caches, uploads, tests, lint, builds | left out |
177
+
178
+ When the repository has a Compose file (`compose.yaml`, `docker-compose.yml`...),
179
+ nk also reads it, together with its override file (`compose.override.yaml`...)
180
+ as `docker compose` does, since many projects start their database there rather than
181
+ in CI. A base file that cannot run on its own (a service with neither an image
182
+ nor a build) is read with its development file instead
183
+ (`docker-compose.yml` + `docker-compose.dev.yml`), as such projects document:
184
+
185
+ | In the Compose file | In the hooks |
186
+ |---|---|
187
+ | a database or similar service used as it is (`postgres`, `redis`, `rabbitmq`, `minio`, `mailpit`...) | create starts it in the workspace's own Compose project, `nook-<repo>-<workspace>`; open starts it again; remove runs `docker compose down --volumes` for that project |
188
+ | a host port written as a variable, `${API_PORT:-3001}:3001` | the variable gets one of the workspace's ports in its `.env`, so you can run that service too without a clash |
189
+ | a value it needs, `${POSTGRES_PASSWORD:?...}` | create stops and names it when `.env` does not have it; set it once in the shared `.env` |
190
+ | a service built from the repository, the project's own image (the app), or one under a profile | not started; start it yourself when you need it |
191
+ | more services than the workspace's ten ports hold | the last ones are not started, and listed |
192
+ | a fixed host port, `5432:5432` | moved to one of the workspace's ports (`127.0.0.1:<port>:5432`) by a small Compose file nk writes into the worktree's git folder; `COMPOSE_FILE` in `.env` lists it, so a `docker compose` you run there uses it too |
193
+ | a fixed `container_name`, or a network or volume with a fixed `name` | given the workspace's own name in that same file, so two workspaces never share a container, network or data; `external` ones are shared on purpose and stay |
194
+ | `network_mode: host` | the service runs on the workspace's own network instead, its ports moved to the workspace's, since on the host network it would take the main checkout's ports |
195
+
196
+ When the chosen CI job has services of its own, nk starts those and none from
197
+ Compose, so the same database is not started twice.
198
+
199
+ The workspace's `.env` also gets `COMPOSE_PROJECT_NAME` set to the
200
+ workspace's project, replacing whatever the copied file said, so a
201
+ `docker compose` you run there never reaches the main checkout's project.
202
+ Values in the copied `.env` that point at a moved port are pointed at the
203
+ workspace's port: `DATABASE_URL=postgres://...@localhost:5432/app` becomes
204
+ `localhost:<workspace port>`, and so does `DB_PORT=5432`. A service's own
205
+ port counts too: when CI publishes MySQL's 3306 on 32768, `DB_PORT=3306` in the
206
+ copied `.env` is moved as well.
207
+ Other env files at the root that many projects load after `.env`, such as
208
+ `.env.development` or `.env.test`, are pointed at the workspace's ports the
209
+ same way; templates (`.env.example`, `.env.sample`) are left alone. When the
210
+ repository commits one of these files, the workspace's copy comes from git and
211
+ is changed in place; nk marks it `skip-worktree`, so these changes never show
212
+ in `git status` or reach a commit.
213
+
214
+ CI runs on a throwaway machine; a developer machine also has the real
215
+ database and other workspaces. So nk copies less than CI does, never more:
216
+
217
+ - Only commands that install dependencies into the folder (`pnpm install`,
218
+ `npm ci`, `uv sync`, `pip install`...) are copied. A script, a `docker`
219
+ command or a migration is listed for review instead, since nk cannot tell
220
+ what it would touch here.
221
+ - An `env:` value that points at a local port that is not one of CI's
222
+ services (say `localhost:5432` when CI starts Postgres itself), or at a
223
+ database elsewhere, is left out: on this machine it may be the real one.
224
+ - Services get containers named after the workspace, on the workspace's own
225
+ ports, bound to 127.0.0.1. Every step runs with `COMPOSE_PROJECT_NAME` set
226
+ to the workspace's own project.
227
+ - Generated hooks refuse to run unless nk runs them in a workspace, so they
228
+ never run in the main checkout.
229
+
230
+ What it cannot translate (a CI expression such as `${{ secrets.X }}`, a step
231
+ that runs only under a condition, an action it does not know) is listed too,
232
+ not guessed. `nk hooks write <agent>` does the same, then starts the agent in
233
+ the same folder (usually the main checkout) to finish those parts; the agent
234
+ runs `nk hooks test` until it passes and leaves the changes for you to review.
235
+ To keep the agent out of the main checkout, run it inside a workspace instead
236
+ (`nk -b hooks`, then `nk hooks write <agent>` there). Generated hooks start with a
237
+ line saying so; `nk hooks write` replaces only those, never hooks written by
238
+ hand.
239
+
240
+ `nk hooks test` runs the hooks of the folder you are in (the agent's branch,
241
+ before it is merged) in a temporary workspace: create, open twice, then
242
+ remove. It fails when a hook fails, when open cannot run twice, when the
243
+ hooks change files git would commit (files a fresh checkout already shows as
244
+ changed, such as line endings `.gitattributes` would change, are the
245
+ repository's and do not count), or when remove leaves a Docker
246
+ container, volume, network or built image behind.
247
+
248
+ A passing test records the hooks, the CI files and the Compose file. If any of them changes, `nk -b`
249
+ stops before creating a workspace (pass `--untested` to start anyway), and
250
+ `nk <name>`, `nk hooks`, `nk doctor` and `nk rm` say so: run `nk hooks write`
251
+ again for a CI or Compose change, then `nk hooks test`.
252
+
253
+ [docs/setup-hooks.md](docs/setup-hooks.md) has the rules and examples for
254
+ pnpm and npm, uv, and a Docker Compose project per workspace.
255
+
256
+ Before hooks run for the first time, and again after one changes, Nook lists
257
+ them (event and file, not the contents) and asks once: "Allow and remember?".
258
+ Rerunning `nk init` also asks. `nk init --yes` skips the other questions but
259
+ not this one; `nk init --allow-hooks` allows them without asking, for a person
260
+ who has read them (in a script, say). Each run's output is saved in `~/nook/logs/`.
261
+
262
+ Output lines start with ◎ (running now), ✓ (done), ▲ (needs attention) or
263
+ ○ (information), as in worktrunk. Colors are used on a terminal unless
264
+ `NO_COLOR` is set.
265
+
266
+ ## Removing a workspace
267
+
268
+ `nk rm <name>` refuses while you are inside the workspace. It lists
269
+ uncommitted changes, unpushed commits, an unmerged branch and programs still
270
+ running in the folder (terminals, dev servers, agents: any process whose
271
+ working directory is inside it), asks once, then:
272
+
273
+ 1. stops those programs and their child processes;
274
+ 2. saves changes and unpushed commits to `~/nook/backup/<repo>/<name>-<time>/`
275
+ (`changes.patch`, `untracked/`, `commits.bundle`);
276
+ 3. runs the `remove` hook, if the repository has one;
277
+ 4. removes the Docker Compose projects started inside the folder: their
278
+ containers, volumes, networks and built images;
279
+ 5. deletes the folder and the branch.
280
+
281
+ Nothing has to be written down when the workspace is created. Compose labels
282
+ every container with the folder it was started from, so nk asks Docker at
283
+ removal time and also finds services an agent started later. A project that
284
+ also has containers started elsewhere (the main checkout's, for example) is
285
+ kept. `nk doctor` lists Compose projects left behind by workspaces that no
286
+ longer exist.
287
+
288
+ This is a safety net: the `remove` hook should still undo everything `create`
289
+ did, and `nk hooks test` checks that it does.
290
+
291
+ ## Listing and cleaning up
292
+
293
+ `nk ls` shows each workspace's uncommitted changes, its commits ahead (↑) and
294
+ behind (↓) the default branch, and its pull request with the CI result
295
+ (`#12 open running`, `#12 merged`). PR and CI come from the
296
+ [GitHub CLI](https://cli.github.com/) (`gh`), one request per repository. When
297
+ `gh` is missing or not logged in, the PR column stays empty and a ▲ line says
298
+ why. Merged rows are dimmed. `nk ls --json` prints the same data for scripts
299
+ and agents.
300
+
301
+ Git is the source of truth, as in worktrunk: `nk ls` shows every worktree of
302
+ the repository, including ones made by other tools (Orca, Conductor, plain
303
+ `git worktree add`). Those have no port, and are named by their branch with
304
+ the same conversion as `nk -b` (`ree-auz/REE-228` becomes `ree-auz-ree-228`).
305
+ The first column marks the main checkout `^`, the worktree you are in `@`, and
306
+ the others `+`. `nk rm`, `nk <name>` and `nk clean` work on all of them; a name
307
+ matches the branch or the folder. The main checkout and worktrees locked with
308
+ `git worktree lock` are never removed, but `nk main [agent]` opens the main
309
+ checkout like any workspace.
310
+
311
+ `nk clean` removes workspaces whose work is merged, like worktrunk's
312
+ `wt step prune` and gh-poi. It first fetches the default branch, then counts a
313
+ workspace as merged when:
314
+
315
+ - its pull request is merged and the local branch has nothing the PR did not
316
+ have (so squash merges count), or
317
+ - it has no pull request, its branch is already in the default branch, and it
318
+ is at least a day old (so a workspace you just created is kept).
319
+
320
+ It keeps locked worktrees, workspaces with uncommitted changes, with commits made after the PR
321
+ was merged, with files git ignores that are not rebuilt or shared (a local
322
+ database, say; `nk rm` removes them after listing them), and the one you are inside. It lists what it would remove, what it
323
+ keeps, and the programs running in the ones it removes, asks once, then stops
324
+ those programs and removes each workspace the way `nk rm` does (backup,
325
+ `remove` hook, folder and branch). `-n` only shows
326
+ the list; `-y` removes without asking.
327
+
328
+ It also deletes folders left from earlier removals: on Windows, git can
329
+ remove a worktree and still leave ignored folders such as `node_modules` or
330
+ `.venv` behind. Such a folder is neither a workspace nor a git worktree any
331
+ more; `nk clean` deletes it, with any Compose project started in it, unless a
332
+ program is still running there. `nk rm` itself now deletes whatever git leaves
333
+ and stops with the reason when Windows keeps a file open.
334
+
335
+ ## Forgetting a repository
336
+
337
+ `nk forget` (in the repository, or `nk forget <short-name>` from anywhere)
338
+ undoes `nk init`: it removes the repository's registration and its shared
339
+ files in `~/nook/shared/<short-name>/`, after one confirmation. The repository
340
+ folder and the backups `nk rm` made stay.
341
+
342
+ It refuses while the repository has workspaces, so their work is backed up by
343
+ `nk rm` first. If the repository folder is already deleted, git can no longer
344
+ remove its workspaces; then `nk forget` lists what is left in
345
+ `~/nook/workspace/<short-name>/` and deletes it too, with the containers,
346
+ volumes, networks and built images started there (Compose projects, and the
347
+ service containers generated hooks start). Their remove hooks cannot run, since
348
+ they were in the deleted folder.
349
+
350
+ ## Uninstalling
351
+
352
+ `npm uninstall -g @ree-auz/nook` removes the `nk` command. What nk made stays:
353
+ workspaces, backups and `~/nook/`. To remove those too, first run, in each
354
+ repository, `nk rm` for its workspaces (or `nk clean` for the merged ones)
355
+ and then `nk forget`. Then delete `~/nook/`; it holds the backups, so look
356
+ in `~/nook/backup/` first.
357
+
358
+ ## Linear
359
+
360
+ To open a Linear issue in a new workspace with one click, see
361
+ [integrations/linear](integrations/linear/README.md).
362
+
363
+ ## For agents
364
+
365
+ [`skills/nook/SKILL.md`](skills/nook/SKILL.md) tells a coding agent how to work
366
+ inside a workspace: the `NOOK_*` variables, which ports to use, which files not
367
+ to commit, and which commands are safe to run. Copy the `skills/nook` folder
368
+ into your agent's skills folder (for example `~/.claude/skills/`).
369
+
370
+ ## Development
371
+
372
+ ```
373
+ npm ci
374
+ npm test
375
+ ```
376
+
377
+ CI runs the tests on Linux, macOS and Windows.
378
+
379
+ To release, set the new `version` in `package.json` on main, then publish a
380
+ GitHub Release tagged `v<version>`. `.github/workflows/release.yml` checks the
381
+ tag against `package.json`, runs the tests and publishes to npm with trusted
382
+ publishing (no npm token is stored). npm lets only an existing package trust a
383
+ workflow, so the first version was published by hand; a release whose version
384
+ is already on npm is not published again.
385
+
386
+ `npm run corpus` checks what `nk hooks write` makes of real repositories
387
+ (`tests/corpus/repos.json`, each at a pinned commit; only their CI, Compose and
388
+ env template files are fetched, and nothing in them is run) against the
389
+ reviewed results in `tests/corpus/expected/`. CI runs it too. After a change to
390
+ how hooks are written, review the lines it prints and accept them with
391
+ `npm run corpus -- --update`.
392
+
393
+ The repositories marked `"full": true`, plus about ten popular ones nk has not
394
+ seen (`scripts/real-explore.mjs` picks different ones each time, each with a
395
+ root Compose file or CI services), also get a real run every Monday
396
+ (`.github/workflows/real-repos.yml`; start it by hand from the Actions tab with
397
+ Run workflow, optionally for some repositories only). On a fresh GitHub runner
398
+ with the tool versions its CI uses, `scripts/real-run.mjs` runs `nk init`,
399
+ `nk hooks write` and `nk hooks test`. Then, with the ports a main checkout's
400
+ services usually take (5432, 3306, 6379 and others) held as if they were
401
+ running, and a stand-in for the main checkout's own database container and
402
+ volume, it creates two workspaces, `try1` and `try2`. Each must be clean in
403
+ git, have no address or port in its `.env` files that still points at the
404
+ usual port of a service it runs itself (5432 when it has its own Postgres),
405
+ and have its own services answering. Then:
406
+
407
+ - the two share no port, container or volume, and `try1` still answers once
408
+ `try2` runs;
409
+ - a mark written into each workspace's volumes stays in that workspace;
410
+ - with its services stopped (as after a restart), `nk try1` brings them back
411
+ on the same ports with their data;
412
+ - `nk rm try1` leaves `try2` answering with its data, and the main checkout's
413
+ container and volume untouched;
414
+ - after `nk rm try2`, no container, volume, network or folder is left.
415
+
416
+ The run's summary is one table: each repository, the step it failed at, and
417
+ that step's last lines. New repositories that failed are added to
418
+ `tests/corpus/repos.json` once fixed, so the fixed list keeps growing.
419
+
420
+ ## License
421
+
422
+ MIT