Skip to content

feat(hooks): add Claude PreCompact snapshot hook#224

Open
Gradata wants to merge 3 commits into
mainfrom
gra-1210-pre-compact
Open

feat(hooks): add Claude PreCompact snapshot hook#224
Gradata wants to merge 3 commits into
mainfrom
gra-1210-pre-compact

Conversation

@Gradata
Copy link
Copy Markdown
Owner

@Gradata Gradata commented May 26, 2026

Summary

  • write Claude Code PreCompact snapshots to /.precompact-snapshots/.json
  • wire gradata install --agent claude-code to add a PreCompact hook entry
  • add regression coverage for stdin snapshot writes and Claude Code settings.json wiring

Verification

  • python3 -m pytest tests/test_hook_adapters.py tests/test_cli_install_agent.py tests/test_pre_compact_hook.py -q
  • python3 -m py_compile src/gradata/hooks/pre_compact.py src/gradata/hooks/adapters/_base.py src/gradata/hooks/adapters/claude_code.py tests/test_pre_compact_hook.py tests/test_hook_adapters.py
  • isolated HOME smoke: gradata.cli install --agent claude-code writes PreCompact command containing -m gradata.hooks.pre_compact

Closes GRA-1210

Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 26, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: ef6bd757-c59a-4556-b268-b0a612a6620a

📥 Commits

Reviewing files that changed from the base of the PR and between 4087ea4 and 31c8e64.

📒 Files selected for processing (3)
  • Gradata/src/gradata/hooks/adapters/claude_code.py
  • Gradata/src/gradata/hooks/pre_compact.py
  • Gradata/tests/test_hook_adapters.py
📜 Recent review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: pytest windows-latest / py3.11
  • GitHub Check: pytest windows-latest / py3.12
🧰 Additional context used
📓 Path-based instructions (2)
Gradata/tests/**/*.py

📄 CodeRabbit inference engine (Gradata/AGENTS.md)

Gradata/tests/**/*.py: Set BRAIN_DIR environment variable via tmp_path in conftest.py for test isolation — ensure _paths.py module cache refreshes when calling Brain.init() directly inside tests
Add unit tests in tests/test_*.py for every CI push without LLM calls (deterministic); mark integration tests with @pytest.mark.integration and skip them by default (they hit real LLM APIs)

Files:

  • Gradata/tests/test_hook_adapters.py
Gradata/src/**/*.py

📄 CodeRabbit inference engine (Gradata/AGENTS.md)

Gradata/src/**/*.py: Prefer sentence-transformers for local embeddings, google-genai for Gemini embeddings, cryptography for AES-GCM encrypted system.db, bm25s for BM25 rule ranking, and mem0ai for external memory adapters — guard all optional dependency imports with try / except ImportError at the call site, never at module level
Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/) must never import from Layer 1 (Enhancements: enhancements/, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)
Never use bare except: pass — use typed exceptions or at minimum logger.warning(...) with exc_info=True to avoid silent failure in a memory product
Never import from out-of-scope sibling directories ../Sprites/ or ../Hausgem/ within gradata/* code — that is a layering bug
Never leak private-sibling paths into public docs/code — no references to ../Sprites/, ../Hausgem/, email addresses, OneDrive paths, or Sprites-specific examples from inside gradata/*
Use atomic-write helper when writing JSON files to prevent corruption from mid-write crashes

Files:

  • Gradata/src/gradata/hooks/pre_compact.py
  • Gradata/src/gradata/hooks/adapters/claude_code.py
🔇 Additional comments (3)
Gradata/src/gradata/hooks/pre_compact.py (1)

7-7: LGTM!

Also applies to: 26-26, 53-54, 56-56, 109-109

Gradata/src/gradata/hooks/adapters/claude_code.py (1)

32-36: LGTM!

Also applies to: 38-60, 164-165, 173-174

Gradata/tests/test_hook_adapters.py (1)

5-5: LGTM!

Also applies to: 87-87, 96-124


📝 Walkthrough

Walkthrough

This PR adds a PreCompact snapshot mechanism that atomically writes JSON snapshots (with sanitized session IDs and captured context files) under .precompact-snapshots/, provides command/signature helpers, integrates PreCompact hook install/uninstall in the claude-code adapter, and includes tests for adapter wiring and end-to-end snapshot creation.

Changes

Pre-compact hook snapshot and integration

Layer / File(s) Summary
Pre-compact snapshot mechanism
src/gradata/hooks/pre_compact.py
New snapshot persistence writes atomic JSON files to .precompact-snapshots/ with sanitized session ID, captured context files (lessons.md, rules.md, meta-rules.json, brain.manifest.json, handoff.md, handoff.json) with truncation, and schema metadata (timestamps, hook event, trigger type, payload). write_snapshot() is added and main() delegates to it and returns the saved snapshot path.
Hook command generation helpers
src/gradata/hooks/adapters/_base.py, src/gradata/hooks/adapters/claude_code.py
pre_compact_hook_command() in _base.py constructs the shell command with BRAIN_DIR set. _pre_compact_signature() and matching/prune helpers in claude_code.py derive and detect the pre-compact hook signature.
Claude-code adapter hook management
src/gradata/hooks/adapters/claude_code.py
install() now idempotently adds PreCompact command hooks (matcher `manual
Integration and functional tests
tests/test_hook_adapters.py, tests/test_pre_compact_hook.py, tests/test_hooks_intelligence.py
Tests added/updated: claude-code install writes a PreCompact hook entry with expected matcher, command, BRAIN_DIR, and hook id structure; end-to-end tests invoke the pre_compact hook and assert the snapshot JSON is created under .precompact-snapshots/<session_id>.json with expected fields and captured lessons content.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • Gradata/gradata#215: Related changes to claude-code adapter uninstall behavior and removal of gradata hook signature entries.

Suggested labels

feature

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main change: adding a PreCompact snapshot hook feature for Claude adapters.
Description check ✅ Passed The description provides a clear summary of the changes, verification steps, and references the related issue (GRA-1210).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch gra-1210-pre-compact

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 OpenGrep (1.22.0)

OpenGrep fatal error (exit code 2):
┌──────────────┐
│ Opengrep CLI │
└──────────────┘

�[32m✔�[39m �[1mOpengrep OSS�[0m
�[32m✔�[39m Basic security coverage for first-party code vulnerabilities.

�[1m Loading rules from local config...�[0m
[00.41][ERROR]: Error: exception Glob.Lexer.Syntax_error("malformed glob pattern: missing ']'")
Raised at Glob__Lexer.syntax_error in file "libs/glob/Lexer.mll", line 8, characters 2-26
Called from Glob__Lexer.__ocaml_lex_token_rec in file "libs/glob/Lexer.mll", line 29, characters 26-53
Cal


Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot added the feature label May 26, 2026
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@Gradata/src/gradata/hooks/adapters/claude_code.py`:
- Around line 133-147: The current loop over pre_tool removes entire entries
when sig is found in the entry string, which can drop user hooks; instead, parse
each entry (the dict/object in pre_tool) and mutate its internal hooks list to
remove only hook items that match our sig (and ignore/prevent removing items
that contain pre_compact_sig), then append the pruned entry to kept if it still
has non-empty hooks; if hooks become empty, skip the entry (and remove the
PreToolUse key if no kept entries remain). Apply the same targeted-pruning
approach to the PreCompact handling (the analogous loop that uses
pre_compact_sig) so only matching Gradata hooks are removed rather than deleting
mixed entries wholesale.

In `@Gradata/src/gradata/hooks/pre_compact.py`:
- Around line 52-53: In hooks/pre_compact.py there are except blocks (e.g., the
except OSError that currently just returns None and another at lines ~102-105)
that silently swallow exceptions; update those handlers to log the exception
before returning by calling the module logger (e.g., logger.warning or
logger.exception) with a contextual message and exc_info=True so the stacktrace
is recorded, then continue to return None as before; locate the handlers in
pre_compact.py and replace the bare silent returns with a logging call that
includes exc_info=True.
- Around line 51-56: The code currently calls path.read_text() which loads the
entire file into memory before truncating; change to streaming-read only up to
MAX_FILE_CHARS+1 bytes to avoid memory spikes: open the file via path.open(...)
and call read(MAX_FILE_CHARS + 1) into text, set truncated = len(text) >
MAX_FILE_CHARS, and if truncated slice text = text[:MAX_FILE_CHARS]; preserve
the same exception handling for OSError and keep variable names (path,
MAX_FILE_CHARS, truncated, text) so the rest of the function remains unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: eeccb7bf-97e7-4675-9a5a-ab2741d3b9fb

📥 Commits

Reviewing files that changed from the base of the PR and between a197bff and 843ad79.

📒 Files selected for processing (5)
  • Gradata/src/gradata/hooks/adapters/_base.py
  • Gradata/src/gradata/hooks/adapters/claude_code.py
  • Gradata/src/gradata/hooks/pre_compact.py
  • Gradata/tests/test_hook_adapters.py
  • Gradata/tests/test_pre_compact_hook.py
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: pytest ubuntu-latest / py3.11
  • GitHub Check: pytest ubuntu-latest / py3.12
  • GitHub Check: pytest windows-latest / py3.12
  • GitHub Check: pytest windows-latest / py3.11
🧰 Additional context used
📓 Path-based instructions (2)
Gradata/src/**/*.py

📄 CodeRabbit inference engine (Gradata/AGENTS.md)

Gradata/src/**/*.py: Prefer sentence-transformers for local embeddings, google-genai for Gemini embeddings, cryptography for AES-GCM encrypted system.db, bm25s for BM25 rule ranking, and mem0ai for external memory adapters — guard all optional dependency imports with try / except ImportError at the call site, never at module level
Maintain strict layering: Layer 0 (Primitives: _types.py, _db.py, _events.py, _paths.py, _file_lock.py; Patterns: contrib/patterns/) must never import from Layer 1 (Enhancements: enhancements/, rules/) or Layer 2 (Public API: brain.py, cli.py, daemon.py, mcp_server.py)
Never use bare except: pass — use typed exceptions or at minimum logger.warning(...) with exc_info=True to avoid silent failure in a memory product
Never import from out-of-scope sibling directories ../Sprites/ or ../Hausgem/ within gradata/* code — that is a layering bug
Never leak private-sibling paths into public docs/code — no references to ../Sprites/, ../Hausgem/, email addresses, OneDrive paths, or Sprites-specific examples from inside gradata/*
Use atomic-write helper when writing JSON files to prevent corruption from mid-write crashes

Files:

  • Gradata/src/gradata/hooks/adapters/claude_code.py
  • Gradata/src/gradata/hooks/adapters/_base.py
  • Gradata/src/gradata/hooks/pre_compact.py
Gradata/tests/**/*.py

📄 CodeRabbit inference engine (Gradata/AGENTS.md)

Gradata/tests/**/*.py: Set BRAIN_DIR environment variable via tmp_path in conftest.py for test isolation — ensure _paths.py module cache refreshes when calling Brain.init() directly inside tests
Add unit tests in tests/test_*.py for every CI push without LLM calls (deterministic); mark integration tests with @pytest.mark.integration and skip them by default (they hit real LLM APIs)

Files:

  • Gradata/tests/test_pre_compact_hook.py
  • Gradata/tests/test_hook_adapters.py
🔇 Additional comments (5)
Gradata/src/gradata/hooks/pre_compact.py (1)

36-45: LGTM!

Also applies to: 76-95, 99-101, 106-106

Gradata/src/gradata/hooks/adapters/_base.py (1)

139-143: LGTM!

Gradata/src/gradata/hooks/adapters/claude_code.py (1)

15-15: LGTM!

Also applies to: 21-21, 28-30, 64-107, 112-126

Gradata/tests/test_hook_adapters.py (1)

3-3: LGTM!

Also applies to: 69-92

Gradata/tests/test_pre_compact_hook.py (1)

1-45: LGTM!

Comment thread Gradata/src/gradata/hooks/adapters/claude_code.py
Comment thread Gradata/src/gradata/hooks/pre_compact.py Outdated
Comment thread Gradata/src/gradata/hooks/pre_compact.py
Copy link
Copy Markdown

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant