Clone or create repos for active development — the right hand of /learn.

MITAuto-check passedDevelopment

Install Incubate

skills CLI
$ npx skills add Soul-Brews-Studio/arra-oracle-skills-cli --skill incubate -a claude-code

Project install by default; add -g for ~/.claude/skills/.

GitHub CLI
$ gh skill install Soul-Brews-Studio/arra-oracle-skills-cli incubate --agent claude-code

Project scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).

Manual copy
$ git clone --depth 1 https://github.com/Soul-Brews-Studio/arra-oracle-skills-cli.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/incubate .claude/skills/incubate && rm -rf skills-src

Use ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.

Claude Code skills documentation · loads skills from .claude/skills/

Facts

Skill name
incubate
GitHub stars
123
Token cost
~18k tokens
SKILL.md length
3,332 words
Files
2 (incl. references)
Skills in repo
31
Repo updated
First seen
Licence
MIT

At a glance

Clone or create repos for active development — the right hand of /learn.

  • Works in 3 steps: Detect Input Type + Resolve Path → Detect Workflow Mode → Create/Update Hub File (REPO.md)
  • User says incubate [repo]
  • SKILL.md covers Usage, Workflow Modes, Directory Structure and /incubate --init, plus 7 more sections
  • Calls git and gh; reaches github.com

What it does

Incubate is an agent skill from Soul-Brews-Studio/arra-oracle-skills-cli. 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).

Its SKILL.md is about 18k tokens, which your agent loads only when the skill is triggered. The skill folder holds 2 other files, including reference files (for example `references/worktree-recovery.md`).

It sits in Development, covering Git worktrees. It works with Git. The repository describes itself as: Install Oracle skills to Claude Code, OpenCode, Cursor, and 12+ AI coding agents. The licence is MIT.

When your agent uses it

  • User says incubate [repo]
  • Wants to set up a dev workflow
  • Study/exploration (use /learn)
  • Finding projects (use /trace)

Example prompts

  • “incubate [repo]”
  • “work on [repo]”
  • “clone for dev”
  • “/incubate”

Workflow steps

3 steps, taken from the step headings in SKILL.md.

  1. Detect Input Type + Resolve Path
  2. Detect Workflow Mode
  3. Create/Update Hub File (REPO.md)

What it can do on your machine

Read from SKILL.md and the folder at commit 9feea09. It shows what the files ask for, not the result of running them.

  • Tool permissions

    Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.

    From allowed-tools in the SKILL.md frontmatter.

  • Runs code

    Shell commands in SKILL.md call:

    • git
    • gh

    From the folder's file list and the shell code blocks in SKILL.md.

  • Network

    Hosts in commands or code, which the agent is likely to contact:

    • github.com

    From URLs in SKILL.md, links to its own repository left out.

  • Credentials

    Names no API keys, tokens, secrets or passwords.

    From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.

Context cost

Incubate loads about 18k tokens when it runs, and up to ~22k if it reads all its reference files. Until then it costs about 104 tokens; SKILL.md has 3,332 words of instructions outside code blocks.

Always · name and description, kept in context so the agent knows when to use it
~104
When it runs · the whole SKILL.md, loaded when a task matches
~18k
With references · SKILL.md plus every file in references/, read only if the agent opens them
~22k

Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.

Safety

Auto-check passed

The automated check found no risky patterns in SKILL.md.

Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.

SKILL.md

The full file from Soul-Brews-Studio/arra-oracle-skills-cli at commit 9feea09, republished under its MIT licence (© Soul-Brews-Studio). 3,332 words, ~18,411 tokens.

Download SKILL.mdSave it as .claude/skills/incubate/SKILL.md (or your agent's skills folder). This skill also uses 1 other file; get the full folder from GitHub.
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

FlagScopeDurationCleanup
(default)Long-term devWeeks/monthsManual offload
--wt <slug>One agent's parallel bodyHours/days--offload --wt <slug> (branch survives)
--flashSingle fixMinutesIssue → PR → auto-offload + purge
--contributeMulti-featureDays/weeksOffload when all PRs done
--statusQuery—Read-only listing
--offloadCleanup—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 1meansdo
incubateoursparse who | when | task
initializinggit'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 elseanother tool'sreport 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.

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.

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:

ArgumentModeAction
(none)DefaultClone + symlink + show status
--wt <slug>BodyClaim incubate/<slug> + add worktree + symlink (skip clone — the mother must already exist) (#487)
--flashFlashIssue → branch → fix → PR → offload
--contributeContributeFork if needed → multi-feature PRs
--statusStatusList all incubations + bodies (skip clone)
--offloadOffloadRemove 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.

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.

Done. No hub file update needed.


Mode: --offload (cleanup)

Remove symlink, keep ghq clone and hub file.

InvocationRetires
--offload <repo>the mother symlink — refuses while bodies live
--offload <repo> --wt <slug>one body; the branch always survives
--offload <repo> --all-wtevery body on this machine, one outcome line each
… --purgeadditionally 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.


Show full SKILL.md (1,325 more words)Show less

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

WrongRight
git clone directly to ψ/ghq get then symlink
Flat: ψ/incubate/repo-nameOrg structure: ψ/incubate/owner/repo
Copy filesSymlink always
Manual clone outside ghqEverything through ghq
Delete ghq clone after workOffload symlink only (Nothing is Deleted)
Second clone of the same repo for a second agentOne mother, N worktrees (--wt)
ln -sf onto an existing linkln -sfn — -sf leaves the old target and drops a stray link inside it
sort -u -o F F on a shared fileGuarded 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 linegrep -vxF "$SEL" — . is a regex wildcard, so acme/api.js also matches acme/apiXjs
mkdir/flock/.lock dirs to claim a sluggit worktree add -b — the ref transaction is the claim
worktree add -b then a separate worktree lockworktree add --lock --reason … — two commands leave the body observable unowned
worktree add -b onto an existing pathGuard 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 toolIt is git's own lock (E11) — report the crash, offer the unlock
worktree unlock before checking for dirtProbe status --porcelain first — a refused offload must not drop the ownership record
git worktree remove --forcePlain 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 existgit fetch --prune — pulling stashes a peer's uncommitted work
rm -rf the mother while bodies liveRefuse, 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 pathswhile IFS= read -r — BSD xargs dies on any line ≥ 255 bytes
git gc / worktree prune / branch -D from inside a bodyYour worktree, your branch — no git op outside it
Committing a per-body manifest — or a REPO.md session block — per bodygit worktree list --porcelain -z + the lock reason (no ψ git race)
declare -A in a skill's bashSorted 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.

ARGUMENTS: $ARGUMENTS

© Soul-Brews-Studio, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file

Files

SKILL.md and 1 other file (references) in skills/incubate of Soul-Brews-Studio/arra-oracle-skills-cli.

  • SKILL.md
  • references/worktree-recovery.md

Open the folder on GitHubat commit 9feea09

Compare with similar skills

Incubate next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.

Incubate compared with similar skills
SkillStarsUsed inTokensAuto-checkLicenceRepo updated
Incubate this skillSoul-Brews-Studio/arra-oracle-skills-cli123—~18kAutomated safety check: PassMIT
Build Px4 macOSPX4/PX4-Autopilot13k—~1.1kAutomated safety check: PassBSD-3-Clause
Git Worktree IsolationjnMetaCode/superpowers-zh8.3k1 repos~982Automated safety check: PassMIT
WorktreeAgentsMesh/AgentsMesh2.4k—~553Automated safety check: NotesCustom licence
Cursor Composer Task DelegateChachamaru127/claude-code-harness3.2k—~4.4kAutomated safety check: NotesMIT
Git Worktree Setupk8zdev/k8z155—~930Automated safety check: PassNone

Similar skills

  • Build Px4 macOS

    PX4/PX4-Autopilot

    Build PX4 board firmware on macOS in the px4-dev Docker container, including git worktrees, and stage commit-labeled artifacts without flashing hardware.

    13k GitHub stars~1.1k tokensUpdated today
    DevelopmentAuto-check passed
  • Git Worktree Isolation

    jnMetaCode/superpowers-zh

    Sets up an isolated workspace before feature work or plan execution, preferring native worktree tools and falling back to git worktree, with instructions in Chinese.

    8.3k GitHub starsUsed in 1 repo~982 tokens
    DevelopmentAuto-check passed
  • Worktree

    AgentsMesh/AgentsMesh

    Creates or reuses an isolated AgentsMesh Git worktree from a verified base branch, preserves existing changes, and optionally starts the worktree-scoped development environment.

    2.4k GitHub stars~553 tokensUpdated 17 days ago
    DevelopmentAuto-check: notes
  • Cursor Composer Task Delegate

    Chachamaru127/claude-code-harness

    Hands one implementation task to Cursor Composer in an isolated git worktree, then reviews its diff and cherry-picks the result into the main branch.

    3.2k GitHub stars~4.4k tokensUpdated 6 days ago
    DevelopmentAuto-check: notes
  • Automate git worktree creation and copy private files (not tracked in git) to new workdirs.

    155 GitHub stars~930 tokensUpdated 3 mo ago
    DevelopmentAuto-check passed
  • New Feature

    michaelshimeles/skills

    Start a new task in an isolated Git worktree branched from origin/main so multiple agents can work on the same repo in parallel without conflicts.

    1.3k GitHub starsUsed in 1 repo~699 tokens
    DevelopmentAuto-check passed

More from Soul-Brews-Studio/arra-oracle-skills-cli

All 31 skills in this repo
  • Dig

    Soul-Brews-Studio/arra-oracle-skills-cli

    Mine Claude Code sessions — timeline, gaps, repo attribution, session history.

    123 GitHub stars~1.8k tokensUpdated 8 days ago
    Auto-check passed
  • Forward

    Soul-Brews-Studio/arra-oracle-skills-cli

    Hand off the current session to the next one. An agent skill from Soul-Brews-Studio/arra-oracle-skills-cli.

    123 GitHub stars~2.9k tokensUpdated 8 days ago
    Auto-check passed
  • Project

    Soul-Brews-Studio/arra-oracle-skills-cli

    Clone and track external repos. An agent skill from Soul-Brews-Studio/arra-oracle-skills-cli.

    123 GitHub stars~1.4k tokensUpdated 8 days ago
    Auto-check passed
  • Round Table

    Soul-Brews-Studio/arra-oracle-skills-cli

    Convene a Round Table (โต๊ะกลม): 3–8 souls — persona files, Oracles, or real people's DNA — debate ONE stakes-bearing decision over exactly 4 rounds (OPEN, DEEPEN, CONVERGE, VOTE), one subagent per…

    123 GitHub stars~2.1k tokensUpdated 8 days ago
    Auto-check passed
  • Soul

    Soul-Brews-Studio/arra-oracle-skills-cli

    Bring an Oracle into the current repo — its soul, ALL its skills and its expertise — by link, never by copy.

    123 GitHub stars~1.3k tokensUpdated 8 days ago
    Auto-check passed
  • Trace

    Soul-Brews-Studio/arra-oracle-skills-cli

    Find projects, code, and knowledge across git history, repos, docs, and Oracle.

    123 GitHub stars~2.9k tokensUpdated 8 days ago
    Auto-check passed

Works with

Questions about Incubate

What does Incubate do?

Clone or create repos for active development — the right hand of /learn. Incubate is an agent skill from Soul-Brews-Studio/arra-oracle-skills-cli. Clone or create repos for active development — the right hand of /learn.

When should I use Incubate?

Incubate fits situations like: user says incubate [repo]; wants to set up a dev workflow; study/exploration (use /learn); finding projects (use /trace).

How do I install Incubate in Claude Code?

Run `npx skills add Soul-Brews-Studio/arra-oracle-skills-cli --skill incubate -a claude-code`. Or copy the skill folder (skills/incubate in Soul-Brews-Studio/arra-oracle-skills-cli) into .claude/skills/incubate in your project. Claude Code loads it when a task matches its description.

How do I install Incubate in Codex?

Run `npx skills add Soul-Brews-Studio/arra-oracle-skills-cli --skill incubate -a codex`. Or copy the skill folder (skills/incubate in Soul-Brews-Studio/arra-oracle-skills-cli) into .agents/skills/incubate in your project. Codex loads it when a task matches its description.

Can I use Incubate in Cursor, Gemini CLI or GitHub Copilot?

Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add Soul-Brews-Studio/arra-oracle-skills-cli --skill incubate -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/incubate, .gemini/skills/incubate, .github/skills/incubate and .opencode/skills/incubate in your project.

What does Incubate need to run?

Going by SKILL.md and its folder, Incubate needs the command-line tools its instructions call (git and gh).

Does Incubate access the network?

SKILL.md names 1 domain. In commands or code: github.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.

Is Incubate safe to install?

Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.

What licence does Incubate use?

Incubate is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.

How many tokens does Incubate use?

About 18k tokens (SKILL.md is roughly 74k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 3.3k tokens, read only when the agent opens those files.

What are the alternatives to Incubate?

Skills that share tags, products or a category with Incubate: Build Px4 macOS (PX4/PX4-Autopilot, 13k stars), Git Worktree Isolation (jnMetaCode/superpowers-zh, 8.3k stars), Worktree (AgentsMesh/AgentsMesh, 2.4k stars) and Cursor Composer Task Delegate (Chachamaru127/claude-code-harness, 3.2k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.

Who maintains Incubate?

Soul-Brews-Studio (a GitHub organization) maintains it in Soul-Brews-Studio/arra-oracle-skills-cli, which has 123 GitHub stars. The repository holds 31 skills in this directory. The repository was last updated on October 3, 2026.

Source: Soul-Brews-Studio/arra-oracle-skills-cli on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.