Skip to content
This repository was archived by the owner on Jan 2, 2026. It is now read-only.

Commit 8e9b291

Browse files
zircoteclaude
andcommitted
feat(hooks): implement Stop hook enhancement for Phase 4
- Add session_analyzer.py for transcript analysis and uncaptured memory detection - Add stop_handler.py with session analysis, capture prompts, and index sync - Update stop.py wrapper to delegate to new handler module - Export SessionAnalyzer and TranscriptContent from hooks module - Update PROGRESS.md with Phase 4 completion notes Phase 4 complete: All 5 tasks done (20/27 total tasks, 74%) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 2f0b065 commit 8e9b291

5 files changed

Lines changed: 743 additions & 61 deletions

File tree

docs/spec/active/2025-12-19-hook-based-memory-capture/PROGRESS.md

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ format_version: "1.0.0"
44
project_id: SPEC-2025-12-19-001
55
project_name: "Hook-Based Memory Capture"
66
project_status: in-progress
7-
current_phase: 4
7+
current_phase: 5
88
implementation_started: 2025-12-19T00:00:00Z
99
last_session: 2025-12-19T00:00:00Z
1010
last_updated: 2025-12-19T00:00:00Z
@@ -42,11 +42,11 @@ This document tracks implementation progress against the spec plan.
4242
| 3.4 | Create UserPromptSubmit Hook Handler | done | 2025-12-19 | 2025-12-19 | Created `hooks/user_prompt_handler.py` |
4343
| 3.5 | Format Capture Suggestions | done | 2025-12-19 | 2025-12-19 | XML formatter in handler via XMLBuilder |
4444
| 3.6 | Register UserPromptSubmit Hook | done | 2025-12-19 | 2025-12-19 | Updated hooks.json, created wrapper |
45-
| 4.1 | Implement Session Analyzer | pending | | | Transcript analysis |
46-
| 4.2 | Detect Uncaptured Memories | pending | | | Filter already-captured |
47-
| 4.3 | Enhance Stop Hook Handler | pending | | | Update hooks/stop.py |
48-
| 4.4 | Implement Capture Prompt | pending | | | Format uncaptured content prompt |
49-
| 4.5 | Index Synchronization | pending | | | Sync on session end |
45+
| 4.1 | Implement Session Analyzer | done | 2025-12-19 | 2025-12-19 | Created `hooks/session_analyzer.py` |
46+
| 4.2 | Detect Uncaptured Memories | done | 2025-12-19 | 2025-12-19 | Integrated in SessionAnalyzer with novelty filtering |
47+
| 4.3 | Enhance Stop Hook Handler | done | 2025-12-19 | 2025-12-19 | Created `hooks/stop_handler.py` |
48+
| 4.4 | Implement Capture Prompt | done | 2025-12-19 | 2025-12-19 | XML formatting in stop_handler |
49+
| 4.5 | Index Synchronization | done | 2025-12-19 | 2025-12-19 | SyncService integration in stop_handler |
5050
| 5.1 | Unit Tests - Hook Services | pending | | | XMLBuilder, ContextBuilder, etc. |
5151
| 5.2 | Unit Tests - Hook Handlers | pending | | | Hook script tests |
5252
| 5.3 | Integration Tests | pending | | | End-to-end flows |
@@ -64,7 +64,7 @@ This document tracks implementation progress against the spec plan.
6464
| 1 | Core Hook Infrastructure | 100% | done |
6565
| 2 | SessionStart Context Injection | 100% | done |
6666
| 3 | Capture Signal Detection | 100% | done |
67-
| 4 | Stop Hook Enhancement | 0% | pending |
67+
| 4 | Stop Hook Enhancement | 100% | done |
6868
| 5 | Testing & Documentation | 0% | pending |
6969

7070
---
@@ -151,3 +151,27 @@ This document tracks implementation progress against the spec plan.
151151
- `mypy` - No issues found in 11 source files
152152
- `pytest` - 910 tests passed
153153
- Ready for Phase 4: Stop Hook Enhancement
154+
155+
### 2025-12-19 - Phase 4 Complete
156+
- **Phase 4 completed**: All 5 tasks done
157+
- Created `src/git_notes_memory/hooks/session_analyzer.py`:
158+
- `SessionAnalyzer` class for parsing and analyzing session transcripts
159+
- `TranscriptContent` frozen dataclass for parsed transcript data
160+
- Pattern-based user message extraction from transcripts
161+
- Novelty filtering to skip already-captured content
162+
- Confidence-based ranking and result limiting
163+
- Created `src/git_notes_memory/hooks/stop_handler.py`:
164+
- Full Stop hook handler with session analysis
165+
- Uncaptured content detection via SessionAnalyzer integration
166+
- XML-formatted output for uncaptured memories via XMLBuilder
167+
- Index synchronization via SyncService.reindex(full=False)
168+
- Configuration support: `stop_enabled`, `stop_prompt_uncaptured`, `stop_sync_index`
169+
- Non-blocking error handling (exit 0 on all paths)
170+
- Updated `hooks/stop.py` wrapper script:
171+
- Delegates to stop_handler module
172+
- Graceful fallback if library not installed
173+
- All quality gates passed:
174+
- `ruff check` - All checks passed
175+
- `mypy` - No issues found in 13 source files
176+
- `pytest` - 910 tests passed
177+
- Ready for Phase 5: Testing & Documentation

hooks/stop.py

Lines changed: 31 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,49 @@
11
#!/usr/bin/env python3
2-
"""Hook: Sync memory index on session end.
3-
4-
This hook performs a lightweight incremental sync of the memory index
5-
when a Claude Code session ends. This ensures any memories captured
6-
during the session are properly indexed for future retrieval.
2+
"""Hook: Process session end with uncaptured content detection and index sync.
3+
4+
This hook performs session-end tasks:
5+
1. Analyzes session transcript for uncaptured memorable content
6+
2. Prompts user to capture worthy content (if configured)
7+
3. Synchronizes the memory index
8+
9+
Environment Variables:
10+
HOOK_ENABLED: Master switch for hooks (default: true)
11+
HOOK_STOP_ENABLED: Enable this hook (default: true)
12+
HOOK_STOP_PROMPT_UNCAPTURED: Prompt for uncaptured content (default: true)
13+
HOOK_STOP_SYNC_INDEX: Sync index on session end (default: true)
14+
HOOK_DEBUG: Enable debug logging (default: false)
15+
16+
Exit codes:
17+
0 - Success (non-blocking)
718
"""
819

920
from __future__ import annotations
1021

11-
import json
1222
import sys
1323

1424

15-
def sync_index() -> dict:
16-
"""Perform incremental index sync.
25+
def main() -> None:
26+
"""Main hook entry point.
1727
18-
Returns dict with sync result.
28+
Delegates to the stop_handler module for actual processing.
29+
Falls back gracefully if the library is not installed.
1930
"""
2031
try:
21-
from git_notes_memory import get_sync_service
22-
23-
sync = get_sync_service()
24-
stats = sync.incremental_sync()
25-
26-
return {
27-
"success": True,
28-
"stats": {
29-
"scanned": stats.get("scanned", 0),
30-
"added": stats.get("added", 0),
31-
"updated": stats.get("updated", 0),
32-
}
33-
}
32+
from git_notes_memory.hooks.stop_handler import main as handler_main
3433

34+
handler_main()
3535
except ImportError:
36-
# Library not installed, skip silently
37-
return {"success": True, "skipped": True}
38-
except Exception as e:
39-
return {
40-
"success": False,
41-
"error": str(e)
42-
}
43-
44-
45-
def main() -> None:
46-
"""Main hook entry point."""
47-
# Read hook input from stdin (may be empty for stop hook)
48-
try:
49-
input_data = json.load(sys.stdin)
50-
except (json.JSONDecodeError, ValueError):
51-
input_data = {}
52-
53-
# Perform sync
54-
result = sync_index()
55-
56-
# Output result
57-
output = {"continue": True}
36+
# Library not installed, exit silently (non-blocking)
37+
import json
5838

59-
if result.get("skipped"):
60-
# Silently skip if library not installed
61-
pass
62-
elif result.get("success"):
63-
stats = result.get("stats", {})
64-
if stats.get("added", 0) > 0 or stats.get("updated", 0) > 0:
65-
output["message"] = f"Memory index synced: +{stats.get('added', 0)} new, ~{stats.get('updated', 0)} updated"
66-
else:
67-
output["warning"] = f"Memory sync failed: {result.get('error', 'Unknown error')}"
39+
print(json.dumps({"continue": True}))
40+
sys.exit(0)
41+
except Exception:
42+
# Any unexpected error, exit silently (non-blocking)
43+
import json
6844

69-
print(json.dumps(output))
45+
print(json.dumps({"continue": True}))
46+
sys.exit(0)
7047

7148

7249
if __name__ == "__main__":

src/git_notes_memory/hooks/__init__.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,9 @@
5353
"CaptureDecider",
5454
"CaptureDecision",
5555
"CaptureAction",
56+
# Session Analysis
57+
"SessionAnalyzer",
58+
"TranscriptContent",
5659
# Project Detection
5760
"detect_project",
5861
]
@@ -131,6 +134,16 @@ def __getattr__(name: str) -> object:
131134

132135
return CaptureAction
133136

137+
# Session Analysis
138+
if name == "SessionAnalyzer":
139+
from git_notes_memory.hooks.session_analyzer import SessionAnalyzer
140+
141+
return SessionAnalyzer
142+
if name == "TranscriptContent":
143+
from git_notes_memory.hooks.session_analyzer import TranscriptContent
144+
145+
return TranscriptContent
146+
134147
# Project Detection
135148
if name == "detect_project":
136149
from git_notes_memory.hooks.project_detector import detect_project

0 commit comments

Comments
 (0)