Polyhydra Skills  /  Agent Workflow

project-home

Scaffold or extend a ~/projects/<topic> project-home hub that coordinates multiple ~/code repos around one theme (symlinks + workspace.yaml + worktrees/ + README + LOCAL-WORK.md), matching the existing stepmania/rpgs convention. Use when the user wants to "set up a workspace for X", "make a project hub for these repos", "pull related repos together", or asks to replicate the rpgs/stepmania pattern for another topic.

Agent Workflow

Drop this in — save the block below as ~/.claude/skills/project-home/SKILL.md, or run:

mkdir -p ~/.claude/skills/project-home
cat > ~/.claude/skills/project-home/SKILL.md <<'EOF'
# (paste the full source block below into this file)
EOF

Full source

SKILL.md — copy everything inside
# project-home

Builds the house convention used under `~/projects` for grouping related
`~/code` repos into one coordinated hub — see `~/projects/rpgs` and
`~/projects/stepmania` for reference examples, and
`~/projects/LOCAL-WORK-RUNBOOK.md` for the shared contract every hub links to.

A project-home is **not** a git repo and does not copy or move source code.
It's a symlink tree plus a manifest, sitting alongside the real checkouts in
`~/code`.

## Before scaffolding anything: check for the two-tier reality

`~/projects` currently has two conventions in use — check which one already
fits before picking:

1. **Flat symlink hub** (`batocera`, `gaming`, `spottie`, `socials`) — just
   repo symlinks + `LOCAL-WORK.md`, no `workspace.yaml`/README. Fine for a
   handful of clearly-related repos with no need for role documentation.
2. **Structured project-home** (`stepmania`, `rpgs`) — adds `workspace.yaml`
   (name/kind/purpose/repositories with role+note), a fuller README with
   ownership boundaries, and a `worktrees/` dir for folded-in git worktrees.
   Use this when there are >5 repos, when repos have distinct roles worth
   documenting, or when the user is folding in existing per-repo hubs.

Default to the structured form unless the user asks for something lighter —
it's what was chosen for `rpgs` and is the richer, more maintained pattern.

## Step 1 — find candidate repos, don't just pattern-match names

Grep for name matches, but a name match is not enough — a repo can share a
word with the topic and be unrelated (e.g. `Lonewolf` is a Xamarin scaffold,
not the Lone Wolf gamebooks; `Campaign` at first glance looked like an
unrelated ChannelCheevos repo until its README said "the sibling
`rpg-gm-tools` repo consumes this backend"). For each candidate:

```sh
head -c 300 ~/code/<name>/README.md
```

Read enough to confirm the repo is actually about the topic, not just named
near it. When two repos look like duplicates (same README content, similar
name), check whether they're the same GitHub repo cloned twice, or genuinely
different repos:

```sh
git -C ~/code/<name-a> remote -v
git -C ~/code/<name-b> remote -v
git -C ~/code/<name-a> log -1 --format='%ci %s'
git -C ~/code/<name-b> log -1 --format='%ci %s'
```

Same remote URL → same repo, pick one (prefer the more recently touched /
canonically named clone) and note the excluded one in `workspace.yaml`'s
`excluded:` list with a one-line reason. Different remotes → real duplicates;
ask the user whether to include both or pick one (see Step 5).

**Present the candidate list and ask before finalizing scope** — this is
exactly the kind of judgment call `AskUserQuestion` exists for: which repos
are in, which duplicates to keep, how wide to cast the net (core vs.
core+adjacent vs. everything). Don't silently decide a 20+ repo scope.

## Step 2 — check for existing single-repo hubs covering the same repos

Before creating `repos/<name>` symlinks, check whether `~/projects/<name>`
already exists as its own single-repo hub (a dir with just `main ->
~/code/<name>` + `LOCAL-WORK.md` + maybe `.claude`/`prs`/`.worktrees`):

```sh
ls -la ~/projects/<candidate-repo-name> 2>/dev/null
```

If it exists and has **only** symlinks and stale `.claude/*.lock` files, it's
safe to fold in and remove. If it has real content — active `.worktrees/` or
`prs/*` directories — **do not delete it silently**. Those are live git
worktrees (registered branches, uncommitted state). Confirm with the user
whether to fold them in (see Step 4) or leave the old hub standing.

## Step 3 — scaffold the hub

```sh
mkdir -p ~/projects/<topic>/repos ~/projects/<topic>/worktrees
for name in <repo1> <repo2> ...; do
  ln -s ~/code/"$name" ~/projects/<topic>/repos/"$name"
done
ln -s ../LOCAL-WORK-RUNBOOK.md ~/projects/<topic>/LOCAL-WORK.md
```

Then write `workspace.yaml` (see `~/projects/rpgs/workspace.yaml` for the
full shape: `name`, `kind: project-home`, `purpose`, `repositories` map with
`path`/`role`/optional `note`, `worktrees_dir`, `excluded` list with
reasons) and a `README.md` grouping repos by role with a short table per
group, plus a "what was left out and why" section.

## Step 4 — fold in worktrees, carefully

If migrating an existing per-repo hub's live worktrees, **never `mv` a git
worktree directory** — it breaks the administrative link back to the main
repo's `.git/worktrees/<name>`. Always use:

```sh
git -C <main-repo-checkout> worktree move <old-path> <new-path>
```

Run `git -C <repo> worktree list` first to see every worktree and which repo
it actually belongs to — a worktree living inside `hubA/prs/foo` is not
necessarily a worktree of the repo `hubA` is named after; check the branch
shown against each repo before assuming. Plain (non-git) scaffold
directories — no `.git` file/dir, doesn't appear in `worktree list` — should
just `mv` normally into a `scratch/` dir, not treated as a worktree.

### Optional: sweep for stray worktrees anywhere under ~/code

A repo can accumulate worktrees scattered outside the hub entirely. To find
them all:

```sh
for repo in <repo1> <repo2> ...; do
  git -C ~/code/"$repo" worktree list | tail -n +2
done
```

Triage each hit before touching it:

- **Dead** (directory no longer exists — `git worktree list` still shows the
  path even if it's gone): run `git -C <repo> worktree prune -v` to clean up
  the metadata. Safe, non-destructive to real files.
- **Parked under `~/code/worktrees/` or `~/code/.worktrees/`**: this is a
  shared, established convention used across unrelated repos too — leave it
  alone, don't move into the new hub.
- **Inside the repo's own `.worktrees/` subdir**: already self-contained,
  leave it.
- **In ephemeral/agent-scratch locations** (`/tmp`, `/dev/shm`, `/var/tmp`,
  `~/.cache/codex*`, `~/.cache/hermes`, `~/.cache/deploy`,
  `~/.cache/worktrees`): often tied to active automation expecting a fixed
  path (a deploy script, an agent runner). Do not move these without asking
  — report them to the user instead and let them name any that should move.
- **A genuinely orphaned top-level `~/code/<name>` directory** (not inside
  any of the above conventions): this is the real "stray branch" case —
  `git worktree move` it into the hub's `worktrees/` dir.

Report what you pruned, what you moved, and what you deliberately left in
place (with why) — don't silently mass-migrate ephemeral scratch state.

## Step 5 — duplicates and exclusions go in `workspace.yaml`, not silence

Every repo that looked relevant but got excluded (legacy clone, unrelated
name collision, ambiguous duplicate) belongs in the `excluded:` list with a
one-line reason, so a future pass doesn't re-litigate the same judgment call
from scratch.

## Step 6 — verify before declaring done

```sh
git -C ~/code/<repo> worktree list   # confirm new paths registered correctly
ls ~/projects/<topic>/repos | wc -l  # matches intended repo count
ls ~/projects/<topic>/worktrees      # matches migrated count
```

Then end the turn with a picker of real next steps (open in editor, write
more docs, review scope again, stop here) — every handback here still ends
in a picker; see CLAUDE.md's MAID section for the rule, not restated here.