workstation: per-user code_layout — workspace puts project repos under ~/code (ancamilea + tripit)

Viktor asked to restructure Anca's setup: her ~/code WAS the infra clone
itself; he wants ~/code to be the directory where all her project repos
(tripit etc.) live side by side, with infra moved to a subdirectory.

- roster.yaml gains per-user 'code_layout: single|workspace' + 'repos',
  validated + derived by roster_engine.py (12 new tests, 40 total).
- t3-provision-users reconcile: auto-migrates a single-layout ~/code to
  ~/code/infra (running processes follow the moved inode), hoists nested
  project clones to the workspace root, clones roster repos from Forgejo
  AS the user (their PAT makes private repos work), and wires the
  documented forgejo remote + forgejo/master upstream into clones that
  predate that contract.
- Fixed a latent TSV bug: empty jq @tsv fields collapse under tab-IFS
  read, shifting later fields left (groups was only safe by being the
  last field) — emit '-' sentinels instead.
- start-claude.sh session freshen is layout-aware (freshens each repo
  under ~/code for workspace users).
- managed claudeMd + AGENTS.md non-admin recipe + multi-tenancy.md
  updated in the same change.

Applied live: ancamilea = workspace (infra at ~/code/infra, her existing
tripit clone hoisted to ~/code/tripit, master upstream switched to
forgejo/master); emo stays single layout, untouched. [ci skip]
This commit is contained in:
Viktor Barzin 2026-06-10 18:05:31 +00:00
parent 3b6a5c6737
commit 2825cb1703
8 changed files with 306 additions and 44 deletions

View file

@ -19,17 +19,25 @@ fi
cd "$HOME/code" 2>/dev/null || cd "$HOME"
# Freshen ~/code at session start so the user begins on current upstream state
# (the hourly t3-provision-users reconcile does the same in the background).
# Fast-forward only, and only when safe (on master + clean tree); hard 15s cap so
# an offline remote never stalls the launch. No-op for repos without remotes.
if [ -d "$HOME/code/.git" ]; then
GIT_TERMINAL_PROMPT=0 timeout 15 git -C "$HOME/code" fetch --all --prune --quiet 2>/dev/null || true
if [ "$(git -C "$HOME/code" symbolic-ref --short -q HEAD)" = master ] \
&& [ -z "$(git -C "$HOME/code" status --porcelain 2>/dev/null)" ] \
&& git -C "$HOME/code" rev-parse --verify -q 'master@{upstream}' >/dev/null 2>&1; then
git -C "$HOME/code" merge --ff-only 'master@{upstream}' >/dev/null 2>&1 || true
# Freshen the user's clone(s) at session start so they begin on current upstream
# state (the hourly t3-provision-users reconcile does the same in the background).
# Single layout freshens ~/code itself; workspace layout freshens each repo under
# ~/code. Fast-forward only, and only when safe (on master + clean tree); hard
# 10s fetch cap per repo so an offline remote never stalls the launch.
freshen_repo() {
GIT_TERMINAL_PROMPT=0 timeout 10 git -C "$1" fetch --all --prune --quiet 2>/dev/null || true
if [ "$(git -C "$1" symbolic-ref --short -q HEAD)" = master ] \
&& [ -z "$(git -C "$1" status --porcelain 2>/dev/null)" ] \
&& git -C "$1" rev-parse --verify -q 'master@{upstream}' >/dev/null 2>&1; then
git -C "$1" merge --ff-only 'master@{upstream}' >/dev/null 2>&1 || true
fi
}
if [ -d "$HOME/code/.git" ]; then
freshen_repo "$HOME/code"
else
for repo_git in "$HOME"/code/*/.git; do
[ -d "$repo_git" ] && freshen_repo "${repo_git%/.git}"
done
fi
# Prefer the system-wide `claude` (installed by setup-devvm.sh); fall back to npx.