handbook/docs/workflow.md
disqualifier dafc1dcacd fix path: /srv/configs -> /srv/config (singular); fix broken anchor
- deploy.md: /srv/configs -> /srv/config (singular is canonical) across the
  compose mount, paths table, secrets path, and rotation snippet. Add the
  repo+compose row (/srv/docker/<workspace>/<project>, created by the clone,
  not pre-provisioned) and a note that all /srv paths are owned by the
  services user (1337) — rounding out the canonical layout.
- workflow.md: the first 'per-project git identity' link pointed at a
  nonexistent #per-project-git-identity anchor; point it at #handy-shell-setup
  (where the gitsetup alias lives), matching the other link to the same spot.

Did not touch pip/requirements, chmod a+rwX + user 1337, HOME=/tmp, init,
layer caching, or the git-install caveat — all intentional standards.

Verified: mkdocs build --strict clean (validates anchors); table renders.
Signed-off-by: disqualifier <dev@disqualifier.me>
2026-06-30 00:57:24 -04:00

10 KiB

Workflow

Get hands on with how we dev — where code lives, how we use git, the AI-assisted flow we recommend, and the shell setup that ties it together. This is the our-flavored version: why we do it this way and how our setup is wired. For the truly generic parts (installing WSL, learning git), we link the official docs rather than reteach them.

!!! info "Public, sanitized" Examples use placeholders — <you>, <key>, dev@<you>, /mnt/c/<your>/.... Swap in your own real values locally; never commit personal paths, key names, or emails.

Why git, why WSL

Git is the backbone of everything here. Version history and branching are the obvious part, but our whole deploy model is built on git too: we pip install libraries straight from git by tag, servers pull via per-repo deploy keys, and changes flow develop → main before they ship. If you know git, you already understand how our code moves from your machine to production.

WSL2 (Ubuntu) is where we develop — a real Linux toolchain on a Windows desktop. You get native Linux tooling (the same environment our servers run) plus the Windows apps you actually use day to day. Install WSL2 from Microsoft's docs; this page documents the our-setup layer that goes on top.

Signing up on our Gitea

Our code lives on Gitea at git.rethinkstudios.io. Two orgs you'll use:

Get an account:

  1. Register at git.rethinkstudios.io and verify your email.
  2. Add your SSH public key under Settings → SSH / GPG Keys so you can clone and push over SSH.
  3. For servers, we use a per-repo deploy-key model rather than your personal key — see the Deploy guide for how a box gets read access to just the repos it needs.

Our git vs. public git (GitHub / GitLab)

It's the same git — just a different host. The thing most devs trip on is identity and keys per host: you may have a GitHub identity and a Gitea identity on one machine, and commits need to be attributed (and signed/pushed) with the right one per project.

We solve that with per-repo local git config — run a small alias inside a repo to set its local user and the SSH key it pushes with (see per-project git identity below). No global identity juggling.

Our conventions, in short:

  • Signed commitsgit commit -s.
  • No AI co-author trailer on your own work. (Intern/dev work you're crediting gets that dev's Co-Authored-By — nothing else.)
  • develop is staging, merge to main via MR when it's ready to ship.
  • Libraries install from git by tag — pin a version in your deps, bump the tag when the lib releases.

!!! warning "Set your per-repo identity before the first commit" One machine often carries more than one Gitea identity/key. If you forget to run gitsetup in a fresh clone, your commits attribute to the wrong user — or push with the wrong key and bounce. Run it right after cloning; see per-project git identity.

Git basics (our-flavored)

Not a git tutorial — just how the everyday loop looks against our Gitea. For the generic command reference, keep the Git cheat sheet or the Pro Git book handy.

Clone over SSH (the alias maps to a key — see the shell setup):

git clone git@<alias>:rethink-public/<repo>.git

The everyday loop:

git switch -c <feature>      # branch off
# ...make a logical change...
git commit -s -m "..."       # commit, signed
git push -u origin <feature> # push
# open an MR: develop -> main

Commit small and often — one commit per logical change, not a giant end-of-day dump. Small commits are easier to review, revert, and git bisect when something breaks.

Read history as a graph with the gl alias below:

gl   # git log --graph, oneline, decorated

The dev workflow

This is the part that's distinctly ours. AI is a force multiplier, but only with discipline around it — the two habits that matter most are plan before you build and verify by executing, not asserting.

You can use whatever AI you like — but we recommend Claude (via Claude Code, in the terminal or VS Code). The whole project structure we recommend below — the .claude/ folder, CLAUDE.md instructions, numbered specs — is built on that preference: it's designed around how Claude Code reads project context and takes handoffs. Other tools can read these files too, but the convention assumes Claude-first.

Plan here, build in Claude Code. Do the thinking in chat — plan, write the spec, make the decisions. Then hand that spec to a Claude Code agent that does the build: it implements, verifies by running, and pushes. The chat plans; the agent implements.

tmux + Claude Code split. In practice that's a Claude Code agent running in a tmux pane doing the build work while you plan/review in another. One spec in, a verified change out.

claudedo (optional, hands-free). Voice control for Claude Code over tmux: a wake-word plus local Whisper speech-to-text drives your tmux session without the keyboard — handy when you're fullscreen or away from the desk. It's available here: git.rethinkstudios.io/rethink-software/claudedo.

The .claude/ project convention. Every project has a top-level .claude/ folder (always gitignored — nothing under it is committed):

  • CLAUDE.md — project-specific instructions for the agent (stack, layout, conventions). Layered on top of your global instructions.
  • compact.md — a running state log (done / decided / in-flight), updated at checkpoints so a fresh session catches up fast.
  • commands.log — an append-only record of shell commands run.
  • spec/ — numbered, per-change specs named NN-<type>-<short>.md (e.g. 01-feature-logging.md).

Tell the agent to "setup project" to scaffold all of that (and add .claude/ to .gitignore) in a new repo.

AI-assist habits. When you're stuck, give the AI the exact context instead of describing it — the WSL clipboard bridge makes this trivial:

git diff | clip.exe   # then paste the diff straight into the chat

Use AI for the plan and the spec, let the agent build, and always prove it works by running it — don't accept "this should work."

!!! tip "Verify by executing, not asserting" The single habit that separates good AI-assisted work from plausible-looking nonsense: run it. A passing build, a real screenshot, actual output — that is proof. "It should work" is not.

  • WSL2 (Ubuntu) + VS Code + Claude Code. Develop in WSL, edit in VS Code over Remote — WSL, and run Claude Code as your agent (terminal or the VS Code extension).
  • pyenv for per-project Python versions (we target 3.10+) and isolated .venvs — full setup on the Virtual environments page.
  • flake8 with a shared config — max line length 120 (see the alias below).

Handy shell setup

Copy-paste these into your .zshrc / .bashrc. Replace every placeholder with your real values. The high-value ones are explained underneath.

# --- WSL <-> Windows bridges ---
alias explorer="explorer.exe ."          # open current dir in Windows Explorer
# pipe to the Windows clipboard (great for pasting context into AI):
#   git diff | clip.exe    -> paste the diff straight into a chat
# auto-alias Windows .exe tools on PATH (e.g. adb/platform-tools):
export PATH="$PATH:/mnt/c/<your>/platform-tools"
for exe in /mnt/c/<your>/platform-tools/*.exe; do
  alias "$(basename "${exe}" .exe)"="${exe}"
done

# --- pyenv (Python version management) ---
export PYENV_ROOT="$HOME/.pyenv"
export PATH="$PYENV_ROOT/bin:$PATH"
eval "$(pyenv init --path)"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"

# --- flake8 with a shared config ---
alias flake8='flake8 --config ~/.config/flake8'

# --- per-project git identity (switch identity per repo) ---
# set the LOCAL (per-repo) user + the key to sign/push with:
alias gitsetup='git config --local user.name "<you>"; \
                git config --local user.email "dev@<you>"; \
                git config --local core.sshCommand "ssh -i $HOME/.ssh/<key>"'
# a second identity for a different account, same pattern:
alias gitea='git config --local user.name "<alt>"; \
             git config --local user.email "<alt>@<host>"; \
             git config --local core.sshCommand "ssh -i $HOME/.ssh/<alt-key>"'

# --- git log graph ---
alias gl='git log --graph --abbrev-commit --pretty=oneline --decorate'

# --- local bins on PATH ---
export PATH="$HOME/.local/bin:$PATH"
export PATH="$HOME/.npm-global/bin:$PATH"
# cherry-pick a commit onto master/main quickly
gitcs() {
    if [ -z "$1" ]; then echo "Usage: gitcs <commit>"; return 1; fi
    git checkout master && git cherry-pick "$1"
}

The ones worth understanding:

  • git diff | clip.exe — the killer WSL trick for AI-assisted dev. Pipe your working changes straight to the Windows clipboard and paste them into the chat so the AI sees exactly what changed instead of your paraphrase of it.
  • gitsetup / gitea — per-repo identity. One machine, multiple Gitea identities and keys; run the alias inside a repo to set its local user and signing/push key, so commits attribute correctly without touching your global config.
  • gl — a readable branch graph for understanding history at a glance.
  • pyenv — per-project Python versions, so each repo builds against the version it targets.