Retro
Mine THIS session's transcript for safeword friction and file it — the manual counterpart
to the Stop-hook auto-trigger. This skill is thin: it resolves the transcript path (which
a user-invoked command, unlike the Stop hook, does not receive in a payload), then defers
to the existing guides and the safeword retro run CLI, which owns extraction, the egress
guard, and filing.
1. Resolve the transcript path (never guess it)
safeword retro run requires --transcript <path> and refuses to guess. Resolve the
current session's transcript for THIS harness. Each rule below resolves deterministically
from the environment; only fall back to asking the user when the deterministic path
comes up empty.
Claude Code: ~/.claude/projects/<encoded-cwd>/$CLAUDE_SESSION_ID.jsonl — the cwd
is encoded with non-alphanumerics (/, ., _, …) → -. When $CLAUDE_SESSION_ID
is unset, list that projects directory and take the newest .jsonl:
ls -t ~/.claude/projects/"${PWD//[^a-zA-Z0-9]/-}"/*.jsonl | head -1
Codex: Codex does not reliably expose the session id to agent-launched commands
(openai/codex#8923), so resolve the
newest rollout by mtime — the current
session's rollout is the one being appended to right now. Rollouts live under
${CODEX_HOME:-$HOME/.codex}/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl
(-exec ls -t {} + is portable to macOS's BSD find, unlike -printf):
find "${CODEX_HOME:-$HOME/.codex}/sessions" -name 'rollout-*.jsonl' -exec ls -t {} + 2> /dev/null | head -1
If $CODEX_THREAD_ID happens to be set, prefer it for an exact match:
find "${CODEX_HOME:-$HOME/.codex}/sessions" -name "rollout-*-$CODEX_THREAD_ID.jsonl" 2>/dev/null | head -1.
Cursor: Cursor delivers transcript_path only in hook payloads (never env), so the
safeword Cursor hooks stash it to /tmp/safeword-cursor-transcript-<key> on every edit
and shell command. $safeword:retro itself runs a shell command, so THIS conversation's stash is
the freshest — read the newest one:
f=$(ls -t /tmp/safeword-cursor-transcript-* 2> /dev/null | head -1)
transcript=$([ -n "$f" ] && cat "$f")
key=${f##*/safeword-cursor-transcript-}
session=$([ -n "$key" ] && cat "/tmp/safeword-cursor-conversation-$key" 2> /dev/null)
If either stash is absent (a session with no edits or shell commands yet), ask the
user for the transcript path or conversation id rather than guessing. Public delivery
proceeds only when the CLI can bind that transcript and conversation to the current
project using the paired hook state; a mismatch silently keeps the existing private
recovery path.
Echo the resolved path back to the user before proceeding, so a wrong path is caught
before anything is filed.
2. Produce findings — pick one
Hands-off (Claude only): let the CLI extract out-of-band via an isolated headless
session. This spawns a claude -p subprocess, so it needs the claude CLI on PATH —
on Codex / Cursor (no claude binary) prefer the in-context path below.
safeword retro run --transcript < path > --auto-extract
In-context (any harness): extract the friction yourself following
the packaged retro procedure (fresh-context read of the transcript → structured findings
JSON), then hand them off:
safeword retro run --public-retro --transcript <path> --findings <findings.json> --session-id <session-id>
On Cursor, use the paired transcript and session values resolved above. Claude and
Codex may omit --public-retro when no stable session identity is available.
Optional: --session-id <id> for stable ledger attribution across fires;
--window-start <chars> to digest only the transcript from an offset (delta mode).
When the safeword binary isn't on PATH, substitute bunx --bun safeword@latest.
This carrier is independent of the project's dependencies, so retro remains available
while Safeword is asking the user to refresh a stale project toolchain.
3. Filing is code-owned — do not hand-write issues
Everything flows through the egress guard, then files upstream:
- Lane 1 (token available): the CLI files via REST and drains the spool, silently.
- Lane 2 (no token / cloud): the CLI spools the sanitized, post-egress drafts and a
nudge surfaces them; file each verbatim through the supported filing route
using your GitHub access. Never re-word or re-sanitize a spooled draft — the body is
already code-assembled and egress-clean; the sanitizer is the security boundary.
Enforced at the code-owned seams: each body is sealed with a
bodyDigest at
assembly, and verifyDraftBody (hooks/lib/retro-draft-spool.ts) refuses a
mismatch — a re-worded draft stays spooled instead of filing.
Reminders
- The transcript is the input; the CLI + egress guard do the sanitizing and filing. Keep
this skill thin — don't reimplement extraction or sanitization here.
- Over-redaction is the safe direction (findings file to a PUBLIC tracker); trust the
guard, don't loosen it.
- If nothing substantial surfaced, say so — filing zero findings is a valid outcome.
1---2name: retro-23description: Run a safeword retrospective on the current session on demand — pull friction (bugs / rough edges / gaps) out of the session transcript and file it upstream through the outbound safety check. Use when the user says "run a retro", "$safeword:retro", "retrospective on this session", or wants to capture friction before the session ends. The retro also auto-fires at Stop; this is the manual, on-demand path.4---56# Retro78Mine THIS session's transcript for safeword friction and file it — the manual counterpart9to the Stop-hook auto-trigger. This skill is thin: it resolves the transcript path (which10a user-invoked command, unlike the Stop hook, does not receive in a payload), then defers11to the existing guides and the `safeword retro run` CLI, which owns extraction, the egress12guard, and filing.1314## 1. Resolve the transcript path (never guess it)1516`safeword retro run` **requires** `--transcript <path>` and refuses to guess. Resolve the17current session's transcript for THIS harness. Each rule below resolves deterministically18from the environment; only fall back to asking the user when the deterministic path19comes up empty.2021- **Claude Code:** `~/.claude/projects/<encoded-cwd>/$CLAUDE_SESSION_ID.jsonl` — the cwd22 is encoded with non-alphanumerics (`/`, `.`, `_`, …) → `-`. When `$CLAUDE_SESSION_ID`23 is unset, list that projects directory and take the newest `.jsonl`:2425 ```bash26 ls -t ~/.claude/projects/"${PWD//[^a-zA-Z0-9]/-}"/*.jsonl | head -127 ```2829- **Codex:** Codex does not reliably expose the session id to agent-launched commands30 ([openai/codex#8923](https://github.com/openai/codex/issues/8923)), so resolve the31 **newest rollout by mtime** — the current32 session's rollout is the one being appended to right now. Rollouts live under33 `${CODEX_HOME:-$HOME/.codex}/sessions/YYYY/MM/DD/rollout-<timestamp>-<id>.jsonl`34 (`-exec ls -t {} +` is portable to macOS's BSD `find`, unlike `-printf`):3536 ```bash37 find "${CODEX_HOME:-$HOME/.codex}/sessions" -name 'rollout-*.jsonl' -exec ls -t {} + 2> /dev/null | head -138 ```3940 If `$CODEX_THREAD_ID` happens to be set, prefer it for an exact match:41 `find "${CODEX_HOME:-$HOME/.codex}/sessions" -name "rollout-*-$CODEX_THREAD_ID.jsonl" 2>/dev/null | head -1`.4243- **Cursor:** Cursor delivers `transcript_path` only in hook payloads (never env), so the44 safeword Cursor hooks stash it to `/tmp/safeword-cursor-transcript-<key>` on every edit45 and shell command. `$safeword:retro` itself runs a shell command, so THIS conversation's stash is46 the freshest — read the newest one:4748 ```bash49 f=$(ls -t /tmp/safeword-cursor-transcript-* 2> /dev/null | head -1)50 transcript=$([ -n "$f" ] && cat "$f")51 key=${f##*/safeword-cursor-transcript-}52 session=$([ -n "$key" ] && cat "/tmp/safeword-cursor-conversation-$key" 2> /dev/null)53 ```5455 If either stash is absent (a session with no edits or shell commands yet), **ask the56 user for the transcript path or conversation id** rather than guessing. Public delivery57 proceeds only when the CLI can bind that transcript and conversation to the current58 project using the paired hook state; a mismatch silently keeps the existing private59 recovery path.6061Echo the resolved path back to the user before proceeding, so a wrong path is caught62before anything is filed.6364## 2. Produce findings — pick one6566- **Hands-off (Claude only):** let the CLI extract out-of-band via an isolated headless67 session. This spawns a `claude -p` subprocess, so it needs the `claude` CLI on PATH —68 on **Codex / Cursor** (no `claude` binary) prefer the in-context path below.6970 ```bash71 safeword retro run --transcript < path > --auto-extract72 ```7374- **In-context (any harness):** extract the friction yourself following75 the packaged retro procedure (fresh-context read of the transcript → structured findings76 JSON), then hand them off:7778 ```bash79 safeword retro run --public-retro --transcript <path> --findings <findings.json> --session-id <session-id>80 ```8182 On Cursor, use the paired `transcript` and `session` values resolved above. Claude and83 Codex may omit `--public-retro` when no stable session identity is available.8485Optional: `--session-id <id>` for stable ledger attribution across fires;86`--window-start <chars>` to digest only the transcript from an offset (delta mode).8788When the `safeword` binary isn't on PATH, substitute `bunx --bun safeword@latest`.89This carrier is independent of the project's dependencies, so retro remains available90while Safeword is asking the user to refresh a stale project toolchain.9192## 3. Filing is code-owned — do not hand-write issues9394Everything flows through the egress guard, then files upstream:9596- **Lane 1 (token available):** the CLI files via REST and drains the spool, silently.97- **Lane 2 (no token / cloud):** the CLI spools the sanitized, post-egress drafts and a98 nudge surfaces them; file each **verbatim** through the supported filing route99 using your GitHub access. Never re-word or re-sanitize a spooled draft — the body is100 already code-assembled and egress-clean; the sanitizer is the security boundary.101 Enforced at the code-owned seams: each body is sealed with a `bodyDigest` at102 assembly, and `verifyDraftBody` (`hooks/lib/retro-draft-spool.ts`) refuses a103 mismatch — a re-worded draft stays spooled instead of filing.104105## Reminders106107- The transcript is the input; the CLI + egress guard do the sanitizing and filing. Keep108 this skill thin — don't reimplement extraction or sanitization here.109- Over-redaction is the safe direction (findings file to a PUBLIC tracker); trust the110 guard, don't loosen it.111- If nothing substantial surfaced, say so — filing zero findings is a valid outcome.