A small Go CLI for bare-repo + worktree git workflows. Clone, manage worktrees, prune orphans, wire up direnv per-repo.
v0.1.0 is in progress. Release archives are published from tags via GoReleaser.
Primary installer:
curl -fsSL https://raw.githubusercontent.com/pedromvgomes/gt/main/install.sh | shFallback with Go:
go install github.com/pedromvgomes/gt/cmd/gt@latestIf macOS blocks the downloaded binary on first run, remove the quarantine attribute:
xattr -d com.apple.quarantine "$(which gt)"gt clone git@github.com:pedromvgomes/foo.git
cd foo
gt wt add feature/new-thing
cd feature/new-thing
# ... do work ...
gt wt rm new-thing --branchgt clone https://... prompts to use SSH by default, then runs the direnv authentication setup unless --no-setup-auth is passed.
Clone a repository into a bare repo plus worktree layout:
- bare repository at
<folder>/.bare .gitpointer at<folder>/.git- default branch worktree at
<folder>/<default-branch>/ - worktree type directories from config (
feature,fix,choreby default)
Flags:
--sshconverts HTTPS URLs to SSH without prompting.--no-sshkeeps HTTPS URLs without prompting.--no-setup-authskips post-clone direnv setup.--user <name>chooses the GitHub user for post-clone direnv setup.--setup <a,b,c>runs only the named setup templates (overrides match-based selection).--no-setupskips post-clone setup templates entirely.--yesskips the setup confirmation prompt.--show-setupprints full template bodies before prompting.--dry-run-setupprints the setup plan without executing it.
Create a typed worktree and branch, for example gt wt add feature/new-thing --from main.
Remove a worktree by its short name. With --branch/-b, also delete the matching local branch.
Before removing, gt checks whether the worktree has uncommitted changes (any git status --porcelain output — staged, unstaged, or untracked):
- A clean worktree is removed without prompting.
- A dirty worktree prompts for confirmation in an interactive terminal (
Force delete and lose them? [y/N]); answering no aborts with a non-zero exit and leaves the worktree untouched. - In a non-interactive session (no TTY) a dirty worktree aborts with an error instead of prompting — pass
--force/-fto remove it anyway.
--force/-f skips the dirty check entirely and removes without prompting (use it in scripts and CI). A confirmed or forced removal also covers --branch deletion of an unmerged branch, so you are asked at most once.
List typed worktrees and the scratch worktree, including the latest commit summary for each.
Remove all typed worktrees. Worktrees with uncommitted changes are skipped with a warning unless --force/-f is given, which removes them and discards those changes. With --branches/-b, force-delete the corresponding local branches.
Delete local branches that do not have an active worktree, while preserving main, master, the default branch, and scratch.
Create or manage the top-level scratch worktree. gt scratch creates it when missing or prints its current commit when it already exists, --reset checks it out from a new source branch, and --delete removes it plus the local scratch branch.
Write an idempotent .envrc at the gt-managed root that exports GH_TOKEN from gh auth token --user <name>, runs direnv allow, and prints shell-hook instructions if direnv is not active in new shells.
Run the configured setup templates against the current repository. Works inside any git repository (gt-managed or plain). Reads the origin remote URL and matches it against each template's match patterns; every matching template runs in config-file order. With --setup, runs exactly those templates in the given order. --from <name> resumes a previously-failed run from that template. --show jumps straight to the detailed plan; --dry-run prints the plan without running anything.
gt config path— print the absolute path of the global config file.gt config show— print the current contents.gt config validate— parse and validate, surfacing any errors.gt config edit— open the config in$VISUAL/$EDITOR. On save, the file is parsed and validated; if invalid, you are prompted to re-open the editor and the original file is left untouched until a valid edit is saved.
gt bootstraps YAML config on first command run at $XDG_CONFIG_HOME/gt/config.yaml, or ~/.config/gt/config.yaml when XDG_CONFIG_HOME is unset.
worktree_types:
- feature
- fix
- chore
ssh:
host_aliases:
github.com: github-personal
setup:
templates:
- name: agentic-toolkit
match:
- "github.com:pedromvgomes/*"
- "github.com/pedromvgomes/*"
run: |
git clone git@github.com:pedromvgomes/agentic-toolkit.git "${GT_WORKDIR}/.agentic"
ln -sfn "${GT_WORKDIR}/.agentic/CLAUDE.md" "${GT_WORKDIR}/CLAUDE.md"
- name: golang-extras
match: ["*"]
script: ${HOME}/.config/gt/setup-scripts/golang-extras.shPer-repo overrides live at <gt-managed-root>/.gt.yaml and override global config per key. A per-repo config may declare its own setup.templates; they are merged on top of the global templates by name — a per-repo template that reuses a global template's name replaces it in place, and new names are appended after the global ones. This file lives at your gt-managed root (not committed inside the repo), so its templates are as trusted as the ones in your global config.
Templates run after gt clone and on demand via gt setup. They are simple shell scripts; gt does not introspect them.
- Each template needs
nameand exactly one ofrun(inline shell) orscript(path to an executable file).${VAR}references inscriptpaths are expanded. matchis a list of glob patterns checked as substrings against the clone URL.*matches any sequence;?matches a single character.match: ["*"]runs the template for every repo. An empty/missingmatchmakes the template only runnable via--setup <name>.- Templates execute in the order they appear in the file. Every template whose
matchmatches the URL runs. - Available env vars (also substituted into
script:paths):GT_ROOT— gt-managed root, or git repo root when not gt-managed.GT_WORKDIR—${GT_ROOT}/${GT_DEFAULT_BRANCH}in bare layout,${GT_ROOT}in plain.GT_LAYOUT—bareorplain.GT_DEFAULT_BRANCH,GT_REPO_OWNER,GT_REPO_NAME,GT_REPO_URL.
- Before running, gt prints the list of templates that will execute and prompts
[Y/n/d](dshows the full bodies). The prompt is skipped when--yesis passed or stdin is not a TTY. - On the first non-zero exit gt stops and tells you to resume with
gt setup --from <template>.
Templates in your global config and in a per-repo <gt-managed-root>/.gt.yaml run as you with your environment. Both files live on your machine where you put them, so treat editing them the same way you treat editing your shell profile. Templates a repository ships in its own committed .gt.yaml are different: they are untrusted, gated behind an explicit confirmation, and never auto-run without a TTY unless you pass --yes. gt setup will not silently run anything you have not added to a config you control.
This repo ships an agentic/ directory that lets coding agents (Claude Code, Cursor, etc.) discover and follow the bare-repo + worktree workflow without you having to re-explain it every session.
agentic/
skills/use-gt/
SKILL.md # how to drive gt from an agent
scripts/ensure-gt.sh # idempotent installer the skill calls
rules/
worktree-per-session.md # short "rules of the road" for agents
agentic/skills/use-gt/is a Claude Code skill (with the standardname+descriptionfrontmatter). Point your agent at it once and it will reach forgt clone/gt wt add/gt wt rminstead of rawgit, installgton demand via the helper script, and ask the right pre-clone questions (whichghuser to authenticate as, SSH vs HTTPS, etc.).agentic/rules/worktree-per-session.mdcaptures the non-negotiables: always use the bare-repo layout, sessions start at the gt-managed root, and every session mustgt wt addits own worktree before doing any work.
Why bother:
- Consistency across repos. Same auth wiring, same typed worktrees, same branch naming, regardless of which repo the agent is dropped into.
- Parallel-safe sessions. One worktree per session means multiple agent runs can work side-by-side without stepping on each other or on your default-branch checkout.
- No bypassed validation. Raw
git worktree addskips the configured worktree-types check; routing throughgtkeeps everything inside the layout you've set up. - Self-bootstrapping. The skill's
ensure-gt.shinstallsgtfrom the official release script if it's missing, so a fresh agent environment becomes productive on the first command.
The installer prompts to install completions for zsh, bash, or fish when run interactively. You can regenerate them manually:
gt completion zsh > "$(brew --prefix)/share/zsh/site-functions/_gt" # Homebrew zsh, already on $fpath
gt completion bash > ~/.local/share/bash-completion/completions/gt
gt completion fish > ~/.config/fish/completions/gt.fishIf you don't use Homebrew zsh, pick a directory on your $fpath (check with echo $fpath | tr ' ' '\n') or add your own:
mkdir -p ~/.zsh/completions
gt completion zsh > ~/.zsh/completions/_gt
# then in ~/.zshrc, before `compinit`:
# fpath=(~/.zsh/completions $fpath)Restart your shell (or exec zsh) to pick up new completions. If oh-my-zsh has cached an older compinit, also run rm -f ~/.zcompdump.
go test ./tests/... -coverpkg=./internal/... -coverprofile=coverage.out
go vet ./...
golangci-lint runReleases are cut by pushing a semver tag:
git tag v0.1.0
git push origin v0.1.0The release workflow runs GoReleaser and publishes four tar.gz archives plus checksums.txt.