Skip to content

Releases: Vasilispapg/claude-handoff

0.20.0 — graphify bridge, brief mirrors, /claude-handoff skill

Choose a tag to compare

@Vasilispapg Vasilispapg released this 27 Aug 18:35
  • --install-skill — the tool teaches Claude to drive it: ships
    the /claude-handoff Claude Code skill (canonical copy at
    skills/claude-handoff/SKILL.md, embedded as SKILL_MD so 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-handoff heading is respected, never
    duplicated. --uninstall-skill removes 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 vs claude = API key, --fit (not --max-tokens)
    for budgets, bare chf inside 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 --exclude picker remembers: bare --exclude now 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
    none inside 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 into raw/ — graphify's ingest
    folder — with the YAML frontmatter graphify maps onto nodes
    (captured_at, source_url, contributor). The brief becomes ONE
    evolving raw/project-memory.md (grafted distillation included,
    overwritten so the graph always holds the current state — git owns
    the history), a handoff becomes raw/session-<id>.md; the next
    /graphify --update links 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
    when raw/ isn't gitignored, and --brief --grep -o graphify is a
    loud error so a thematic export can't overwrite the standing corpus
    doc.
  • ## Code map in the brief: when graphify-out/graph.json sits
    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.json when present), the
    graph's build date, and how many commits it is behind HEAD (via
    built_at_commit against 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 a flows: 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
    explicit chf --brief AND by the SessionEnd hook. Two mirrors, both
    strictly refresh-only (the hook never creates files): create
    raw/project-memory.md once with -o graphify and it stays fresh
    for /graphify --update; touch BRIEF.md at 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

Choose a tag to compare

@Vasilispapg Vasilispapg released this 26 Aug 19:14
  • Standing memory from a web history: chf conversations.json --brief -o brief.md distills EVERY conversation of a claude.ai or
    ChatGPT data export into one cited brief (--llm included — notes
    cached per conversation). Explicit -o only: an export brief never
    touches the project store. --grep / --keep / --exclude compose
    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 -o and 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/2h relative to now, or an ISO
    date), combinable as first: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 named clipboard.

  • --brief --format json fails loudly instead of silently ignoring
    the flag.

0.18.0 — curated project memory that hands over a plan

Choose a tag to compare

@Vasilispapg Vasilispapg released this 26 Aug 18:23
  • Curate what feeds the memory — --exclude and --keep:
    --brief --exclude ID[,ID…] leaves sessions out of the brief entirely
    (timeline, counts, notes, freshness nags); bare --exclude opens a
    numbered multi-select picker (date, id, title — 1,3 / 2-4).
    --keep first:2,last:20 (bare 20 = 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 is section — 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 stand section separates shipped-and-verified from
    in-flight (naming exactly where it stopped) from not-yet-started, and
    ## Open threads is 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 is included; word budget 600 → 800. Reduce-prompt-only
    again: cached notes survive, a refresh is one LLM call.

  • A distillation is never silent: --brief --llm now 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-cli always bills the CLI login: a stray
    ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN exported in the shell is
    now scrubbed from the spawned claude -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

Choose a tag to compare

@Vasilispapg Vasilispapg released this 26 Aug 16:49

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 inside build_single.py --check so 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

Choose a tag to compare

@Vasilispapg Vasilispapg released this 26 Aug 10:52
  • Plain --brief no 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 -o elsewhere still writes a plain skeleton.
  • Git-aware freshness: the brief now knows the repo, not just the
    session store. The skeleton carries a Repo HEAD line (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 — no git subprocess, 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 memory instead 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

Choose a tag to compare

@Vasilispapg Vasilispapg released this 26 Aug 08:20
  • 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 Y requires ALL terms (AND);
    --project A --project B searches 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 session messages print
    ~-collapsed paths.

v0.14.0 — injection defense at every LLM boundary, PreCompact snapshots, --debug

Choose a tag to compare

@Vasilispapg Vasilispapg released this 26 Aug 07:15
  • 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-hook and --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.tape regenerates it).
  • Tolerant, not mute: hooks now report swallowed errors on stderr
    (still exit 0, stdout untouched); --grep says how many unreadable
    files it skipped; new --debug flag / 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

Choose a tag to compare

@Vasilispapg Vasilispapg released this 25 Aug 22:14
  • 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 --llm a
    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_provider extracted (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

Choose a tag to compare

@Vasilispapg Vasilispapg released this 25 Aug 22:14
  • --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
    anonymize argument on the MCP handoff tool.
  • Picker multi-select: -i accepts 1,3 or 2-4 and merges the
    picked sessions into one handoff.
  • --mcp --allow-llm: explicit opt-in that lets MCP clients
    request LLM-written summaries (llm/model/focus tool 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_CONFIG overrides) feeds allow-listed defaults —
    CLI flags always win; no_redact is deliberately not configurable.

v0.11.0 — package split, single-file curl build, 6× faster grep

Choose a tag to compare

@Vasilispapg Vasilispapg released this 25 Aug 20:11
  • 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.py stitches the package
    into the generated single/claude_handoff.py; CI fails if it goes
    stale or drifts behaviorally (--check).
  • Performance: load_records streams (peak RSS on a 20 MB
    session: 80 MB → 26 MB); --grep prefilters 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.