From hand
Use at end of a session to update HANDOFF.yaml with completed work, new gaps discovered, and current project state. Also use when asked to create a HANDOFF.yaml for a project that doesn't have one yet.
How this skill is triggered — by the user, by Claude, or both
Slash command
/hand:handoffThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
`HANDOFF.yaml` is the committed source of truth for task/workflow tracking. It syncs with doob.
HANDOFF.yaml is the committed source of truth for task/workflow tracking. It syncs with doob.
Project state (build, tests, branch) lives separately in .ctx/HANDOFF.state.yaml — generated,
never committed. A rendered reference doc is also written to .ctx/HANDOFF.md.
| File | Location | Committed | Purpose |
|---|---|---|---|
HANDOFF.<project>.<base>.yaml | repo root | yes | Tasks, items, log — doob source of truth |
.ctx/HANDOFF.state.yaml | .ctx/ | no | Project snapshot — build/tests/branch/notes |
.ctx/HANDOFF.md | .ctx/ | no | Generated reference doc (rendered view of both) |
.ctx/ must be in .gitignore. Never commit anything under it.
Always use handoff-detect to resolve the HANDOFF.yaml path:
handoff-detect # returns path if exists, expected path + exit 2 if not
handoff-detect --name # expected filename only (e.g. HANDOFF.devkit.devkit.yaml)
handoff-detect --root # repo root
handoff-detect --project # project name
File naming convention: HANDOFF.<project>.<cwd-basename>.yaml
project = name from Cargo.toml / go.mod / pyproject.toml, fallback to repo root dir namecwd-basename = basename $(pwd) at time of invocationLegacy fallback (read-only): if handoff-detect exits 2 and a HANDOFF.md exists at repo root,
read it as freeform. Do not convert unless asked.
Task/workflow tracking only. No build state — that goes in .ctx/HANDOFF.state.yaml.
project: <name>
id: <prefix> # first 7 chars of project name, used for item IDs
updated: <YYYY-MM-DD>
items:
- id: <prefix>-<n> # sequential integer from 1, no leading zeros, never reuse
doob_uuid: <uuid> # written back by doob handoff sync; omit on first write
name: <kebab-slug> # immutable after creation
priority: P0 | P1 | P2 # immutable after creation
status: open | done | parked | blocked # mutable; doob wins on conflict
title: <one-line> # immutable after creation
description: <detail> # immutable after creation, null ok
files: [<path>] # immutable after creation, omit if empty
completed: <YYYY-MM-DD> # only when status: done
extra: # append-only; never edit existing entries
- date: <YYYY-MM-DD>
type: note | blocker | decision | discovery | escalation | human-edit
field: <field-name> # human-edit only: which field was changed
value: <new-value> # human-edit only: the value set
reviewed: <YYYY-MM-DD> # set by handoff skill after handon acknowledges it
note: <text>
log:
- date: <YYYY-MM-DD>
summary: <one-liner of what happened>
commits: [<short-hash>] # optional
Only status and extra may change after creation. For materially changed scope, create a new
item and park the old one.
When a human directly edits a field in HANDOFF.yaml (bypassing doob), record it explicitly:
extra:
- date: 2026-04-04
type: human-edit
field: status
value: done
note: "marked done manually — PR merged out of band"
Rules:
human-edit entry win over doob on that field.
doob handoff sync updates doob to match rather than overwriting.reviewed field on the entry, or reviewed is absent.handon has surfaced it and the user has seen it; handoff sets
reviewed: <today> on the entry at session end.status is the most common case.Fully overwritten each session. No append rules, no doob sync.
updated: <YYYY-MM-DD>
branch: <git branch>
build: clean | failing | unknown
tests: "<N passing>" | "failing: N" | "unknown"
notes: <one-line or null>
Extend freely with project-specific facts (e.g. rust_edition, open_prs, last_deploy).
| Priority | Meaning |
|---|---|
| P0 | Broken, blocked, security, data loss — validate before acting |
| P1 | Known fix, clear scope, safe to execute |
| P2 | Safe to delegate, well-understood |
git branch --show-current
git log --oneline -5
cargo check 2>&1 | tail -3 # or language equivalent
cargo test 2>&1 | tail -5
items — apply immutability rules:
id (no doob_uuid yet)status: done, add completed: <today>status: blocked, append extra entry with type: blockerhuman-edit acknowledgement — for any extra entry with type: human-edit and no
reviewed field that was surfaced by handon this session, add reviewed: <today> to that
entry. This signals to the next sync that doob should accept the human-set value as canonical.
log — prepend a new entry (newest first). One line, past tense. Include commit hashes.
updated — set to today.
Emit clean YAML. No anchors, no aliases.
Create .ctx/ if it does not exist. Overwrite completely with current state from step 1.
doob handoff sync --file <path-to-HANDOFF.yaml>
Writes doob_uuid back for new items, pulls status updates from doob, merges extra
bidirectionally. Skip and note if doob is not on PATH.
Render a combined reference doc from both files. Overwrite completely.
# Handoff — <project> (<updated>)
**Branch:** <branch> | **Build:** <build> | **Tests:** <tests>
<notes if non-null>
## Items
| ID | P | Status | Title |
|---|---|---|---|
| <id> | <P> | <status> | <title> |
## Log
- <date>: <summary> [<commits>]
Rules:
/handoverAdd .ctx/ to .gitignore if not present.
git add HANDOFF.yaml
git commit -m "docs: update handoff"
Stage only HANDOFF.yaml. Never stage anything under .ctx/.
Bootstrap from git context:
git log --oneline -10
git status
Populate log from recent commits. Leave items empty or with one P1 if there's an obvious next
step. Write .ctx/HANDOFF.state.yaml from actual build/test output.
If HANDOFF.md exists at repo root and HANDOFF.yaml does not: read it as freeform context, do
not auto-convert. Note that a HANDOFF.yaml could be created.
npx claudepluginhub 89jobrien/handCreates, edits, and optimizes skills for Claude Code, including drafting, evaluating with test prompts, iterating on performance, and improving skill descriptions for better triggering accuracy.