Recall/
├── skills/session/
│ └── scripts/ # Core scripts
│ ├── parse-transcript.py # JSONL → markdown parser (main script)
│ ├── condense-tail.py # Split + combine for large transcripts (>20K tokens)
│ ├── pre-compact.sh # PreCompact hook entry point
│ ├── post-compact.sh # Post-compaction stdout reminder
│ └── session-start.sh # SessionStart hook (cleanup)
├── tests/
│ ├── conftest.py # Shared fixtures, module imports
│ ├── generate_fixtures.py # Generates synthetic JSONL + markdown fixtures
│ ├── fixtures/ # Generated test data (fictional "TaskTracker" project)
│ │ ├── jsonl/ # 16 JSONL transcript variants
│ │ └── markdown/ # 5 parsed markdown variants
│ ├── test_parse_tool_calls.py
│ ├── test_parse_condensers.py
│ ├── test_parse_session.py
│ ├── test_parse_postprocess.py
│ └── test_condense_tail.py # Tests for split/combine logic
├── commands/
│ ├── session.md # /recall:session — manual session recall
│ ├── compact-on.md # /recall:compact-on — enable compaction integration
│ └── compact-off.md # /recall:compact-off — disable compaction integration
└── README.md # User-facing documentation
Claude Code's SessionStart hook stdout is silently dropped (bug).
However, CLAUDE.md IS re-read from disk after every compaction, and @file references are resolved during that re-read.
-
PreCompact hook (
pre-compact.sh):- Parses transcript → generates recall content
- If >20K tokens: condenses with a single
claude -p --model sonnetcall (~30-40s) - Writes result to
.claude/recall-context.mdin the project directory - Also writes to
/tmp/recall-<session-id>.mdas a persistent copy
-
Compaction happens:
- Claude Code re-reads CLAUDE.md from disk
- CLAUDE.md contains
@.claude/recall-context.md(first line) - The recall content is pulled into Claude's context automatically
-
SessionStart(compact) hook (
post-compact.sh):- Outputs a short stdout reminder to Claude to act on the recall transcript
-
SessionStart hook (
session-start.sh):- Empties
.claude/recall-context.md(writes placeholder comment) - Matcher
""matches ALL sources (startup, compact, resume, clear) - After compaction: safe because CLAUDE.md was already re-read (content consumed)
- Essential for parallel sessions — stale content from one session must not leak into another
- On fresh starts: catches leftovers from interrupted compactions
- Empties
- CLAUDE.md is read BEFORE hooks fire (confirmed by research)
- PreCompact runs BEFORE compaction → file has content when CLAUDE.md is re-read
- SessionStart runs AFTER CLAUDE.md is read → cleaning up doesn't affect current read
- Multiple sessions same project: PreCompact overwrites the file (last writer wins). SessionStart always cleans up, so parallel sessions don't see stale content.
- Interrupted compaction: If the user exits mid-compaction, the file may still have content. SessionStart on the next fresh session cleans it up.
Tail-preservation approach (replaced the old 48-parallel-Haiku-call approach that took 5 minutes):
- ≤20K tokens: Keep the full transcript as-is, no API call
- >20K tokens: Keep last ~15K tokens verbatim (snapping to exchange boundaries), summarize up to 85K of older context with a single Sonnet call
Produces ~17.5K token output (within the 15-20K target range, ~10% of Claude Code's 200K context window).
Each project needs four things (configured by /recall:compact-on):
@.claude/recall-context.mdas the first line of CLAUDE.md.claude/recall-context.mdfile (placeholder, auto-populated by hook).claude/recall-context.mdin.gitignore- Recall hooks in
.claude/settings.json(per-project, written by compact-on so users control which projects use it)
pytest tests/ -vAll 170 tests should pass. If pytest is not installed: pipx install pytest.
- After ANY change to scripts in
skills/session/scripts/— run the full suite - After modifying test fixtures — regenerate with
python3 tests/generate_fixtures.pythen run tests - Before committing — always verify all tests pass
- New script functionality — add tests covering the new behavior
- Bug fixes — add a regression test that reproduces the bug before fixing it
- New tool type support in
summarize_tool_call()— add parametrized test cases intest_parse_tool_calls.py - Changes to split/combine logic — add tests in
test_condense_tail.py - Changed JSONL parsing — add or update fixture variants in
generate_fixtures.py
All fixtures use a fictional "TaskTracker" Rust CLI project with user "alex". This is intentional — the Recall repo is public, so test data must contain zero personal information. When adding fixtures:
- Use paths like
~/projects/tasktracker/src/... - Use commands like
cargo test,cargo build,git commit - Use realistic but generic Rust code snippets
- Never use real usernames, project names, or private content
- Scripts have hyphens in names → imported via
importlibin conftest.py - HOME is monkeypatched to
/home/alexfor stable path shortening assertions - Token estimation:
len(text.encode('utf-8')) / 3.0 condense-tail.pyhandles splitting/combining, andpre-compact.shmakes a singleclaude -p --model sonnetcall when needed