tesseraworkspaces

module
v1.2.16 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 24, 2026 License: MIT

README

tesseraworkspaces

A CLI tool for creating feature-scoped workspaces with multiple git worktrees. Work on parallel branches or stacked diffs within a single feature, each with its own coding agent. Built for teams and solo developers who use AI coding agents in parallel.

Quick Start

# Install
go install github.com/jdbencardinop/tesseraworkspaces/cmd/tws@latest

# Create a feature with a branch and start coding
cd ~/projects/myapp
tws add auth -n auth-models --open

How it works

  1. Add a feature — creates a workspace with shared context and inject files
  2. Create worktrees — spin up isolated branches, stacked or parallel
  3. Open a worktree — launch your coding agent in the worktree directory
  4. Decide — broadcast design decisions to sibling worktrees
  5. Sync — rebase all branches in dependency order, amend-aware
  6. Push — push all branches with --force-with-lease

Features

Stacked & Divergent Branches
tws new auth auth-models                              # selected repo's origin/HEAD
tws new auth auth-middleware --base auth-models        # stacks on auth-models
tws new auth auth-tests --base auth-models             # parallel to middleware
tws new auth release-check --base origin/release        # explicit remote ref
tws new auth wiki-docs --repo ../wiki --base master     # local master in wiki repo

# Result:
# (<default>)
# └── auth-models
#     ├── auth-middleware
#     └── auth-tests
Cross-Worktree Agent Communication

Agents in different worktrees can communicate via decisions:

tws decide auth "Changed User.ID to uuid" --type breaking
tws decide auth "Review API surface" --type review --to auth-middleware
tws decisions show                  # auto-detects feature, shows unread only
tws decisions ack                   # mark as read

With hooks installed, Claude Code agents see new decisions automatically on session start:

tws hooks install auth              # install on all worktrees
tws config set auto_hooks true      # auto-install on every tws new
Smart Sync
tws sync auth                       # quiet fetch + rebase in dependency order
tws sync auth --push                # sync + push all branches
tws sync auth --continue            # resume after conflict resolution
tws sync auth --abort               # discard sync state

tws sync auth --only api            # sync exactly one stack entry
tws sync auth --from api            # sync one entry and its descendant subtree
tws sync auth --local-only          # replay local parent tips; never advance an anchor
tws sync auth --no-fetch            # plan from local refs; no automatic network input
tws sync auth --fetch --full        # name both axes explicitly (external defaults)

tws sync auth --plan                                    # preview: old base, new base, candidates per entry
tws sync auth --plan --max-replay-per-entry 10          # bound the previewed run's replay work
tws sync auth --max-replay-per-entry 10 \
  --approve-plan <fingerprint>                          # execute the approved plan

Sync modes. Three independent axes select what a run does. --fetch / --no-fetch chooses the input-ref policy (external defaults to fetch, checkout to no-fetch); --full / --local-only chooses propagation; --only / --from chooses scope. Running tws sync <feature> with no flags is unchanged. Notes:

  • --no-fetch means "no automatic network input" — not "offline". An explicit --push is still allowed and is the only way a no-fetch run reaches the network.
  • --local-only never advances a root from its remote base; selecting only an anchor is a no-op success, not an error.
  • A scoped run drops git rebase --update-refs, so it cannot move a branch outside the selection.
  • Incompatible combinations are refused before any fetch, lock, or rebase.
  • A scoped (--only/--from) --push is strict: the run stops at the first rejected push, keeps its recovery state, and tws sync <feature> --continue retries only the entries that were never pushed. A scope=all run, tws push, and the no-flag tws sync --push push the whole feature and keep today's lenient per-entry failure line.
  • Running two syncs against one feature concurrently is still unsafe: a scoped run is guarded, but a no-flag run takes no lock. This is not fixed.
  • Downgrading in the middle of a scoped run: an older tws fails closed on plain sync and on --continue. Downgrading after an explicit old --abort is unsupported.

Plan and guard. Run --plan before a wide sync that could rebase several branches at once, and read its entries[] rows first. --plan describes the rebase this invocation would perform and exits: it moves no branch, rewrites no working tree, and writes no tws state — but it is not Git-write-free. A plan fetches exactly where the run it describes fetches: an external plan fetches by default, a checkout plan only under --fetch, and --plan --continue never fetches — so --plan --no-fetch previews a different, new-mode route, not the one a bare tws sync <feature> takes. Bare --plan (no other flag) describes exactly the no-flag run. Its candidates counts are an upper bound on what a guarded run might replay, never a promise of what gets applied.

--max-replay-per-entry <n> and --max-replay-total <n> refuse before rebasing if this invocation would replay more candidates than the bound, for one entry or in total — the bound applies to this invocation only and is never cumulative across resumes. --approve-plan <fingerprint> re-supplies the 64-hex fingerprint --plan printed and requires at least one of those two limits, --plan included; a workflow that mints or presents a limitless fingerprint is a documentation bug, never a valid one. Extract the fingerprint explicitly — e.g. sed -n 's/^Approval fingerprint: //p' — never pipe tail -1 into --approve-plan.

A guarded run's limits are recorded in recovery state, so an older tws release refuses to resume it instead of silently dropping the guard. A guard refusal exits 1 and writes exactly one plan-guard: <kind>: <detail> line on stderr; a detail beginning state-preserved: means something on disk outlives the refusal. A refusal tws already performs — a dirty tree, a held lock, a base that does not resolve, an incomplete previous run — keeps its own wording, exits 1, and is never marked. --plan itself exits 0 even when it describes a refusal, so decide whether to execute from the plan's own fields, never from that exit status.

  • Amend-aware — uses --onto to avoid ghost conflicts from amended commits
  • Archived branch support — syncs archived branches via --update-refs or optimistic rebase
  • Post-rebase validation — run test_command after each rebase (e.g., go build ./...)
  • Conflict recovery — saves state, guides resolution, resumes with --continue
Context Injection

Shared files in inject/ are symlinked into every worktree:

# Edit once, all worktrees see changes
echo "# Auth context" > ../myapp.tws/auth/inject/CLAUDE.local.md

# Re-sync after adding new files
tws inject auth

# Target a gitignored subdirectory
tws inject auth --into .context
Multi-Repo Workspaces

Work on code and docs repos in the same feature:

tws add auth -n code-branch
tws new auth wiki-docs --repo ~/projects/myapp-wiki
Workspace Portability
tws export auth                     # YAML to stdout
tws export auth --to-repo           # save to .tws/workspaces/ (travels with git push)
tws export auth --full -o auth.tar.gz  # tarball with inject files
tws import --from-repo auth         # recreate on another machine
3-Tier Skill System
  • Worktree skills — injected into each worktree, agents work on code
  • Orchestrator skill — auto-installed in feature dir, coordinates agents
  • Global skills — installed via tws init, knows how to create workspaces
tws init                            # install Claude + Copilot skills
tws init --agent claude             # Claude only
tws init --register --register-alias myapp   # also enroll in the global registry

All Commands

Command Description
tws add <feature> [-n branch] [--open] [--tmux] Create feature workspace
tws new <feature> <branch> [--base] [--repo] [--force] Create worktree branch
tws open [feature] [branch] [--tmux] [--no-agent] Open worktree (interactive picker if no args)
tws sync <feature> [--push] [--continue] [--abort] [--verbose] [--fetch|--no-fetch] [--full|--local-only] [--only <entry>|--from <entry>] Rebase in dependency order, optionally scoped
tws sync <feature> --plan [--json] [--max-replay-per-entry N] [--max-replay-total N] Preview the rebase; candidates are an upper bound, never applied
tws sync <feature> --approve-plan <fingerprint> [--max-replay-per-entry N | --max-replay-total N] Execute a previewed, guarded plan
tws push <feature> [--dry-run] Push all branches
tws stack <feature> Show dependency tree
tws stack status <feature> [--json] Stack ancestry, materialization, and upstream status
tws list / tws ls List features and branches
tws delete <feature> Remove feature and worktrees
tws archive <feature> <branch> Remove worktree, keep branch
tws decide <feature> "<msg>" [--type] [--to] Record a decision
tws decisions show [feature] [--mine] [--all] View decisions
tws decisions ack [feature] Mark decisions as read
tws inject <feature> [branch] [--into path] Sync inject files
tws doctor [feature] Health checks
tws status [feature] [--json] Agent work status per branch
tws rename feature/branch Rename feature or branch
tws config show/set/get Manage configuration
tws hooks install/remove [--all] Manage agent hooks
tws export <feature> [--full] [--to-repo] Export workspace
tws import <file> [--from-repo] Import workspace
tws template sync [--all] [--template dir] Backfill templates
tws close <feature> <branch> Kill tmux session
tws registry add <path> [--alias name] Register a repo/workspace for discovery
tws registry list/show/check [--json] Inspect registered workspaces
tws registry alias <selector> <alias> [--remove] Manage aliases
tws registry repair <selector> <new-path> [--allow-identity-change] Re-point a moved entry
tws registry remove <selector> Drop registry metadata (never deletes files)
tws registry prune --missing [--force] Drop entries whose targets are gone
tws space add <name> <path> --kind <kind> [--description text] [--feature f] Link a tool-owned sibling space
tws space list [--feature f] [--all] [--kind k] [--json] Discover linked sibling spaces (bare list is cwd-scoped)
tws space show <name> [--feature f | --workspace] [--json] Show one linked space
tws space remove <name> [--feature f | --workspace] Drop the link (never deletes the target)
tws init [--agent] [--force] [--register] [--register-alias name] Install agent skills
Global Workspace Registry

Opt-in discovery index at ${XDG_DATA_HOME:-~/.local/share}/tws/registry.yaml (directory 0700, file 0600). Nothing is created until you enroll explicitly.

tws registry add . --alias myapp          # enroll the current repo/workspace
tws init --register --register-alias myapp  # enroll after a successful init
tws registry list --json                  # deterministic output; empty is []
tws registry check                        # ok / missing / mismatched / invalid
tws registry repair myapp /new/path       # re-point after a move
tws registry prune --missing --force      # --force required in non-TTY use

Selectors are exact: entry ID, alias, or canonical path. Aliases may not shadow an entry ID or a registered path.

Identity and markers. Git-backed targets carry a small opaque marker at .git/tws/workspace-id; checkout mode and linked worktrees share the main repository's Git common directory. External workspaces use .tws-workspace/workspace-id. Markers are created only on explicit enrollment, survive moves and workspace-mode switches, and detect replacement.

  • Moved target: tws registry repair <selector> <new-path> — no extra flag needed.
  • Replaced target (marker or Git identity changed): add --allow-identity-change.
  • tws registry remove/prune only drop registry metadata; targets and marker files are never deleted.

A tws workspace is surrounded by tool-owned sibling spaces: learning notes, ticket stores, patch metadata, research, and authored documentation. tws space records where they live in <workspace-root>/spaces.yaml so agents and humans discover them by command instead of by hard-coded path.

tws space add learning ./learning --kind learning --description "notes"
tws space add patching ./acme/patching --kind patching --feature acme
tws space list                        # cwd-scoped: workspace-wide + detected feature
tws space list --all                  # the complete registry, from anywhere
tws space list --json                 # deterministic output; empty is []
tws space list --feature acme         # workspace-wide entries plus acme's
tws space show learning --workspace   # scope selectors disambiguate a shared name
tws space remove learning --workspace # drops the link; never deletes the target
  • Location metadata only. tws never reads, writes, validates, or deletes the content of a linked space, and it never learns the linked tool's schema or lifecycle. tws space add is the only command that creates anything for this feature, and it never creates the target directory.
  • Where the file lives. External mode uses the resolved external root ($TWS_ROOT when set); checkout mode uses <repo>/.tws and ignores TWS_ROOT, as every other checkout command does. tws space list always prints Workspace: <root> (mode: <mode>, scope: <scope>) before its results, including the empty state, so the active file and scope are unambiguous.
  • Default scope is your location. A bare tws space list shows every workspace-wide entry plus the entries of the feature you are inside when one is detected; outside a feature it is already complete. Use --all for the complete registry from anywhere, and --kind to filter. --json is a bare array with no header. A filter that hides everything says so and reports how many entries are registered, which is never confused with an empty registry.
  • Scope selectors. When the same name exists workspace-wide and inside a feature, tws space show / tws space remove report the ambiguity and accept --workspace or --feature <name> (mutually exclusive) to select exactly one.
  • Two path forms. Targets inside the workspace root are stored workspace-relative and stay portable; targets outside are stored absolute. A target must exist and be a directory, but it does not need to be a Git repository.
  • Local state. spaces.yaml and .spaces.lock are mode 0600, are not shared, and are not included in tws export / tws import. The advisory lock is POSIX-only (macOS and Linux).
  • Feature-name protection. A registered target directory can never masquerade as a feature. Ownership is decided by filesystem identity, so a hand-edited absolute path inside the workspace root, a symlinked spelling, or a different letter case on a case-insensitive volume is recognised as the same directory. tws add, new, delete, rename, archive, sync, export, import, open, stack, inject, push, decide, doctor, template sync, hooks install, and tws migrate-layout all refuse a feature name owned by a registered space, and feature listings exclude it. tws delete and tws migrate-layout refuse when a registered target lives inside the feature — migrate-layout never rewrites a registered path, it names the blockers and the exact scope-qualified tws space remove command for each, and --all is all-or-nothing — and tws rename feature rewrites relative entries while refusing pinned absolute ones.
  • Strict on untrusted metadata. If spaces.yaml exists but is unreadable, symlinked, malformed, carries an unknown field, or declares a future schema version, every command that consults workspace features or spaces exits nonzero having changed nothing. Only shell completion degrades, silently offering no candidates. When the file is absent — the normal state — nothing is created and every pre-existing command behaves exactly as before.
  • Inside a sibling space the enclosing .tws-workspace marker wins, so tws space list keeps targeting the parent workspace even when the space is its own Git repository. No spaces.yaml or .tws directory is ever created for the sibling repo.

Requirements

Configuration

tws config set agent_command opencode        # change agent
tws config set use_tmux true --repo          # per-repo tmux default
tws config set test_command "go build ./..."  # post-rebase validation
tws config set auto_hooks true               # auto-install hooks on tws new
tws config set inject_into .context          # inject target subdirectory

Config files: ~/.config/tws/config.yaml (global), .tws/config.yaml (per-repo). Env: TWS_ROOT.

Shell completions: tws completion zsh/bash/fish/powershell

Documentation

Install from Source

git clone https://github.com/jdbencardinop/tesseraworkspaces.git
cd tesseraworkspaces
make install       # installs to $GOPATH/bin/tws
make build         # builds to bin/tws with version from git tag

License

MIT

Directories

Path Synopsis
assets
cmd
tws command
cli

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL