<feed xmlns='http://www.w3.org/2005/Atom'>
<title>sl0ppi.git/cli/test/discover.test.ts, branch main</title>
<subtitle>a sl0p.foo repository</subtitle>
<id>https://git.sl0p.foo/sl0ppi.git/atom/cli/test/discover.test.ts?h=main</id>
<link rel='self' href='https://git.sl0p.foo/sl0ppi.git/atom/cli/test/discover.test.ts?h=main'/>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/'/>
<updated>2026-08-12T19:55:12Z</updated>
<entry>
<title>cli: the idempotency core (discovery, tab identity, plan diff)</title>
<updated>2026-08-12T19:55:12Z</updated>
<author>
<name>user</name>
<email>user@clank</email>
</author>
<published>2026-08-12T19:55:12Z</published>
<link rel='alternate' type='text/html' href='https://git.sl0p.foo/sl0ppi.git/commit/?id=72441afa75c058e4b1454108d962f2bebaee941b'/>
<id>urn:sha1:72441afa75c058e4b1454108d962f2bebaee941b</id>
<content type='text'>
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 -&gt; {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:&lt;47 a's&gt;.&lt;hash8&gt;` 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.
</content>
</entry>
</feed>
