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

Commit 3b884e5

Browse files
zircoteclaude
andcommitted
test(hooks): add comprehensive hook test suite (Phase 5)
Add 132 hook-specific tests covering: - Unit tests for hook services (51 tests) - Unit tests for hook handlers (43 tests) - Integration tests for end-to-end flows (21 tests) - Performance tests with timing benchmarks (17 tests) Update documentation: - README.md: hooks integration section and env vars - USER_GUIDE.md: comprehensive hooks documentation (~150 lines) - CHANGELOG.md: hooks feature in [Unreleased] section Bug fix: - session_start_handler.py: add JSON output on error paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 8e9b291 commit 3b884e5

12 files changed

Lines changed: 3463 additions & 14 deletions

File tree

CHANGELOG.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,53 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
77

88
## [Unreleased]
99

10+
### Added
11+
12+
#### Claude Code Hooks Integration
13+
- **SessionStart Hook**: Automatic context injection at session start
14+
- Project and spec detection from git repo, pyproject.toml, package.json
15+
- Adaptive token budget calculation (adaptive/fixed/full/minimal modes)
16+
- Working memory injection: pending actions, recent decisions, active blockers
17+
- Semantic context: relevant learnings and patterns for the project
18+
- XML-formatted output for Claude Code additionalContext
19+
20+
- **UserPromptSubmit Hook**: Capture signal detection (opt-in)
21+
- Pattern-based detection for decisions, learnings, blockers, progress
22+
- Confidence scoring with configurable thresholds
23+
- AUTO capture for high-confidence signals (≥95%)
24+
- SUGGEST action for medium-confidence signals (70-95%)
25+
- Novelty checking to avoid duplicate captures
26+
27+
- **Stop Hook**: Session-end processing
28+
- Session transcript analysis for uncaptured memorable content
29+
- Prompts for uncaptured decisions, learnings, blockers
30+
- Automatic search index synchronization
31+
32+
#### Hook Infrastructure
33+
- `HookConfig` dataclass with environment variable configuration
34+
- `XMLBuilder` for structured context serialization
35+
- `ContextBuilder` for memory context assembly
36+
- `ProjectDetector` for automatic project/spec identification
37+
- `SignalDetector` for capture-worthy content detection
38+
- `NoveltyChecker` for semantic similarity against existing memories
39+
- `CaptureDecider` for threshold-based capture decisions
40+
- `SessionAnalyzer` for transcript parsing and analysis
41+
42+
#### Hook Configuration
43+
- Environment variables: HOOK_ENABLED, HOOK_SESSION_START_ENABLED, HOOK_USER_PROMPT_ENABLED, HOOK_STOP_ENABLED
44+
- Budget configuration: HOOK_SESSION_START_BUDGET_MODE, HOOK_SESSION_START_FIXED_BUDGET, HOOK_SESSION_START_MAX_BUDGET
45+
- Detection thresholds: HOOK_CAPTURE_DETECTION_MIN_CONFIDENCE, HOOK_CAPTURE_DETECTION_AUTO_THRESHOLD, HOOK_CAPTURE_DETECTION_NOVELTY_THRESHOLD
46+
- Debug mode: HOOK_DEBUG for stderr logging
47+
48+
### Testing
49+
- 132 hook-specific tests (51 services + 43 handlers + 21 integration + 17 performance)
50+
- Performance benchmarks: <5ms signal detection, <50ms single prompt, <10ms full pipeline
51+
52+
### Documentation
53+
- Hooks Integration section in User Guide
54+
- Configuration reference for all hook environment variables
55+
- Troubleshooting guide for common hook issues
56+
1057
## [0.1.0] - 2024-12-19
1158

1259
### Added

README.md

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,16 @@ When used as a Claude Code plugin, the following commands are available:
5858
- `/memory verify` - Check index consistency
5959
- `/memory gc` - Garbage collect old memories
6060

61+
### Hooks Integration
62+
63+
The plugin includes hooks that integrate with Claude Code's hook system for automatic memory context:
64+
65+
- **SessionStart**: Injects relevant project memories at session start
66+
- **UserPromptSubmit**: Detects capture-worthy content in user prompts (opt-in)
67+
- **Stop**: Prompts for uncaptured content and syncs the search index
68+
69+
See [User Guide](docs/USER_GUIDE.md#hooks-integration) for configuration options.
70+
6171
## Development
6272

6373
```bash
@@ -84,7 +94,11 @@ Environment variables:
8494
| `MEMORY_PLUGIN_DATA_DIR` | Data directory path | `~/.local/share/memory-plugin/` |
8595
| `MEMORY_PLUGIN_GIT_NAMESPACE` | Git notes namespace | `refs/notes/mem` |
8696
| `MEMORY_PLUGIN_EMBEDDING_MODEL` | Embedding model name | `all-MiniLM-L6-v2` |
87-
| `MEMORY_PLUGIN_AUTO_CAPTURE` | Enable auto-capture hook | `false` |
97+
| `HOOK_ENABLED` | Master switch for hooks | `true` |
98+
| `HOOK_SESSION_START_ENABLED` | Enable SessionStart context injection | `true` |
99+
| `HOOK_USER_PROMPT_ENABLED` | Enable signal detection in prompts | `false` |
100+
| `HOOK_STOP_ENABLED` | Enable Stop hook processing | `true` |
101+
| `HOOK_DEBUG` | Enable debug logging to stderr | `false` |
88102

89103
## Requirements
90104

docs/USER_GUIDE.md

Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -432,6 +432,145 @@ git fetch origin refs/notes/mem:refs/notes/mem
432432

433433
---
434434

435+
## Hooks Integration
436+
437+
The memory plugin includes hooks that integrate with Claude Code's hook system for automatic memory context injection and capture assistance.
438+
439+
### Overview
440+
441+
Three hooks are available:
442+
443+
| Hook | Event | Purpose | Default |
444+
|------|-------|---------|---------|
445+
| SessionStart | Session begins | Inject project memories | Enabled |
446+
| UserPromptSubmit | User sends prompt | Detect capture signals | Disabled |
447+
| Stop | Session ends | Prompt for uncaptured content, sync index | Enabled |
448+
449+
### SessionStart Hook
450+
451+
Automatically injects relevant memories at the start of each Claude Code session.
452+
453+
**What it does:**
454+
1. Detects the current project (from git repo, package.json, pyproject.toml)
455+
2. Identifies the active spec (from CLAUDE.md or docs/spec/active/)
456+
3. Builds context within a token budget
457+
4. Injects XML-formatted memory context
458+
459+
**Context includes:**
460+
- Working memory: pending actions, recent decisions, active blockers
461+
- Semantic context: learnings and patterns relevant to the project
462+
- Available commands: quick reference for memory operations
463+
464+
**Configuration:**
465+
466+
```bash
467+
# Disable SessionStart hook
468+
export HOOK_SESSION_START_ENABLED=false
469+
470+
# Budget modes: adaptive (default), fixed, full, minimal
471+
export HOOK_SESSION_START_BUDGET_MODE=adaptive
472+
473+
# Fixed budget (when mode=fixed)
474+
export HOOK_SESSION_START_FIXED_BUDGET=1000
475+
476+
# Maximum budget cap
477+
export HOOK_SESSION_START_MAX_BUDGET=3000
478+
```
479+
480+
### UserPromptSubmit Hook
481+
482+
Analyzes user prompts for capture-worthy content and suggests or auto-captures memories.
483+
484+
**What it detects:**
485+
- **Decisions**: "I decided to use...", "we're going with..."
486+
- **Learnings**: "I learned that...", "TIL:", "turns out..."
487+
- **Blockers**: "blocked by...", "stuck on...", "can't because..."
488+
- **Progress**: "completed...", "finished...", "done with..."
489+
490+
**Capture actions:**
491+
- **AUTO**: High-confidence signals (≥95%) are captured automatically
492+
- **SUGGEST**: Medium-confidence signals (70-95%) show suggestions
493+
- **SKIP**: Low-confidence or duplicate content is ignored
494+
495+
**Configuration:**
496+
497+
```bash
498+
# Enable signal detection (disabled by default)
499+
export HOOK_USER_PROMPT_ENABLED=true
500+
501+
# Minimum confidence for suggestions (0.0-1.0)
502+
export HOOK_CAPTURE_DETECTION_MIN_CONFIDENCE=0.7
503+
504+
# Threshold for auto-capture (0.0-1.0)
505+
export HOOK_CAPTURE_DETECTION_AUTO_THRESHOLD=0.95
506+
507+
# Novelty threshold (how different from existing memories)
508+
export HOOK_CAPTURE_DETECTION_NOVELTY_THRESHOLD=0.3
509+
```
510+
511+
### Stop Hook
512+
513+
Performs session-end cleanup and memory assistance.
514+
515+
**What it does:**
516+
1. Analyzes session transcript for uncaptured memorable content
517+
2. Prompts user with suggestions if valuable content found
518+
3. Synchronizes the memory search index
519+
520+
**Configuration:**
521+
522+
```bash
523+
# Disable Stop hook
524+
export HOOK_STOP_ENABLED=false
525+
526+
# Disable uncaptured content prompts
527+
export HOOK_STOP_PROMPT_UNCAPTURED=false
528+
529+
# Disable index sync on session end
530+
export HOOK_STOP_SYNC_INDEX=false
531+
```
532+
533+
### Global Hook Configuration
534+
535+
```bash
536+
# Master switch - disable all hooks
537+
export HOOK_ENABLED=false
538+
539+
# Enable debug logging to stderr
540+
export HOOK_DEBUG=true
541+
542+
# Hook timeout in seconds (default: 30)
543+
export HOOK_TIMEOUT=30
544+
```
545+
546+
### Hook Installation
547+
548+
The hooks are installed automatically when you configure the plugin in Claude Code. The hook scripts are in the `hooks/` directory:
549+
550+
- `hooks/session_start.py` - SessionStart event handler
551+
- `hooks/user_prompt.py` - UserPromptSubmit event handler
552+
- `hooks/stop.py` - Stop event handler
553+
- `hooks/hooks.json` - Hook registration configuration
554+
555+
### Troubleshooting Hooks
556+
557+
**Hook not triggering:**
558+
1. Check if hooks are enabled: `echo $HOOK_ENABLED`
559+
2. Verify hook registration in `hooks/hooks.json`
560+
3. Enable debug mode: `export HOOK_DEBUG=true`
561+
562+
**Slow session start:**
563+
1. Reduce budget: `export HOOK_SESSION_START_BUDGET_MODE=minimal`
564+
2. Check index size with `/memory status`
565+
3. Run incremental reindex: `/memory sync`
566+
567+
**Too many capture suggestions:**
568+
1. Increase confidence threshold: `export HOOK_CAPTURE_DETECTION_MIN_CONFIDENCE=0.8`
569+
2. Disable auto-capture: `export HOOK_CAPTURE_DETECTION_AUTO_THRESHOLD=1.0`
570+
3. Disable hook entirely: `export HOOK_USER_PROMPT_ENABLED=false`
571+
572+
---
573+
435574
## Next Steps
436575

437576
- See [Developer Guide](DEVELOPER_GUIDE.md) for API reference

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

Lines changed: 66 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ document_type: progress
33
format_version: "1.0.0"
44
project_id: SPEC-2025-12-19-001
55
project_name: "Hook-Based Memory Capture"
6-
project_status: in-progress
6+
project_status: complete
77
current_phase: 5
88
implementation_started: 2025-12-19T00:00:00Z
99
last_session: 2025-12-19T00:00:00Z
@@ -47,13 +47,13 @@ This document tracks implementation progress against the spec plan.
4747
| 4.3 | Enhance Stop Hook Handler | done | 2025-12-19 | 2025-12-19 | Created `hooks/stop_handler.py` |
4848
| 4.4 | Implement Capture Prompt | done | 2025-12-19 | 2025-12-19 | XML formatting in stop_handler |
4949
| 4.5 | Index Synchronization | done | 2025-12-19 | 2025-12-19 | SyncService integration in stop_handler |
50-
| 5.1 | Unit Tests - Hook Services | pending | | | XMLBuilder, ContextBuilder, etc. |
51-
| 5.2 | Unit Tests - Hook Handlers | pending | | | Hook script tests |
52-
| 5.3 | Integration Tests | pending | | | End-to-end flows |
53-
| 5.4 | Performance Tests | pending | | | Timing benchmarks |
54-
| 5.5 | Hook Script Testing | pending | | | Manual testing with fixtures |
55-
| 5.6 | Documentation Updates | pending | | | README, CLAUDE.md |
56-
| 5.7 | Update CHANGELOG | pending | | | Release notes |
50+
| 5.1 | Unit Tests - Hook Services | done | 2025-12-19 | 2025-12-19 | 51 tests covering all hook services |
51+
| 5.2 | Unit Tests - Hook Handlers | done | 2025-12-19 | 2025-12-19 | 43 tests covering all handlers |
52+
| 5.3 | Integration Tests | done | 2025-12-19 | 2025-12-19 | 21 tests covering end-to-end flows |
53+
| 5.4 | Performance Tests | done | 2025-12-19 | 2025-12-19 | 17 tests covering timing requirements |
54+
| 5.5 | Hook Script Testing | done | 2025-12-19 | 2025-12-19 | Manual testing complete, bug fix applied |
55+
| 5.6 | Documentation Updates | done | 2025-12-19 | 2025-12-19 | README, USER_GUIDE updated |
56+
| 5.7 | Update CHANGELOG | done | 2025-12-19 | 2025-12-19 | Added hooks feature to [Unreleased] |
5757

5858
---
5959

@@ -65,7 +65,7 @@ This document tracks implementation progress against the spec plan.
6565
| 2 | SessionStart Context Injection | 100% | done |
6666
| 3 | Capture Signal Detection | 100% | done |
6767
| 4 | Stop Hook Enhancement | 100% | done |
68-
| 5 | Testing & Documentation | 0% | pending |
68+
| 5 | Testing & Documentation | 100% | done |
6969

7070
---
7171

@@ -74,6 +74,8 @@ This document tracks implementation progress against the spec plan.
7474
| Date | Type | Task ID | Description | Resolution |
7575
|------|------|---------|-------------|------------|
7676
| 2025-12-19 | refinement | 2.4 | Handler renamed to `session_start_handler.py` | Clarifies two-layer architecture (wrapper script vs handler module) |
77+
| 2025-12-19 | bugfix | 1.3 | Budget tier allocations exceeded total when commands added | Fixed tiers: total ≥ working_memory + semantic_context + commands(100) |
78+
| 2025-12-19 | bugfix | 2.4 | session_start_handler.py missing JSON output on error paths | Added `print(json.dumps({"continue": True}))` to exception handlers |
7779

7880
---
7981

@@ -175,3 +177,58 @@ This document tracks implementation progress against the spec plan.
175177
- `mypy` - No issues found in 13 source files
176178
- `pytest` - 910 tests passed
177179
- Ready for Phase 5: Testing & Documentation
180+
181+
### 2025-12-19 - Task 5.4 Complete (Performance Tests)
182+
- **Task 5.4 completed**: 17 performance tests added
183+
- Created `tests/test_hooks_performance.py`:
184+
- Signal detection throughput: <5ms per prompt (parametrized 10/50/100 iterations)
185+
- Single prompt latency: <50ms per detection
186+
- Long prompt handling: <100ms for 5000 char prompts
187+
- XML generation performance: <0.1ms creation, <5ms for 100 elements, <2ms serialization
188+
- Config loading: <1ms per load
189+
- Project detection: <10ms per detection
190+
- Capture decision: <1ms per decision (with novelty checks disabled)
191+
- Session transcript analysis: <50ms for 100-message transcripts
192+
- Full pipeline timing: <10ms per prompt through detection→decision pipeline
193+
- Memory usage tests for detector and XML builder reuse
194+
- Fixed test compatibility:
195+
- Used `check_novelty_enabled=False` to avoid database calls in performance tests
196+
- Updated budget tier test values to match current implementation (300/100)
197+
- All 131 tests pass (51+43+20+17)
198+
- Quality gates: `ruff check` ✓, `mypy` ✓, `pytest` 131 passed ✓
199+
200+
### 2025-12-19 - Task 5.5 Complete (Hook Script Testing)
201+
- **Task 5.5 completed**: Manual testing of all hook scripts
202+
- **Bug found and fixed**: `session_start_handler.py` error paths didn't output JSON
203+
- Root cause: Exception handlers logged errors but didn't write to stdout
204+
- Fix: Added `print(json.dumps({"continue": True}))` after each logger call
205+
- This ensures hook contract is satisfied even on errors
206+
- Manual test results:
207+
- `session_start.py`: Outputs valid JSON on all paths (empty input, invalid JSON, valid input)
208+
- `user_prompt.py`: Signal detection working (detected decision signals)
209+
- `stop.py`: Outputs sync stats and handles empty input gracefully
210+
- Edge cases verified:
211+
- Empty stdin → `{"continue": true}`
212+
- Invalid JSON → `{"continue": true}`
213+
- `HOOK_ENABLED=false``{"continue": true}` (early exit)
214+
- Valid input with signals → proper JSON with additionalContext
215+
- All 132 hook tests pass (51+43+21+17)
216+
- Quality gates: `ruff check` ✓, `mypy` ✓, `pytest` 132 passed ✓
217+
218+
### 2025-12-19 - Phase 5 Complete (All Tasks Done)
219+
- **Task 5.6 completed**: Documentation Updates
220+
- Updated README.md with hooks integration section
221+
- Added hook environment variables to configuration table
222+
- Updated USER_GUIDE.md with comprehensive hooks documentation (~150 lines)
223+
- Overview table of three hooks with events, purposes, defaults
224+
- Detailed configuration for SessionStart, UserPromptSubmit, Stop hooks
225+
- Global hook configuration section
226+
- Troubleshooting section for common issues
227+
- **Task 5.7 completed**: CHANGELOG Updates
228+
- Added hooks feature to [Unreleased] section
229+
- Documented all three hooks with feature details
230+
- Listed new hook infrastructure components
231+
- Documented all configuration environment variables
232+
- Added testing metrics (132 tests, performance benchmarks)
233+
- **Project Status**: All 27 tasks across 5 phases complete
234+
- Quality gates: `ruff check` ✓, `mypy`

src/git_notes_memory/hooks/config_loader.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,13 +99,14 @@ class HookConfig:
9999
debug: bool = False
100100

101101
# Budget tier thresholds (for adaptive mode)
102+
# Note: total must >= working_memory + semantic_context + commands (default 100)
102103
budget_tiers: tuple[tuple[str, int, int, int], ...] = field(
103104
default=(
104105
# (complexity, total_budget, working_memory, semantic_context)
105-
("simple", 500, 350, 150),
106-
("medium", 1000, 600, 400),
107-
("complex", 2000, 1000, 1000),
108-
("full", 3000, 1500, 1500),
106+
("simple", 500, 300, 100), # 300+100+100=500
107+
("medium", 1000, 500, 300), # 500+300+100=900 (margin for commands)
108+
("complex", 2000, 900, 900), # 900+900+100=1900 (margin)
109+
("full", 3000, 1400, 1400), # 1400+1400+100=2900 (margin)
109110
)
110111
)
111112

src/git_notes_memory/hooks/session_start_handler.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,8 +197,10 @@ def main() -> None:
197197

198198
except json.JSONDecodeError as e:
199199
logger.error("Failed to parse hook input: %s", e)
200+
print(json.dumps({"continue": True}))
200201
except Exception as e:
201202
logger.exception("SessionStart hook error: %s", e)
203+
print(json.dumps({"continue": True}))
202204
finally:
203205
_cancel_timeout()
204206

0 commit comments

Comments
 (0)