Attach a code repo's ψ vault to a caretaker oracle — check, link, heal, unlink. Use when a plain code repo (open-source, not an oracle) should keep memory in another oracle's vault, when the user says "psi check", "psi link", "share the vault", "symlink ψ to neo", "who takes care of this repo's memory", or when a ψ symlink went missing after a checkout. Do NOT trigger for creating an oracle (use /awaken), cloning repos for development (use /incubate), or writing a retrospective (use /rrr).
Scanned 9/5/2026
Install to Claude Code
npx -y skills add Soul-Brews-Studio/arra-oracle-skills-cli --skill psi --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Psi?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/soul-brews-studio-psi)More formats (shields.io, HTML) on the badges page.
---
name: psi
description: Attach a code repo's ψ vault to a caretaker oracle — check, link, heal, unlink. Use when a plain code repo (open-source, not an oracle) should keep memory in another oracle's vault, when the user says "psi check", "psi link", "share the vault", "symlink ψ to neo", "who takes care of this repo's memory", or when a ψ symlink went missing after a checkout. Do NOT trigger for creating an oracle (use /awaken), cloning repos for development (use /incubate), or writing a retrospective (use /rrr).
argument-hint: "[check | link | heal | unlink]"
---
# /psi
> "A code repo does not need to be an oracle to have a memory."
Point a plain code repo's `ψ` at a **caretaker oracle's** vault, so the repo keeps a brain
without becoming an oracle and without committing vault content into public source history.
```text
/psi # same as check
/psi check # what is ψ here, who takes care of it, is it safe
/psi link # ask which oracle, then absorb → symlink → ignore → verify
/psi heal # symlink vanished after a checkout, or ignore rules incomplete
/psi unlink # go back to a standalone real ψ
```
## The model
```
code repo/ψ ──symlink──▶ caretaker oracle/ψ
(ignored, never committed) (tracked — it IS the brain)
```
| Repo kind | ψ | git |
|---|---|---|
| **code repo** — software someone else could clone and build | symlink to caretaker | **ignored, never tracked** |
| **oracle repo** — has an oracle identity | real directory | **tracked** |
## Pick the caretaker — show, don't guess
Never assume which oracle takes care of a repo. **Show the fleet and let the human name
it.** Linking to the wrong vault mixes two oracles' memory.
```bash
maw ls
```
Ask: *"Which oracle should take care of this repo's memory?"* The human answers with a
name (`neo`, `pulse`, `beta`). Resolve it — the short name works:
```bash
maw locate "$ORACLE_NAME" # prints: repo: /path/to/<name>-oracle and ψ/: present
```
Take the `repo:` line as `CARETAKER`. If `maw locate` finds nothing, or its `ψ/` is not
`present`, stop and say so — do not fall back to a guess.
Do **not** loop over repos or scan the filesystem looking for candidates. One repo, one
question, one answer.
## `check`
Report what you verified, never what you assume. Every line is the output of a command —
`readlink -f` for the real target, `ls-files -s` for the mode bits, `check-ignore -v` for
the rule that actually matched. Close with one **FOCUS** line: the single next action.
```bash
REPO=$(git rev-parse --show-toplevel) || exit 1
GI="$REPO/.gitignore"
# ── ψ: type, size, staleness ─────────────────────────────────────────────
if [ -L "$REPO/ψ" ]; then
TARGET=$(readlink "$REPO/ψ"); REAL=$(readlink -f "$REPO/ψ")
[ -e "$REPO/ψ/" ] && ALIVE=alive || ALIVE='DANGLING'
case "$TARGET" in /*) ABS=' ⚠️ absolute';; *) ABS='';; esac
PSI="symlink → $TARGET$ABS"; PSI2="resolves $REAL ($ALIVE)"
elif [ -d "$REPO/ψ" ]; then
N=$(find "$REPO/ψ" -type f ! -name '.DS_Store' | wc -l | tr -d ' ')
NEW=$(find "$REPO/ψ" -type f ! -name '.DS_Store' -exec stat -f '%m' {} \; 2>/dev/null | sort -rn | head -1)
AGE=$(( ( $(date +%s) - ${NEW:-$(date +%s)} ) / 86400 ))
PSI="real dir · $N files · newest $(date -r "${NEW:-0}" +%F 2>/dev/null) (${AGE}d old)"; PSI2=""
else
PSI="absent"; PSI2=""
fi
# ── git: is any of it tracked? is the LINK itself committed? ─────────────
TRACKED=$(git -C "$REPO" ls-files ψ | wc -l | tr -d ' ')
git -C "$REPO" ls-files -s ψ | rg -q '^120000' && BLOB='⚠️ SYMLINK COMMITTED' || BLOB=no
# ── ignore: both forms, and the rule git actually matched ───────────────
rg -qx 'ψ' "$GI" 2>/dev/null && BARE=yes || BARE='NO ⚠️'
rg -qx 'ψ/' "$GI" 2>/dev/null && SLASH=yes || SLASH=NO
# cut -f1, NOT an awk positional field ref — see "Never use positional parameters"
# below. check-ignore -v separates source from pathname with a TAB, so field 1 is
# the matching rule.
RULE=$(git -C "$REPO" check-ignore -v ψ 2>/dev/null | cut -f1)
[ -n "$RULE" ] || RULE='NOT IGNORED ⚠️'
# ── kind + caretaker ────────────────────────────────────────────────────
[ "$TRACKED" -gt 0 ] && KIND='oracle repo (ψ is tracked — it IS the brain)' \
|| KIND='code repo (ψ must stay ignored)'
# NOTE: no \K — this rg build rejects it, and the error would silently read as "none".
CARE=$(sed -n 's/^oracle: *//p' "$REPO/.claude/PSI_CARETAKER" 2>/dev/null)
[ -n "$CARE" ] || CARE='none recorded'
# ── render: no borders. two-space margin, aligned columns, grouped by blank
# lines. identity block first, then the checks, then one FOCUS line.
# Each check names the command that proved it — the report is its own audit.
# NOTE: printf inline per row — no row()/fact() helpers, because a helper
# would need positional parameters and the host rewrites those. See below.
F=' %-14s%s\n' # identity line
R=' %-14s%-42s%-3s %s\n' # check line
echo
printf "$F" repo "$(basename "$REPO")"
printf "$F" kind "$KIND"
printf "$F" ψ "$PSI"
[ -n "$PSI2" ] && printf "$F" '' "$PSI2"
echo
printf "$R" tracked "$TRACKED entries" "$([ "$TRACKED" -eq 0 ] && echo ✓ || echo ⚠)" 'git ls-files ψ'
printf "$R" symlink-blob "$BLOB" "$([ "$BLOB" = no ] && echo ✓ || echo ⚠)" 'ls-files -s ψ → 120000'
printf "$R" bare-ψ "$BARE" "$([ "$BARE" = yes ] && echo ✓ || echo ⚠)" 'rg -qx ψ .gitignore'
printf "$R" 'ψ/' "$SLASH" "$([ "$SLASH" = yes ] && echo ✓ || echo ·)" 'rg -qx ψ/ .gitignore'
printf "$R" ignore-rule "$RULE" "$(case $RULE in *NOT*) echo ⚠;; *) echo ✓;; esac)" 'git check-ignore -v ψ'
printf "$R" caretaker "$CARE" "$([ "$CARE" = 'none recorded' ] && echo · || echo ✓)" '.claude/PSI_CARETAKER'
echo
```
Keep the two-space margin and the blank-line grouping — the whitespace is what makes it
readable without rules. Widen a column only if a value would otherwise wrap; never add
borders back.
### Never use positional parameters (dollar-digit) in this skill's shell
The host substitutes the invocation's positional arguments into the skill body before the
model ever sees it. Running `/psi link to neo but …` rewrote an `awk` program that
referenced field one into `awk '{print to}'`, and a four-parameter `printf` helper into
`printf … "to" "neo" "but" "when"` — silent corruption, no error.
So: no shell functions taking positional parameters, and no dollar-digit inside
`awk`/`sed` programs. Use `cut -f1`, a named variable, or a printf format string held in
a variable — all three are immune.
### Deciding FOCUS
Emit exactly one, first match wins — most dangerous first:
| Condition | FOCUS |
|---|---|
| `symlink-blob` committed | **leaking a machine path in git history** → `/psi heal`, then decide on a history rewrite |
| symlink `DANGLING` | **brain unreachable** → `/psi heal` |
| symlink target absolute | **breaks on every other machine** → `/psi heal` (rewrites relative) |
| code repo & `bare-ψ=NO` | **ψ will leak the moment it is linked** → add the bare rule |
| code repo & `NOT IGNORED` | **do not commit** → fix `.gitignore` first |
| code repo, real dir, no caretaker | **orphaned vault — no oracle reads these N files** → `/psi link` |
| oracle repo, ψ tracked | ✅ correct — the vault belongs here, nothing to do |
| symlink alive, relative, ignored | ✅ linked and safe |
A stale `newest` date is worth naming even on a ✅ — a vault whose newest file is weeks old
is why a later `/recap` will hand back a stale handoff as if it were current.
## `link` — a six-phase ritual
Run it **step by step**, not as one script. Announce each phase as you enter it, do that
phase's work, show the result, then continue. Two phases are hard **gates**: stop and wait
for a human answer. After the phases, run the checklist, then print the report.
```text
Phase 1/6 · Survey what ψ is now
Phase 2/6 · Caretaker ← GATE: the human names the oracle
Phase 3/6 · Preview ← GATE: the human approves the dry run
Phase 4/6 · Absorb copy + count both sides
Phase 5/6 · Link park → symlink → verify through it
Phase 6/6 · Seal ignore rules + caretaker record
```
Never run a later phase's commands while announcing an earlier one. A gate that is
"announced and then passed in the same breath" is not a gate.
### Phase 1/6 · Survey — refuse bad ground
Run `check` first and show it. Then stop, with the reason, when:
- the repo is itself an oracle (ψ tracked with real content),
- ψ already points at that same caretaker → say `already linked`, exit 0,
- there is no origin remote (the vault path cannot be derived).
### Phase 2/6 · Caretaker — GATE
Show `maw ls`, then **ask which oracle should take care of this repo's memory and wait for
the answer.** Resolve it with `maw locate`, and confirm the caretaker's `ψ/: present` plus
that it is not behind its remote. Never pick a name yourself, never carry one over from a
previous run.
### Phase 3/6 · Preview — GATE
Show the itemized dry run and the counts, then **wait for approval.** Dry-run output is not
consent. If the human says nothing, nothing happens.
### Phase 4/6 · Absorb — dry run first, always
Replacing a populated ψ with a symlink orphans everything inside it. Fetch the caretaker
first so a stale vault is not merged over newer content.
Every linked repo lives under **`ψ/family/<host>/<owner>/<repo>`**, always lowercased.
Below `family/` the path is the ghq tree exactly, so it reads the same in both places:
```text
/opt/Code/github.com/soul-brews-studio/arra-oracle-skills-cli ← the code (ghq)
neo-oracle/ψ/family/github.com/soul-brews-studio/arra-oracle-skills-cli ← its memory
└ family ┘└─ host ──┘└─ owner ───────┘└─ repo ────────────┘
```
**`family/` keeps the oracle's kin clear of its own organs.** Without it an owner directory
lands beside `memory/`, `inbox/`, `teams/` — and an org literally named `teams` collides
with the oracle's own. One directory to list to see everything an oracle tends.
**The host is derived, never assumed.** `gitlab.com`, `codeberg.org`, or a self-hosted
`git.example.com` each get their own subtree, so two repos sharing an `owner/name` on
different hosts never collide.
Lowercase is not cosmetic either: `Soul-Brews-Studio` and `soul-brews-studio` are the same
repo, but on a case-sensitive volume they become two vaults, and the split is invisible
until memory goes missing.
```bash
git -C "$CARETAKER" fetch --quiet 2>/dev/null
# host/owner/repo from the origin remote, lowercased — never from the directory
# name, which may have been renamed locally. Handles https, ssh, scp-style,
# git://, an embedded user, a custom port, and gitlab subgroups.
URL=$(git -C "$REPO" remote get-url origin 2>/dev/null)
[ -n "$URL" ] || { echo "✗ no origin remote — cannot derive the vault path"; exit 1; }
SLUG=$(printf '%s' "$URL" | sed -E \
-e 's#^(ssh|git\+ssh|https?|git)://##' \
-e 's#^[^@/]+@##' \
-e 's#:[0-9]+/#/#' \
-e 's#:#/#' \
-e 's#\.git$##' \
-e 's#/+$##' \
| tr '[:upper:]' '[:lower:]')
NS="$CARETAKER/ψ/family/$SLUG" # ψ/family/<host>/<owner>/<repo>, lowercase
rsync -a --dry-run --itemize-changes "$REPO/ψ/" "$NS/"
```
Verified against every remote shape:
| remote | slug |
|---|---|
| `https://github.com/Soul-Brews-Studio/arra-oracle-skills-cli` | `github.com/soul-brews-studio/arra-oracle-skills-cli` |
| `git@github.com:laris-co/Neo-Oracle.git` | `github.com/laris-co/neo-oracle` |
| `git@gitlab.com:MyGroup/sub/proj.git` | `gitlab.com/mygroup/sub/proj` |
| `ssh://git@git.example.com:2222/team/thing.git` | `git.example.com/team/thing` |
| `git://codeberg.org/Owner/Proj.git` | `codeberg.org/owner/proj` |
Print the itemized list and the file count, then **wait for approval**. Only then re-run
without `--dry-run`. Never `--delete`.
The absorb and the removal are never the same command, and never the same step. Step 3
sequences them: **copy → count both sides → park the original → link → verify through the
link**, with an automatic rollback if the last check fails.
### Phase 5/6 · Link — copy, verify, park, link, verify again
**Order is the safety.** The source is never removed; it is *parked* outside the repo and
only after the copy has been counted. If the link fails to resolve, roll back automatically.
Never `rm -rf` the vault — see "nothing deleted".
```bash
mkdir -p "$NS"
# 3a. copy for real (same command as the dry run, minus --dry-run)
rsync -a --exclude '.DS_Store' "$REPO/ψ/" "$NS/"
# 3b. VERIFY before anything moves. Counts must match exactly, or stop —
# the source is still untouched at this point, so aborting costs nothing.
SRC_N=$(find "$REPO/ψ" -type f ! -name '.DS_Store' | wc -l | tr -d ' ')
DST_N=$(find "$NS" -type f ! -name '.DS_Store' | wc -l | tr -d ' ')
if [ "$SRC_N" -ne "$DST_N" ]; then
echo "✗ ABORT — copied $DST_N of $SRC_N files. Source untouched; nothing removed."
exit 1
fi
echo "✓ verified $DST_N/$SRC_N files copied"
# 3c. PARK the original outside the repo (never delete, never leave it in git's way)
PARK="${TMPDIR:-/tmp}/psi-replaced-$(echo "$SLUG" | tr / -)-$(date +%Y%m%d-%H%M%S)"
mv "$REPO/ψ" "$PARK"
# 3d. link, relative
REL=$(python3 -c 'import os,sys;print(os.path.relpath(sys.argv[1],sys.argv[2]))' "$NS" "$REPO")
ln -sfn "$REL" "$REPO/ψ" # -n: do not descend into an existing symlink
# 3e. verify the link RESOLVES and the content is reachable THROUGH it.
# Any failure restores the parked original and exits.
restore() { rm -f "$REPO/ψ"; mv "$PARK" "$REPO/ψ"; echo "↩ restored original ψ"; }
[ -e "$REPO/ψ/" ] || { echo "✗ link does not resolve"; restore; exit 1; }
THRU=$(find "$REPO/ψ/" -type f ! -name '.DS_Store' | wc -l | tr -d ' ')
[ "$THRU" -ge "$SRC_N" ] || { echo "✗ only $THRU/$SRC_N reachable through the link"; restore; exit 1; }
readlink -f "$REPO/ψ"
echo "✓ $THRU files reachable through the link"
echo " original parked: $PARK (delete it yourself once satisfied)"
```
`ln -sfn`, never `ln -sf` — with a pre-existing symlinked directory `-f` alone creates the
link *inside* the target instead of replacing it.
**Why park instead of delete:** at 3c the content exists in two places and has been counted
in both, so parking looks redundant — until 3d writes a link that silently resolves
somewhere unexpected. Parking makes 3e's rollback possible. Announce the park path; let the
human delete it. Nothing deleted.
### Phase 6/6 · Seal — ignore it, prove it, record the caretaker
```bash
for pat in 'ψ' 'ψ/'; do
rg -qx "$pat" "$REPO/.gitignore" 2>/dev/null || echo "$pat" >> "$REPO/.gitignore"
done
if git -C "$REPO" ls-files --error-unmatch ψ >/dev/null 2>&1; then
git -C "$REPO" rm --cached -r --quiet ψ
echo "✓ untracked previously-committed ψ (history still holds it — rewrite separately)"
fi
git -C "$REPO" check-ignore -v ψ || echo "⚠️ ψ is NOT ignored — do not commit"
```
Verification is part of the step. If `check-ignore` prints nothing, the link is not safe —
report that instead of declaring success.
#### Record the caretaker
```bash
mkdir -p "$REPO/.claude"
printf 'oracle: %s\nrepo: %s\nvault: %s\ndate: %s\n' \
"$ORACLE_NAME" "$CARETAKER" "$NS" "$(date +%F)" > "$REPO/.claude/PSI_CARETAKER"
rg -qx '.claude/PSI_CARETAKER' "$REPO/.gitignore" 2>/dev/null \
|| echo '.claude/PSI_CARETAKER' >> "$REPO/.gitignore"
```
Machine-local, ignored like the link. `check` reads it to name the caretaker without asking
again.
### Checklist — run it, show it, and let it fail loudly
After phase 6, verify the outcome. Every line re-runs a real command; nothing is asserted
from memory of what the phases *intended* to do. Print it. Unlike the retro checklist in
`/rrr`, this one is **shown** — it is the evidence that a data move succeeded.
```bash
C=' %-2s %-34s%s\n' # format in a variable — no helper, no positional params
# 1 counts match on both sides
SRC_OK=$([ "${SRC_N:-0}" -eq "$DST_N" ] && echo ✓ || echo ✗)
printf "$C" "$SRC_OK" "files copied" "$DST_N/$SRC_N"
# 2 reachable THROUGH the link, not just present in the vault
printf "$C" "$([ "${THRU:-0}" -ge "${SRC_N:-0}" ] && echo ✓ || echo ✗)" "reachable through ψ" "$THRU"
# 3 the link resolves
printf "$C" "$([ -e "$REPO/ψ/" ] && echo ✓ || echo ✗)" "link resolves" "$(readlink -f "$REPO/ψ")"
# 4 relative, not absolute
case "$(readlink "$REPO/ψ")" in /*) A=✗;; *) A=✓;; esac
printf "$C" "$A" "relative target" "$(readlink "$REPO/ψ")"
# 5 lowercase, nested owner/repo
printf "$C" "$([ "$SLUG" = "$(echo "$SLUG" | tr '[:upper:]' '[:lower:]')" ] && echo ✓ || echo ✗)" "vault path lowercase" "ψ/$SLUG"
# 6 git cannot see it
printf "$C" "$(git -C "$REPO" check-ignore -q ψ && echo ✓ || echo ✗)" "ignored by git" "$(git -C "$REPO" check-ignore -v ψ | cut -f1)"
# 7 the link itself is not committed
printf "$C" "$(git -C "$REPO" ls-files -s ψ | rg -q '^120000' && echo ✗ || echo ✓)" "symlink not committed" "$(git -C "$REPO" ls-files ψ | wc -l | tr -d ' ') tracked"
# 8 the original still exists somewhere
printf "$C" "$([ -d "$PARK" ] && echo ✓ || echo ✗)" "original parked" "$PARK"
```
Any `✗` means **stop and say so** — do not print a success report over a failed checklist.
Items 2 and 8 are the ones that matter most: 2 proves the brain is actually reachable, 8
proves nothing was destroyed.
### Report
Close with a short report in the same borderless style as `check`. State what moved, where
it went, and what the human still owns.
```text
ψ linked · arra-oracle-skills-cli → neo
moved 17 files → neo-oracle/ψ/family/github.com/soul-brews-studio/arra-oracle-skills-cli
symlink ../../laris-co/neo-oracle/ψ/family/github.com/soul-brews-studio/arra-oracle-skills-cli
ignored .gitignore:9:ψ (bare rule — ψ/ no longer matches a symlink)
caretaker neo
parked /var/.../psi-replaced-…-20260820-181051
yours to do delete the parked copy once satisfied
commit the .gitignore change
```
Report only what the checklist verified. If an item failed, the report says what failed and
what state the repo is in — never a clean summary over a broken outcome.
## `heal`
`git checkout` and `git merge` silently delete an **ignored** symlink when they remove the
last tracked sibling at that path — no error is printed. Repair, reporting each fix:
1. `PSI_CARETAKER` exists but `ψ` is missing → recreate the relative symlink.
2. `ψ` is a symlink that `readlink -f` cannot resolve → report the dangling target; do not
invent a replacement.
3. An ignore form is missing → add it.
4. `ψ` is tracked → `git rm --cached -r ψ`.
Print `✓ nothing to heal` when clean.
## `unlink`
Copy the namespaced subtree back into a real `ψ`, replace the symlink, and leave the
caretaker's copy alone — never delete the vault side. Keep the ignore lines unless asked.
## Rules
- Show `maw ls` and let the human name the caretaker. Never guess, never scan for one.
- Ask before the first destructive step; dry-run output is not consent.
- **Nothing deleted.** Never `rm -rf` a vault. Copy → count both sides → park the original
outside the repo → link → verify through the link, rolling back on any failure. The human
deletes the parked copy, not the skill.
- Both `ψ` and `ψ/` in `.gitignore`, every time — a trailing slash matches directories
only, so a `ψ/`-only rule stops matching the moment ψ becomes a symlink.
- Relative symlink targets only — machines disagree on the ghq root.
- Never commit ψ or the symlink in a code repo; never un-track ψ in an oracle repo.
- A shared vault is shared: give each repo its own subtree, and say so when linking.
- Report what you verified (`readlink -f`, `check-ignore`), not what you intended.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!