---
name: incubate
description: Clone or create repos for active development — the right hand of /learn. Use when user says "incubate [repo]", "work on [repo]", "clone for dev", or wants to set up a dev workflow. Use --wt SLUG to give each agent its own git worktree body when several agents develop the same repo in parallel. Do NOT trigger for study/exploration (use /learn), finding projects (use /trace), or session mining (use /dig).
argument-hint: "<repo-url|slug|name> [--wt <slug> [--task \"...\"] [--from <ref>]] [--flash | --contribute | --status | --offload [--wt <slug> | --all-wt]]"
---

# /incubate — Active Development Workflow

Clone or create repos for active development → set up branches, make changes, push PRs.

> "/learn reads the book. /incubate writes the next chapter."

## Usage

```
/incubate [url]                          # Clone via ghq, symlink, ready for dev
/incubate [slug]                         # Use slug from ψ/memory/slugs.yaml
/incubate [repo-name]                    # Finds in ghq or creates with default org
/incubate [url] --flash "fix desc"       # Issue → branch → fix → PR → offload
/incubate [url] --contribute             # Fork if needed → branch per feature → PRs
/incubate --status                       # List all active ψ/incubate/ with git status
/incubate --status --include-offloaded   # Also list offloaded entries from .origins (#280)
/incubate --offload [slug]               # Remove symlink, keep ghq clone
/incubate --offload [slug] --purge       # Also drop entry from .origins manifest (#280)
/incubate --init                         # Restore all origins after git clone

# Parallel bodies — one git worktree per agent, same mother clone (#487)
/incubate [slug] --wt <name>             # Claim a body on branch incubate/<name>
/incubate [slug] --wt <name> --task "…"  # Same, recording who + why (worktree lock reason)
/incubate [slug] --wt <name> --from <ref># Branch from <ref> (default: origin/HEAD)
/incubate --offload [slug] --wt <name>   # Retire ONE body (branch survives)
/incubate --offload [slug] --all-wt      # Retire every body on this machine
```

---

## Workflow Modes

| Flag | Scope | Duration | Cleanup |
|------|-------|----------|---------|
| (default) | Long-term dev | Weeks/months | Manual offload |
| `--wt <slug>` | One agent's parallel body | Hours/days | `--offload --wt <slug>` (branch survives) |
| `--flash` | Single fix | Minutes | Issue → PR → auto-offload + purge |
| `--contribute` | Multi-feature | Days/weeks | Offload when all PRs done |
| `--status` | Query | — | Read-only listing |
| `--offload` | Cleanup | — | Remove symlink (keep ghq) |

```
incubate        → Long-term dev (manual cleanup)
    ↓
--wt <slug>     → N agents, N worktrees, 1 mother clone (parallel bodies)
    ↓
--contribute    → Push → offload (keep ghq)
    ↓
--flash         → Issue → Branch → PR → offload → purge (complete cycle)
```

**Mother and bodies (#487).** One `ghq` clone per repo per machine = one working
tree = one checked-out branch, so N agents on the same repo stomp each other.
`--wt` gives each agent a **body**: a `git worktree` on its own branch
`incubate/<slug>`, sharing the mother's object store. The mother clone is
untouched — on the `--wt` path it is **fetch-only**, never pulled, never stashed.

The claim is atomic for free: `git worktree add -b` creates the branch ref in a
single ref transaction, so of N agents racing for the same slug **exactly one
wins** and the losers touch nothing. No lockfiles, no `flock` (unreliable on NFS,
meaningless across machines), no claim directory.

---

## Directory Structure

```
ψ/incubate/
├── .origins                          # Manifest of incubated MOTHERS (committed)
└── OWNER/
    └── REPO/
        ├── origin                    # Symlink to mother ghq clone (gitignored)
        ├── REPO.md                   # Hub file — tracks incubation sessions (committed)
        └── wt/                       # Parallel bodies (#487)
            └── <slug>/
                └── origin            # Symlink to the worktree (gitignored)
```

Machine-local side (never in ψ, never committed):

```
$(ghq root)/github.com/OWNER/REPO   # mother clone — one per repo per machine
$WT_ROOT/OWNER/REPO/<slug>/         # body — branch incubate/<slug>

WT_ROOT="${INCUBATE_WT_ROOT:-${XDG_STATE_HOME:-$HOME/.local/state}/incubate/worktrees}"
```

**ψ is the committed soul; worktrees are machine-local scratch.** Bodies live
outside ψ **and** outside `$(ghq root)`. ψ gets one gitignored symlink per body
and nothing else.

**Bodies must not live under `$(ghq root)` (#487).** ghq decides "this is a
repository" by `stat`-ing `<dir>/.git`, and a linked worktree's `.git` is a
regular file that passes that test. Measured on ghq 1.10.1, with bodies under
`$(ghq root)/.worktrees/`:

```
$ ghq list
.worktrees/acme/api/fix-auth      ← phantom
github.com/acme/api
```

Every consumer of `ghq list` then sees phantom repos, and `.worktrees` sorts
*before* `github.com`, so any "first match wins" resolver returns the body
instead of the repo. That is a regression in **other** skills caused by this
one, so the body root lives in the XDG state directory. `$INCUBATE_WT_ROOT`
relocates it; any path outside `$(ghq root)` works, including a different
filesystem (E17).

**The body link filename is literally `origin` — this is load-bearing.** It means
the existing `.gitignore` rule `ψ/incubate/**/origin` already covers bodies (no
new rule), `wt/<slug>/` contains nothing git tracks (no `.gitkeep`), and the
`--status`/`--offload` finders keep working unmodified.

**There is no per-body manifest file.** The registry is
`git -C <mother> worktree list --porcelain -z`, which git maintains atomically
and which cannot drift from reality. A committed `.origins.d/<slug>.yaml` was
rejected (#487): committed files mean N agents each `git add`/commit/push into
ψ, converting a benign file race into a git non-fast-forward race one layer up.

Per-body metadata (who, when, why) rides in the **worktree lock reason** —
machine-local at `<mother>/.git/worktrees/<slug>/locked`, surviving `kill -9`,
never committed:

```
incubate|<owner-id>|<iso8601>|<task text>
```

**Read the registry with `--porcelain -z | tr '\0' '\n'`, never plain
`--porcelain` (#487).** git C-quotes the *entire* `locked` line the moment the
reason holds a non-ASCII byte or a `"`. Measured on git 2.50.1 with
`--task "แก้บั๊ก auth"`:

```
locked "incubate|nat@m5|2026-07-27T…|\340\271\201\340\270\201…"   ← --porcelain
locked incubate|nat@m5|2026-07-27T…|แก้บั๊ก auth                   ← --porcelain -z
```

Under the quoted form `awk -F'|'` sees field 1 as `"incubate`, so every
ownership guard silently misses, the body is misreported as another tool's, and
`--offload` refuses it forever. `-z` never quotes (it terminates records with
NUL, so there is nothing to escape) and `tr '\0' '\n'` restores the exact shape
every existing `awk` expects. Requires git ≥ 2.36.

Three reason classes, and only three:

| field 1 | means | do |
|---|---|---|
| `incubate` | ours | parse `who \| when \| task` |
| `initializing` | **git's own** lock, written during `worktree add` and left by a `kill -9` mid-checkout (E11) | report as crashed-mid-create, offer the heal, allow offload once unlocked |
| anything else | another tool's | report as `(locked by another tool)`, never parse, never break |

`initializing` is deliberately **not** in the "another tool" bucket — treating
it as foreign wedges the body, the slug, and the mother's offload permanently.
The full set of registry queries is in
[`references/worktree-recovery.md`](references/worktree-recovery.md).

**Offload source, keep hub:**
```bash
unlink ψ/incubate/OWNER/REPO/origin   # Remove symlink
# ghq clone preserved for future use
# Hub file (REPO.md) remains in ψ/incubate/OWNER/REPO/
```

---

## /incubate --init

Restore all origins after cloning (like `git submodule init`):

```bash
ROOT="$(pwd)"
GHQ_ROOT=$(ghq root)                     # hoisted — ~24ms per call (#487)
WT_ROOT="${INCUBATE_WT_ROOT:-${XDG_STATE_HOME:-$HOME/.local/state}/incubate/worktrees}"

# Read .origins through a de-duplicated private copy, NEVER the shared file
# directly (#487). The guarded append at Step 0 can leave a duplicate line
# (measured: 15/30 trials with 4 concurrent runs) and every reader must absorb
# it. Redirected, not piped — a pipe would run the loop in a subshell.
ORIGINS_SNAP=$(mktemp "${TMPDIR:-/tmp}/incubate-origins.XXXXXX")
sort -u "$ROOT/ψ/incubate/.origins" > "$ORIGINS_SNAP"

while IFS= read -r repo; do
  [ -z "$repo" ] && continue
  OWNER=$(dirname "$repo")
  REPO=$(basename "$repo")
  ghq get -u "https://github.com/$repo"
  MOTHER="$GHQ_ROOT/github.com/$repo"
  mkdir -p "$ROOT/ψ/incubate/$OWNER/$REPO"
  # ln -sfn, NEVER ln -sf: onto an EXISTING symlink-to-directory, `ln -sf`
  # follows the link and creates a stray link INSIDE the old target, leaving
  # the ψ link still pointing at the old path. Measured (#487).
  ln -sfn "$MOTHER" "$ROOT/ψ/incubate/$OWNER/$REPO/origin"
  echo "✓ Restored: $repo"

  # Body relink pass (#487). Re-points ψ at any worktrees this machine already
  # has. No-ops on a vault with zero bodies — `worktree list` reports only the
  # mother — so old vaults behave exactly as before.
  #   -z: mandatory, see "Directory Structure" — plain --porcelain C-quotes.
  #   sub(/^worktree /,"") + $0: `print $2` truncates at the first space, and
  #   `$(ghq root)` on a macOS home directory with a space in its name is
  #   ordinary. Measured: relinking `/…/my ghq root/…` produced `/…/my` (#487).
  git -C "$MOTHER" worktree list --porcelain -z 2>/dev/null | tr '\0' '\n' \
    | awk '/^worktree /{sub(/^worktree /,""); print $0}' | while IFS= read -r wt; do
      [ "$wt" = "$MOTHER" ] && continue
      s=$(basename "$wt")
      # Registered but gone from disk (E9) — relinking would manufacture a
      # DANGLING ψ symlink and report it as a restored body. Report instead.
      [ -d "$wt" ] || { echo "  ⋯ body wt/$s registered but gone from disk — /incubate --status"; continue; }
      mkdir -p "$ROOT/ψ/incubate/$OWNER/$REPO/wt/$s"
      ln -sfn "$wt" "$ROOT/ψ/incubate/$OWNER/$REPO/wt/$s/origin"
      echo "  ↳ relinked body wt/$s"
    done

  # Bodies that exist only on ANOTHER machine — report, never auto-materialize.
  # A vault with 20 recorded bodies would otherwise detonate 20 `worktree add`
  # calls on a fresh clone. The human opts in per body (#487).
  git -C "$MOTHER" ls-remote --heads origin 'incubate/*' 2>/dev/null \
    | sed 's|.*refs/heads/incubate/||' | while IFS= read -r s; do
        [ -z "$s" ] && continue
        [ -d "$WT_ROOT/$OWNER/$REPO/$s" ] \
          || echo "  ⋯ body '$s' lives on another machine — /incubate $OWNER/$REPO --wt $s"
      done
done < "$ORIGINS_SNAP"

rm -f "$ORIGINS_SNAP"
```

`.origins` keeps its exact format — flat, one `OWNER/REPO` per line, committed.
**Bodies are never recorded in it**, because `.origins` answers "which mothers
must `--init` re-clone" and a body's mother is already listed. No migration, no
dual-read, no version marker.

---

## Step 0: Detect Input Type + Resolve Path

**CRITICAL: Capture ABSOLUTE paths first:**
```bash
date "+🕐 %H:%M %Z (%A %d %B %Y)" && ROOT="$(pwd)"
echo "Incubating from: $ROOT"
```

### If URL (http* or owner/repo format)

Clone or create, symlink origin, update manifest:

```bash
# Replace [URL] with actual URL
URL="[URL]"
ROOT="$(pwd)"
OWNER=$(echo "$URL" | sed -E 's|.*github.com/([^/]+)/.*|\1|')
REPO=$(echo "$URL" | sed -E 's|.*/([^/]+)(\.git)?$|\1|')
SLUG="$OWNER/$REPO"

# Auto-stash unstaged changes in source clone before pulling (#279).
# `ghq get -u` runs `git pull` under the hood and aborts on dirty trees,
# stranding the ritual. Detect + stash with a clear log + restore hint.
GHQ_ROOT_PRECHECK=$(ghq root 2>/dev/null)
SOURCE_PRECHECK="$GHQ_ROOT_PRECHECK/github.com/$SLUG"
if [ -d "$SOURCE_PRECHECK/.git" ]; then
  if [ -n "$(git -C "$SOURCE_PRECHECK" status --porcelain 2>/dev/null)" ]; then
    STASH_NAME="pre-incubate-$(date +%Y-%m-%d)"
    echo "⚠️  Source clone has uncommitted changes — auto-stashing as '$STASH_NAME'"
    git -C "$SOURCE_PRECHECK" stash push -u -m "$STASH_NAME"
    echo "    (run \`git -C $SOURCE_PRECHECK stash pop\` to restore)"
  fi
fi

# Check if repo exists on GitHub
if gh repo view "$SLUG" --json name &>/dev/null; then
  ghq get -u "https://github.com/$SLUG"
else
  echo "Repo not found — creating private repo..."
  NEW_REPO_CREATED=1   # our own fresh repo — commit-ignore the breadcrumb (Step 0.5)
  gh repo create "$SLUG" --private --clone=false
  ghq get "https://github.com/$SLUG"
  GHQ_ROOT=$(ghq root)
  LOCAL="$GHQ_ROOT/github.com/$SLUG"
  # Seed a .gitignore that ignores the incubation breadcrumb from the very first
  # commit, so the rule travels with every clone (a local .git/info/exclude does
  # not). Safe here — WE just created this private repo; the #447 "don't edit a
  # foreign repo's committed .gitignore" rule applies only to CLONES.
  grep -qxF '.claude/INCUBATED_BY' "$LOCAL/.gitignore" 2>/dev/null \
    || echo '.claude/INCUBATED_BY' >> "$LOCAL/.gitignore"
  [ -f "$LOCAL/README.md" ] || echo "# $REPO" > "$LOCAL/README.md"
  git -C "$LOCAL" add README.md .gitignore
  git -C "$LOCAL" diff --cached --quiet || git -C "$LOCAL" commit -m "Initial commit"
  git -C "$LOCAL" push origin main 2>/dev/null || git -C "$LOCAL" push origin master
fi

GHQ_ROOT=$(ghq root)
mkdir -p "$ROOT/ψ/incubate/$OWNER/$REPO"
# ln -sfn, NEVER ln -sf (#487) — see the comment in --init above.
ln -sfn "$GHQ_ROOT/github.com/$OWNER/$REPO" "$ROOT/ψ/incubate/$OWNER/$REPO/origin"

# Auto-add gitignore pattern if missing (#250)
GITIGNORE="$ROOT/.gitignore"
if [ -f "$GITIGNORE" ]; then
  if ! grep -q 'ψ/incubate/\*\*/origin' "$GITIGNORE" 2>/dev/null; then
    echo 'ψ/incubate/**/origin' >> "$GITIGNORE"
    echo "✓ Added ψ/incubate/**/origin to .gitignore"
  fi
else
  # Also check ψ/.gitignore as fallback
  PSI_GITIGNORE="$ROOT/ψ/.gitignore"
  if [ -f "$PSI_GITIGNORE" ] && ! grep -q 'incubate/\*\*/origin' "$PSI_GITIGNORE" 2>/dev/null; then
    echo 'incubate/**/origin' >> "$PSI_GITIGNORE"
    echo "✓ Added incubate/**/origin to ψ/.gitignore"
  fi
fi

# Update manifest — guarded O_APPEND, never read-modify-write (#487).
# The `sort -u -o F F` that lived here re-read and rewrote the whole file:
# measured 8 concurrent writers x 10 trials -> 8/10 trials LOST entries (worst
# kept 5 of 8), and 5 trials emitted `sort: No such file or directory` because
# the file transiently does not exist — so a concurrent --init or --status read
# an EMPTY manifest. A guarded append measured 0/10 lost. Residual TOCTOU can
# only ever produce a DUPLICATE line, never a missing one, and every reader
# absorbs duplicates via `sort -u`. .origins is no longer kept sorted; nothing
# ever consumed its sortedness.
ORIGINS="$ROOT/ψ/incubate/.origins"
mkdir -p "$ROOT/ψ/incubate"
grep -qxF "$OWNER/$REPO" "$ORIGINS" 2>/dev/null || printf '%s\n' "$OWNER/$REPO" >> "$ORIGINS"

echo "✓ Ready: $ROOT/ψ/incubate/$OWNER/$REPO/origin → source"
```

### Step 0.5: Drop INCUBATED_BY Breadcrumb (#226, #228)

After clone/symlink, write `.claude/INCUBATED_BY` in the **target repo** (not the oracle repo):

```bash
TARGET_REPO="$GHQ_ROOT/github.com/$OWNER/$REPO"
mkdir -p "$TARGET_REPO/.claude"

# Check if this repo was previously /learn'd
LEARNED_FROM=""
if [ -d "$ROOT/ψ/learn/$OWNER/$REPO" ]; then
  LEARNED_FROM="learned-from: ψ/learn/$OWNER/$REPO/"
fi

cat > "$TARGET_REPO/.claude/INCUBATED_BY" << BREADCRUMB
oracle: $(basename "$ROOT")
oracle-repo: $(git -C "$ROOT" remote get-url origin 2>/dev/null || echo "local")
date: $(date +%Y-%m-%d)
mode: ${MODE:-default}
source: https://github.com/$OWNER/$REPO
${LEARNED_FROM}
BREADCRUMB

echo "✓ Breadcrumb dropped: $TARGET_REPO/.claude/INCUBATED_BY"

# Keep the breadcrumb OUT of the target repo's tracked history. Pick the mechanism
# by ownership:
# - NEW repo we just created ($NEW_REPO_CREATED): commit-ignore it in .gitignore so
#   the rule travels with clones. The initial commit above already staged it.
# - CLONE we don't own (may be public/foreign): use .git/info/exclude — local-only,
#   never committed, so we never edit someone else's committed .gitignore (#447).
if [ -d "$TARGET_REPO/.git" ]; then
  if [ "${NEW_REPO_CREATED:-0}" = "1" ]; then
    grep -qxF '.claude/INCUBATED_BY' "$TARGET_REPO/.gitignore" 2>/dev/null \
      || echo '.claude/INCUBATED_BY' >> "$TARGET_REPO/.gitignore"
    echo "✓ Ignored in git: .claude/INCUBATED_BY (committed .gitignore — new repo)"
  else
    grep -qxF '.claude/INCUBATED_BY' "$TARGET_REPO/.git/info/exclude" 2>/dev/null \
      || echo '.claude/INCUBATED_BY' >> "$TARGET_REPO/.git/info/exclude"
    echo "✓ Excluded from git: .claude/INCUBATED_BY (local .git/info/exclude)"
  fi

  # Either way: if a PRIOR incubation already committed the breadcrumb, an
  # exclude/ignore rule won't hide an already-tracked file. Auto-untrack it
  # (index-only; the file stays on disk) so "must be ignored" actually holds.
  if git -C "$TARGET_REPO" ls-files --error-unmatch .claude/INCUBATED_BY >/dev/null 2>&1; then
    git -C "$TARGET_REPO" rm --cached --quiet .claude/INCUBATED_BY
    echo "✓ Untracked previously-committed .claude/INCUBATED_BY (git rm --cached)"
  fi
fi
```

The breadcrumb enables:
- **Orphan detection**: Any Claude session can check who tracks this repo
- **Provenance chain**: `learned-from` links /learn → /incubate (#232)
- **/recap awareness**: /recap shows a warning when INCUBATED_BY exists (#229)

The breadcrumb stays out of the target repo's tracked history. Step 0.5 picks the
mechanism by ownership: a **committed `.gitignore` rule** for a repo we just created
(so the rule travels with every clone), or a machine-local **`.git/info/exclude`** for
a clone we don't own (#447 — never edit a foreign/public repo's committed .gitignore).
Either way it auto-untracks the file if a prior incubation already committed it
(`git rm --cached`, index-only — the breadcrumb stays on disk). Nothing manual is
left for you to remember.

### Step 0.6: Share the vault — symlink target ψ → parent oracle vault

An incubated repo that writes its own memory (retros, learnings, traces) into a
standalone `ψ/` strands that brain — the parent oracle never sees it. Point the
target's `ψ` at the parent vault so incubated work lands in one shared brain.

```bash
# Only if the parent actually has a vault to share
if [ -e "$ROOT/ψ" ]; then
  ln -sfn "$ROOT/ψ" "$TARGET_REPO/ψ"   # -n: don't descend into an existing symlink
  echo "✓ Vault shared: $TARGET_REPO/ψ → $ROOT/ψ"

  # Keep it OUT of the target's git history via LOCAL exclude (never the
  # committed .gitignore — target may be public; #447 rule).
  # GOTCHA (neo, 2026-08-16): a bare symlink `ψ` is NOT matched by `ψ/` — the
  # trailing slash only matches a directory. Exclude BOTH forms or the symlink
  # shows up as untracked.
  if [ -d "$TARGET_REPO/.git" ]; then
    for pat in 'ψ' 'ψ/'; do
      grep -qxF "$pat" "$TARGET_REPO/.git/info/exclude" 2>/dev/null \
        || echo "$pat" >> "$TARGET_REPO/.git/info/exclude"
    done
    echo "✓ Excluded from git: ψ and ψ/ (local .git/info/exclude)"
  fi
fi
```

**Consequence — the vault is now GLOBAL, not per-repo.** Once the target's `ψ`
symlinks into the parent, `incubate/`, `learn/`, and `memory/` are the same
directory on disk across every repo that shares that vault. `/incubate` or
`/learn` in one shows up for all of them. If the parent vault itself is a
symlink into a **private** companion repo (e.g. `neo-oracle/ψ → neo-oracle-vault`),
that's deliberate: it keeps memory out of open-source-bound repos while still
sharing one brain. Don't assume an incubated repo's memory is private to it.

### If just a name (no slash, no URL)

Try ghq first, then create with default org:

```bash
NAME="[NAME]"
ROOT="$(pwd)"
DEFAULT_ORG="laris-co"  # Configurable via --org flag

# Anchor to github.com/ (#487) — ghq enumerates dot-directories and non-GitHub
# hosts alike, so an unanchored match can select a backup tree, a gitlab clone,
# or any stray directory holding a `.git`. Measured on one machine: 54 dot-dir
# entries, 156 non-github.com. (Bodies deliberately live outside $(ghq root)
# entirely, so they never appear here — see "Directory Structure".)
MATCH=$(ghq list | grep '^github\.com/' | grep -i "/$NAME$" | head -1)
if [ -n "$MATCH" ]; then
  OWNER=$(echo "$MATCH" | cut -d'/' -f2)
  REPO=$(echo "$MATCH" | cut -d'/' -f3)
else
  OWNER="$DEFAULT_ORG"
  REPO="$NAME"
fi
# Then proceed with URL flow using OWNER/REPO
```

### Verify

```bash
ls -la "$ROOT/ψ/incubate/$OWNER/$REPO/"
```

---

## Step 1: Detect Workflow Mode

Check arguments for workflow flags:

| Argument | Mode | Action |
|----------|------|--------|
| (none) | Default | Clone + symlink + show status |
| `--wt <slug>` | Body | Claim `incubate/<slug>` + add worktree + symlink (**skip clone — the mother must already exist**) (#487) |
| `--flash` | Flash | Issue → branch → fix → PR → offload |
| `--contribute` | Contribute | Fork if needed → multi-feature PRs |
| `--status` | Status | List all incubations + bodies (skip clone) |
| `--offload` | Offload | Remove symlink (skip clone); `--wt`/`--all-wt` retire bodies |

> **Steps 0 and 0.5 do not run for `--wt`, `--status` or `--offload`.** Read the
> mode first, then jump straight to that mode's section. Running Step 0 on the
> `--wt` path would `git stash push -u` a *peer's* uncommitted work in the mother
> and then `ghq get -u` (a `git pull`) underneath them — the exact operation the
> `--wt` section forbids, printing the restore hint into the wrong transcript
> (#487). The `[ -d "$MOTHER/.git" ]` check at Step W2 is the entry gate.

`--wt` **always takes a value**. Bare `--wt` on create is an error — list the
existing slugs instead of guessing one. The branch is **always** `incubate/<slug>`,
never configurable.

**Calculate ACTUAL paths (replace variables with real values):**
```
REPO_DIR   = [ROOT]/ψ/incubate/[OWNER]/[REPO]/
SOURCE_DIR = [ROOT]/ψ/incubate/[OWNER]/[REPO]/origin/          ← symlink to mother
WORK_DIR   = [GHQ_ROOT]/github.com/[OWNER]/[REPO]/             ← mother working dir
BODY_LINK  = [ROOT]/ψ/incubate/[OWNER]/[REPO]/wt/[SLUG]/origin ← symlink to body
BODY_DIR   = [WT_ROOT]/[OWNER]/[REPO]/[SLUG]/                  ← agent working dir

WT_ROOT    = ${INCUBATE_WT_ROOT:-${XDG_STATE_HOME:-$HOME/.local/state}/incubate/worktrees}
```

⚠️ With `--wt`, **BODY_DIR is your working directory**, not WORK_DIR. The mother
is fetch-only.

⚠️ IMPORTANT: Always use literal paths. Never pass shell variables to subagents.

---

## Mode: Default (long-term dev)

After Step 0 (clone + symlink), the repo is ready for development.

**Verify working state:**
```bash
WORK_DIR="$ROOT/ψ/incubate/$OWNER/$REPO/origin"
echo "Branch: $(git -C "$WORK_DIR" branch --show-current)"
echo "Status: $(git -C "$WORK_DIR" status --short | wc -l) changed files"
echo "Remote: $(git -C "$WORK_DIR" remote get-url origin)"
echo "Last commit: $(git -C "$WORK_DIR" log --oneline -1)"
```

**Skip to Step 2** (create/update hub file).

---

## Mode: --wt (parallel bodies, one per agent) — #487

Use when **more than one agent develops the same repo at once**. Each agent gets
its own worktree on its own branch; they share the mother's object store and
never touch each other's files.

**Step 0 and Step 0.5 do NOT run on this path.** No clone, no `ghq get -u`, no
auto-stash — the mother must already exist, and the `[ -d "$MOTHER/.git" ]`
check at the top of W2 is the entry gate. Run plain `/incubate OWNER/REPO`
first if it does not.

### Step W1: Validate the slug

Two stages, both needed. `git check-ref-format` **accepts** `-lead` (which would
be read as an option by the next command) and `a/b` (which would nest inside
`wt/`), so the `case` guard is load-bearing — it runs first.

```bash
SLUG="[SLUG]"
case "$SLUG" in
  ''|-*|*/*|*[!a-zA-Z0-9._-]*)
    echo "✗ invalid slug '$SLUG' — [a-zA-Z0-9._-] only, no slashes, no leading dash"
    exit 2 ;;
esac
git check-ref-format --branch "incubate/$SLUG" >/dev/null 2>&1 \
  || { echo "✗ invalid slug '$SLUG'"; exit 2; }
```

### Step W2: Claim the body — and own it in the same command

The **only** atomic primitive is `git worktree add`. Branch creation is a ref
transaction (`O_EXCL` + `rename(2)` inside `.git`), so 8 concurrent adds of the
same slug yield exactly one `rc=0` and seven loud fatals — measured. `git worktree
add` also creates missing nested parent directories itself, so there is no `mkdir`
to race on either.

The claim and the ownership record must be **one** command, not two. With
`add -b` followed by a separate `worktree lock`, a body is observable *unowned*
for the ~10–20 ms between them, and a peer running `--offload --all-wt` in that
window finds an unlocked clean worktree, passes both ownership guards, and
retires it — measured, `✓ wt/<slug> retired` while the claiming agent was still
running. `git worktree add --lock --reason … -b …` closes the window; verified
on git 2.50.1.

```bash
ROOT="$(pwd)"
GHQ_ROOT=$(ghq root)
WT_ROOT="${INCUBATE_WT_ROOT:-${XDG_STATE_HOME:-$HOME/.local/state}/incubate/worktrees}"
MOTHER="$GHQ_ROOT/github.com/$OWNER/$REPO"
[ -d "$MOTHER/.git" ] || { echo "✗ no mother clone — run: /incubate $OWNER/$REPO"; exit 1; }

# -z is MANDATORY, never plain --porcelain (#487): git C-quotes the WHOLE
# `locked` line once the reason holds a non-ASCII byte or a `"`, and every
# `awk -F'|'` guard below then silently misses. See "Directory Structure".
wtlist() { git -C "$MOTHER" worktree list --porcelain -z 2>/dev/null | tr '\0' '\n'; }

# FETCH-ONLY mother (#487). NEVER `ghq get -u` on this path: it runs `git pull`,
# which is exactly why the auto-stash at Step 0 exists (#279) — and with N agents
# that stash silently pockets a PEER's uncommitted work, printing the restore
# hint into THIS agent's transcript where the peer will never see it.
# `git fetch` has no working tree to disturb. Measured: 8 concurrent fetches, 8x rc=0.
git -C "$MOTHER" fetch --prune origin >/dev/null 2>&1 || true

# Base the body on the remote default head — never on the mother's current
# checkout, or body #2 silently inherits body #1's work.
# FAIL CLOSED (#487): `symbolic-ref` exits 128 with EMPTY stdout when
# refs/remotes/origin/HEAD is absent, which is the case for every repo Step 0
# creates itself (`gh repo create` + `ghq get` of an empty repo never sets it,
# and neither does `fetch --prune`). The old `${FROM:+…}` then expanded to
# nothing and `worktree add -b` silently based the body on the mother's HEAD —
# measured: a body born on top of an unrelated `--flash` branch, no warning.
if [ -z "$FROM" ]; then
  FROM=$(git -C "$MOTHER" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null)
fi
if [ -z "$FROM" ]; then
  git -C "$MOTHER" remote set-head origin -a >/dev/null 2>&1
  FROM=$(git -C "$MOTHER" symbolic-ref --short refs/remotes/origin/HEAD 2>/dev/null)
fi
if [ -z "$FROM" ]; then
  if git -C "$MOTHER" rev-parse --verify -q HEAD >/dev/null 2>&1; then
    echo "✗ cannot determine origin/HEAD on $OWNER/$REPO — refusing to guess"
    echo "  basing on the mother's checkout would inherit whatever it has checked out"
    echo "  → name the base: /incubate $OWNER/$REPO --wt $SLUG --from main"
    exit 1
  fi
  echo "ⓘ mother has no commits yet (E8) — body starts from its unborn HEAD"
fi

WT="$WT_ROOT/$OWNER/$REPO/$SLUG"
BODY="$ROOT/ψ/incubate/$OWNER/$REPO/wt/$SLUG"
ERR=$(mktemp "${TMPDIR:-/tmp}/incubate.XXXXXX")

# The ownership record is computed BEFORE the claim so `add --lock --reason`
# can write both at once. `|` is the delimiter and `"` is what makes git
# C-quote a reason, so neither survives the free-text field. Non-ASCII DOES
# survive — Thai tasks are first class, and `-z` reads them back exactly.
TASK=$(printf '%s' "${TASK:-(unstated)}" | tr '|\n"' '/  ')
OWNER_ID="${INCUBATE_AGENT:-$(id -un)@$(hostname -s)}"
REASON_NEW="incubate|$OWNER_ID|$(date -u +%Y-%m-%dT%H:%M:%SZ)|$TASK"

if wtlist | grep -qxF "worktree $WT" && [ -d "$WT" ]; then
  # Already mine (E4). Re-running the identical command is NOT an error.
  ACTION="↩ attached"
  rm -f "$ERR"
elif wtlist | grep -qxF "worktree $WT"; then
  # Registered but the directory is GONE (E9) — the 🧟 state. Testing only the
  # registry here used to fall through to W3, which symlinked ψ at a path that
  # does not exist and printed `cd <nothing>` as success (#487).
  echo "✗ wt/$SLUG is registered but its directory is gone: $WT"
  echo "  heal: git -C $MOTHER worktree unlock $WT; git -C $MOTHER worktree prune"
  echo "  then re-run: /incubate $OWNER/$REPO --wt $SLUG"
  echo "  (branch incubate/$SLUG and every commit on it are untouched)"
  rm -f "$ERR"; exit 1
elif [ -e "$WT" ] && [ -n "$(ls -A "$WT" 2>/dev/null)" ]; then
  # E5/E10: something occupies the path and git does not own it. This guard MUST
  # run before `add -b`: git creates the branch ref BEFORE it validates the path,
  # so a failed `-b` LEAVES `incubate/<slug>` behind and wedges the slug forever
  # — measured on git 2.50.1, `rc=128` yet `branch --list` shows it (#487).
  # An EMPTY directory is harmless crash residue (E6) and falls through.
  echo "✗ $WT already exists and git does not own it — nothing was touched"
  echo "  inspect it first (E10): ls -la $WT"
  rm -f "$ERR"; exit 1
elif git -C "$MOTHER" worktree add --lock --reason "$REASON_NEW" \
       -b "incubate/$SLUG" "$WT" ${FROM:+"$FROM"} >/dev/null 2>"$ERR"; then
  ACTION="🌱 created"
  rm -f "$ERR"
elif grep -q "already exists\|cannot lock ref\|already used by worktree" "$ERR"; then
  # Lost the race, or the branch outlived a previous body.
  # sub(/^worktree /,"")+$0, never $2 — `$2` truncates a path at its first space.
  HOLDER=$(wtlist | awk -v b="branch refs/heads/incubate/$SLUG" \
    '/^worktree /{sub(/^worktree /,""); p=$0; next} $0==b{print p; exit}')
  if [ -n "$HOLDER" ]; then
    # `f=0` on every `worktree` line and `exit` at the record boundary: without
    # them a holder carrying no `locked` line leaks the NEXT body's reason, and
    # the collision message names the wrong human and the wrong task (#487).
    REASON=$(wtlist | awk -v b="branch refs/heads/incubate/$SLUG" \
      '/^worktree /{f=0} $0==b{f=1} f&&/^locked /{sub(/^locked /,"");print;exit} f&&/^$/{exit}')
    WHO=$(printf '%s' "$REASON" | awk -F'|' '$1=="incubate"{print $2" · "$3}')
    WHY=$(printf '%s' "$REASON" | awk -F'|' '$1=="incubate"{print $4}')
    if [ "$REASON" = "initializing" ]; then
      WHO="(git's own lock — crashed mid-create, E11)"
      WHY="git -C $MOTHER worktree unlock $HOLDER, inspect, then retry"
    elif [ -n "$REASON" ] && [ -z "$WHO" ]; then
      WHO="(locked by another tool)"; WHY="—"
    fi
    echo "✗ Slug '$SLUG' is already claimed on $OWNER/$REPO"
    echo "    by:   ${WHO:-(unlocked)}"
    echo "    task: ${WHY:-(unstated)}"
    echo "    tree: $HOLDER"
    echo "  → join it:      cd $HOLDER"
    echo "  → or branch it: /incubate $OWNER/$REPO --wt ${SLUG}-2 --task \"...\""
    rm -f "$ERR"; exit 1
  fi
  # Branch exists but no worktree holds it — a previously offloaded body. Resume
  # it. Capture THIS attempt's stderr (2>"$ERR", never 2>&1): reporting the
  # stale `-b` error told operators the BRANCH was the blocker when the real
  # cause was the directory, and deleting the branch did not help (#487).
  if ! git -C "$MOTHER" worktree add --lock --reason "$REASON_NEW" \
         "$WT" "incubate/$SLUG" >/dev/null 2>"$ERR"; then
    cat "$ERR"; rm -f "$ERR"; exit 1
  fi
  ACTION="♻ revived"
  rm -f "$ERR"
else
  cat "$ERR"; rm -f "$ERR"; exit 1
fi
```

A slug collision is the **normal** outcome of N agents racing — it is a routing
decision, not a crash. That is why the message names the holder, the task, the
tree, and the two commands that resolve it.

### Step W3: Reconcile ownership, then link ψ

The lock reason is machine-local, it survives `kill -9`, it blocks an accidental
`worktree remove`, and it does **not** block commits inside the body. A lock
protects only its own entry — pruning unrelated worktrees still works (measured).

W2 already wrote it on the create and revive paths. W3 only handles the
`↩ attached` case, where a claim already exists and must **never** be
overwritten: re-attaching to your own body keeps the ORIGINAL owner and
timestamp, and a lock written by another tool is left completely alone.

```bash
wtlist() { git -C "$MOTHER" worktree list --porcelain -z 2>/dev/null | tr '\0' '\n'; }

EXISTING=$(wtlist | awk -v w="worktree $WT" \
  '$0==w{f=1;next} f&&/^locked /{sub(/^locked /,"");print;exit} f&&/^$/{exit}')

if [ -z "$EXISTING" ]; then
  # Unowned — a crash between an old `add` and its `lock`, or a peer's unlock.
  # Adopt it; do not leave a body nobody owns.
  git -C "$MOTHER" worktree lock --reason "$REASON_NEW" "$WT" 2>/dev/null
elif [ "$EXISTING" = "initializing" ]; then
  # git's OWN lock, left by a kill -9 mid-checkout (E11) — not a foreign tool.
  echo "⚠ wt/$SLUG was created but never finished (git's 'initializing' lock, E11)"
  echo "  the tree is half-populated. Heal, then re-run:"
  echo "    git -C $WT status"
  echo "    git -C $WT checkout -- ."
  echo "    git -C $MOTHER worktree unlock $WT"
  exit 1
else
  TASK=$(printf '%s' "$EXISTING" | awk -F'|' '$1=="incubate"{print $4}')
  TASK="${TASK:-(locked by another tool)}"
fi

mkdir -p "$BODY"
ln -sfn "$WT" "$BODY/origin"      # -sfn, NEVER -sf (see Anti-Patterns)

echo "${ACTION:-↩ attached} body wt/$SLUG → $WT"
echo "  branch: incubate/$SLUG · task: $TASK"
echo "  cd $WT"
```

`.origins` is **not** touched, and **Step 2 does not run for `--wt`** — the
mother is already recorded, the body's record *is* its lock reason, and no new
committed file is created anywhere. That is the whole point: N agents never
contend on the vault's git index.

### Step W4: The one discipline

The mother's `.git` is genuinely shared. Concurrent `fetch` is safe (measured), but
`git gc`, `git worktree prune`, and `git branch -D` reach **every** sibling body.

> **Your worktree, your branch. No git operation outside it.**

Cross-machine slugs are **not** coordinated: two agents on two machines can each
create `incubate/fix-auth` from different bases and only discover it at push time.
The namespace is per-mother, per-machine. Known limitation (#487).

---

## Mode: --flash (single-fix cycle)

Complete contribution cycle: Issue → Branch → Fix → PR → Offload.

### Step F1: Create Issue (document intent)
```bash
WORK_DIR="$ROOT/ψ/incubate/$OWNER/$REPO/origin"
# Compose issue title and description from user's intent
ISSUE_URL=$(gh issue create --repo "$OWNER/$REPO" --title "[TITLE]" --body "[DESCRIPTION]")
ISSUE_NUM=$(echo "$ISSUE_URL" | grep -oP '\d+$')
echo "Created: #$ISSUE_NUM"
```

### Step F2: Create Branch
```bash
BRANCH="issue-${ISSUE_NUM}-[short-description]"
git -C "$WORK_DIR" checkout -b "$BRANCH"
echo "Branch: $BRANCH"
```

### Step F3: Make Changes
Let the user describe what to fix. Make changes, then:
```bash
git -C "$WORK_DIR" add -A
git -C "$WORK_DIR" commit -m "[commit message]

Closes #$ISSUE_NUM"
git -C "$WORK_DIR" push -u origin "$BRANCH"
```

### Step F4: Create PR
```bash
PR_URL=$(gh pr create --repo "$OWNER/$REPO" \
  --title "[PR title]" \
  --body "$(cat <<'EOF'
## Summary
[what was fixed]

Closes #$ISSUE_NUM

---
**From**: [Oracle Name]
Rule 6: "Oracle Never Pretends to Be Human"
Written by an Oracle — AI speaking as itself.
EOF
)" --head "$BRANCH")
PR_NUM=$(echo "$PR_URL" | grep -oP '\d+$')
echo "PR: #$PR_NUM (closes #$ISSUE_NUM)"
```

### Step F5: Auto-offload + purge
```bash
cd "$ROOT"
MOTHER="$(ghq root)/github.com/$OWNER/$REPO"
wtlist() { git -C "$MOTHER" worktree list --porcelain -z 2>/dev/null | tr '\0' '\n'; }

# REFUSE to purge the mother while bodies live (#487). Every body's `.git` is a
# pointer file into the mother's admin dir, and the body's COMMITS live in the
# mother's object store — so deleting the mother destroys every unpushed commit
# on every `incubate/*` branch. Measured: after `rm -rf $MOTHER` and a fresh
# re-clone, `git cat-file -t <body-commit>` → `could not get object info`, and
# `branch --list 'incubate/*'` is empty. **This guard is the only protection
# there is.** Restoring the mother does NOT heal the bodies; only PUSHED
# branches survive it.
LIVE=$(wtlist | grep -c '^worktree ')
if [ "${LIVE:-0}" -gt 1 ]; then
  echo "✗ Refusing to purge $OWNER/$REPO — $((LIVE - 1)) body/bodies still live:"
  wtlist | awk '/^worktree /{sub(/^worktree /,""); print "    "$0}' | tail -n +2
  echo "  → retire them first: /incubate --offload $OWNER/$REPO --all-wt"
  exit 1
fi

# Unpushed work on a body branch dies with the mother even when no worktree
# holds it any more. Never purge over the top of it.
UNPUSHED=$(git -C "$MOTHER" log --oneline --branches='incubate/*' --not --remotes 2>/dev/null | head -5)
if [ -n "$UNPUSHED" ]; then
  echo "✗ Refusing to purge $OWNER/$REPO — unpushed commits on incubate/* branches:"
  printf '    %s\n' "$UNPUSHED"
  echo "  → push them first, or accept the loss explicitly by deleting $MOTHER by hand"
  exit 1
fi

unlink "$ROOT/ψ/incubate/$OWNER/$REPO/origin"
rmdir "$ROOT/ψ/incubate/$OWNER" 2>/dev/null

# Rename FIRST, then delete (#487). `rm -rf` on a large clone takes seconds, and
# a peer's `--wt` only checks `[ -d "$MOTHER/.git" ]` — anywhere in that window
# it passes, then creates a branch ref and a partial checkout inside a tree being
# deleted. `mv` is atomic and fails every peer's check instantly. Re-check the
# body count against the renamed path before the point of no return.
PURGING="$MOTHER.purging.$$"
mv "$MOTHER" "$PURGING"
LIVE=$(git -C "$PURGING" worktree list --porcelain -z 2>/dev/null | tr '\0' '\n' | grep -c '^worktree ')
if [ "${LIVE:-0}" -gt 1 ]; then
  mv "$PURGING" "$MOTHER"
  echo "✗ A body was claimed while purging — mother restored, nothing deleted"
  exit 1
fi
rm -rf "$PURGING"

# --flash owns the whole cycle, so it purges the manifest entry too. Same
# compare-and-swap as `--offload --purge`; see that section for why.
ORIGINS="$ROOT/ψ/incubate/.origins"
if [ -f "$ORIGINS" ]; then
  I=0
  while [ "$I" -lt 10 ]; do
    I=$((I + 1)); SIZE=$(wc -c < "$ORIGINS" | tr -d ' ')
    TMPFILE=$(mktemp "$ROOT/ψ/incubate/.origins.XXXXXX")
    grep -vxF "$OWNER/$REPO" "$ORIGINS" > "$TMPFILE" || true
    chmod 644 "$TMPFILE"
    if [ "$(wc -c < "$ORIGINS" | tr -d ' ')" = "$SIZE" ]; then
      mv "$TMPFILE" "$ORIGINS"; break
    fi
    rm -f "$TMPFILE"
  done
fi

echo "✓ Issue #$ISSUE_NUM → PR #$PR_NUM → Offloaded & Purged"
```

**Update hub file before offload** (Step 2), then offload.

---

## Mode: --contribute (multi-feature contribution)

For extended contribution over days/weeks. Forks if needed.

### Step C1: Fork if not your repo
```bash
WORK_DIR="$ROOT/ψ/incubate/$OWNER/$REPO/origin"
ME=$(gh api user --jq '.login')
if ! gh repo view "$OWNER/$REPO" --json viewerPermission --jq '.viewerPermission' | grep -qE 'ADMIN|MAINTAIN|WRITE'; then
  echo "No push access — forking..."
  gh repo fork "$OWNER/$REPO" --clone=false
  git -C "$WORK_DIR" remote add fork "https://github.com/$ME/$REPO.git"
  echo "Fork remote added. Push to 'fork' instead of 'origin'."
fi
```

### Step C2: Create feature branch
```bash
BRANCH="feat/[feature-name]"
git -C "$WORK_DIR" checkout -b "$BRANCH"
```

### Step C3: Work cycle (repeat per feature)
```bash
# ... make changes ...
git -C "$WORK_DIR" add -A
git -C "$WORK_DIR" commit -m "[commit message]"
REMOTE=$(git -C "$WORK_DIR" remote | grep fork || echo origin)
git -C "$WORK_DIR" push -u "$REMOTE" "$BRANCH"
gh pr create --repo "$OWNER/$REPO" \
  --title "[PR title]" \
  --body "[description]" \
  --head "$ME:$BRANCH"
```

### Step C4: Offload when all PRs done
```bash
unlink "$ROOT/ψ/incubate/$OWNER/$REPO/origin"
rmdir "$ROOT/ψ/incubate/$OWNER" 2>/dev/null
echo "✓ Offloaded (ghq kept for PR feedback)"
```

---

## Mode: --status (list incubations)

No clone needed. Lists every mother, every body under it, and everything that
needs attention. This is the only window a human has at 3am, so it names the
branch, the dirt, the owner, the task, and the absolute path.

Add `--include-offloaded` to also list entries in `.origins` whose symlinks
have been removed (#280) — surfaces the historical record without losing it.

State is carried by **glyph shape, never colour**: `●` dirty, `○` clean,
`⚠` needs attention, `🧟` registered-but-gone.

```bash
ROOT="$(pwd)"
INCLUDE_OFFLOADED="${1:-}"   # pass "--include-offloaded" to enable
GHQ_ROOT=$(ghq root 2>/dev/null)   # hoisted out of every loop — ~24ms per call (#487)
WT_ROOT="${INCUBATE_WT_ROOT:-${XDG_STATE_HOME:-$HOME/.local/state}/incubate/worktrees}"

# -z is MANDATORY, never plain --porcelain (#487) — see "Directory Structure".
wtlist() { git -C "$1" worktree list --porcelain -z 2>/dev/null | tr '\0' '\n'; }

echo "🌱 Incubations"

# Stock /bin/bash on macOS is 3.2.57 and has NO associative arrays: `declare -A`
# there makes `${ACTIVE[acme/api]}` an ARITHMETIC subscript and aborts with
# `division by 0 (error token is "b")`, rc=1 — `--status --include-offloaded`
# was hard-broken on stock bash and only appeared to work under Homebrew bash.
# Sets now live in temp files queried with `grep -qxF` (#487).
LINKS=$(mktemp "${TMPDIR:-/tmp}/incubate-links.XXXXXX")
ACTIVE=$(mktemp "${TMPDIR:-/tmp}/incubate-active.XXXXXX")
PORC=$(mktemp "${TMPDIR:-/tmp}/incubate-porc.XXXXXX")

# `find | sort`, never `for x in $(find)` — that word-splits paths on spaces.
# Sorting full paths also makes every mother precede its own bodies, always:
# all strings sharing a prefix form one contiguous block under lexical sort.
find "$ROOT/ψ/incubate" -name origin -type l -print 2>/dev/null | sort > "$LINKS"

# ISO8601 → "2h ago"; BSD `date -j -f` first, then GNU `date -d`, else raw stamp.
incubate_age() {
  [ -n "$1" ] || return 0
  _e=$(date -j -u -f '%Y-%m-%dT%H:%M:%SZ' "$1" +%s 2>/dev/null \
       || date -u -d "$1" +%s 2>/dev/null) || { printf '%s' "$1"; return 0; }
  _d=$(( $(date -u +%s) - _e ))
  [ "$_d" -lt 0 ] && _d=0        # clocks drift across a fleet; never print "-7h ago"
  if   [ "$_d" -lt 3600 ];  then printf '%sm ago' "$((_d/60))"
  elif [ "$_d" -lt 86400 ]; then printf '%sh ago' "$((_d/3600))"
  else                           printf '%sd ago' "$((_d/86400))"; fi
}

REPOS=0; BODIES=0; LIVE=0; DIRTY=0; LASTBODY=""; PORC_FOR=""

# Redirected, NOT piped — a piped `while` runs in a subshell and loses counters.
while IFS= read -r link; do
  DIR=$(dirname "$link")
  SLUG=${DIR#"$ROOT/ψ/incubate/"}     # pure shell — a `sed` here would treat
  TARGET=$(readlink "$link")          # regex metachars in $ROOT as syntax

  case "$SLUG" in
    */wt/*)
      # ---- body ----
      NAME=${SLUG##*/wt/}
      PARENT=${SLUG%%/wt/*}
      # Derive the parent from the body's OWN path (#487). The loop used to
      # carry $PORC forward from whichever mother rendered last, so a body whose
      # mother has no ψ link was drawn as a child of an unrelated repo with its
      # owner and task blanked — measured: acme/api's orphan body rendered under
      # acme/api-gateway. Sorted full paths put a mother immediately before its
      # own bodies, but only when the mother's link exists.
      if [ "$PARENT" != "$PORC_FOR" ]; then
        if [ -L "$ROOT/ψ/incubate/$PARENT/origin" ]; then
          wtlist "$ROOT/ψ/incubate/$PARENT/origin" > "$PORC"
          PORC_FOR="$PARENT"
        else
          BODIES=$((BODIES + 1))
          echo "  ⚠  orphan body wt/$NAME — mother $PARENT is not incubated"
          echo "      $TARGET"
          echo "      heal: /incubate $PARENT   (or: /incubate --offload $PARENT --all-wt)"
          continue
        fi
      fi
      BODIES=$((BODIES + 1))
      if [ "$link" = "$LASTBODY" ]; then TEE="└─"; CONT="  "; else TEE="├─"; CONT="│ "; fi
      if [ -d "$TARGET" ]; then
        LIVE=$((LIVE + 1))
        BRANCH=$(git -C "$TARGET" branch --show-current 2>/dev/null)
        N=$(git -C "$TARGET" status --short 2>/dev/null | wc -l | tr -d ' ')
        if [ "$N" -gt 0 ]; then STATE="● dirty $N"; DIRTY=$((DIRTY + 1)); else STATE="○ clean"; fi
      else
        BRANCH="?"; STATE="⚠ missing"
      fi
      # Owner + task come from the worktree lock reason cached in $PORC.
      REASON=$(awk -v w="worktree $TARGET" '$0==w{f=1;next} f&&/^locked /{sub(/^locked /,"");print;exit} f&&/^$/{exit}' "$PORC")
      WHO=$(printf  '%s' "$REASON" | awk -F'|' '$1=="incubate"{print $2}')
      WHEN=$(printf '%s' "$REASON" | awk -F'|' '$1=="incubate"{print $3}')
      TASK=$(printf '%s' "$REASON" | awk -F'|' '$1=="incubate"{print $4}')
      if [ "$REASON" = "initializing" ]; then
        # git's OWN lock (E11), not a foreign tool's — name it as such.
        WHO="⚠ crashed mid-create — git's own lock (E11)"; TASK="(unfinished)"
      elif [ -n "$REASON" ] && [ -z "$WHO" ]; then
        WHO="(locked by another tool)"
      fi
      printf '  %s %-16s %-11s %-22s %s\n' "$TEE" "wt/$NAME" "$STATE" "${BRANCH:-?}" "\"${TASK:-(unstated)}\""
      printf '  %s %-16s %s · %s · %s\n' "$CONT" "" "${WHO:-(unlocked)}" "$(incubate_age "$WHEN")" "$TARGET"
      ;;
    *)
      # ---- mother ----
      REPOS=$((REPOS + 1))
      printf '\n  %-34s origin ' "$SLUG"
      if [ -d "$TARGET" ]; then
        BRANCH=$(git -C "$TARGET" branch --show-current 2>/dev/null)
        N=$(git -C "$TARGET" status --short 2>/dev/null | wc -l | tr -d ' ')
        if [ "$N" -gt 0 ]; then STATE="● dirty $N"; DIRTY=$((DIRTY + 1)); else STATE="○ clean"; fi
        echo "✓ ${BRANCH:-(detached)} · $STATE · $TARGET"
        wtlist "$TARGET" > "$PORC"
      else
        echo "✗ broken → $TARGET"
        : > "$PORC"
      fi
      PORC_FOR="$SLUG"
      printf '%s\n' "$SLUG" >> "$ACTIVE"
      # index()==1 is a literal prefix test — `grep "^$DIR/wt/"` would read any
      # regex metachar in the path as syntax.
      LASTBODY=$(awk -v p="$DIR/wt/" 'index($0,p)==1' "$LINKS" | tail -1)
      ;;
  esac
done < "$LINKS"

echo ""

# Orphans — REPORT ONLY, never removed (Rule 6: Nothing is Deleted).
while IFS= read -r rslug; do
  M="$GHQ_ROOT/github.com/$rslug"
  [ -d "$M/.git" ] || continue
  wtlist "$M" > "$PORC"

  # Registered but gone from disk. Do NOT grep for `prunable` alone: a LOCKED
  # worktree is never labelled prunable and `git worktree prune` skips it — and
  # /incubate locks every body, so that is the COMMON case, not the rare one
  # (measured, #487). Test the directory instead, and emit the heal command that
  # actually works for each case.
  # sub(/^worktree /,"")+$0, never $2 — `$2` truncates a path at its first space,
  # and then EVERY live body reads as gone-from-disk (measured on a ghq root
  # containing a space, #487).
  awk '/^worktree /{sub(/^worktree /,""); p=$0; k=0; next}
       /^locked/{k=1; next}
       /^$/{if(p!=""){print (k?"L":"U")"\t"p; p=""}; next}
       END{if(p!="")print (k?"L":"U")"\t"p}' "$PORC" \
  | while IFS="$(printf '\t')" read -r flag wtpath; do
      [ "$wtpath" = "$M" ] && continue
      [ -d "$wtpath" ] && continue
      if [ "$flag" = "L" ]; then
        echo "  🧟 $wtpath (registered, gone from disk, locked)"
        echo "      heal: git -C $M worktree unlock $wtpath && git -C $M worktree prune"
      else
        echo "  🧟 $wtpath (registered, gone from disk)"
        echo "      heal: git -C $M worktree prune"
      fi
    done

  # Registered, on disk, but with NO ψ symlink — a crash between `worktree add`
  # and `ln -sfn`. Invisible to the render loop above (which is driven by ψ
  # links) and to both other orphan classes, yet it still blocks the mother
  # offload, so --status used to contradict --offload about how many bodies
  # exist (#487).
  awk '/^worktree /{sub(/^worktree /,""); print $0}' "$PORC" | tail -n +2 \
  | while IFS= read -r wtpath; do
      [ -d "$wtpath" ] || continue
      [ -L "$ROOT/ψ/incubate/$rslug/wt/$(basename "$wtpath")/origin" ] && continue
      echo "  ⚠  $wtpath (registered, no ψ link)"
      echo "      heal: /incubate $rslug --wt $(basename "$wtpath")"
    done

  # The invisible orphan: admin dir deleted, body kept. `worktree list` cannot
  # see it at all, so it is found by set-difference against what IS listed.
  for d in "$WT_ROOT/$rslug"/*; do
    [ -d "$d" ] || continue
    grep -qxF "worktree $d" "$PORC" || echo "  ⚠  $d (on disk, unknown to git) — report only"
  done
done < "$ACTIVE"

# Repos and bodies are counted SEPARATELY — the old single counter printed
# "Total: 3" for 1 repo + 2 bodies (#487).
echo ""
echo "  $REPOS repo · $BODIES bodies ($LIVE live) · $DIRTY dirty"

# --include-offloaded: also list .origins entries with no live symlink (#280)
if [ "$INCLUDE_OFFLOADED" = "--include-offloaded" ] && [ -f "$ROOT/ψ/incubate/.origins" ]; then
  echo ""
  echo "📦 Offloaded (in .origins, no live symlink)"
  echo ""
  OFFLOADED_COUNT=0
  # De-dup through a private snapshot, never read the shared file raw (#487).
  # The guarded append can leave a duplicate line (measured: 15/30 trials, 4
  # concurrent runs) and this loop used to list the repo twice and print
  # `Offloaded: 3` for two repos — disagreeing with the counter below, which
  # already did `sort -u`, in the same command. A `sort -u | while` PIPE would
  # put OFFLOADED_COUNT in a subshell and lose it, hence the temp file.
  OSNAP=$(mktemp "${TMPDIR:-/tmp}/incubate-origins.XXXXXX")
  sort -u "$ROOT/ψ/incubate/.origins" > "$OSNAP"
  while IFS= read -r slug; do
    [ -z "$slug" ] && continue
    if ! grep -qxF "$slug" "$ACTIVE"; then
      if [ -d "$GHQ_ROOT/github.com/$slug" ]; then
        echo "  $slug (ghq preserved at $GHQ_ROOT/github.com/$slug)"
      else
        echo "  $slug (ghq absent — fully purged)"
      fi
      OFFLOADED_COUNT=$((OFFLOADED_COUNT + 1))
    fi
  done < "$OSNAP"
  rm -f "$OSNAP"
  echo ""
  echo "  Offloaded: $OFFLOADED_COUNT"
elif [ -f "$ROOT/ψ/incubate/.origins" ]; then
  # `sort -u` absorbs the duplicate lines a guarded append can produce (#487).
  TOTAL_RECORDED=$(sort -u "$ROOT/ψ/incubate/.origins" | grep -cv '^$')
  OFFLOADED_HIDDEN=$((TOTAL_RECORDED - REPOS))
  if [ "$OFFLOADED_HIDDEN" -gt 0 ]; then
    echo "  ($OFFLOADED_HIDDEN offloaded — use --include-offloaded to view)"
  fi
fi

rm -f "$LINKS" "$ACTIVE" "$PORC"
```

Crash states, heal recipes, and the full error catalogue live in
[`references/worktree-recovery.md`](references/worktree-recovery.md).

**Done.** No hub file update needed.

---

## Mode: --offload (cleanup)

Remove symlink, keep ghq clone and hub file.

| Invocation | Retires |
|---|---|
| `--offload <repo>` | the mother symlink — **refuses while bodies live** |
| `--offload <repo> --wt <slug>` | one body; **the branch always survives** |
| `--offload <repo> --all-wt` | every body on this machine, one outcome line each |
| `… --purge` | additionally drops `.origins` entry / deletes a merged branch |

Add `--purge` to also remove the entry from `.origins` manifest (#280) —
useful when you want the offloaded slug to NOT count toward the total any more.

```bash
ROOT="$(pwd)"
GHQ_ROOT=$(ghq root)
WT_ROOT="${INCUBATE_WT_ROOT:-${XDG_STATE_HOME:-$HOME/.local/state}/incubate/worktrees}"
SLUG="[OWNER/REPO or REPO]"
PURGE=""   # "--purge" — also drop from .origins / delete a merged branch
WT=""      # bare slug for ONE body (e.g. "fix-auth"), or the literal "--all-wt".
           # NOT "--wt fix-auth" — see the dispatch guard below.

# ---- Resolve the repo EXACTLY (#487) ----
# The old `grep -i "$SLUG" | head -1` picked by readdir order: with acme/api and
# acme/api-gateway both present, `--offload api` selected acme/api-gateway — the
# WRONG repo, which becomes destructive once bodies exist. Exact match wins,
# suffix match is the fallback, ties are reported instead of guessed. `/wt/` is
# excluded so a body can never be mistaken for a mother.
# NO `xargs -I` (#487): BSD xargs — macOS, the primary machine — aborts with
# `command line cannot be assembled, too long` on any input line ≥ 255 bytes
# (measured: 254 ok, 255 rc=1), which a deep ghq root reaches easily. GNU xargs
# has no such limit, so this failed only on macOS. Same `while read` shape the
# --status block already uses.
CANDS=$(find "$ROOT/ψ/incubate" -name origin -type l -print 2>/dev/null \
        | while IFS= read -r l; do d=$(dirname "$l"); printf '%s\n' "${d#"$ROOT/ψ/incubate/"}"; done \
        | grep -v '/wt/' | sort)
# Exact first, then a LITERAL suffix match. Not `grep -xE ".*/$SLUG"`: `.` is a
# regex wildcard there too, so `--offload api.js` could select `acme/apiXjs`.
SEL=$(printf '%s\n' "$CANDS" | grep -xF "$SLUG")
[ -z "$SEL" ] && SEL=$(printf '%s\n' "$CANDS" \
  | awk -v s="/$SLUG" 'length($0)>length(s) && substr($0,length($0)-length(s)+1)==s')
if [ -z "$SEL" ]; then
  echo "✗ Not found: $SLUG"
  # QUOTED — unquoted $CANDS word-splits any path containing a space.
  echo "Active incubations:"; printf '  %s\n' "$CANDS"
  exit 1
fi
if [ "$(printf '%s\n' "$SEL" | grep -c .)" -gt 1 ]; then
  echo "✗ Ambiguous '$SLUG' — name the owner too:"; printf '  %s\n' "$SEL"
  exit 2
fi
OWNER=$(dirname "$SEL"); REPO=$(basename "$SEL")
MOTHER="$GHQ_ROOT/github.com/$SEL"

# -z is MANDATORY, never plain --porcelain (#487) — see "Directory Structure".
# Without it a Thai or quoted --task makes every guard below misread its own
# lock as a foreign tool's and refuse the body forever.
wtlist() { git -C "$MOTHER" worktree list --porcelain -z 2>/dev/null | tr '\0' '\n'; }

# ---- Retire ONE body. Returns non-zero when it refuses; never destroys. ----
offload_body() {
  _s="$1"
  _wt="$WT_ROOT/$OWNER/$REPO/$_s"
  _link="$ROOT/ψ/incubate/$OWNER/$REPO/wt/$_s"
  if [ ! -d "$_wt" ]; then
    echo "  ⓘ wt/$_s — no directory on this machine"
    if wtlist | grep -qxF "worktree $_wt"; then
      echo "      still registered — heal: git -C $MOTHER worktree unlock $_wt; git -C $MOTHER worktree prune"
      return 1
    fi
    return 0
  fi
  if ! wtlist | grep -qxF "worktree $_wt"; then
    echo "  ⚠  wt/$_s — on disk but unknown to git; report only, not touched"; return 1
  fi
  _r=$(wtlist | awk -v w="worktree $_wt" \
       '$0==w{f=1;next} f&&/^locked /{sub(/^locked /,"");print;exit} f&&/^$/{exit}')
  _by=$(printf '%s' "$_r" | awk -F'|' '$1=="incubate"{print $2}')
  _me="${INCUBATE_AGENT:-$(id -un)@$(hostname -s)}"
  if [ -n "$_by" ] && [ "$_by" != "$_me" ]; then
    echo "  ✗ wt/$_s — held by $_by, refusing (their work, their call)"; return 1
  fi
  if [ "$_r" = "initializing" ]; then
    # git's OWN lock, written during `worktree add` and left by a kill -9
    # mid-checkout (E11). Classifying it as "another tool" wedged the body, the
    # slug AND the mother offload permanently, with no --force to escape (#487).
    echo "  ⚠ wt/$_s — crashed mid-create (git's own 'initializing' lock, E11)"
    echo "      git -C $_wt status                 # see what landed"
    echo "      git -C $MOTHER worktree unlock $_wt"
    echo "      then re-run this offload"
    return 1
  fi
  if [ -n "$_r" ] && [ -z "$_by" ]; then
    echo "  ✗ wt/$_s — locked by another tool, refusing"; return 1
  fi
  # Probe for dirt BEFORE unlocking (#487). `unlock` then a refused `remove`
  # left the body in place with its owner/timestamp/task erased — the only
  # ownership metadata the design has — so `--status` showed `(unlocked)`, a
  # peer could re-claim it, and the very next `--all-wt` would delete work that
  # had just been reported as "skipped, nothing was destroyed".
  if [ -n "$(git -C "$_wt" status --porcelain 2>/dev/null)" ]; then
    echo "  ✗ wt/$_s — uncommitted work, NOT removed (still locked). Save it first:"
    echo "      cd $_wt && git add -A && git commit -m 'wip' && git push -u origin incubate/$_s"
    return 1
  fi
  git -C "$MOTHER" worktree unlock "$_wt" 2>/dev/null || true
  # NO --force. EVER. Plain `remove` fails closed on modified AND untracked-only
  # trees and leaves the body intact — the one place Rule 6 matters most (#487).
  if ! git -C "$MOTHER" worktree remove "$_wt" 2>/dev/null; then
    # Lost a race with the body's own agent, or a submodule/permission problem.
    # Restore the lock we took off: a refusal must leave NOTHING changed.
    [ -n "$_r" ] && git -C "$MOTHER" worktree lock --reason "$_r" "$_wt" 2>/dev/null
    echo "  ✗ wt/$_s — could not be removed, NOT touched. Save any work first:"
    echo "      cd $_wt && git add -A && git commit -m 'wip' && git push -u origin incubate/$_s"
    return 1
  fi
  unlink "$_link/origin" 2>/dev/null
  rmdir "$_link" 2>/dev/null
  rmdir "$ROOT/ψ/incubate/$OWNER/$REPO/wt" 2>/dev/null
  echo "  ✓ wt/$_s retired — branch incubate/$_s survives"
  if [ "$PURGE" = "--purge" ]; then
    # -d only, NEVER -D: -d refuses to delete an unmerged branch.
    if git -C "$MOTHER" branch -d "incubate/$_s" 2>/dev/null; then
      echo "    ✂ branch incubate/$_s deleted"
    else
      echo "    ⓘ branch incubate/$_s kept (unmerged — branch -d refuses, and we never -D)"
    fi
  fi
  return 0
}

# ---- Dispatch ----
if [ "$WT" = "--all-wt" ]; then
  # Collect outcomes, never abort the batch: with N agents, some bodies
  # legitimately belong to live peers. Exit 1 if anything was skipped.
  #
  # The slug list is the UNION of the git registry and the disk (#487). Driving
  # it from the disk glob alone made a registered-but-gone body invisible here
  # while the mother guard below — which counts the REGISTRY — still saw it, so
  # the refusal pointed at the command that had just silently done nothing:
  # `--all-wt` rc=0 printing only its header, then `--offload` rc=1 forever.
  # offload_body already handles both halves correctly once it is reached.
  echo "🧹 Retiring all bodies of $SEL"
  RC=0
  SLUGS=$(mktemp "${TMPDIR:-/tmp}/incubate-slugs.XXXXXX")
  { wtlist | awk '/^worktree /{sub(/^worktree /,""); print $0}' | tail -n +2
    for d in "$WT_ROOT/$OWNER/$REPO"/*; do [ -e "$d" ] && printf '%s\n' "$d"; done
  } | while IFS= read -r p; do [ -n "$p" ] && basename "$p"; done | sort -u > "$SLUGS"
  while IFS= read -r s; do
    [ -z "$s" ] && continue
    offload_body "$s" || RC=1
  done < "$SLUGS"
  rm -f "$SLUGS"
  [ "$RC" -eq 0 ] || echo "  ⚠ some bodies were skipped — nothing was destroyed"
  exit $RC
elif [ -n "$WT" ]; then
  # A flag-shaped value means the caller filled $WT the way the old comment read
  # ("--wt fix-auth"). That used to fall through to a bare-slug lookup, print
  # `ⓘ wt/--wt fix-auth — no directory` and exit 0 — the one silent no-op in the
  # whole offload path (#487).
  case "$WT" in -*) echo "✗ WT must be a bare slug (e.g. fix-auth), not a flag"; exit 2 ;; esac
  offload_body "$WT"; exit $?
fi

# ---- Mother offload: refuse while bodies live (#487) ----
LIVE=$(wtlist | grep -c '^worktree ')
if [ "${LIVE:-0}" -gt 1 ]; then
  echo "✗ $SEL still has $((LIVE - 1)) live body/bodies:"
  wtlist | awk '/^worktree /{sub(/^worktree /,""); print "    "$0}' | tail -n +2
  echo "  → retire them first: /incubate --offload $SEL --all-wt"
  echo "    (it retires what it can and names the exact heal for anything it will not touch)"
  exit 1
fi

unlink "$ROOT/ψ/incubate/$SEL/origin"
rmdir "$ROOT/ψ/incubate/$OWNER" 2>/dev/null
echo "✓ Offloaded: $SEL"
echo "  Hub file remains: $ROOT/ψ/incubate/$SEL/$REPO.md"
echo "  ghq clone preserved for future use"

# --purge: remove from .origins manifest (#280)
if [ "$PURGE" = "--purge" ] && [ -f "$ROOT/ψ/incubate/.origins" ]; then
  # RACE A, second half (#487). Step 0's append is guarded and safe, but THIS is
  # still a read-modify-write of the same shared committed file, and `mv` only
  # makes the swap atomic for READERS — it does nothing about lost UPDATES.
  # Measured 20/20 trials: one purge against 8 concurrent appends, survivors
  # ranged 0/8 to 6/8. A repo whose clone, symlink and hub file all exist
  # vanished from the manifest, so --init never restored it again.
  #
  # Compare-and-swap: if a byte landed while we were rewriting, throw the
  # rewrite away and start over; after 10 attempts give up rather than lose an
  # entry. This narrows the window from the whole rewrite to a single syscall
  # — it does not erase it, which is one more reason Principle 1 prefers
  # --include-offloaded over --purge.
  #
  # mktemp in the DESTINATION directory, not $TMPDIR: $TMPDIR is often a
  # different filesystem, where `mv` is copy+unlink rather than an atomic rename,
  # and mktemp's 0600 mode would then land on a tracked file.
  ORIGINS="$ROOT/ψ/incubate/.origins"
  PURGED=""; I=0
  while [ "$I" -lt 10 ]; do
    I=$((I + 1))
    SIZE=$(wc -c < "$ORIGINS" | tr -d ' ')
    TMPFILE=$(mktemp "$ROOT/ψ/incubate/.origins.XXXXXX")
    # -xF, NEVER "^${SEL}$": `.` is a regex wildcard, so purging `acme/api.js`
    # would also delete `acme/apiXjs`.
    grep -vxF "$SEL" "$ORIGINS" > "$TMPFILE" || true
    chmod 644 "$TMPFILE"
    if [ "$(wc -c < "$ORIGINS" | tr -d ' ')" = "$SIZE" ]; then
      mv "$TMPFILE" "$ORIGINS"; PURGED=1; break
    fi
    rm -f "$TMPFILE"
  done
  if [ -n "$PURGED" ]; then
    echo "  ✂ Purged '$SEL' from .origins manifest"
    echo "  ⚠ This breadcrumb is gone — re-incubate to restore it (Principle 1: prefer --include-offloaded over --purge)"
  else
    echo "  ⓘ .origins is being appended to concurrently — purge skipped, nothing lost"
    echo "    (the symlink is already gone; re-run --offload $SEL --purge when the fleet is idle)"
  fi
fi
```

`--all-wt` is **not atomic** and cannot be without a journal — but every step is
idempotent, so re-running after a partial failure is safe. Every refusal path
also restores whatever it touched, so a re-run starts from the same state.

---

## Step 2: Create/Update Hub File (REPO.md)

Runs for **default**, `--flash` and `--contribute` only. It does **not** run for
`--status`, `--offload`, or `--wt`.

`--wt` is excluded deliberately (#487): `REPO.md` is a single **committed** file
that every body would append to, so N agents claiming bodies in the same minute
each commit and push the vault, two of three pushes are rejected
non-fast-forward, and the rebase collides in one hunk and drops a session
record. That is the ψ-git contention the `--wt` design exists to eliminate —
worse than the `.origins.d/<slug>.yaml` this file already rejects, because
`.origins.d` was at least one file per body. **A body's record is its worktree
lock reason, not a hub-file entry.**

For the modes that do run it: create or update the hub file:

```markdown
# [REPO] Incubation Log

## Source
- **Origin**: ./origin/
- **GitHub**: https://github.com/OWNER/REPO

## Sessions

### [TODAY] — [mode]
- **Branch**: [branch-name]
- **Status**: active | offloaded | flash-completed
- **Changes**: [summary of what was done]
- **PRs**: #N, #M (if any)
```

Append new sessions. Never overwrite existing entries (Nothing is Deleted).

---

## Output Summary

### Default mode

announce-mode → bash substitutes $ROOT/$OWNER/$REPO/$WORK_DIR to absolute paths;
never print "Location: ψ/..." literally. See CONVENTIONS.md.

```bash
echo "🌱 Incubating: $REPO"
echo ""
echo "  Mode:     default (long-term dev)"
echo "  Location: $ROOT/ψ/incubate/$OWNER/$REPO/"
echo "  Working:  $WORK_DIR"
echo "  Branch:   $(git -C "$WORK_DIR" branch --show-current)"
echo "  Remote:   $(git -C "$WORK_DIR" remote get-url origin)"
echo "  Status:   $(git -C "$WORK_DIR" status --short | wc -l) changed files"
echo ""
echo "  Next: make changes, commit, push, create PR"
echo "  Done: /incubate --offload $OWNER/$REPO"
```

### --wt mode

announce-mode → bash substitutes; the body path is always absolute.

```bash
echo "🌱 Body claimed: $OWNER/$REPO wt/$SLUG"
echo ""
echo "  Branch:  incubate/$SLUG"
echo "  Working: $WT"
echo "  Task:    $TASK"
echo "  Owner:   ${OWNER_ID:-${INCUBATE_AGENT:-$(id -un)@$(hostname -s)}}"
echo "  Mother:  $MOTHER (fetch-only — never pull it from here)"
echo ""
echo "  Next: cd $WT — your worktree, your branch, no git op outside it"
echo "  Done: /incubate --offload $OWNER/$REPO --wt $SLUG"
```

### --flash mode
```
⚡ Flash Complete: [REPO]

  Issue:  #N created
  Branch: issue-N-description
  PR:     #M (closes #N)
  Result: Offloaded & Purged

  ✓ Issue #N → PR #M → Done
```

### --contribute mode

announce-mode → bash substitutes; never print "Location: ψ/..." literally.

```bash
echo "🤝 Contributing: $REPO"
echo ""
echo "  Location: $ROOT/ψ/incubate/$OWNER/$REPO/"
echo "  Fork:     ${FORK_URL:-[none]}"
echo "  Branches: ${BRANCHES:-[list]}"
echo "  PRs:      ${PRS:-[list]}"
echo ""
echo "  Next: continue working, or /incubate --offload when done"
```

### --status mode

The actual emission lives in the bash block under `Mode: --status` above — every
path is resolved via `readlink`, so all of them are absolute (CONVENTIONS.md).
Ordering is stable (repo, then slug, lexical) so two runs diff cleanly.
Example rendering:

```
🌱 Incubations

  acme/hermes-gateway                origin ✓ main · ○ clean · /opt/Code/github.com/acme/hermes-gateway
  ├─ wt/fix-auth      ● dirty 3 incubate/fix-auth      "restore session cookie on 401 retry"
  │                   nat@m5 · 2h ago · ~/.local/state/incubate/worktrees/acme/hermes-gateway/fix-auth
  └─ wt/rate-limit    ○ clean   incubate/rate-limit    "add token bucket to /v1/send"
                      nat@m5 · 20m ago · ~/.local/state/incubate/worktrees/acme/hermes-gateway/rate-limit

  🧟 …/worktrees/acme/hermes-gateway/old   (registered, gone from disk) — heal: git worktree prune
  ⚠  …/worktrees/acme/hermes-gateway/half  (registered, no ψ link) — heal: /incubate acme/hermes-gateway --wt half
  ⚠  …/worktrees/acme/hermes-gateway/lost  (on disk, unknown to git) — report only

  1 repo · 2 bodies (2 live) · 1 dirty
```

Four states are reported and **none** is auto-removed (Rule 6): live bodies in
the tree, `🧟` registered-but-gone, `⚠` registered-with-no-ψ-link, and `⚠` on
disk but unknown to git. A body that is registered but has no ψ symlink is the
crash residue of dying between `worktree add` and `ln -sfn`; it must be listed
because it still blocks the mother offload, and `--status` used to be the one
command that could not see it (#487).

A mother with no bodies prints one line and nothing else. Repos and bodies are
counted separately — the old single counter reported `Total: 3` for 1 repo with
2 bodies (#487).

---

## .gitignore Pattern

The pattern is auto-added to `.gitignore` on first `/incubate` run (#250). If you need to add it manually:

```gitignore
# Ignore origin symlinks only (source lives in ghq)
# Note: no trailing slash — origin is a symlink, not a directory
ψ/incubate/**/origin
```

**Bodies need no new rule (#487).** Because every body link is also named
`origin`, `ψ/incubate/OWNER/REPO/wt/<slug>/origin` is already matched by the
`**` in the existing pattern — confirmed with `git check-ignore -v`. Naming the
link after the slug instead would leave it **untracked and unignored**, which is
exactly why the filename is fixed. Do not add a `wt/` rule.

Since the only thing inside `wt/<slug>/` is that ignored symlink, git never sees
the directory at all — no `.gitkeep`, nothing committed, nothing to conflict on
when N agents work at once.

---

## Trace Connection

After incubation work, log to Oracle so it's discoverable via `/trace`:

### Save the lesson (two-layer pattern)

1. Write to `ψ/memory/learnings/YYYY-MM-DD_incubate-<slug>.md` with frontmatter:
   ```yaml
   ---
   pattern: "Incubated [REPO]: [what was done — PR#, branch, outcome]"
   date: <today>
   source: incubate: OWNER/REPO
   concepts: ["incubate", "development", <relevant-tags>]
   ---

   # Incubated [REPO]
   <body: what was done, PR#, branch, outcome>
   ```

2. The Oracle's auto-memory layer picks up new files in `ψ/memory/learnings/` automatically — no separate API call needed.

This connects `/incubate` to the shared knowledge layer.

---

## Anti-Patterns

| Wrong | Right |
|-------|-------|
| `git clone` directly to ψ/ | `ghq get` then symlink |
| Flat: `ψ/incubate/repo-name` | Org structure: `ψ/incubate/owner/repo` |
| Copy files | Symlink always |
| Manual clone outside ghq | Everything through ghq |
| Delete ghq clone after work | Offload symlink only (Nothing is Deleted) |
| Second clone of the same repo for a second agent | One mother, N worktrees (`--wt`) |
| `ln -sf` onto an existing link | `ln -sfn` — `-sf` leaves the old target and drops a stray link *inside* it |
| `sort -u -o F F` on a shared file | Guarded append — read-modify-write loses concurrent updates |
| Rewriting `.origins` in place (`grep -v … \| mv`) | Compare-and-swap on its byte size, and give up rather than lose a peer's append |
| `grep -v "^$SEL$"` to drop a manifest line | `grep -vxF "$SEL"` — `.` is a regex wildcard, so `acme/api.js` also matches `acme/apiXjs` |
| `mkdir`/`flock`/`.lock` dirs to claim a slug | `git worktree add -b` — the ref transaction *is* the claim |
| `worktree add -b` then a separate `worktree lock` | `worktree add --lock --reason …` — two commands leave the body observable *unowned* |
| `worktree add -b` onto an existing path | Guard with `[ -e ]` first — git creates the branch *before* validating the path and leaks it on failure |
| `git worktree list --porcelain` for parsing | `--porcelain -z \| tr '\0' '\n'` — plain porcelain C-quotes any non-ASCII or quoted lock reason |
| `awk '/^worktree /{print $2}'` | `sub(/^worktree /,""); print $0` — `$2` truncates at the first space |
| Treating `locked initializing` as a foreign tool | It is **git's own** lock (E11) — report the crash, offer the unlock |
| `worktree unlock` before checking for dirt | Probe `status --porcelain` first — a refused offload must not drop the ownership record |
| `git worktree remove --force` | Plain `remove` — it fails closed on dirty *and* untracked-only trees |
| `git branch -D incubate/<slug>` | `-d` only, and only under `--purge` |
| `ghq get -u` on the mother while bodies exist | `git fetch --prune` — pulling stashes a peer's uncommitted work |
| `rm -rf` the mother while bodies live | Refuse, then `mv` aside before deleting — bodies' commits live in the mother's object store and do **not** come back |
| Bodies under `$(ghq root)` | Outside it — ghq reads a worktree's `.git` *file* as a repo and lists phantoms |
| `xargs -I{}` over a list of paths | `while IFS= read -r` — BSD xargs dies on any line ≥ 255 bytes |
| `git gc` / `worktree prune` / `branch -D` from inside a body | Your worktree, your branch — no git op outside it |
| Committing a per-body manifest — or a `REPO.md` session block — per body | `git worktree list --porcelain -z` + the lock reason (no ψ git race) |
| `declare -A` in a skill's bash | Sorted temp file + `grep -qxF` — stock macOS bash is 3.2 |

---

## Notes

- Default: clone + symlink, ready for long-term development
- `--wt <slug>`: one `git worktree` body per agent on the shared mother clone (#487)
- `--flash`: complete cycle (issue → branch → PR → offload + purge) for quick fixes
- `--contribute`: fork-aware multi-feature workflow for external repos
- `--status`: query all active incubations and their bodies without cloning
- `--offload`: remove symlink, keep ghq and hub file; `--wt`/`--all-wt` retire bodies
- Auto-creates private repos when target doesn't exist on GitHub
- `origin/` symlink structure allows easy offload without losing ghq clone
- `.origins` manifest enables `--init` restore after fresh git clone
- Mirror of `/learn`: learn = LEFT hand (study), incubate = RIGHT hand (work)

### Multi-agent notes (#487)

- **Plain `git worktree` + POSIX shell only.** No maw, no fleet, no extra
  toolchain — `--wt` works on a fresh machine with git and ghq alone.
  Needs git ≥ 2.36 for `worktree list --porcelain -z`.
- **Nothing new is committed.** Bodies leave no trace in ψ except a gitignored
  symlink — no `.origins` line, no `REPO.md` session block (Step 2 does not run
  for `--wt`) — so N agents never contend on the vault's git index.
- **`.origins` format is unchanged.** A vault written by the previous `/incubate`
  reads identically here, and vice versa. No migration, mandatory or otherwise.
- **Never typing `--wt` gives the old behaviour**, minus four measured bugs
  (lost `.origins` entries, `ln -sf` mis-linking, the bash-3.2 `--status` abort,
  and `--offload` selecting the wrong repo).
- **Bodies live outside `$(ghq root)`** — `$INCUBATE_WT_ROOT`, defaulting to
  `${XDG_STATE_HOME:-$HOME/.local/state}/incubate/worktrees` — because ghq reads
  a linked worktree's `.git` file as a repository and would list every body as a
  phantom entry to every other skill on the shelf.
- **Known limitation:** slugs are not coordinated across machines. Two agents on
  two machines can both create `incubate/fix-auth`; the conflict only surfaces at
  push time.
- **Known limitation:** `--purge` on `.origins` is compare-and-swap, not atomic.
  It gives up rather than lose a concurrent append, so the worst case is a
  breadcrumb that stays instead of a breadcrumb that vanishes.
- **A body's unpushed commits live in the mother's object store**, not in the
  body directory. Deleting the mother destroys them and no re-clone brings them
  back — which is why `--flash --purge` refuses over live bodies *and* over
  unpushed `incubate/*` commits. Push early.
- **`INCUBATE_AGENT`** overrides the owner id. Without it the fallback is
  `$(id -un)@$(hostname -s)`, which is identical for N agents on one host — so
  "is this my body?" degrades to "is this my host?".
- Crash states and heal recipes: [`references/worktree-recovery.md`](references/worktree-recovery.md).

---

ARGUMENTS: $ARGUMENTS
