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:

  1. Run the command shown in the code block.
  2. Read the output.
  3. Check every Assert listed for that step.
  4. Record the result: PASS or FAIL with the actual output.
  5. When a step says Capture, save the value into the named variable and use it in subsequent steps (e.g., $REPO gets 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:

  1. Creates a temp directory under $TMPDIR (e.g., /tmp/thinslice-test-XXXXXX)
  2. Runs git init and creates a few commits so the repo has history
  3. Writes a minimal thinslice.new-state executable that:
    • Calls slice give-me-a-port once (for HTTPD_PORT)
    • Creates a $SLICE_STATE_DIR/www directory for the httpd to serve
    • Outputs TOML with [env], two [process.*] sections, and a simple hooks.up.dev definition
  4. Prints the absolute path to the created repo on stdout

The two processes defined by the template are intentionally trivial:

  • process.httpdpython3 -m http.server $HTTPD_PORT -d $SLICE_STATE_DIR/www
  • process.idlesleep 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 from slice-test-fixture output
  • 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 the main worktree
  • main entry shows the branch name main

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-clean directory

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 least HTTPD_PORT
  • Contains [process.httpd] with a command and port field
  • Contains [process.idle] with a command field
  • 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
  • $REPO directory 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

PhaseWhat is testedKey assertion
0FixtureNormal git repo with executable template exists
1Directory structure+clone+/, +state+/, main/, local/, pr/, tmp/ created
2Git internals+clone+ is bare, main/ is its worktree, working tree intact
3Template statetemplate-clean created with valid thinslice.toml, ports registered
4IdempotencySecond slice setup is a no-op
5TeardownTemp repo removed, global port registry cleaned up