Releases: Vasilispapg/claude-handoff
Release list
0.20.0 — graphify bridge, brief mirrors, /claude-handoff skill
--install-skill— the tool teaches Claude to drive it: ships
the/claude-handoffClaude Code skill (canonical copy at
skills/claude-handoff/SKILL.md, embedded asSKILL_MDso pip,
brew and the single file all carry it — pinned byte-identical by a
test) and installs it under~/.claude/skills/claude-handoff/plus
an idempotent trigger section in~/.claude/CLAUDE.md— a
hand-written# claude-handoffheading is respected, never
duplicated.--uninstall-skillremoves both; every other line of
CLAUDE.md survives byte-for-byte (round-trip test-pinned). The
skill's steps encode the traps an agent actually falls into without
it (baseline-measured): flags-not-subcommands,claude-cli=
subscription vsclaude= API key,--fit(not--max-tokens)
for budgets, barechfinside a live session exporting the CURRENT
session (--list+--name <id>reaches the crashed one), and no
TTY pickers (-i, bare--exclude) from an agent shell.- The
--excludepicker remembers: bare--excludenow opens the
numbered list with the stamp's stored exclusions pre-selected (✗),
and typed numbers TOGGLE — add a new exclusion or un-exclude an old
one without re-picking the whole set. Empty input keeps the ✗ set as
is (before, it silently replaced the stored set with nothing), and
noneinside the picker clears everything, consistent with
--exclude none. With no stored set the picker behaves exactly as
before. - graphify bridge — both directions, zero config:
-o graphify
files the (redacted) document intoraw/— graphify's ingest
folder — with the YAML frontmatter graphify maps onto nodes
(captured_at,source_url,contributor). The brief becomes ONE
evolvingraw/project-memory.md(grafted distillation included,
overwritten so the graph always holds the current state — git owns
the history), a handoff becomesraw/session-<id>.md; the next
/graphify --updatelinks your decisions, fixes and open threads
into the code's knowledge graph. Never invokes graphify itself
(tools stay decoupled — the next step is printed instead), warns
whenraw/isn't gitignored, and--brief --grep -o graphifyis a
loud error so a thematic export can't overwrite the standing corpus
doc. ## Code mapin the brief: whengraphify-out/graph.jsonsits
next to the project, the deterministic skeleton — and so the memory
injected at session start — gains a capped structural map:
node/edge/community counts, the top communities with their hub
concepts (labels from.graphify_labels.jsonwhen present), the
graph's build date, and how many commits it is behind HEAD (via
built_at_commitagainst the reflog). Free, automatic, and a silent
no-op without a graph — any surprise in the file means no section,
never an error.- Code map carries knowledge, not just structure: a
bridges:line
names the strongest links that CROSS community boundaries (endpoint,
relation, and the two communities — where the subsystems actually
touch) and aflows:line lists graphify's labeled hyperedges
(multi-node flows and patterns). Both capped at 3, both straight from
graph.json, both silently absent when the graph has none. - The brief lives in the project too: in-project copies refresh
automatically whenever the store brief is (re)written — by an
explicitchf --briefAND by the SessionEnd hook. Two mirrors, both
strictly refresh-only (the hook never creates files): create
raw/project-memory.mdonce with-o graphifyand it stays fresh
for/graphify --update;touch BRIEF.mdat the repo root and a
plain human/git copy of the exact store text stays fresh there.
Delete either file to opt out.
0.19.0 — memory you can aim: thematic, time-windowed, from any history
-
Standing memory from a web history:
chf conversations.json --brief -o brief.mddistills EVERY conversation of a claude.ai or
ChatGPT data export into one cited brief (--llmincluded — notes
cached per conversation). Explicit-oonly: an export brief never
touches the project store.--grep/--keep/--excludecompose
in-memory. -
Thematic memory —
--brief --grep X: distill only the sessions
whose conversation mentions X (AND across repeated flags, said text
only). An export by design: it requires-oand never overwrites or
grafts from the project's standing brief. -
Time windows —
--keep since:7d: alongside the count windows,
keep sessions by last activity (7d/2hrelative to now, or an ISO
date), combinable asfirst:2,since:30d— sticky like the rest, so
the time window slides on every refresh. -
--brief -o clipboard: ships the current brief — grafted
distillation included, like the default destination — straight to the
clipboard: one paste hands your project memory to any other model.
Previously this wrote a literal file namedclipboard. -
--brief --format jsonfails loudly instead of silently ignoring
the flag.
0.18.0 — curated project memory that hands over a plan
-
Curate what feeds the memory —
--excludeand--keep:
--brief --exclude ID[,ID…]leaves sessions out of the brief entirely
(timeline, counts, notes, freshness nags); bare--excludeopens a
numbered multi-select picker (date, id, title —1,3/2-4).
--keep first:2,last:20(bare20=last:20) windows a huge
history to the founding sessions plus the recent ones. Both are
sticky: stored in the brief's stamp, re-applied by hooks and every
refresh (a sliding window, in--keep's case), cleared with
--exclude none/--keep all. Typo'd exclude prefixes warn and are
not stored; excluding everything is a clean error. -
The brief now says what you're building: the distilled memory
opens with a## What this issection — the 2-3 sentences a stranger
(or another model) needs first: product, stack, current state. Added
only to the final reduce prompt, so every cached per-session note
survives — refreshing an existing brief re-runs a single LLM call. -
The brief maps state and hands over a resume plan: a new
## Where things standsection separates shipped-and-verified from
in-flight (naming exactly where it stopped) from not-yet-started, and
## Open threadsis now ordered by what a resuming session should
pick up first — concrete next action per bullet, tagged [in flight] /
[blocked] / [not started]. Citations now cover every section,
## What this isincluded; word budget 600 → 800. Reduce-prompt-only
again: cached notes survive, a refresh is one LLM call. -
A distillation is never silent:
--brief --llmnow prints its
plan ("Distilling N session(s) — at least M LLM call(s)…") before the
first call, then drives the same live progress machinery as the
handoff pipeline — on a TTY a bar with elapsed time and ETA that ticks
during each call, one plain stderr line per event otherwise. Large
sessions announce their part count upfront and the bar's total grows
as parts are discovered; cache hits show as completed steps. -
--llm claude-clialways bills the CLI login: a stray
ANTHROPIC_API_KEY/ANTHROPIC_AUTH_TOKENexported in the shell is
now scrubbed from the spawnedclaude -p— the env key used to win
over the subscription login, failing runs with "Credit balance is too
low" on an empty Console account (and silently rebilling on a funded
one).
0.17.0 — the handoff reads like a summary
Born from dogfooding a 270 MB, 13-day, 88-subagent session whose handoff read like an inventory dump. Every change is visible on that same session.
The transcript is a digest by default
## Conversation now renders one condensed bullet per turn — user ~300 chars, assistant ~500, every cut visible as …. Conversation coverage on the monster session: ~6% of turns (verbatim head+tail) → ~43%, still ending at the current state. --full restores the classic verbatim transcript. LLM summarization (--llm, --brief) always reads the full text — never the digest.
Task-notifications are not the human
<task-notification> records render as one-line 🔔 turns (summary only, entities unescaped, payload dropped) and are counted separately. That session's "333 user messages" were really 94 human + 240 background notifications — now the header says exactly that.
A file inventory that doesn't eat your context
Top 15 files by edit count (was: every file, alphabetically — 546 bullets), paths relative to the project root the header already names, scratchpad/temp files folded into one line. Full absolute list: --format json.
Numbers a reader can trust
- Tokens split cache re-reads:
39,185,271 in / 7,722,484 out (+3,900,024,478 cached reads)instead of one meaningless 3.9-billion "in" figure. - Assistant replies count merged turns (what you see), not API records (~3× fewer).
- Subagent tool calls shown as
(+9,049 in subagents)— the command list can no longer exceed the tool-call count. gitBranch: "HEAD"renders as(detached HEAD).
Small but real
- ℹ stderr heads-up when the written document contains email addresses, pointing at
--anonymize(default redaction targets secrets, not identity — deliberately). - Sessions that dispatched subagents are never auto-skipped as trivial.
<local-command-caveat>wrappers stripped as noise.- The curl single-file build gained a missing
import html(would have crashed on notifications); a notification fixture now runs insidebuild_single.py --checkso a missing runtime import can't slip through again.
187 tests, byte-identical package/single-file builds, docs updated throughout (GUIDE, DEVELOPMENT).
v0.16.0
- Plain
--briefno longer discards a paid distillation: rebuilding
the free skeleton onto the stamped brief file now grafts the existing
distilled section (with freshness notes re-derived) and carries the
distillation stamp forward — same behavior the SessionEnd refresh
always had. An explicit-oelsewhere still writes a plain skeleton. - Git-aware freshness: the brief now knows the repo, not just the
session store. The skeleton carries aRepo HEADline (short hash +
date + subject of the last commit); the SessionEnd refresh and the
SessionStart injection warn when commits landed after the last
distillation ("N commit(s) newer than this memory"), so outdated
memory is never trusted silently. Read straight from
.git/logs/HEAD— nogitsubprocess, keeping the
deterministic-mode invariant; silently absent outside a repo. - Brief layout fix: the LLM-distilled sections (Decisions / Fixed /
Conventions / Open threads) now render as###sub-headings nested
under## Distilled memoryinstead of rivaling it. Demoted
deterministically at assembly — prompts unchanged, so every cached
session note survives.
v0.15.0 — no size limit for project memory; multi paths, --grep AND, --project OR
- No size limit for project memory: sessions beyond the per-note
budget are now map-reduced inside the note (chunk notes on turn
boundaries + one synthesis, all cached) — the brief inherits the
same nothing-silently-dropped guarantee as--llm, at any size. - Multi params:
chf a.jsonl b.jsonl …merges several sessions
into one handoff;--grep X --grep Yrequires ALL terms (AND);
--project A --project Bsearches several projects together. - Docs: new
docs/GUIDE.md(day-with-it walkthrough,--brief
step-by-step, honest cost table, cheatsheet); a second demo GIF shows
project memory end-to-end; brief/Using latest sessionmessages print
~-collapsed paths.
v0.14.0 — injection defense at every LLM boundary, PreCompact snapshots, --debug
- Prompt-injection defense: every LLM prompt that consumes a
transcript, the handoff preamble, and the brief injection wrapper
now explicitly frame session content as data, not instructions —
pinned by tests. (Prompts changed → the chunk cache version bumped
and brief notes re-distill once on next refresh.) - PreCompact hooks:
--install-hookand--install-brief-hook
also register PreCompact — a handoff snapshot before compaction
squeezes detail away, and a free brief-skeleton refresh
mid-session. - Session paths in messages are now ~-collapsed; the README opens
with a 5-second vhs demo (docs/assets/demo.taperegenerates it). - Tolerant, not mute: hooks now report swallowed errors on stderr
(still exit 0, stdout untouched);--grepsays how many unreadable
files it skipped; new--debugflag /CLAUDE_HANDOFF_DEBUG=1
surfaces tolerated failures (corrupt JSONL lines) with file and
line number.
v0.13.0 — Project memory: --brief distills your whole session history, hooks inject it at session start
- Project memory —
--brief: distill EVERY session of a project
into one living document at~/.claude/briefs/<project>.md— a
factual session timeline + most-touched files, and with--llma
distilled memory (Decisions / Fixed / Conventions / Open threads),
every bullet citing the session it came from. Per-session notes are
cached, so refreshing after new sessions costs only the new ones. --install-brief-hook: two hooks — SessionStart injects the
brief as context (Claude starts already knowing the project);
SessionEnd auto-refreshes the factual skeleton for free. Hooks
never trigger LLM calls or create files; a freshness stamp makes
staleness visible in the file and as an injection warning, and the
timeline is capped at 20 bullets so injection stays bounded.- Internals:
_resolve_providerextracted (shared key/model
resolution), hook settings editing generalized, single-file build
now handles multi-line module docstrings.
v0.12.0 — --anonymize, picker multi-select, MCP --allow-llm, JSON list, config defaults
--anonymize: strip identity for public sharing — home paths
collapse to~, emails/IPv4s/username become placeholders. Opt-in,
applied at the same egress seam as redaction; also available as an
anonymizeargument on the MCPhandofftool.- Picker multi-select:
-iaccepts1,3or2-4and merges the
picked sessions into one handoff. --mcp --allow-llm: explicit opt-in that lets MCP clients
request LLM-written summaries (llm/model/focustool args);
without it the server stays strictly deterministic.--list --format json: machine-readable session listing (path,
id, project, mtime, size, title, prompt, 🔍 match with--grep).- Config defaults:
~/.config/claude-handoff/config.json
(CLAUDE_HANDOFF_CONFIGoverrides) feeds allow-listed defaults —
CLI flags always win;no_redactis deliberately not configurable.
v0.11.0 — package split, single-file curl build, 6× faster grep
- Package split: the single 2100-line module became the
claude_handoff/package — nine single-responsibility modules with
acyclic imports (textutil,redact,parse,webexport,
discovery,render,llm,integrations,cli). Proven
mechanical: outputs byte-identical before/after on all fixtures and
a real multi-agent session. - curl still works:
scripts/build_single.pystitches the package
into the generatedsingle/claude_handoff.py; CI fails if it goes
stale or drifts behaviorally (--check). - Performance:
load_recordsstreams (peak RSS on a 20 MB
session: 80 MB → 26 MB);--grepprefilters with a raw-text
superset scan in binary mode when JSON escaping cannot hide a
match (worst-case store-wide search 3.3 s → 0.5 s). - Lint: ruff config in
pyproject.toml(E/F/I/PLW/RUF), codebase
clean; new CI lint job.