diff options
| author | user <user@clank> | 2026-08-12 21:55:12 +0200 |
|---|---|---|
| committer | user <user@clank> | 2026-08-12 21:55:12 +0200 |
| commit | 72441afa75c058e4b1454108d962f2bebaee941b (patch) | |
| tree | d668f1f61d8cbf19127013724db50cf680aa59c9 /cli/package.json | |
| parent | docs: agree the sl0ppi CLI design (diff) | |
| download | sl0ppi-72441afa75c058e4b1454108d962f2bebaee941b.tar.gz sl0ppi-72441afa75c058e4b1454108d962f2bebaee941b.tar.xz sl0ppi-72441afa75c058e4b1454108d962f2bebaee941b.zip | |
cli: the idempotency core (discovery, tab identity, plan diff)
Slice 1 of docs/cli-design.md: the only part with logic worth testing hard.
Pure functions, no subprocesses, so the tests hit them directly.
purpose.ts tab identity as a zellij tab purpose carrying the project's
root-relative path
discover.ts depth-1 scan for .sl0ppi/ markers
plan.ts discovered projects vs list-tabs -> {create, close, keep, foreign}
Identity carries the *path* because `list-panes --json` exposes no cwd (checked
against a live session, not assumed), so there is otherwise no way back from a
tab to a directory on disk. Slashes survive the fork's sanitiser, so nested
paths encode fine.
Two safety properties, both fuzzed over 5000 random names:
- a purpose never exceeds 64 chars and always survives re-sanitising, so what
we generate is what the terminal stores
- projectRelPath() reconstructs the original path exactly or returns null --
never a different path. Wrong answers close the wrong tab; refusing only
leaves a stale one.
The fuzz caught a real bug in the second property. The first implementation
detected lossy encodings by re-encoding and comparing, which silently failed:
`project:<47 a's>.<hash8>` is itself 64 legal characters, so it re-encodes to
itself, the check passed, and a bogus path was "reconstructed" -- which would
have closed a live tab. Lossy purposes now carry a structural marker (a leading
dot, which no discovered path can have) instead of relying on a comparison.
Plus the mount guard from the design: when the ROOT itself is missing, close
nothing. An unmounted filesystem is indistinguishable from mass deletion, and
one of those two readings eats your whole session.
31 unit tests + 2 fuzz properties, 10178 assertions, green.
Diffstat (limited to 'cli/package.json')
| -rw-r--r-- | cli/package.json | 11 |
1 files changed, 11 insertions, 0 deletions
diff --git a/cli/package.json b/cli/package.json new file mode 100644 index 0000000..2d36588 --- /dev/null +++ b/cli/package.json @@ -0,0 +1,11 @@ +{ + "name": "sl0ppi-cli", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Orchestrates sl0ppij sessions: one root dir per session, one project per tab", + "scripts": { + "test": "bun test", + "typecheck": "bunx tsc --noEmit" + } +} |
