Generated banner for cpr-compress-preserve-resume
Generated logo for cpr-compress-preserve-resume

cpr-compress-preserve-resume

Agent plugin

Created by Elia Alberti

Published in EliaAlberti/cpr-compress-preserve-resume. Publisher page

525 GitHub stars

What it is

Persistent memory for Claude Code, packaged as a plugin. Save, search, and restore conversation context across sessions.
— the description by Elia Alberti

AutoGineer overview, from the brain’s own files

CPR (Compress, Preserve & Resume) is a plugin for Claude Code designed to solve the problem of context loss between sessions. It enables AI agents to maintain a persistent state of work by saving structured session logs, updating project instructions, and restoring context when starting new sessions. By utilizing hooks that trigger during session starts and before compactions, it ensures that raw transcripts are archived and key summaries are readily available, significantly reducing the token costs associated with re-explaining project history.

How it works

Memory model: Key-value store

AutoGineer overview, from the brain’s own files

The system uses three slash commands and two automated hooks. /cpr:compress creates a structured markdown log of the session. /cpr:preserve updates the project's CLAUDE.md file with key learnings. /cpr:resume allows users to search past session summaries and restore context. The PreCompact hook automatically saves the raw transcript before any compaction occurs, while the SessionStart hook injects a memory brief into the agent's context at the beginning of a new session.

Folders
FolderRoleFiles
docsdocs1
examplesexamples1
hookshooks1
scriptsscripts6
skillsagent-instructions3
teststests1
.claude-pluginfolder2
videosfolder4

The agent writes

Requirements

Databases: Redis

Works with: Claude Code

Set it up

  1. This brain runs code automatically: 2 hooks (claude-code). Review hooks/hooks.json before opening the folder in Claude Code. No step below turns a hook on for you.

    • hooks/hooks.json: python3 "${CLAUDE_PLUGIN_ROOT}/scripts/precompact_save.py"
    • hooks/hooks.json: python3 "${CLAUDE_PLUGIN_ROOT}/scripts/session_start.py"
  2. Not scanned by AutoGineerThe author’s README also describes installing it as a Claude Code plugin marketplace. Read its install section: AutoGineer did not scan that path. https://github.com/EliaAlberti/cpr-compress-preserve-resume/blob/cfd1e77e07d2b3698591688e7e151d83d3ef51c3/README.md#installation
  3. Not scanned by AutoGineerThe author’s README also describes installing it from a clone of its repository. Read its install section: AutoGineer did not scan that path. https://github.com/EliaAlberti/cpr-compress-preserve-resume/blob/cfd1e77e07d2b3698591688e7e151d83d3ef51c3/README.md#installation
  4. Created by Elia Alberti. Published in EliaAlberti/cpr-compress-preserve-resume. Licence: MIT License.

Usage

AutoGineer overview, from the brain’s own files

  • Save session context before closing
  • Update project documentation with recent decisions
  • Restore context from previous sessions
  • Search historical session logs by topic
  • Automate transcript backups during compaction
“/cpr:compress”
The agent prompts the user to select key learnings and decisions to save, then generates a structured markdown log file in the CC-Session-Logs directory.
“/cpr:resume auth”
The agent displays a summary of recent sessions and lists related past sessions that match the topic 'auth'.

Documentation by Elia Alberti

<p align="center"> <img src="https://img.shields.io/badge/Claude%20Code-Plugin-5A67D8?style=for-the-badge&logo=data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCI+PHBhdGggZmlsbD0id2hpdGUiIGQ9Ik0xMiAyQzYuNDggMiAyIDYuNDggMiAxMnM0LjQ4IDEwIDEwIDEwIDEwLTQuNDggMTAtMTBTMTcuNTIgMiAxMiAyem0wIDE4Yy00LjQxIDAtOC0zLjU5LTgtOHMzLjU5LTggOC04IDggMy41OSA4IDgtMy41OSA4LTggOHoiLz48L3N2Zz4=&logoColor=white" alt="Claude Code Plugin" /> <img src="https://img.shields.io/badge/Version-2.0.0-E74C3C?style=for-the-badge" alt="Version 2.0.0" /> <img src="https://img.shields.io/badge/Skills-3%20%2B%202%20hooks-blue?style=for-the-badge" alt="3 skills and 2 hooks" /> <img src="https://img.shields.io/badge/License-MIT-yellow?style=for-the-badge" alt="MIT License" /> <img src="https://img.shields.io/badge/Token%20Savings-~66%25%20median-brightgreen?style=for-the-badge" alt="Token Savings ~66% median" /> </p>

<h1 align="center">🩺 CPR for Claude Code</h1> <h3 align="center"><em>Compress, Preserve & Resume</em></h3>

<p align="center"> <strong>Persistent memory across sessions. Never lose context again.</strong><br> <em>Estimated ~66% reduction in session-restart token cost (analytical model, range 51-74%).</em> </p>

<table> <tr> <td align="center" width="33%"> <strong>/cpr:preserve</strong><br> <sub>Save key learnings to CLAUDE.md</sub><br><br> <video src="https://github.com/user-attachments/assets/3c7e0f30-dea0-4c67-a990-021db84b6d81" width="100%" controls></video> </td> <td align="center" width="33%"> <strong>/cpr:compress</strong><br> <sub>Capture the full session to a searchable log</sub><br><br> <video src="https://github.com/user-attachments/assets/a31fe4b7-483e-47c1-8d78-01850482244f" width="100%" controls></video> </td> <td align="center" width="33%"> <strong>/cpr:resume</strong><br> <sub>Restore context from past sessions</sub><br><br> <video src="https://github.com/user-attachments/assets/1a3aff44-904d-463a-8f4e-6af6925e8a7a" width="100%" controls></video> </td> </tr> </table>

<p align="center"> Three skills and two hooks that save, search, and restore your conversation context, so you can pick up exactly where you left off. </p>

<p align="center"> <a href="#is-cpr-for-me">Is it for me?</a> &bull; <a href="#the-problem">Problem</a> &bull; <a href="#the-solution">Solution</a> &bull; <a href="#why-it-saves-tokens">Why It Saves Tokens</a> &bull; <a href="#installation">Installation</a> &bull; <a href="#usage">Usage</a> &bull; <a href="#recommended-workflow">Workflow</a> &bull; <a href="#upgrading-from-v1">Upgrading</a> &bull; <a href="#faq">FAQ</a> </p>


What Is CPR?

CPR is a Claude Code plugin. It adds three slash commands and two hooks:

PieceWhat it does
/cpr:compressWrites a structured, searchable session log. A script appends the raw transcript.
/cpr:preserveUpdates CLAUDE.md with the learnings every future session should start with.
/cpr:resumeLoads recent session summaries and searches past sessions by topic.
PreCompact hookSaves the raw transcript before every compaction, manual or automatic.
SessionStart hookHands the model a short memory brief when a session starts or compacts.

No build step, no dependencies beyond Python 3.10, and every file is plain markdown or a small script you can read.


Is CPR For Me?

Yes if you work on the same project across several Claude Code sessions and find yourself re-explaining what happened last time, or if you want the full conversation kept on disk and searchable by topic rather than flattened into a compaction summary.

No if your sessions are one-off fixes or throwaway scripts. There is no next session to pay the save back.

CPR sits next to CLAUDE.md and Claude Code's auto memory rather than replacing them. CLAUDE.md holds instructions, auto memory holds preferences, CPR holds the state of the work. Install takes one line, and removing it is /plugin uninstall cpr@cpr.


The Problem

Claude Code has no memory of the work between sessions. When you close a conversation, the decisions, the fixes, the paths and the reasons go with it.

It gets worse:

ProblemImpact
Compaction flattens detailsWhen the context window fills up, history is summarised and specific values, file paths and nuanced decisions get lost
Long sessions lose early contextThat critical decision from the first 10 minutes? Gone by hour two
New session = blank slateRe-explaining your project, re-discovering paths, re-making decisions every time
Past work is unsearchableNo way to look up what you discussed three sessions ago
CLAUDE.md and auto memory aren't enoughThey hold instructions and preferences, not the state of the work, the errors, or the solutions

The Solution

During work  ──> PreCompact hook  ──> raw transcript autosaved before any compaction
End session  ──> /cpr:preserve    ──> CLAUDE.md updated (optional)
                 /cpr:compress    ──> curated session log saved
New session  ──> SessionStart hook──> latest Quick Resume Context injected automatically
                 /cpr:resume      ──> last N summaries + topic search ──> full context restored

The skills are where the curated summaries come from. The hooks are the safety net for the sessions you forget to save.

CPR next to Claude Code's own memory

Claude Code now has auto memory and re-injects CLAUDE.md after compaction. CPR is built to sit next to both, not replace them:

CLAUDE.mdAuto memoryCPR session logs
HoldsInstructions and conventionsPreferences and correctionsDecisions, fixes, errors, pending work, the full transcript
Written byYou (and /cpr:preserve)Claude Code/cpr:compress and the hooks
Shared with the teamYes, via gitNo, machine-localYes, if you commit CC-Session-Logs/
Searchable by topicNoNoYes, /cpr:resume <topic>

Works With Auto-Compact

Earlier versions of CPR told you to disable auto-compact. You no longer need to. The PreCompact hook runs before both /compact and automatic compaction, receives the transcript path from Claude Code, and writes one autosave log per session into CC-Session-Logs/. After compaction, the SessionStart hook puts the latest Quick Resume Context back into context.

You still get the best results by running /cpr:compress before /compact, because that is where the curated summary comes from. The autosave only guarantees you never lose the raw conversation.


Why It Saves Tokens

Re-establishing context at the start of every session is expensive. The user re-explains the project, Claude re-reads files, prior decisions get re-derived, and conversations rebuild from scratch. CPR replaces that with a compact log of what mattered.

Across the modelled range, session-restart token cost drops by 51% in the low case, 66% in the median case, and 74% in the high case. On a 10-session project that's roughly ~99,450 tokens saved at the median. On a 20-session, high-context project it's ~579,500 tokens saved.

CaseWithout CPRWith CPRSavings% saved
Low4,8502,4002,45051%
Median16,7505,70011,05066%
High41,20010,70030,50074%

Two things make v2.0 cheaper than v1. The raw archive is appended by script, and the PreCompact hook does the same before any compaction, so none of it is model output. And /cpr:resume no longer opens files or re-reads CLAUDE.md, which Claude Code already loads: a script injects the summaries straight into the skill, and the session-start brief costs about 200 tokens.

These are analytical estimates, not telemetry. CPR is net positive for multi-session projects with cross-session context, and net negative for one-off bug fixes or single-session work. The breakeven is reached when the next session would otherwise repeat ~2,400 tokens of context-rebuild work, which most multi-session projects cross by session #2.

Full methodology, baseline scenarios, and per-component cost breakdown: docs/token-savings-analysis.md.


The Session Log System

Every /cpr:compress creates a structured markdown file in CC-Session-Logs/ at your project root. The hooks add autosave files to the same folder.

Filename format: YYYY-MM-DD-HH_MM-topic-name.md, so files sort chronologically in any listing.

<details> <summary><strong>Log structure (click to expand)</strong></summary>

# Session Log: 2026-03-05 14:20 - api-auth-refactor

## Quick Reference (for AI scanning)
**Confidence keywords:** auth, JWT, refresh-tokens, middleware
**Projects:** my-saas-app
**Outcome:** Replaced cookie-based auth with JWT + refresh tokens

## Decisions Made
- JWT over session cookies, stateless scales better

## Key Learnings
- Redis EX flag is cleaner than separate EXPIRE calls

## Solutions & Fixes
- Login race condition fixed with SETNX

## Files Modified
- `src/middleware/auth.ts`: JWT verification

## Pending Tasks
- [ ] Add refresh token rotation

---
## Quick Resume Context
2-3 sentence summary. This is what the SessionStart hook shows next time.

---
## Raw Session Log
{Full conversation archive, appended by script, searchable but never loaded by /cpr:resume}

</details>

The key insight: /cpr:resume only reads the summary sections (everything above "Raw Session Log"). The raw conversation is there for searchability, but it never wastes tokens during context loading.

And it's free to write, too. The raw log is appended by scripts/dump_transcript.py, which reads the Claude Code transcript straight from ~/.claude/projects/. Only the structured sections are model output.

See examples/session-log-example.md for a complete example.


Installation

Prerequisites
  • Claude Code with plugin support. Verified on v2.1.266.

  • Python 3.10 or newer on your PATH as python3

Inside Claude Code:

/plugin marketplace add EliaAlberti/cpr-compress-preserve-resume
/plugin install cpr@cpr

Or from your shell:

claude plugin marketplace add EliaAlberti/cpr-compress-preserve-resume
claude plugin install cpr@cpr

The install wires the skills and both hooks. Run /reload-plugins or restart Claude Code if you installed from inside a session. Updates arrive when the plugin version is bumped; /plugin shows what is installed and lets you update or uninstall.

Option B: skills directory

Clone the repo into your personal skills folder and Claude Code loads it as a plugin on the next start, hooks included:

git clone https://github.com/EliaAlberti/cpr-compress-preserve-resume.git ~/.claude/skills/cpr

git pull in that folder updates it. Edits to a SKILL.md apply immediately; changes to the hooks need /reload-plugins or a restart. If you prefer to keep a development clone elsewhere, a symlink at ~/.claude/skills/cpr pointing at it works the same way.

Option C: try it without installing
claude --plugin-dir /path/to/cpr-compress-preserve-resume

The skills and hooks are active for that session only. Pass the flag again next time.

Check it works
You: /cpr:compress

If the "What to preserve" selector appears, it's working. The hooks are active whenever the plugin is enabled: the next time you start a session in a project that already has logs, the memory brief is there before your first message.

One detail: the skills are user-invoked only, so they don't appear when you ask the model to list its skills. Type them.

Model

The skills run on whatever model your session uses. CPR is written for the current Claude 5 family and does not pin a model.


Usage

End of session: save your work
You: /cpr:compress
Claude: What would you like to preserve? [multi-select]
You: Key Learnings, Solutions & Fixes, Decisions Made, Files Modified
Claude: Anything specific to highlight? [Skip / Add a custom note]
You: Skip
Claude: Topic name: "api-auth-refactor" [Accept / Provide a different name]
You: Accept
Claude: Session Saved
        CC-Session-Logs/2026-03-05-17_30-api-auth-refactor.md
        Raw log: 84 turns appended

In a hurry? /cpr:compress quick skips the questions and saves every section that has content.

Update CLAUDE.md: preserve key learnings
You: /cpr:preserve
Claude: What should be preserved? [multi-select]
You: Key Decisions, Next Steps
Claude: CLAUDE.md updated
        - Added JWT auth decision rationale
        - Updated next steps with token rotation
        CLAUDE.md is now 185 lines (target: under 200)
Starting a new session: restore context

The moment a session starts, the SessionStart hook has already added the latest log's Quick Resume Context and pending tasks. For the full picture:

You: /cpr:resume
Claude:
══════════════════════════════════════════════
 RESUMING: my-saas-app
══════════════════════════════════════════════

CONTEXT:
- JWT auth flow implemented, tests passing
- Redis used for refresh token storage

MOST RECENT SESSION: 2026-03-05 17:30
Topic: api-auth-refactor
...

READY TO:
- Add refresh token rotation
- Set up token blacklist for logout
══════════════════════════════════════════════
Search past sessions by topic
You: /cpr:resume auth
Claude: [Shows recent sessions + RELATED SESSIONS matching "auth"]

══════════════════════════════════════════════
 RELATED SESSIONS (topic: "auth")
══════════════════════════════════════════════

- 2026-03-05: api-auth-refactor, JWT + refresh tokens
- 2026-02-28: oauth-google-setup, Google OAuth integration
══════════════════════════════════════════════

/cpr:resume 10 migration reads the last 10 sessions and searches for "migration". Topic search covers the raw logs too, so a detail you only mentioned in passing is still findable.


┌──────────────────────────────────────────────────────────┐
│  1. Start session                                        │
│     └── (brief injected)  then /cpr:resume if you need   │
│                           the last few sessions in full  │
│                                                          │
│  2. Do work...                                           │
│     └── compaction happens? the PreCompact hook has      │
│         already saved the raw transcript                 │
│                                                          │
│  3. Before ending or before /compact                     │
│     ├── /cpr:preserve     Update CLAUDE.md (optional)    │
│     └── /cpr:compress     Save the curated session log   │
└──────────────────────────────────────────────────────────┘

When to /cpr:preserve:

  • After making important architectural decisions

  • When you discover patterns you'll need in future sessions

  • When project phase or status changes

When to /cpr:compress:

  • Before ending a session

  • Before /compact, so the summary is written from the full conversation

  • After completing a significant chunk of work


Customisation

<details> <summary><strong>Session log storage path and mirroring</strong></summary>

By default, logs go to {project_root}/CC-Session-Logs/. The project root is the nearest parent directory containing CLAUDE.md or .git, falling back to the current directory.

To route logs elsewhere for some projects, or mirror them to a second folder, create ~/.claude/cpr.json:

{
  "routes": [
    {
      "when_cwd_startswith": "/Users/me/vault",
      "logs_dir": "/Users/me/Desktop/Session Logs",
      "mirror_to": ["/Users/me/vault/Areas/CC-Session-Logs"]
    }
  ]
}

The longest matching prefix wins. Every skill and hook honours it. Set CPR_CONFIG to use a different config file.

</details>

<details> <summary><strong>CLAUDE.md line target</strong></summary>

/cpr:preserve aims to keep CLAUDE.md under 200 lines, which is what Claude Code recommends for adherence. Change the number in skills/preserve/SKILL.md.

</details>

<details> <summary><strong>Protected and archivable sections</strong></summary>

Mark any section in your CLAUDE.md as immune to archiving:

## My Important Section (PROTECTED)

Or mark sections as safe to archive:

## Old Notes (ARCHIVABLE)

Archived content goes to CLAUDE-Archive.md next to your CLAUDE.md.

</details>

<details> <summary><strong>Turning the hooks off</strong></summary>

Delete the entry you don't want from hooks/hooks.json in your installed copy, or disable the plugin with /plugin. The skills work without the hooks.

</details>


Upgrading From v1

  • Your logs keep working. /cpr:resume reads the old DD-MM-YYYY filenames and the new ISO ones, and sorts them by real date.

  • Remove the old command files. Delete compress.md, preserve.md and resume.md from ~/.claude/commands/ (or .claude/commands/) and the ~/.claude/scripts/dump_transcript.py copy. Otherwise you have two of each.

  • The commands are namespaced now. /cpr:compress, /cpr:preserve, /cpr:resume. Claude Code ships its own /resume session picker, which is why CPR's version moved.

  • You can turn auto-compact back on. See Works With Auto-Compact.


How It Scales

Session logsBehaviour
Any count/cpr:resume reads summaries only, never raw logs, so it stays cheap at any scale
Any countTopic search is done by a script, across summaries first and raw logs second, capped at 10 matches
AutosavesOne file per session, refreshed on each compaction, so they never pile up

FAQ

<details> <summary><strong>Do I need all three skills?</strong></summary>

/cpr:compress + /cpr:resume is the minimum viable setup. /cpr:preserve is optional but recommended. It keeps your CLAUDE.md up to date without manual editing. The hooks come with the plugin and need no setup.

</details>

<details> <summary><strong>Where are logs stored?</strong></summary>

{project_root}/CC-Session-Logs/. Project root is the nearest parent directory containing CLAUDE.md or .git. If neither is found, it falls back to the current working directory. See Customisation for routing.

</details>

<details> <summary><strong>Will this work with any project?</strong></summary>

Yes. The skills auto-detect your project root and create the CC-Session-Logs/ folder on first use. No configuration needed.

</details>

<details> <summary><strong>How big do logs get?</strong></summary>

A full session log with the raw conversation can be several hundred KB. But the raw part is appended by script, not generated as model output, and /cpr:resume only reads the summary header (typically 30-80 lines), so token usage stays low regardless of log size.

</details>

<details> <summary><strong>Should I commit session logs to git?</strong></summary>

Up to you. They're useful for team knowledge sharing but can be large. Consider adding CC-Session-Logs/ to .gitignore if you prefer to keep them local.

</details>

<details> <summary><strong>What if I forget to /cpr:compress before /compact?</strong></summary>

The PreCompact hook saved the raw transcript for you. You lose the curated summary for that stretch, but the conversation is on disk and /cpr:resume <topic> can search it. Run /cpr:compress after compaction to write a summary from what remains in context.

</details>

<details> <summary><strong>Does the transcript parsing break when Claude Code changes?</strong></summary>

It can. Claude Code documents the transcript format as internal. CPR reads it defensively, takes the path from the hook input rather than guessing, and skips anything it doesn't recognise. If a release changes the layout, the raw log may come out shorter until the parser is updated. The curated summary never depends on it.

</details>

<details> <summary><strong>Can I use this with CLAUDE.md files in subdirectories?</strong></summary>

The skills look for CLAUDE.md at the project root. If you have multiple CLAUDE.md files (e.g., monorepo), run from the relevant subdirectory.

</details>


Development

llms.txt at the repo root is the AI-readable summary of the project. Keep it in step with this README.

python3 -m unittest discover -s tests -v   # script tests
claude plugin validate .                    # manifest check
claude --plugin-dir .                       # try the working copy

Credits

Created by Elia Alberti. Built with and for Claude Code.


License

MIT. See LICENSE.

tests/test_scripts.py

Shown as plain text.

"""Smoke tests for the CPR scripts. Run with: python3 -m unittest discover tests"""

from __future__ import annotations

import json
import os
import subprocess
import sys
import tempfile
import unittest
from pathlib import Path

ROOT = Path(__file__).resolve().parents[1]
SCRIPTS = ROOT / "scripts"
sys.path.insert(0, str(SCRIPTS))

import cpr_common as cc  # noqa: E402


def write_transcript(path: Path) -> None:
    rows = [
        {"type": "user", "message": {"content": "<local-command-stdout>ignored</local-command-stdout>"}},
        {"type": "user", "message": {"content": "Hello there<system-reminder>hidden</system-reminder>"}},
        {"type": "assistant", "message": {"content": [
            {"type": "thinking", "thinking": "secret"},
            {"type": "text", "text": "Hi! Let me look."},
            {"type": "tool_use", "name": "Read", "input": {}},
        ]}},
        {"type": "user", "message": {"content": [{"type": "tool_result", "content": "file body"}]}},
        {"type": "user", "isSidechain": True, "message": {"content": "subagent prompt"}},
        {"type": "assistant", "message": {"content": [{"type": "text", "text": "Done."}]}},
        {"type": "attachment", "message": {"content": "meta"}},
    ]
    path.write_text("\n".join(json.dumps(r) for r in rows) + "\n", encoding="utf-8")


class TranscriptTests(unittest.TestCase):
    def test_extract_turns_keeps_only_main_thread_text(self):
        with tempfile.TemporaryDirectory() as tmp:
            t = Path(tmp) / "s.jsonl"
            write_transcript(t)
            turns = list(cc.extract_turns(t))
        self.assertEqual(turns, [("User", "Hello there"), ("Assistant", "Hi! Let me look."), ("Assistant", "Done.")])

    def test_dump_transcript_appends_section(self):
        with tempfile.TemporaryDirectory() as tmp:
            t = Path(tmp) / "s.jsonl"
            write_transcript(t)
            log = Path(tmp) / "2026-09-09-10_00-topic.md"
            log.write_text("# Session Log\n\n## Quick Resume Context\nx\n", encoding="utf-8")
            out = subprocess.run(
                [sys.executable, str(SCRIPTS / "dump_transcript.py"), str(log), "--transcript", str(t)],
                capture_output=True, text=True, cwd=tmp, env={**os.environ, "CPR_CONFIG": "/nonexistent"},
            )
            self.assertEqual(out.returncode, 0, out.stderr)
            self.assertIn("Appended 3 turns", out.stdout)
            text = log.read_text(encoding="utf-8")
        self.assertIn(cc.RAW_MARKER, text)
        self.assertIn("**User:**\nHello there", text)
        self.assertNotIn("secret", text)


class NamingTests(unittest.TestCase):
    def test_parses_iso_and_legacy_names_and_sorts_by_date(self):
        with tempfile.TemporaryDirectory() as tmp:
            d = Path(tmp)
            for name in ("02-01-2026-09_00-old-year.md", "2026-09-09-10_00-new.md", "31-12-2025-23_59-older.md"):
                (d / name).write_text("# x\n", encoding="utf-8")
            rows = cc.list_logs(d)
        self.assertEqual([r[0].name for r in rows], ["2026-09-09-10_00-new.md", "02-01-2026-09_00-old-year.md", "31-12-2025-23_59-older.md"])

    def test_autosave_detection(self):
        self.assertTrue(cc.is_autosave("2026-09-09-10_00-autosave-auto-compact-abcd1234.md"))
        self.assertFalse(cc.is_autosave("2026-09-09-10_00-api-auth.md"))


class RoutingTests(unittest.TestCase):
    def test_default_paths_use_project_root(self):
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp).resolve()
            (root / "CLAUDE.md").write_text("# p\n", encoding="utf-8")
            sub = root / "a" / "b"
            sub.mkdir(parents=True)
            old = cc.CONFIG_PATH
            cc.CONFIG_PATH = Path("/nonexistent")
            try:
                paths = cc.resolve_paths(sub)
            finally:
                cc.CONFIG_PATH = old
        self.assertEqual(paths["project_root"], root)
        self.assertEqual(paths["logs_dir"], root / cc.LOG_DIR_NAME)
        self.assertEqual(paths["mirrors"], [])

    def test_route_overrides_logs_dir_and_mirrors(self):
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp).resolve()
            cfg = root / "cpr.json"
            cfg.write_text(json.dumps({"routes": [{
                "when_cwd_startswith": str(root / "vault"),
                "logs_dir": str(root / "desk"),
                "mirror_to": [str(root / "mirror")],
            }]}), encoding="utf-8")
            (root / "vault" / "x").mkdir(parents=True)
            old = cc.CONFIG_PATH
            cc.CONFIG_PATH = cfg
            try:
                paths = cc.resolve_paths(root / "vault" / "x")
            finally:
                cc.CONFIG_PATH = old
        self.assertEqual(paths["logs_dir"], root / "desk")
        self.assertEqual(paths["mirrors"], [root / "mirror"])


class HookTests(unittest.TestCase):
    def run_hook(self, script: str, payload: dict, cwd: str):
        return subprocess.run(
            [sys.executable, str(SCRIPTS / script)], input=json.dumps(payload), capture_output=True,
            text=True, cwd=cwd, env={**os.environ, "CPR_CONFIG": "/nonexistent"},
        )

    def test_precompact_writes_one_autosave_per_session_and_session_start_reads_it(self):
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp).resolve()
            (root / ".git").mkdir()
            t = root / "s.jsonl"
            write_transcript(t)
            payload = {"session_id": "abcd1234-ffff", "transcript_path": str(t), "cwd": str(root), "trigger": "auto"}
            first = self.run_hook("precompact_save.py", payload, str(root))
            second = self.run_hook("precompact_save.py", payload, str(root))
            self.assertEqual(first.returncode, 0, first.stderr)
            self.assertEqual(second.returncode, 0, second.stderr)
            files = list((root / cc.LOG_DIR_NAME).glob("*.md"))
            self.assertEqual(len(files), 1)
            self.assertIn("autosave-auto-compact-abcd1234", files[0].name)

            curated = root / cc.LOG_DIR_NAME / "2026-09-09-09_00-curated-topic.md"
            curated.write_text(
                "# Session Log: 2026-09-09 09:00 - curated-topic\n\n## Quick Reference (for AI scanning)\n"
                "**Confidence keywords:** a, b\n**Outcome:** Shipped the thing\n\n## Pending Tasks\n- [ ] Next step one\n\n---\n\n"
                "## Quick Resume Context\nWe shipped the thing. Next is step one.\n\n---\n\n## Raw Session Log\n\nhidden\n",
                encoding="utf-8",
            )
            brief = self.run_hook("session_start.py", {"cwd": str(root)}, str(root))
            self.assertEqual(brief.returncode, 0, brief.stderr)
        self.assertIn("Outcome: Shipped the thing", brief.stdout)
        self.assertIn("Next step one", brief.stdout)
        self.assertIn("autosaved transcript", brief.stdout)
        self.assertNotIn("hidden", brief.stdout)

    def test_session_start_is_silent_without_logs(self):
        with tempfile.TemporaryDirectory() as tmp:
            out = self.run_hook("session_start.py", {"cwd": tmp}, tmp)
        self.assertEqual(out.returncode, 0)
        self.assertEqual(out.stdout, "")


class ResumeContextTests(unittest.TestCase):
    def test_topic_search_and_recent_summaries(self):
        with tempfile.TemporaryDirectory() as tmp:
            root = Path(tmp).resolve()
            (root / ".git").mkdir()
            logs = root / cc.LOG_DIR_NAME
            logs.mkdir()
            (logs / "2026-09-01-10_00-alpha.md").write_text("# a\n**Outcome:** alpha done\n\n## Raw Session Log\n\nzebra secret\n", encoding="utf-8")
            (logs / "2026-09-02-10_00-beta.md").write_text("# b\n**Outcome:** beta done\n\n## Raw Session Log\n\nnothing\n", encoding="utf-8")
            out = subprocess.run(
                [sys.executable, str(SCRIPTS / "resume_context.py"), "1", "zebra"],
                capture_output=True, text=True, cwd=str(root), env={**os.environ, "CPR_CONFIG": "/nonexistent"},
            )
        self.assertEqual(out.returncode, 0, out.stderr)
        self.assertIn("LOG 1 of 1: 2026-09-02-10_00-beta.md", out.stdout)
        self.assertIn("[raw log]", out.stdout)
        self.assertIn("alpha", out.stdout)


if __name__ == "__main__":
    unittest.main()
25 files · 3 not served here

Advantages

  • Reduces token costs by providing a compact summary of past work instead of re-explaining project history. evidence: README.md
  • Ensures no data loss by automatically saving raw transcripts before session compaction. evidence: README.md

Limitations

  • Not beneficial for one-off tasks or single-session work where no future context is needed. evidence: README.md

Security

Risk level: Low risk

This brain runs

Hooks (2)

  • hooks/hooks.json (PreCompact): python3 "${CLAUDE_PLUGIN_ROOT}/scripts/precompact_save.py"
  • hooks/hooks.json (SessionStart): python3 "${CLAUDE_PLUGIN_ROOT}/scripts/session_start.py"

Agent instructions that run or fetch something (7)

  • skills/compress/SKILL.md: Run it before `/compact` or before ending the session.
  • skills/compress/SKILL.md: !`python3 "${CLAUDE_PLUGIN_ROOT}/scripts/cpr_paths.py"`
  • skills/compress/SKILL.md: Empty: run all three questions.
  • skills/compress/SKILL.md: After the file is saved, run:
  • skills/compress/SKILL.md: python3 "${CLAUDE_PLUGIN_ROOT}/scripts/dump_transcript.py" "{logs_dir}/{filename}" ${CLAUDE_SESSION_ID}
  • skills/preserve/SKILL.md: !`python3 "${CLAUDE_PLUGIN_ROOT}/scripts/cpr_paths.py"`
  • skills/resume/SKILL.md: !`python3 "${CLAUDE_PLUGIN_ROOT}/scripts/resume_context.py" $ARGUMENTS`
  • Low risk: scripts-present (scripts/precompact_save.py)
  • Low risk: scripts-present (scripts/session_start.py)

Attribution and licence

Created by
Elia Alberti
Declared authors
Publisher
Elia Alberti (Community)
Source
EliaAlberti/cpr-compress-preserve-resume · commit cfd1e77
Licence
MIT License (files served here)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by Elia Alberti.

Install from source

3 of its files are not served here, so there is no ZIP. EliaAlberti/cpr-compress-preserve-resume at cfd1e77