Test Spec: Workspace Setup
What to do with this file
You are a coding agent. Your job is to execute this test spec by running commands in a shell, step by step, from Phase 0 through Phase 5. For each step:
- Run the command shown in the code block.
- Read the output.
- Check every Assert listed for that step.
- Record the result: PASS or FAIL with the actual output.
- When a step says Capture, save the value into the named variable and
use it in subsequent steps (e.g.,
$REPOgets reused later).
At the end, produce a summary table showing which phases passed and which failed, with the actual vs. expected values for any failures.
If a prerequisite is not met (binary not found, fixture script missing), stop and report that instead of continuing.
What this test is checking
slice setup is a one-time, destructive transformation that converts a
normal git checkout into a thinslice workspace. It rearranges the directory
structure, creates a bare clone, registers worktrees, and generates the
initial template-clean state.
Because it moves directories around and rewrites git internals, it's the
riskiest command in the CLI and the most important to test in isolation. This
test creates a disposable git repo, runs slice setup, and verifies every
structural invariant the command is supposed to establish.
The test also verifies idempotency: running slice setup a second time on
an already-set-up workspace should be a no-op.
Prerequisites
The slice binary and the slice-test-fixture helper must be on $PATH.
No external infrastructure (databases, message brokers) is required — the
test fixture uses a minimal thinslice.new-state template with trivial
processes.
As a quick smoke test, run:
$ slice --version
If this succeeds, proceed. If it fails, stop and report the error.
Fixture: slice-test-fixture
The slice-test-fixture helper creates disposable git repos for testing.
The init-repo subcommand does the following:
- Creates a temp directory under
$TMPDIR(e.g.,/tmp/thinslice-test-XXXXXX) - Runs
git initand creates a few commits so the repo has history - Writes a minimal
thinslice.new-stateexecutable that:- Calls
slice give-me-a-portonce (forHTTPD_PORT) - Creates a
$SLICE_STATE_DIR/wwwdirectory for the httpd to serve - Outputs TOML with
[env], two[process.*]sections, and a simplehooks.up.devdefinition
- Calls
- Prints the absolute path to the created repo on stdout
The two processes defined by the template are intentionally trivial:
process.httpd—python3 -m http.server $HTTPD_PORT -d $SLICE_STATE_DIR/wwwprocess.idle—sleep infinity
This avoids requiring postgres, redis, or any real infrastructure.
Usage:
$ slice-test-fixture init-repo
/tmp/thinslice-test-a7f2c1
$ slice-test-fixture destroy /tmp/thinslice-test-a7f2c1
destroyed /tmp/thinslice-test-a7f2c1
Notation
$REPO— absolute path to the test repo, captured fromslice-test-fixtureoutput- Lines starting with
$inside code blocks are commands to run +clone+,+state+— thinslice infrastructure directories (the+delimiters are literal)
Phase 0: Create test repo
Before we can test anything, we need a disposable git repository that looks
like a real project — one with some commit history and a thinslice.new-state
template. The fixture helper gives us exactly that.
$ slice-test-fixture init-repo
Capture the output as $REPO.
Now confirm that what we got is actually a normal git checkout, not something already transformed. Setup expects to start from a vanilla repo, so if this invariant doesn't hold, nothing downstream is meaningful.
$ git -C $REPO rev-parse --is-bare-repository
Assert: Output is false — this is a normal (non-bare) checkout.
$ git -C $REPO log --oneline
Assert: At least one commit exists.
Finally, the template executable must be in place. This is the script that
slice setup will invoke to generate the initial state — without it, setup
has nothing to work with.
$ test -x $REPO/thinslice.new-state && echo "executable" || echo "not executable"
Assert: Output is executable.
Phase 1: Run slice setup
This is the main event. slice setup will tear apart the normal git checkout
and reassemble it into a thinslice workspace. Concretely, it should:
- Create a bare clone of the repo at
+clone+/ - Move the original working tree under a
main/worktree - Create namespace directories (
local/,pr/,tmp/) for future worktrees - Run the template to generate
+state+/template-clean
$ cd $REPO && slice setup
Expect: output indicating the workspace was created. No errors.
With setup complete, the top-level directory should now look like a thinslice
workspace, not a normal git repo. The original files (like thinslice.new-state)
should have moved down into main/.
$ ls -1 $REPO
Assert:
- The following directories exist:
+clone+,+state+,main,local,pr,tmp - The original checkout directory contents are NOT at the top level (they've
been moved into
main/)
The namespace directories are scaffolding for future worktrees. At this point
they should be empty — setup only creates the main worktree.
$ ls -1 $REPO/local 2>&1
$ ls -1 $REPO/pr 2>&1
$ ls -1 $REPO/tmp 2>&1
Assert: All three are empty (no worktrees created yet).
Phase 2: Verify git structure
The directory layout looks right, but the real question is whether the git
plumbing was wired up correctly. This is the subtle part of setup: the
original .git directory was replaced by a bare clone, and what used to be
the checkout is now a worktree of that bare clone. If any of these
relationships are wrong, every subsequent git and slice command will
break in confusing ways.
Start by confirming that +clone+ is genuinely a bare repository:
$ git -C $REPO/+clone+ rev-parse --is-bare-repository
Assert: Output is true.
The bare clone should have exactly the same history as the original repo — setup must not create, rewrite, or lose any commits during the conversion.
$ git -C $REPO/+clone+ log --oneline
Assert:
- Same commits as Phase 0 (same hashes, same messages)
- No new commits were created by setup
Next, verify that main/ is properly registered as a worktree of the bare
clone. Git tracks this relationship through internal pointers — if they're
wrong, main/ might look like a detached repo or point at the wrong
upstream.
$ git -C $REPO/main rev-parse --show-toplevel
Assert: Output is $REPO/main.
$ git -C $REPO/main rev-parse --git-common-dir
Assert: Output points to $REPO/+clone+ (the bare repo is the common
git dir).
The working tree itself should have survived the move intact. Every file
that existed in the original checkout should be present in main/, with no
modifications or missing content. This is the most user-visible failure mode
— if setup damages the working tree, the developer's code is corrupted.
$ test -x $REPO/main/thinslice.new-state && echo "found" || echo "missing"
Assert: Output is found — the template executable was moved into
main/.
$ git -C $REPO/main status --porcelain
Assert: Output is empty or shows no unexpected modifications — the working tree was moved cleanly without losing or altering files.
Finally, check git's own view of the worktree topology. There should be
exactly two entries: the bare repo itself and the main worktree.
$ git -C $REPO/+clone+ worktree list
Assert:
- Exactly two entries: the bare repo (
+clone+) and themainworktree mainentry shows the branch namemain
Phase 3: Verify template-clean state
When setup runs the thinslice.new-state template, it captures the output
into a state directory called template-clean. This state serves as the
prototype for all future slices — when a developer creates a new worktree,
thinslice copies template-clean and customizes it. If the template ran
incorrectly or the output wasn't captured properly, every future slice will
inherit the problem.
The +state+ directory is thinslice's state root. It should contain the
bindings registry and the template-clean state.
$ ls -1 $REPO/+state+
Assert:
- Contains
bindings.toml - Contains
template-cleandirectory
The bindings file tracks which worktrees are connected to which state
directories. After a fresh setup, nothing should be bound yet — setup creates
the template but deliberately does not bind main/ to it.
$ cat $REPO/+state+/bindings.toml
Assert: No active bindings (either empty [bind] table or no entries) —
setup does not bind main/ to any state automatically.
Now look at the template-clean state itself. The template script should have
produced a thinslice.toml that defines the environment and processes for this
project.
$ test -f $REPO/+state+/template-clean/thinslice.toml && echo "found" || echo "missing"
Assert: Output is found.
$ cat $REPO/+state+/template-clean/thinslice.toml
Assert:
- Contains an
[env]section with at leastHTTPD_PORT - Contains
[process.httpd]with acommandandportfield - Contains
[process.idle]with acommandfield - Port values are integers in the valid range (1024–65535)
Capture: $TEMPLATE_HTTPD_PORT = the HTTPD_PORT value from [env].
Ports allocated by slice give-me-a-port should be registered in the global
port registry so they aren't handed out to another project. Verify the
template's httpd port shows up as claimed.
$ slice debug port $TEMPLATE_HTTPD_PORT
Assert: Output indicates this port is registered — it belongs to the
template-clean state in this project.
The template script also creates a www directory that the httpd process
will serve from. This is a side effect of the template running, not something
setup creates directly — it confirms the template actually executed, not just
got parsed.
$ test -d $REPO/+state+/template-clean/www && echo "found" || echo "missing"
Assert: Output is found — the template's setup step (mkdir -p www)
ran.
Phase 4: Idempotency
A developer might accidentally run slice setup in a workspace that's
already set up — maybe they forgot, maybe a script calls it unconditionally.
This should be harmless. The second run should detect that setup already
happened and either skip the work or produce identical results. If it
re-runs the template, re-clones the repo, or creates duplicate worktrees,
that's a bug.
First, snapshot the current state so we can detect any changes after the second run.
$ ls -laR $REPO/+state+/template-clean/ | shasum
Capture the output as $STATE_HASH_BEFORE.
$ git -C $REPO/+clone+ worktree list
Capture the output as $WORKTREE_LIST_BEFORE.
Now run setup again.
$ cd $REPO && slice setup
Expect: output indicating the workspace is already set up, or silent success. No errors.
Compare everything against the snapshots. Nothing should have changed.
$ ls -laR $REPO/+state+/template-clean/ | shasum
Assert: Output matches $STATE_HASH_BEFORE — state directory unchanged.
$ git -C $REPO/+clone+ worktree list
Assert: Output matches $WORKTREE_LIST_BEFORE — no new worktrees
created.
$ ls -1 $REPO
Assert: Same directories as Phase 1 — no duplication, no extra artifacts.
Phase 5: Teardown
The test repo was created in $TMPDIR and allocated global resources (ports
in the registry). The fixture's destroy command should clean all of this
up so the test leaves no footprint on the system.
$ slice-test-fixture destroy $REPO
Assert:
- Output confirms destruction
$REPOdirectory no longer exists
The port that was allocated during template generation should also be released from the global registry. If it's still registered after the repo is gone, that's a leak — it will block that port number from being reused by future projects.
$ slice debug port $TEMPLATE_HTTPD_PORT
Assert: Port is no longer registered — the fixture cleaned up global state.
Summary of Key Assertions
| Phase | What is tested | Key assertion |
|---|---|---|
| 0 | Fixture | Normal git repo with executable template exists |
| 1 | Directory structure | +clone+/, +state+/, main/, local/, pr/, tmp/ created |
| 2 | Git internals | +clone+ is bare, main/ is its worktree, working tree intact |
| 3 | Template state | template-clean created with valid thinslice.toml, ports registered |
| 4 | Idempotency | Second slice setup is a no-op |
| 5 | Teardown | Temp repo removed, global port registry cleaned up |

